secrets

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 13, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package secrets is the control plane's encrypted secret store: the master key, the AES-256-GCM envelope around every stored value, and the resolution step that turns a task's SecretRefs into the plaintext an Assign carries to a node.

Nothing in this package logs a value, a key or a decrypted blob. The only thing that ever comes back out of it is a Resolved, and that goes straight into an Assign.

Index

Constants

View Source
const KeyFileMode fs.FileMode = 0o600

KeyFileMode is the most permissive mode a master key file may have. The check is on the group and other bits, so 0400 is accepted too; anything a second account on the machine can read is refused.

View Source
const KeySize = 32

KeySize is the master key length. AES-256 takes nothing else.

Variables

View Source
var ErrDecrypt = errors.New("secrets: decrypt failed (wrong master key, or the stored value was tampered with)")

ErrDecrypt is what a wrong master key, a corrupted ciphertext or a re-labelled row all come back as. It is deliberately one error: distinguishing them for a caller would tell an attacker which of the three they achieved.

View Source
var ErrInvalidSecret = errors.New("secrets: invalid secret")

ErrInvalidSecret marks a caller's mistake: a name that is not a valid secret name, or an empty value. It exists so the API layer can answer InvalidArgument without string matching.

View Source
var ErrMissing = errors.New("secrets: missing secret")

ErrMissing is what Resolve returns when a task names a secret that does not exist. It is not retryable: another node would fail identically, and the task is failed before it is ever assigned.

View Source
var ErrNoKey = errors.New("secrets: no master key configured (set PODIUM_MASTER_KEY_FILE)")

ErrNoKey is returned when a secrets operation is attempted on a server that was started without a master key.

Functions

func Rotate

func Rotate(ctx context.Context, st *store.Store, oldKey, newKey *Key) (int, error)

Rotate re-encrypts every stored secret and registry password from oldKey to newKey, each table in one transaction. It is the offline `podium-server rotate-master-key` path: the server is not running, or is still running under the old key and will be restarted with the new one. The count is secrets plus registries.

func WriteKeyFile

func WriteKeyFile(path string, key *Key) error

WriteKeyFile writes a key to a new file with mode 0600. It refuses to overwrite: losing a master key loses every secret encrypted under it, so replacing one is a deliberate two-step (rotate, then remove).

func Zero

func Zero(b []byte)

Zero overwrites b. It is what the node and the API use on a plaintext they are finished with; it is not a guarantee (Go may have copied the bytes already) but it shortens the window in which a core dump contains a live credential.

Types

type Key

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

Key is a loaded master key. It is immutable and safe for concurrent use.

func GenerateKey

func GenerateKey() (*Key, error)

GenerateKey mints a fresh master key from crypto/rand.

func LoadKeyFile

func LoadKeyFile(path string) (*Key, error)

LoadKeyFile reads a master key from disk. It refuses a file any other account on the machine can read: a master key that leaks decrypts every secret Podium holds, and a stray `chmod 644` is the likeliest way for that to happen.

func NewKey

func NewKey(raw []byte) (*Key, error)

NewKey wraps 32 raw bytes. The caller must not reuse the slice afterwards.

func ParseKey

func ParseKey(b []byte) (*Key, error)

ParseKey accepts either 64 hex characters, with surrounding whitespace ignored, or 32 raw bytes. Both are what a `podium-server gen-master-key` file could plausibly contain after passing through an editor or a secret manager; hex is what gen-master-key writes.

func (*Key) Decrypt

func (k *Key) Decrypt(name string, ciphertext, nonce []byte) ([]byte, error)

Decrypt opens a stored value. Every failure — a wrong key, a truncated ciphertext, a row whose name does not match the one it was sealed under — is ErrDecrypt.

func (*Key) Encrypt

func (k *Key) Encrypt(name string, value []byte) (ciphertext, nonce []byte, err error)

Encrypt seals value under the master key with a fresh random nonce, binding it to name as additional authenticated data: a ciphertext moved to another row fails to decrypt rather than silently becoming a different secret.

func (*Key) Equal

func (k *Key) Equal(other *Key) bool

Equal reports whether two keys are the same key, in constant time.

func (*Key) Hex

func (k *Key) Hex() string

Hex is the key's file form. It is the only method that reveals key material and exists solely for gen-master-key; never log its result.

func (*Key) ID

func (k *Key) ID() string

ID identifies the key without revealing it: the first 8 bytes of its SHA-256, hex encoded. It is stored on every row so a half-finished rotation is visible.

type Provider

type Provider interface {
	// Resolve returns the plaintext of one secret, or an error wrapping ErrMissing when
	// there is no such name.
	Resolve(ctx context.Context, name string) ([]byte, error)
}

Provider is where a value comes from. Only the builtin, Postgres-backed provider exists; Vault, 1Password and cloud KMS would be further implementations of this interface and are deliberately out of scope.

type RegistryCredential

type RegistryCredential struct {
	Host     string
	Username string
	Password []byte
}

RegistryCredential is one registry login with its password attached. Password is plaintext: it goes straight into an Assign and is never stored or logged.

type Resolved

type Resolved struct {
	Name   string
	Target string
	Key    string
	Value  []byte
}

Resolved is one SecretRef with its value attached. Value is the only plaintext this package hands out; it goes straight into an Assign and is never stored or logged.

type Service

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

Service is the secret store: encryption, the CRUD an operator drives, and the resolution the scheduler calls immediately before it assigns a task.

A Service with a nil key is a server started without PODIUM_MASTER_KEY_FILE. Every operation on it fails with ErrNoKey rather than silently storing plaintext, so a misconfigured server is loudly useless rather than quietly unsafe.

func New

func New(st *store.Store, key *Key, logger *slog.Logger) *Service

New returns the secret service. key may be nil, in which case secrets are disabled.

func (*Service) Builtin

func (s *Service) Builtin() Provider

Builtin returns the store-backed Provider for this service.

func (*Service) CheckRefs

func (s *Service) CheckRefs(ctx context.Context, refs []spec.SecretRef) error

CheckRefs reports whether every name a spec references exists, without decrypting anything. It is the admission check: a task naming a secret that is not there can be refused at `podium run` rather than sitting queued until some node happens to connect, which is what used to happen — the scheduler resolved at dispatch, and with an empty cluster there is no dispatch.

It deliberately does not read a value. Admission answers "could this ever run?"; the value still comes out of the database once, at assignment, and lives in server memory for as short a time as it can.

func (*Service) Delete

func (s *Service) Delete(ctx context.Context, actor, name string) error

Delete removes a secret. Tasks already assigned keep the copy in their Assign; the next task that references the name fails to resolve.

func (*Service) DeleteRegistry

func (s *Service) DeleteRegistry(ctx context.Context, actor, host string) error

DeleteRegistry removes a registry credential. Tasks already assigned keep the copy in their Assign; the next pull from that host is anonymous.

func (*Service) Enabled

func (s *Service) Enabled() bool

Enabled reports whether a master key was configured.

func (*Service) KeyID

func (s *Service) KeyID() string

KeyID is the configured master key's identifier, or "" when there is none.

func (*Service) List

func (s *Service) List(ctx context.Context) ([]store.Secret, error)

List returns metadata for every secret. Ciphertext is stripped: nothing outside this package has any use for it.

func (*Service) ListRegistries

func (s *Service) ListRegistries(ctx context.Context) ([]store.Registry, error)

ListRegistries returns metadata for every registry credential, ciphertext stripped.

func (*Service) Resolve

func (s *Service) Resolve(ctx context.Context, taskID string, refs []spec.SecretRef) ([]Resolved, error)

Resolve turns a task's refs into the plaintext an Assign carries. A single missing name fails the whole task: a task that silently runs without half its credentials is worse than one that does not run.

It writes one secret.resolve audit row naming the task and the secret names — never the values.

func (*Service) ResolveRegistries

func (s *Service) ResolveRegistries(ctx context.Context, taskID string, images []string) ([]RegistryCredential, error)

ResolveRegistries returns the credentials for the registries the given images are pulled from, and nothing for the rest: a registry Podium holds no login for is pulled anonymously, and a task never learns about a registry it does not use. A server without a master key holds no credentials, so it resolves none rather than failing every task.

func (*Service) Set

func (s *Service) Set(ctx context.Context, actor, name string, value []byte) (store.Secret, error)

Set stores a value under name, encrypted under the master key. The plaintext is zeroed before Set returns, so the caller must not reuse the slice.

func (*Service) SetRegistry

func (s *Service) SetRegistry(ctx context.Context, actor, host, username string, password []byte) (store.Registry, error)

SetRegistry stores the login for one registry host, the password encrypted under the master key with the host as additional data. The plaintext is zeroed before it returns.

Jump to

Keyboard shortcuts

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