feat(secrets): abstractions seam interfaces
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
namespace ZB.MOM.WW.Secrets.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Supplies the AES-256 master key (KEK) used to wrap and unwrap per-secret data-encryption
|
||||
/// keys. Implementations resolve the key material from a configured source (environment,
|
||||
/// file, external KMS) and expose a stable, non-secret identifier for the active key so
|
||||
/// rotation can be recorded per row and reasoned about later.
|
||||
/// </summary>
|
||||
public interface IMasterKeyProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the 32-byte AES-256 master key material.
|
||||
/// </summary>
|
||||
/// <returns>The 32-byte master key.</returns>
|
||||
/// <exception cref="MasterKeyUnavailableException">The master key cannot be resolved.</exception>
|
||||
ReadOnlyMemory<byte> GetMasterKey();
|
||||
|
||||
/// <summary>
|
||||
/// The stable, non-secret identifier of the active key-encryption key (KEK). This value
|
||||
/// is recorded on each stored row so that key rotation is legible and rows can be matched
|
||||
/// to the KEK that wrapped their DEK.
|
||||
/// </summary>
|
||||
string KekId { get; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace ZB.MOM.WW.Secrets.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Envelope-encrypts and decrypts secret material. Each secret is sealed under a per-secret
|
||||
/// data-encryption key (DEK) that is itself wrapped by the master key (KEK) from an
|
||||
/// <see cref="IMasterKeyProvider"/>. The secret name is bound as additional authenticated
|
||||
/// data (AAD) so ciphertext cannot be silently relocated to a different name.
|
||||
/// </summary>
|
||||
public interface ISecretCipher
|
||||
{
|
||||
/// <summary>
|
||||
/// Envelope-encrypts <paramref name="plaintext"/>, binding it to <paramref name="name"/>
|
||||
/// as additional authenticated data.
|
||||
/// </summary>
|
||||
/// <param name="name">The secret name, bound as AAD.</param>
|
||||
/// <param name="plaintext">The secret plaintext to seal.</param>
|
||||
/// <param name="contentType">How the plaintext should be interpreted by consumers.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="StoredSecret"/> carrying the ciphertext, nonce, tag, and wrapped DEK, with
|
||||
/// <see cref="StoredSecret.Revision"/> 0 and default timestamps — the store stamps the
|
||||
/// revision and timestamps on persist.
|
||||
/// </returns>
|
||||
StoredSecret Encrypt(SecretName name, string plaintext, SecretContentType contentType);
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts the value carried by a stored row, verifying its authentication tag and the
|
||||
/// name it was bound to.
|
||||
/// </summary>
|
||||
/// <param name="secret">The stored row to decrypt.</param>
|
||||
/// <returns>The decrypted plaintext.</returns>
|
||||
/// <exception cref="SecretDecryptionException">
|
||||
/// The authentication tag fails to verify, or the row references an unknown or unavailable KEK.
|
||||
/// </exception>
|
||||
string Decrypt(StoredSecret secret);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace ZB.MOM.WW.Secrets.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Publishes an encrypted row to cluster peers. Core registers a no-op implementation;
|
||||
/// <c>ZB.MOM.WW.Secrets.Akka</c> (deferred) provides the real cluster implementation.
|
||||
/// </summary>
|
||||
public interface ISecretReplicator
|
||||
{
|
||||
/// <summary>
|
||||
/// Broadcasts the already-encrypted <paramref name="row"/> to peer nodes. This is a no-op
|
||||
/// in single-node deployments.
|
||||
/// </summary>
|
||||
/// <param name="row">The encrypted row to replicate.</param>
|
||||
/// <param name="ct">A token to cancel the operation.</param>
|
||||
/// <returns>A task that completes when the row has been published.</returns>
|
||||
Task PublishAsync(StoredSecret row, CancellationToken ct);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace ZB.MOM.WW.Secrets.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// The consumer-facing read seam: resolves the current plaintext of a secret by name. This is
|
||||
/// the surface application code depends on to fetch secrets at runtime; every resolution is
|
||||
/// audited (the access, never the value).
|
||||
/// </summary>
|
||||
public interface ISecretResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves and decrypts the current plaintext for <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The secret to resolve.</param>
|
||||
/// <param name="ct">A token to cancel the operation.</param>
|
||||
/// <returns>
|
||||
/// The decrypted plaintext, or <c>null</c> if the secret is absent or tombstoned.
|
||||
/// </returns>
|
||||
Task<string?> GetAsync(SecretName name, CancellationToken ct);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
namespace ZB.MOM.WW.Secrets.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Persists encrypted secret rows and exposes the read, list, and replication operations the
|
||||
/// core services build on. The store only ever handles the encrypted <see cref="StoredSecret"/>
|
||||
/// representation and its safe <see cref="SecretMetadata"/> projection — it never sees plaintext.
|
||||
/// Deletes are soft (tombstones) so removals can propagate across a cluster.
|
||||
/// </summary>
|
||||
public interface ISecretStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the row for <paramref name="name"/>, including a tombstoned row, or <c>null</c>
|
||||
/// if no row exists.
|
||||
/// </summary>
|
||||
/// <param name="name">The secret to fetch.</param>
|
||||
/// <param name="ct">A token to cancel the operation.</param>
|
||||
/// <returns>The stored row, or <c>null</c> if absent.</returns>
|
||||
Task<StoredSecret?> GetAsync(SecretName name, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the row, or overwrites an existing row in place, bumping its revision and
|
||||
/// updated timestamp.
|
||||
/// </summary>
|
||||
/// <param name="row">The encrypted row to persist.</param>
|
||||
/// <param name="ct">A token to cancel the operation.</param>
|
||||
/// <returns>A task that completes when the row is persisted.</returns>
|
||||
Task UpsertAsync(StoredSecret row, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Soft-deletes (tombstones) the row for <paramref name="name"/> so the deletion can
|
||||
/// propagate to peers.
|
||||
/// </summary>
|
||||
/// <param name="name">The secret to tombstone.</param>
|
||||
/// <param name="actor">The principal to record as <c>updated_by</c>, if known.</param>
|
||||
/// <param name="ct">A token to cancel the operation.</param>
|
||||
/// <returns><c>true</c> if a row was tombstoned; <c>false</c> if the row was absent.</returns>
|
||||
Task<bool> DeleteAsync(SecretName name, string? actor, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Lists the safe metadata projection for all secrets — never ciphertext.
|
||||
/// </summary>
|
||||
/// <param name="includeDeleted">
|
||||
/// When <c>true</c>, tombstoned rows are included; otherwise they are omitted.
|
||||
/// </param>
|
||||
/// <param name="ct">A token to cancel the operation.</param>
|
||||
/// <returns>The metadata for the matching secrets.</returns>
|
||||
Task<IReadOnlyList<SecretMetadata>> ListAsync(bool includeDeleted, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a compact manifest — name, revision, updated timestamp, and tombstone flag per
|
||||
/// secret — used for cluster anti-entropy reconciliation without exchanging ciphertext.
|
||||
/// </summary>
|
||||
/// <param name="ct">A token to cancel the operation.</param>
|
||||
/// <returns>The manifest entries for all secrets.</returns>
|
||||
Task<IReadOnlyList<SecretManifestEntry>> GetManifestAsync(CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Applies a row received from a peer using last-writer-wins on
|
||||
/// (<see cref="StoredSecret.UpdatedUtc"/>, <see cref="StoredSecret.Revision"/>). A row that
|
||||
/// is not newer than the local row is ignored.
|
||||
/// </summary>
|
||||
/// <param name="row">The replicated encrypted row to apply.</param>
|
||||
/// <param name="ct">A token to cancel the operation.</param>
|
||||
/// <returns>A task that completes when the row has been applied or ignored.</returns>
|
||||
Task ApplyReplicatedAsync(StoredSecret row, CancellationToken ct);
|
||||
}
|
||||
Reference in New Issue
Block a user