vault

package
v0.0.0-...-27a64c2 Latest Latest
Warning

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

Go to latest
Published: May 19, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

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

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

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

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

View Source
var ErrCredentialUnavailable = errors.New("vault: credential unavailable")

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.

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

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

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

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

func IsEncrypted(meta Metadata) bool

IsEncrypted reports whether a secret's metadata indicates it was encrypted with a user KEK.

func IsNotFound

func IsNotFound(err error) bool

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

func (*DenyPlaintextVault) Get

func (v *DenyPlaintextVault) Get(_ context.Context, path string) (Secret, error)

func (*DenyPlaintextVault) List

func (v *DenyPlaintextVault) List(_ context.Context) ([]Entry, error)

List returns no entries. The deny-plaintext vault is only consulted inside the enclave for credential resolution; listing has no caller in the host process and the safe default is empty.

func (*DenyPlaintextVault) Put

func (v *DenyPlaintextVault) Put(_ context.Context, path string, _ []byte, _ Metadata) 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) Get

func (v *EncryptedVault) Get(ctx context.Context, path string) (Secret, error)

Get retrieves a secret from the inner vault and decrypts its value.

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.

func (*EncryptedVault) Put

func (v *EncryptedVault) Put(ctx context.Context, path string, value []byte, meta Metadata) error

Put encrypts the value and stores it in the inner vault.

type Entry

type Entry struct {
	Path     string
	Metadata Metadata
}

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

func NewFileVault(path string) (*FileVault, error)

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) Delete

func (fv *FileVault) Delete(_ context.Context, path string) error

func (*FileVault) Get

func (fv *FileVault) Get(_ context.Context, path string) (Secret, error)

func (*FileVault) HasContents

func (fv *FileVault) HasContents() bool

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

func (fv *FileVault) List(_ context.Context) ([]Entry, error)

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) Names

func (fv *FileVault) Names() []string

Names returns the paths of all stored secrets.

func (*FileVault) Put

func (fv *FileVault) Put(_ context.Context, path string, value []byte, meta Metadata) error

func (*FileVault) Salt

func (fv *FileVault) Salt() ([]byte, error)

Salt returns the stored salt, generating one if it doesn't exist yet.

func (*FileVault) SetVerification

func (fv *FileVault) SetVerification(ciphertext []byte) error

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

func (fv *FileVault) Verification() []byte

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

func (l *LockableVault) Get(ctx context.Context, path string) (Secret, error)

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

func (l *LockableVault) Put(ctx context.Context, path string, value []byte, meta Metadata) error

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.

func NewMemVault

func NewMemVault() *MemVault

NewMemVault returns an empty in-memory vault.

func (*MemVault) Delete

func (v *MemVault) Delete(_ context.Context, path string) error

func (*MemVault) Get

func (v *MemVault) Get(_ context.Context, path string) (Secret, error)

func (*MemVault) List

func (v *MemVault) List(_ context.Context) ([]Entry, error)

func (*MemVault) Put

func (v *MemVault) Put(_ context.Context, path string, value []byte, meta Metadata) error

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

func (*PostgresVault) Get

func (v *PostgresVault) Get(ctx context.Context, path string) (Secret, error)

func (*PostgresVault) List

func (v *PostgresVault) List(ctx context.Context) ([]Entry, error)

func (*PostgresVault) Put

func (v *PostgresVault) Put(ctx context.Context, path string, value []byte, meta Metadata) error

type Secret

type Secret struct {
	Path     string
	Value    []byte
	Metadata Metadata
}

Secret is a resolved secret value.

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
)

func CheckState

func CheckState(path string) (State, error)

CheckState reports the State of a vault file at path without touching the file's contents beyond the bytes needed to discover the verification blob.

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

func (v *UserScopedVault) Get(ctx context.Context, path string) (Secret, error)

Get retrieves and decrypts a secret. Requires a KEK — all vault secrets are encrypted and plaintext storage is not supported.

func (*UserScopedVault) List

func (v *UserScopedVault) List(ctx context.Context) ([]Entry, error)

List delegates to the inner vault. No KEK is required because metadata is plaintext.

func (*UserScopedVault) Put

func (v *UserScopedVault) Put(ctx context.Context, path string, value []byte, meta Metadata) error

Put stores a secret encrypted with the user's KEK. Refuses to store plaintext — returns an error if no KEK is available.

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

func Init(path, passphrase string) (Vault, error)

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

func OpenOrCreate(path, passphrase string) (Vault, bool, error)

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

func Unlock(path, passphrase string) (Vault, error)

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.

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).

Jump to

Keyboard shortcuts

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