secrets

package
v0.1.0-alpha.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

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

View Source
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

View Source
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.

View Source
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

func GenerateDataKey() ([]byte, error)

GenerateDataKey returns a fresh random 32-byte cluster data key.

func GeneratePassphrase

func GeneratePassphrase() (string, error)

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

func NewKeyring(dataKey []byte, keyVersion uint32) (*Keyring, error)

NewKeyring wraps a plaintext data key. It copies the key so the caller may zero its own buffer.

func (*Keyring) DataKey

func (k *Keyring) DataKey() []byte

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

func (k *Keyring) KeyVersion() uint32

KeyVersion returns the data key version.

func (*Keyring) Sealer

func (k *Keyring) Sealer() (Sealer, error)

Sealer returns a Sealer bound to the current 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.

func NewSealer

func NewSealer(dataKey []byte, keyVersion uint32) (Sealer, error)

NewSealer wraps a 32-byte data key.

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

func NewUnsealedVault(kr *Keyring) (*Vault, error)

NewUnsealedVault returns a Vault already holding kr. A nil keyring yields a sealed Vault, which is what a restarted node gets from Bootstrap.

func NewVault

func NewVault() *Vault

NewVault returns a sealed Vault.

func (*Vault) DataKey

func (v *Vault) DataKey() []byte

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

func (v *Vault) Install(kr *Keyring) error

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

func (v *Vault) KeyVersion() uint32

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.

func (*Vault) Unsealed

func (v *Vault) Unsealed() bool

Unsealed reports whether the cluster data key is available. A nil Vault reports sealed rather than panicking: every method here is nil-safe on purpose, so a call site that misses a vault degrades to "cannot touch secrets" instead of crashing the node.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL