secrets

package
v2.8.1 Latest Latest
Warning

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

Go to latest
Published: Jun 4, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultMaxVersions = 32

DefaultMaxVersions caps how many versions a Pool may hold before Add returns ErrPoolFull. Operators can override per-pool via the `max_versions` DSL flag.

Variables

View Source
var (
	ErrSealKeyMissing    = errors.New("secrets: encryption key is empty")
	ErrSealKeyLength     = errors.New("secrets: encryption key must decode to 32 bytes")
	ErrSealedTruncated   = errors.New("secrets: sealed payload is truncated")
	ErrSealedVersion     = errors.New("secrets: unsupported sealed version")
	ErrSealedAuthFailure = errors.New("secrets: sealed payload failed authentication")
)
View Source
var (
	ErrDuplicateID   = errors.New("secrets: version id already present in pool")
	ErrPoolFull      = errors.New("secrets: pool is full")
	ErrInvalidWindow = errors.New("secrets: valid_until must be after valid_from")
	ErrEmptyID       = errors.New("secrets: version id is empty")
	ErrEmptyValue    = errors.New("secrets: version value is empty")
	ErrMissingFrom   = errors.New("secrets: version valid_from is missing")
)
View Source
var (
	ErrPoolExists   = errors.New("secrets: pool already registered")
	ErrPoolNotFound = errors.New("secrets: pool not found")
)
View Source
var (
	ErrInvalidSecret = errors.New("invalid secret")
)
View Source
var ErrSecretRef = errors.New("invalid secret reference")

Functions

func LoadRef

func LoadRef(ref string) ([]byte, error)

LoadRef loads a secret value from a reference string.

Supported forms: - env:NAME - file:/path/to/secret - raw:literal-value (intended for tests/dev only) - vault:secret/path[#field] (reads from Vault HTTP API using env-configured address/token)

func ValidateRef

func ValidateRef(ref string) error

ValidateRef validates a secret reference format without loading its value.

Supported forms: - env:NAME - file:/path/to/secret - raw:literal-value - vault:secret/path[#field]

Types

type MemoryStore

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

MemoryStore is an in-memory Store. It is the default when no persistent backend is configured, and it is used throughout tests.

func NewMemoryStore

func NewMemoryStore() *MemoryStore

func (*MemoryStore) Delete

func (m *MemoryStore) Delete(poolName, id string) (bool, error)

func (*MemoryStore) ListAll

func (m *MemoryStore) ListAll() ([]Record, error)

func (*MemoryStore) ListByPool

func (m *MemoryStore) ListByPool(poolName string) ([]Record, error)

func (*MemoryStore) Upsert

func (m *MemoryStore) Upsert(rec Record) error

type Pool

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

Pool holds a named, mutable, thread-safe collection of secret Versions.

Reads (ValidAt, List, ListMetadata, Size) use RLock and are cheap. Writes (Add, Remove, Replace) use Lock.

Pointer identity matters: route-level closures built in internal/app/run.go capture *Pool and call ValidAt on every inbound request. Config reloads must mutate the pool in place (Add, Remove, Replace) rather than creating a new Pool, so that in-flight verification still sees fresh state.

func NewPool

func NewPool(name string, runtime bool, maxVersions int, seed []Version) (*Pool, error)

NewPool constructs a pool with an initial set of versions. If maxVersions <= 0, DefaultMaxVersions is used. The seed slice is validated and copied.

func (*Pool) Add

func (p *Pool) Add(v Version) error

Add inserts v. Returns ErrDuplicateID, ErrPoolFull, or a validation error. If the pool is at capacity, Add first tries to prune versions whose ValidUntil is in the past (relative to time.Now()).

func (*Pool) List

func (p *Pool) List() []Version

List returns a deep copy of all versions, sorted by ValidFrom descending.

func (*Pool) ListMetadata

func (p *Pool) ListMetadata() []VersionMetadata

ListMetadata returns id + validity window for each version, without the secret value. Admin GET handlers use this.

func (*Pool) MaxVersions

func (p *Pool) MaxVersions() int

func (*Pool) Name

func (p *Pool) Name() string

func (*Pool) PruneExpired

func (p *Pool) PruneExpired(now time.Time) []string

PruneExpired removes every version whose ValidUntil is non-zero and not after now, and returns the removed version IDs. Versions with zero ValidUntil (unbounded) are never pruned. Safe for concurrent callers; uses the pool's write lock.

Used by the periodic secret-GC sweeper (internal/app) so that expired-but- not-at-cap versions do not accumulate indefinitely in memory or in the persisted runtime_secrets table. Add() already prunes opportunistically when the pool is at max_versions; PruneExpired is the unconditional variant.

func (*Pool) Remove

func (p *Pool) Remove(id string) (Version, bool)

Remove deletes the version with the given id. Returns (removed, true) if found.

func (*Pool) Replace

func (p *Pool) Replace(versions []Version) error

Replace swaps the entire version set. It validates each entry and enforces the maxVersions cap. Used at startup (seeding from Store) and during config reload for non-runtime pools.

func (*Pool) Runtime

func (p *Pool) Runtime() bool

func (*Pool) Size

func (p *Pool) Size() int

Size returns the number of versions currently in the pool.

func (*Pool) ValidAt

func (p *Pool) ValidAt(t time.Time) []Version

ValidAt returns a freshly-allocated slice of versions valid at t, sorted by ValidFrom descending (most recent first). Safe for concurrent callers.

type Record

type Record struct {
	PoolName  string
	ID        string
	Sealed    []byte
	NotBefore time.Time
	NotAfter  time.Time // zero = unbounded
	CreatedAt time.Time
}

Record is the persisted shape of a single Pool entry. The Sealed field must already be AES-GCM wrapped (see Sealer); Store implementations never see the plaintext secret.

type Registry

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

Registry maps secret names to *Pool. Route-level closures capture pointers from here, so the registry is the single source of truth for "which pool does this secret_ref resolve to".

Pointer identity of a Pool must survive config reload for runtime-mutable pools. Callers achieve this by calling Pool(name) and mutating via Pool.Add/Remove/Replace, rather than creating a fresh Pool and re-registering.

func NewRegistry

func NewRegistry() *Registry

func (*Registry) Names

func (r *Registry) Names() []string

Names returns the registered pool names in sorted order.

func (*Registry) Pool

func (r *Registry) Pool(name string) (*Pool, bool)

Pool returns the named pool, if registered.

func (*Registry) Register

func (r *Registry) Register(p *Pool) error

Register inserts a pool under its name. Returns ErrPoolExists if the name is already taken by a different pool. Re-registering the exact same *Pool is a no-op (idempotent).

func (*Registry) Unregister

func (r *Registry) Unregister(name string) bool

Unregister removes a pool. Returns false if the name was not present. In-flight closures that captured the *Pool pointer continue to see the (now unreferenced-by-registry) pool until they are rebuilt on reload.

type Sealer

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

Sealer wraps plaintext secrets in AES-256-GCM. The output layout is:

[1B version=0x01][12B nonce][N-byte ciphertext || 16B tag]

A version prefix is included so that future algorithm changes (e.g. key rotation via KMS, a different AEAD) can be rolled out without breaking stored records.

func NewSealer

func NewSealer(keyB64 string) (*Sealer, error)

NewSealer decodes a base64-encoded 32-byte key and constructs an AES-256-GCM AEAD. Whitespace around the key is tolerated; both standard and URL encodings are accepted, with or without padding.

func (*Sealer) Open

func (s *Sealer) Open(sealed []byte) ([]byte, error)

Open reverses Seal. It returns ErrSealedAuthFailure on tag mismatch or wrong key.

func (*Sealer) Seal

func (s *Sealer) Seal(plaintext []byte) ([]byte, error)

Seal encrypts plaintext and returns the versioned sealed payload.

type Set

type Set struct {
	Versions []Version
}

func (Set) SigningAt

func (s Set) SigningAt(t time.Time) (Version, bool)

SigningAt returns the newest (most recent ValidFrom) secret that is valid at time t.

func (Set) ValidAt

func (s Set) ValidAt(t time.Time) []Version

ValidAt returns all secrets valid at time t, ordered by ValidFrom descending.

func (Set) Validate

func (s Set) Validate() error

type Store

type Store interface {
	// Upsert inserts or replaces the record keyed by (PoolName, ID).
	Upsert(rec Record) error

	// Delete removes the record with the given (poolName, id). Returns true if
	// something was removed, false if not found. A missing record is not an error.
	Delete(poolName, id string) (bool, error)

	// ListByPool returns all records for the pool. Order is unspecified.
	ListByPool(poolName string) ([]Record, error)

	// ListAll returns records for every pool. Used on startup to hydrate all
	// registered pools in one round trip.
	ListAll() ([]Record, error)
}

Store persists runtime-added secrets so they survive process restart.

Implementations must be safe for concurrent use. Upsert is idempotent on (PoolName, ID): a second call with the same key replaces the previous record. This keeps the store tolerant of admin-API retries and reload replays.

The Store does not know about Version validity windows at the application layer — those live in Pool. The Store only stores and returns Records.

type Version

type Version struct {
	ID         string
	Value      []byte
	ValidFrom  time.Time
	ValidUntil time.Time
}

Version is a single secret value with a validity window.

Semantics: - ValidFrom is inclusive. - ValidUntil is exclusive; a zero value means "no end".

func (Version) IsValidAt

func (v Version) IsValidAt(t time.Time) bool

type VersionMetadata

type VersionMetadata struct {
	ID         string    `json:"id"`
	ValidFrom  time.Time `json:"not_before"`
	ValidUntil time.Time `json:"not_after,omitempty"`
}

VersionMetadata is the safe-to-expose subset of a Version: everything except the secret material itself. Admin GET endpoints return this shape.

Jump to

Keyboard shortcuts

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