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 SandboxProviderConfig
- 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) DeleteSandboxProvider(ctx context.Context, actor, name string) error
- func (s *Service) DeleteScoped(ctx context.Context, actor, scope, owner, name 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) ListSandboxProviders(ctx context.Context) ([]store.SandboxProvider, error)
- func (s *Service) ListVisible(ctx context.Context, owner string) ([]store.Secret, error)
- func (s *Service) ReadForConductor(ctx context.Context, ref spec.SecretRef) ([]byte, 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) ResolveSandboxProvider(ctx context.Context, nodeID, name string) (SandboxProviderConfig, bool, 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)
- func (s *Service) SetSandboxProvider(ctx context.Context, actor, name string, settings map[string]string, ...) (store.SandboxProvider, error)
- func (s *Service) SetScoped(ctx context.Context, actor, scope, owner, name string, value []byte) (store.Secret, 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 ErrForbidden = errors.New("secrets: forbidden")
ErrForbidden marks a caller who may not write or attach this secret.
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 ErrNameTaken = errors.New("secrets: name exists in the other scope")
ErrNameTaken marks a name that already exists in the other scope.
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.
var ErrReservedName = errors.New("secrets: reserved global name")
ErrReservedName marks a personal secret that tried to take podium.agent.*.
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 plus sandbox providers.
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 SandboxProviderConfig ¶ added in v0.7.0
SandboxProviderConfig is a provider's settings with its credential attached. Secret is plaintext: it goes straight onto a node's stream 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 global 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) DeleteSandboxProvider ¶ added in v0.7.0
DeleteSandboxProvider removes a provider. Its nodes stop taking work.
func (*Service) DeleteScoped ¶ added in v0.4.0
DeleteScoped removes one secret.
func (*Service) List ¶
List returns metadata for every secret, every owner included. Ciphertext is stripped. The operator API uses ListVisible: this one is for rotation and the startup warning.
func (*Service) ListRegistries ¶
ListRegistries returns metadata for every registry credential, ciphertext stripped.
func (*Service) ListSandboxProviders ¶ added in v0.7.0
ListSandboxProviders returns every provider, ciphertext stripped.
func (*Service) ListVisible ¶ added in v0.4.0
ListVisible returns every global name plus one login's personal names. An empty owner returns globals only. Another login's personal rows are not included. Ciphertext is stripped.
func (*Service) ReadForConductor ¶ added in v0.6.0
ReadForConductor decrypts one secret for the conductor and audits the read. The caller has already decided the conductor may read this name; see spec.ConductorMayRead.
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) ResolveSandboxProvider ¶ added in v0.7.0
func (s *Service) ResolveSandboxProvider(ctx context.Context, nodeID, name string) (SandboxProviderConfig, bool, error)
ResolveSandboxProvider decrypts a provider's credential for a node of that provider. found is false when no admin has configured it, or the server has no master key.
func (*Service) Set ¶
Set stores a global secret. The plaintext is zeroed before Set returns, so the caller must not reuse the slice. The associated data is the name alone, so a row written before scopes existed still decrypts.
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.
func (*Service) SetSandboxProvider ¶ added in v0.7.0
func (s *Service) SetSandboxProvider( ctx context.Context, actor, name string, settings map[string]string, maxConcurrent int32, secret []byte, ) (store.SandboxProvider, error)
SetSandboxProvider stores a provider's settings, its concurrency cap and, when secret is not empty, its credential, encrypted under the master key. An empty secret keeps the stored one; a provider with none stored yet must be given one. The plaintext is zeroed before it returns.
func (*Service) SetScoped ¶ added in v0.4.0
func (s *Service) SetScoped(ctx context.Context, actor, scope, owner, name string, value []byte) (store.Secret, error)
SetScoped stores a global or personal secret. owner is empty for a global secret and the owning login for a personal one. The plaintext is zeroed before SetScoped returns.