secret

package
v0.5.0 Latest Latest
Warning

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

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

Documentation

Overview

Package secret is the relay's unified secret-resolution layer: a backend-agnostic Ref pointing at a credential, and a Resolver that turns it into plaintext. It is the foundation for pluggable secret backends — today the built-in env and stored (AES-GCM in Postgres) resolvers, later vendorable subpackages for Vault, AWS Secrets Manager, etc.

Every secret the relay handles — upstream HostKey credentials AND the relay's own native secrets (e.g. object-store keys) — resolves through this one seam, so adding a backend is additive rather than a rewrite.

pkg purity: this package imports only pkg/crypto. The stored resolver's persistence is injected via the Store interface, whose Postgres implementation lives in app/ — keeping pkg/secret vendorable.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type EnvResolver

type EnvResolver struct{}

EnvResolver resolves KindEnv refs from the process environment. Pure — no persistence, no master key. An unset or empty variable is a loud error: a secret that silently resolves empty is worse than a failure.

func (EnvResolver) Resolve

func (EnvResolver) Resolve(_ context.Context, ref Ref) ([]byte, error)

type Kind

type Kind string

Kind identifies which backend resolves a Ref.

const (
	// KindEnv reads plaintext from an OS environment variable. No
	// ciphertext is ever persisted.
	KindEnv Kind = "env"
	// KindStored decrypts AES-GCM ciphertext held in the relay's own
	// store (Postgres today) using the master key.
	KindStored Kind = "stored"
	// KindOAuth resolves an OAuth credential: the AES-GCM-stored value is an
	// oauth token blob (access + refresh + expiry); the resolver returns the
	// access token, refreshing via the refresh token when it has expired. The
	// stored ciphertext lives in the same secret_values table as KindStored
	// (Ref.ID), so it is rotated and decrypted by the same machinery; Ref.Provider
	// selects the oauth:<provider> config (endpoints/client) used to refresh.
	KindOAuth Kind = "oauth"
	// KindAWS fetches a secret from AWS Secrets Manager. Fetch-only: the
	// secret lives in AWS, relay holds the resolved value in memory only.
	// Locator is Path = "<secretName>[:<jsonKey>]".
	KindAWS Kind = "aws"
	// KindBitwarden fetches a secret from a Bitwarden/Vaultwarden server,
	// decrypted client-side. Fetch-only; held in memory only. Locator is
	// Path = "<itemNameOrID>[/<field>]" (field defaults to "password").
	KindBitwarden Kind = "bitwarden"
	// KindGCP fetches a secret version from GCP Secret Manager. Fetch-only,
	// in-memory. Locator is Path = "<secretName>[:<version>]" (version
	// defaults to "latest"); project comes from resolver config.
	KindGCP Kind = "gcp"
	// KindAzure fetches a secret from an Azure Key Vault. Fetch-only,
	// in-memory. Locator is Path = "<secretName>[/<version>]"; the vault
	// URL comes from resolver config.
	KindAzure Kind = "azure"
	// KindOnePassword resolves a secret via the official 1Password SDK
	// (service-account token). Fetch-only, in-memory. Locator is Path =
	// "op://<vault>/<item>/<field>".
	KindOnePassword Kind = "onepassword"
)

type MaxVersioner

type MaxVersioner interface {
	MaxKeyVersion(ctx context.Context) (int32, error)
}

MaxVersioner optionally reports the highest key_version stored, so the resolver can align its in-memory version at boot.

type Ref

type Ref struct {
	Kind Kind `json:"kind"`

	// Env is the OS variable name when Kind == KindEnv.
	Env string `json:"env,omitempty"`

	// ID is the backend key when Kind == KindStored (and future
	// id-addressed backends): the secret_values row id.
	ID string `json:"id,omitempty"`

	// Path is the locator for path-addressed external backends (KindAWS,
	// KindBitwarden, …): an opaque per-backend reference string the
	// resolver parses (e.g. "prod/openai-key:apiKey", "openai-key/password").
	Path string `json:"path,omitempty"`

	// Provider names the oauth:<provider> settings section whose config
	// (endpoints, client id) refreshes this credential. Meaningful only when
	// Kind == KindOAuth.
	Provider string `json:"provider,omitempty"`
}

Ref is a backend-agnostic pointer to a secret. It is JSON-serializable so it can live inside a spec or settings JSONB blob. Exactly one locator field is meaningful per Kind.

func (Ref) Validate

func (r Ref) Validate() error

Validate checks the Ref has the locator its Kind requires.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry dispatches a Ref to the Resolver registered for its Kind. Built once at boot (Register is not safe for concurrent use with Resolve); Resolve is safe for concurrent use thereafter.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty registry. Register backends before use.

func (*Registry) Register

func (r *Registry) Register(kind Kind, res Resolver)

Register binds a Resolver to a Kind, replacing any prior registration.

func (*Registry) Resolve

func (r *Registry) Resolve(ctx context.Context, ref Ref) ([]byte, error)

Resolve validates the Ref and dispatches to the registered Resolver. Returns a clear error when no backend is registered for the Kind — e.g. a "stored" ref in a minimal build that wired only env.

type Resolver

type Resolver interface {
	Resolve(ctx context.Context, ref Ref) ([]byte, error)
}

Resolver turns a Ref into plaintext. Implementations handle one Kind and register with a Registry. Resolution is expected off the hot path (load-time / boot-time), so implementations may do I/O.

type RotateResult

type RotateResult struct {
	Rotated    int
	NewVersion int32
}

RotateResult is the outcome of a successful Rotate.

type Rotator

type Rotator interface {
	// Rotate re-encrypts every stored value within one transaction,
	// stamping newKeyVersion. reencrypt maps old (ct, nonce) → new.
	Rotate(ctx context.Context, newKeyVersion int32,
		reencrypt func(ct, nonce []byte) (newCt, newNonce []byte, err error)) (rotated int, err error)
}

Rotator is the optional transactional re-encryption seam. The store implements the transaction boundary; pkg/secret supplies the crypto via the reencrypt callback, so all key material stays in this package.

type Store

type Store interface {
	// Get returns the ciphertext, nonce, and key version for id, or an
	// error if absent.
	Get(ctx context.Context, id string) (ciphertext, nonce []byte, keyVersion int32, err error)
	// Put writes (overwriting) the ciphertext for id.
	Put(ctx context.Context, id string, ciphertext, nonce []byte, keyVersion int32) error
	// Delete removes the secret at id. Deleting a missing id is not an
	// error (idempotent cleanup).
	Delete(ctx context.Context, id string) error
}

Store is the persistence seam for stored-secret ciphertext. Its Postgres implementation lives in app/ (keeping pkg/secret vendorable). Ciphertext + nonce are AES-GCM outputs; key_version records which master-key generation encrypted the row, for rotation.

type StoredResolver

type StoredResolver struct {
	// contains filtered or unexported fields
}

StoredResolver decrypts AES-GCM ciphertext from a Store using the master key. The key is held behind an RWMutex so rotation (SetMasterKey) can swap it on a live process without a restart, mirroring the prior hostkey-store behavior.

func NewStoredResolver

func NewStoredResolver(store Store, masterKey []byte, keyVersion int32) *StoredResolver

NewStoredResolver constructs a resolver over store, encrypting new secrets at keyVersion with masterKey.

func (*StoredResolver) Create

func (s *StoredResolver) Create(ctx context.Context, id string, plaintext []byte) (Ref, error)

Create encrypts plaintext under the current master key and persists it at id, returning a stored Ref. Overwrites any existing secret at id.

func (*StoredResolver) Delete

func (s *StoredResolver) Delete(ctx context.Context, id string) error

Delete removes the stored secret at id (idempotent).

func (*StoredResolver) KeyVersion

func (s *StoredResolver) KeyVersion() int32

KeyVersion returns the current in-memory master-key version.

func (*StoredResolver) LoadKeyVersion

func (s *StoredResolver) LoadKeyVersion(ctx context.Context) error

LoadKeyVersion aligns the in-memory version to the store's maximum, so new secrets are tagged with the generation operators last rotated to. No-op when the store doesn't report a max or the table is empty.

func (*StoredResolver) Resolve

func (s *StoredResolver) Resolve(ctx context.Context, ref Ref) ([]byte, error)

func (*StoredResolver) Rotate

func (s *StoredResolver) Rotate(ctx context.Context, newKey []byte) (RotateResult, error)

Rotate re-encrypts every stored secret with newKey in a single store transaction, then swaps the live master key so subsequent Resolve/Create use it without a restart. The caller persists newKey to the deployment's RELAY_MASTER_KEY so future boots can decrypt.

func (*StoredResolver) SetMasterKey

func (s *StoredResolver) SetMasterKey(masterKey []byte, keyVersion int32)

SetMasterKey swaps the active master key + version (post-rotation).

type StoredRow

type StoredRow struct {
	ID         string
	Ciphertext []byte
	Nonce      []byte
	KeyVersion int32
}

StoredRow is one stored secret's ciphertext, for rotation enumeration.

type Writer

type Writer interface {
	// Create persists plaintext under id and returns a Ref resolving to
	// it. Overwrites any existing secret at id.
	Create(ctx context.Context, id string, plaintext []byte) (Ref, error)
}

Writer is the optional create side: a backend that can persist a new secret and hand back a Ref addressing it. env-style backends are read-only and do not implement it.

Directories

Path Synopsis
Package aws resolves secret.KindAWS refs by fetching from AWS Secrets Manager.
Package aws resolves secret.KindAWS refs by fetching from AWS Secrets Manager.
Package azure resolves secret.KindAzure refs by fetching from Azure Key Vault.
Package azure resolves secret.KindAzure refs by fetching from Azure Key Vault.
Package bitwarden resolves KindBitwarden secrets by fetching encrypted vault data from Vaultwarden/Bitwarden and decrypting client-side.
Package bitwarden resolves KindBitwarden secrets by fetching encrypted vault data from Vaultwarden/Bitwarden and decrypting client-side.
Package gcp resolves secret.KindGCP refs by fetching from GCP Secret Manager.
Package gcp resolves secret.KindGCP refs by fetching from GCP Secret Manager.
Package oauth resolves OAuth credentials for the relay's secret layer.
Package oauth resolves OAuth credentials for the relay's secret layer.
Package onepassword resolves secret.KindOnePassword refs via the official 1Password Go SDK and a service-account token.
Package onepassword resolves secret.KindOnePassword refs via the official 1Password Go SDK and a service-account token.

Jump to

Keyboard shortcuts

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