Documentation
¶
Overview ¶
Package vault defines the SPI for credential and secret storage.
The vault stores sensitive values (API keys, OAuth tokens, service account credentials) and injects them into connector execution requests without ever exposing them to agents. Agents see only a credential reference ID; the vault resolves the actual secret at execution time.
The built-in implementation encrypts secrets at rest using AES-GCM with a key stored in the environment. Alternative implementations can delegate to HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, etc.
Index ¶
- Constants
- Variables
- func IsEncrypted(meta Metadata) bool
- func IsNotFound(err error) bool
- type DenyPlaintextVault
- func (v *DenyPlaintextVault) Delete(_ context.Context, path string) error
- func (v *DenyPlaintextVault) Get(_ context.Context, path string) (Secret, error)
- func (v *DenyPlaintextVault) List(_ context.Context) ([]Entry, error)
- func (v *DenyPlaintextVault) Put(_ context.Context, path string, _ []byte, _ Metadata) error
- type EncryptedVault
- func (v *EncryptedVault) Delete(ctx context.Context, path string) error
- func (v *EncryptedVault) Get(ctx context.Context, path string) (Secret, error)
- func (v *EncryptedVault) List(ctx context.Context) ([]Entry, error)
- func (v *EncryptedVault) Put(ctx context.Context, path string, value []byte, meta Metadata) error
- type Entry
- type FileVault
- func (fv *FileVault) Delete(_ context.Context, path string) error
- func (fv *FileVault) Get(_ context.Context, path string) (Secret, error)
- func (fv *FileVault) HasContents() bool
- func (fv *FileVault) List(_ context.Context) ([]Entry, error)
- func (fv *FileVault) Names() []string
- func (fv *FileVault) Put(_ context.Context, path string, value []byte, meta Metadata) error
- func (fv *FileVault) Salt() ([]byte, error)
- func (fv *FileVault) SetVerification(ciphertext []byte) error
- func (fv *FileVault) Verification() []byte
- type LockableVault
- func (l *LockableVault) Delete(ctx context.Context, path string) error
- func (l *LockableVault) Get(ctx context.Context, path string) (Secret, error)
- func (l *LockableVault) Inner() Vault
- func (l *LockableVault) List(ctx context.Context) ([]Entry, error)
- func (l *LockableVault) Put(ctx context.Context, path string, value []byte, meta Metadata) error
- func (l *LockableVault) Set(v Vault)
- type MemVault
- type Metadata
- type PostgresVault
- func (v *PostgresVault) Delete(ctx context.Context, path string) error
- func (v *PostgresVault) Get(ctx context.Context, path string) (Secret, error)
- func (v *PostgresVault) List(ctx context.Context) ([]Entry, error)
- func (v *PostgresVault) Put(ctx context.Context, path string, value []byte, meta Metadata) error
- type Secret
- type State
- type UserScopedVault
- func (v *UserScopedVault) Delete(ctx context.Context, path string) error
- func (v *UserScopedVault) Get(ctx context.Context, path string) (Secret, error)
- func (v *UserScopedVault) List(ctx context.Context) ([]Entry, error)
- func (v *UserScopedVault) Put(ctx context.Context, path string, value []byte, meta Metadata) error
- type Vault
Constants ¶
const CurrentFileFormatVersion = 1
CurrentFileFormatVersion is the format version this build writes. Existing files without a version field are treated as v0 and upgraded on the next write.
const EncryptedLabel = "encrypted"
EncryptedLabel is the metadata label key that indicates a secret's value is encrypted with a user's KEK. Consumers must check this label before attempting to use the raw value.
const VerificationPlaintext = "aileron-vault-v1"
VerificationPlaintext is the well-known string the FileVault encrypts under the KEK on first creation. On unlock, decrypting the stored verification ciphertext must yield this exact value; any other result means wrong passphrase or tampered vault.
Variables ¶
ErrCredentialUnavailable indicates that a credential could not be retrieved from the vault — the vault session may be locked, the escrow stale, or the KEK invalid. Callers should prompt the user to unlock their vault.
var ErrPassphraseRejected = errors.New("vault: passphrase rejected")
ErrPassphraseRejected is returned when a supplied passphrase does not derive the KEK that the verification blob was encrypted with. Callers prompt for a retry on this error.
var ErrVaultExists = errors.New("vault: already exists")
ErrVaultExists is returned by Init when the target path already has a vault file. The caller should fall through to Unlock.
var ErrVaultMissing = errors.New("vault: not initialised")
ErrVaultMissing is returned by Unlock when the vault file doesn't exist yet. The caller should fall through to Init.
var ErrVaultTampered = errors.New("vault: tampered (verification blob mismatch)")
ErrVaultTampered is returned when the verification blob decrypts successfully but contains an unexpected plaintext, indicating the file was modified outside of Aileron. Callers refuse to start.
Functions ¶
func IsEncrypted ¶
IsEncrypted reports whether a secret's metadata indicates it was encrypted with a user KEK.
func IsNotFound ¶
IsNotFound returns true if the error indicates a secret was not found.
Types ¶
type DenyPlaintextVault ¶
type DenyPlaintextVault struct{}
DenyPlaintextVault is a Vault implementation that refuses to return credential data. It is used as the pipeline's vault when source connectors execute inside the TEE enclave — any attempt to retrieve credentials on the host is a zero-knowledge violation and must fail loudly rather than silently leak plaintext.
func NewDenyPlaintextVault ¶
func NewDenyPlaintextVault() *DenyPlaintextVault
NewDenyPlaintextVault returns a vault that blocks all credential access.
func (*DenyPlaintextVault) Delete ¶
func (v *DenyPlaintextVault) Delete(_ context.Context, path string) error
type EncryptedVault ¶
type EncryptedVault struct {
// contains filtered or unexported fields
}
EncryptedVault is a decorator that encrypts secret values before storing them in the inner vault and decrypts them on retrieval. Metadata is stored unencrypted (it contains no sensitive data).
This enables zero-knowledge storage: the inner vault (database, file system, cloud service) holds only ciphertext. Without the KEK, the stored values are meaningless.
func NewEncryptedVault ¶
func NewEncryptedVault(inner Vault, kek []byte) (*EncryptedVault, error)
NewEncryptedVault wraps an existing vault with AES-256-GCM envelope encryption. The kek (Key Encryption Key) must be exactly 32 bytes (256 bits).
func (*EncryptedVault) Delete ¶
func (v *EncryptedVault) Delete(ctx context.Context, path string) error
Delete removes a secret from the inner vault.
func (*EncryptedVault) List ¶
func (v *EncryptedVault) List(ctx context.Context) ([]Entry, error)
List delegates to the inner vault. Metadata is stored unencrypted, so the encryption decorator passes the entries through unchanged. The caller never receives ciphertext from this path because Entry omits the value bytes by construction.
type Entry ¶
Entry is a vault listing row: the path plus its plaintext metadata, without the encrypted value.
type FileVault ¶
type FileVault struct {
// contains filtered or unexported fields
}
FileVault is a Vault backed by a JSON file on disk. Secrets are stored as raw bytes (callers should wrap with EncryptedVault for encryption). The file also stores an Argon2id salt for key derivation.
func NewFileVault ¶
NewFileVault opens or creates a vault file at path. If the file exists, its contents are loaded into memory. If not, an empty vault is created.
func (*FileVault) HasContents ¶
HasContents reports whether the vault file held any data when it was opened. False means the file did not exist (or contained an empty schema), so the caller is in the "first run" path and should populate the salt + verification blob before using it.
func (*FileVault) List ¶
List returns plaintext metadata for every entry. The encrypted `Value` is not returned — listing must work without decrypting any secret per ADR-0011.
func (*FileVault) SetVerification ¶
SetVerification stores the KEK-encrypted verification blob and marks the file as the current format version. Called once on vault creation; subsequent writes preserve it. Returns an error when called on a vault that already has a verification blob (the caller should use Verification() to read it instead, never overwrite it — overwriting would silently invalidate every existing encrypted secret).
func (*FileVault) Verification ¶
Verification returns the ciphertext stored under the `verification` key, or nil if the vault hasn't been initialised with one. Callers validate by decrypting with the candidate KEK and comparing the plaintext to VerificationPlaintext.
type LockableVault ¶
type LockableVault struct {
// contains filtered or unexported fields
}
LockableVault wraps a Vault and lets the daemon construct its dependency graph before the underlying vault has been unlocked.
When the inner vault is nil, mutating operations (Get / Put / Delete) return ErrCredentialUnavailable — the canonical "credential cannot be retrieved, prompt the user to unlock" signal already plumbed through the binding-resolution path. List returns an empty slice with no error, mirroring the property documented on [Vault.List]: listing must succeed without decryption, and a locked vault legitimately has nothing to enumerate yet.
Per ADR-0011 the daemon may run with the vault locked: Aileron accepts requests, vault-needing endpoints return 423 Locked, and the user unlocks via the webapp modal (#429). LockableVault is the piece that makes the apiServer's static dependency graph (binding store, executor, source registries) work in that state — every component holds the same Vault reference; on unlock, [Set] swaps the inner vault in atomically and operations resume.
The receiver is safe for concurrent use.
func NewLockableVault ¶
func NewLockableVault() *LockableVault
NewLockableVault returns a LockableVault with no inner vault. Until [Set] is called, all credential operations return ErrCredentialUnavailable; List returns an empty slice.
func (*LockableVault) Delete ¶
func (l *LockableVault) Delete(ctx context.Context, path string) error
Delete implements Vault. Returns ErrCredentialUnavailable when no inner vault is set.
func (*LockableVault) Get ¶
Get implements Vault. Returns ErrCredentialUnavailable when no inner vault is set.
func (*LockableVault) Inner ¶
func (l *LockableVault) Inner() Vault
Inner returns the current inner vault, or nil if not yet unlocked. Exposed so the apiServer can determine "is the vault unlocked" without imitating the LockableVault contract elsewhere.
func (*LockableVault) List ¶
func (l *LockableVault) List(ctx context.Context) ([]Entry, error)
List implements Vault. Returns an empty slice when no inner vault is set — listing is documented (per [Vault.List]) to succeed without decryption, and a locked wrapper has nothing to enumerate.
func (*LockableVault) Put ¶
Put implements Vault. Returns ErrCredentialUnavailable when no inner vault is set.
func (*LockableVault) Set ¶
func (l *LockableVault) Set(v Vault)
Set installs the inner vault, transitioning the wrapper from "locked" to "unlocked." Subsequent calls overwrite the previous inner — used in tests; in production the daemon sets the inner exactly once per session.
type MemVault ¶
type MemVault struct {
// contains filtered or unexported fields
}
MemVault is a thread-safe in-memory implementation of the Vault interface. Suitable for development and testing only.
type Metadata ¶
type Metadata struct {
// Type classifies the credential (e.g. "api_key", "oauth_refresh_token").
Type string
Environment string
// Labels are arbitrary key-value pairs for organization.
Labels map[string]string
}
Metadata carries non-secret attributes stored alongside a secret.
type PostgresVault ¶
type PostgresVault struct {
// contains filtered or unexported fields
}
PostgresVault is a PostgreSQL-backed implementation of the Vault interface. Stores secrets as byte values with JSON metadata. The table name is configurable to support separate storage for user secrets (vault_secrets) and infrastructure secrets (system_vault_secrets, ADR-0020).
func NewPostgresVault ¶
func NewPostgresVault(pool *pgxpool.Pool) *PostgresVault
NewPostgresVault creates a Postgres-backed vault using the given connection pool. Secrets are stored in the vault_secrets table.
func NewPostgresVaultForTable ¶
func NewPostgresVaultForTable(pool *pgxpool.Pool, table string) *PostgresVault
NewPostgresVaultForTable creates a Postgres-backed vault that reads and writes to the specified table. The table must have the same schema as vault_secrets (path, value, metadata, created_at, updated_at).
func (*PostgresVault) Delete ¶
func (v *PostgresVault) Delete(ctx context.Context, path string) error
type State ¶
type State int
State reports whether a vault file exists and is in a usable shape without committing to a passphrase.
const ( // StateMissing — no file at the given path; the caller is on the // first-run path and must initialise the vault. StateMissing State = iota // StateLegacy — file exists but predates ADR-0011 (no // verification blob). The caller can still open it with the // legacy heuristic, but should encourage migration. StateLegacy // StateReady — file exists with a verification blob; ready for // passphrase-validated unlock. StateReady )
type UserScopedVault ¶
type UserScopedVault struct {
// contains filtered or unexported fields
}
UserScopedVault is a decorator that applies per-user encryption to vault operations. Both Put and Get require a KEK — plaintext storage and retrieval are not supported. All vault secrets are encrypted.
Secrets are tagged with the metadata label "encrypted": "true".
func NewUserScopedVault ¶
func NewUserScopedVault(inner Vault, kek []byte) *UserScopedVault
NewUserScopedVault wraps a vault with optional per-user encryption. If kek is nil, all operations pass through unchanged.
func (*UserScopedVault) Delete ¶
func (v *UserScopedVault) Delete(ctx context.Context, path string) error
Delete removes a secret from the inner vault.
func (*UserScopedVault) Get ¶
Get retrieves and decrypts a secret. Requires a KEK — all vault secrets are encrypted and plaintext storage is not supported.
type Vault ¶
type Vault interface {
// Get retrieves a secret by its vault path.
Get(ctx context.Context, path string) (Secret, error)
// Put stores a secret at the given path, creating or overwriting it.
Put(ctx context.Context, path string, value []byte, meta Metadata) error
// Delete permanently removes a secret.
Delete(ctx context.Context, path string) error
// List returns plaintext metadata for every entry in the vault. The
// credential bytes are NOT returned — listing must work without
// decrypting any value, so the operation succeeds on a locked vault.
// Per ADR-0011, this is the load-bearing property that makes
// `aileron binding list` and the binding-resolution lookup work
// without prompting for a passphrase. Order is unspecified.
List(ctx context.Context) ([]Entry, error)
}
Vault stores and retrieves secrets by path.
func Init ¶
Init creates a new vault at path, derives a KEK from the passphrase + a freshly-generated salt, encrypts the well-known VerificationPlaintext under that KEK, and writes the file. The returned Vault is the EncryptedVault wrapping the new FileVault — ready to accept Put/Get calls.
Returns ErrVaultExists if a non-legacy file already exists at path.
func OpenOrCreate ¶
OpenOrCreate is a convenience that runs Init when the vault is missing and Unlock when it already exists. Returns the active vault and a boolean reporting whether this call performed the initial creation. Useful for first-launch flows where the same passphrase is offered for both paths.
func Unlock ¶
Unlock opens an existing vault at path. The supplied passphrase is run through Argon2id with the file's salt; the resulting KEK decrypts the verification blob. On success, the returned Vault is the EncryptedVault wrapping the loaded FileVault.
- ErrVaultMissing if the file doesn't exist.
- ErrPassphraseRejected if the verification blob decryption fails (wrong passphrase, or AEAD tag mismatch).
- ErrVaultTampered if decryption succeeds but the plaintext is not the expected constant.
Legacy vaults (created before ADR-0011 added the verification blob) are accepted with a fallback heuristic — the call succeeds if at least one stored secret decrypts cleanly under the derived KEK, OR if the vault is empty (in which case any passphrase is accepted; the next Init-equivalent write will populate the verification blob).