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
- Variables
- func Rotate(ctx context.Context, st *store.Store, oldKey, newKey *Key) (int, error)
- func WriteKeyFile(path string, key *Key) error
- func Zero(b []byte)
- type Key
- type Provider
- type RegistryCredential
- type Resolved
- type Service
- func (s *Service) Builtin() Provider
- func (s *Service) CheckRefs(ctx context.Context, refs []spec.SecretRef) error
- func (s *Service) Delete(ctx context.Context, actor, name string) error
- func (s *Service) DeleteRegistry(ctx context.Context, actor, host string) error
- func (s *Service) Enabled() bool
- func (s *Service) KeyID() string
- func (s *Service) List(ctx context.Context) ([]store.Secret, error)
- func (s *Service) ListRegistries(ctx context.Context) ([]store.Registry, error)
- func (s *Service) Resolve(ctx context.Context, taskID string, refs []spec.SecretRef) ([]Resolved, error)
- func (s *Service) ResolveRegistries(ctx context.Context, taskID string, images []string) ([]RegistryCredential, error)
- func (s *Service) Set(ctx context.Context, actor, name string, value []byte) (store.Secret, error)
- func (s *Service) SetRegistry(ctx context.Context, actor, host, username string, password []byte) (store.Registry, error)
Constants ¶
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.
const KeySize = 32
KeySize is the master key length. AES-256 takes nothing else.
Variables ¶
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.
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.
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.
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 ¶
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 ¶
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).
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 ¶
GenerateKey mints a fresh master key from crypto/rand.
func LoadKeyFile ¶
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 ParseKey ¶
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 ¶
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 ¶
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.
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 ¶
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 ¶
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 (*Service) CheckRefs ¶
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 ¶
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 ¶
DeleteRegistry removes a registry credential. Tasks already assigned keep the copy in their Assign; the next pull from that host is anonymous.
func (*Service) List ¶
List returns metadata for every secret. Ciphertext is stripped: nothing outside this package has any use for it.
func (*Service) ListRegistries ¶
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 ¶
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.