Documentation
¶
Overview ¶
Package secrets implements envelope encryption for cluster secrets (spec §3.13, design §2.10):
recovery passphrase ──argon2id──▶ KEK ──AES-GCM──▶ sealed data key (in Raft) data key (memory only) ──AES-GCM──▶ every secret value (in Raft)
Control nodes hold the plaintext data key in memory; a joining control node receives it from the leader over mTLS. Restore from backup requires the passphrase.
Index ¶
- Constants
- Variables
- func GenerateDataKey() ([]byte, error)
- func GeneratePassphrase() (string, error)
- func SealDataKey(dataKey []byte, passphrase string, keyVersion uint32) (*zatterav1.ClusterKeyMaterial, error)
- func UnsealDataKey(m *zatterav1.ClusterKeyMaterial, passphrase string) ([]byte, error)
- type Keyring
- type Sealer
- type Vault
- func (v *Vault) DataKey() []byte
- func (v *Vault) Install(kr *Keyring) error
- func (v *Vault) KeyVersion() uint32
- func (v *Vault) OnUnseal(fn func())
- func (v *Vault) Open(val *zatterav1.EncryptedValue) ([]byte, error)
- func (v *Vault) Seal(plaintext []byte) (*zatterav1.EncryptedValue, error)
- func (v *Vault) UnsealWithPassphrase(m *zatterav1.ClusterKeyMaterial, passphrase string) error
- func (v *Vault) Unsealed() bool
Constants ¶
const ( ArgonTime = argonTime ArgonMemoryKiB = argonMemoryKiB ArgonThreads = argonThreads ArgonKeyLen = keyLen ArgonSaltLen = saltLen )
Exported argon2id parameters, reused by password hashing (T-04) so it stays in lockstep with the cluster-key KDF.
Variables ¶
var ErrSealed = errors.New("secrets: cluster key is sealed")
ErrSealed is returned by a Vault that does not (yet) hold the cluster data key. Callers that can degrade gracefully should check Unsealed() first and report a clear FailedPrecondition rather than surfacing this raw.
var ErrSealedDataInvalid = errors.New("secrets: wrong passphrase or corrupted key material")
ErrSealedDataInvalid covers wrong passphrase and corrupted material — AES-GCM cannot distinguish them.
Functions ¶
func GenerateDataKey ¶
GenerateDataKey returns a fresh random 32-byte cluster data key.
func GeneratePassphrase ¶
GeneratePassphrase returns a human-typable recovery passphrase (8 groups of 4 base32 chars ≈ 160 bits).
func SealDataKey ¶
func SealDataKey(dataKey []byte, passphrase string, keyVersion uint32) (*zatterav1.ClusterKeyMaterial, error)
SealDataKey encrypts the data key under a passphrase-derived KEK, producing the ClusterKeyMaterial stored in Raft.
func UnsealDataKey ¶
func UnsealDataKey(m *zatterav1.ClusterKeyMaterial, passphrase string) ([]byte, error)
UnsealDataKey recovers the data key from ClusterKeyMaterial + passphrase. Uses the argon2 parameters recorded in the material (forward compat).
Types ¶
type Keyring ¶
type Keyring struct {
// contains filtered or unexported fields
}
Keyring holds the cluster data key in memory only. Control nodes keep it for the lifetime of the process; it is never written to disk. A joining control node receives it from the leader over mTLS (M2); a restore from backup derives it from the recovery passphrase.
func NewKeyring ¶
NewKeyring wraps a plaintext data key. It copies the key so the caller may zero its own buffer.
func (*Keyring) DataKey ¶
DataKey returns a copy of the plaintext data key (for handing to a joining control node over mTLS). Handle with care; never log or persist it.
func (*Keyring) KeyVersion ¶
KeyVersion returns the data key version.
type Sealer ¶
type Sealer interface {
Seal(plaintext []byte) (*zatterav1.EncryptedValue, error)
Open(v *zatterav1.EncryptedValue) ([]byte, error)
}
Sealer encrypts/decrypts individual secret values with the cluster data key. The zero Sealer is unusable; obtain one via NewSealer.
type Vault ¶
type Vault struct {
// contains filtered or unexported fields
}
Vault holds the cluster keyring behind a stable, always-non-nil handle.
It exists because the data key is not available at every startup: only the process that bootstraps a cluster (or first-joins as a control node) is handed one, so a restarted node comes up sealed and must acquire the key later — from an operator's passphrase (`zattera unseal`) or from a control peer (T-112). Passing a nil Sealer around made that unrepresentable: every consumer had to nil-check, several construction sites skipped whole subsystems, and nothing could recover without a restart.
Vault implements Sealer, so a sealed cluster returns ErrSealed from Seal and Open instead of panicking on a nil interface. It is safe for concurrent use.
func NewUnsealedVault ¶
NewUnsealedVault returns a Vault already holding kr. A nil keyring yields a sealed Vault, which is what a restarted node gets from Bootstrap.
func (*Vault) DataKey ¶
DataKey returns a copy of the plaintext data key, or nil when sealed. Handle with care: it is handed to joining control nodes over mTLS and to nothing else.
func (*Vault) Install ¶
Install stores the keyring and derives a sealer. Installing over an already unsealed vault is a no-op: the data key is cluster-wide and immutable, so a second unseal (an operator racing the auto-unseal path, say) must not swap the sealer out from under in-flight work.
func (*Vault) KeyVersion ¶
KeyVersion returns the data key version, or 0 when sealed.
func (*Vault) OnUnseal ¶
func (v *Vault) OnUnseal(fn func())
OnUnseal registers fn to run when the vault is unsealed. If it already is, fn runs immediately. Used by subsystems that memoize something derived from the key and would otherwise stay broken after a late unseal.
func (*Vault) Open ¶
func (v *Vault) Open(val *zatterav1.EncryptedValue) ([]byte, error)
Open implements Sealer.
func (*Vault) Seal ¶
func (v *Vault) Seal(plaintext []byte) (*zatterav1.EncryptedValue, error)
Seal implements Sealer.
func (*Vault) UnsealWithPassphrase ¶
func (v *Vault) UnsealWithPassphrase(m *zatterav1.ClusterKeyMaterial, passphrase string) error
UnsealWithPassphrase derives the data key from the cluster key material and installs it. Returns ErrSealedDataInvalid for a wrong passphrase.