Documentation
¶
Index ¶
- Constants
- Variables
- func DetectSopsCollisions(placements []SopsPlacement) error
- func ExtractDeclarations(componentSection map[string]any) map[string]Declaration
- func ExtractProviders(componentSection map[string]any) map[string]any
- func Resolve(atmosConfig *schema.AtmosConfiguration, input, currentStack string, ...) (any, error)
- func TagScope(section map[string]any, scope Scope) (map[string]any, error)
- type BackendType
- type Declaration
- type GenerableVault
- type ImportSource
- type ResolveOptions
- type Scope
- type Service
- func (s *Service) Declarations() []Declaration
- func (s *Service) Delete(name string) error
- func (s *Service) DeleteAll() (int, error)
- func (s *Service) FileDependencies() []string
- func (s *Service) GenerateKeyForVault(v GenerableVault) (*providers.KeygenResult, error)
- func (s *Service) Get(name string, opts ResolveOptions) (any, error)
- func (s *Service) ImportFromStore(name string, src ImportSource, dryRun bool) error
- func (s *Service) IsDeclared(name string) bool
- func (s *Service) Reset() (bool, error)
- func (s *Service) ScopeOf(name string) (Scope, bool)
- func (s *Service) Set(name string, value any) error
- func (s *Service) SopsPlacements() []SopsPlacement
- func (s *Service) Status() []Status
- func (s *Service) Validate() ValidationResult
- func (s *Service) VaultsMissingKeys() ([]GenerableVault, error)
- type SopsPlacement
- type Status
- type ValidationResult
Constants ¶
const ( // ScopeInstance stores a value per component instance (stack + component). Default. ScopeInstance = providers.ScopeInstance // ScopeStack stores a single value shared by every instance in a stack. ScopeStack = providers.ScopeStack // ScopeGlobal stores a single value shared by every stack and component using the backend. ScopeGlobal = providers.ScopeGlobal )
Variables ¶
var ( ErrStoreNotFound = providers.ErrStoreNotFound ErrStoreNotSecret = providers.ErrStoreNotSecret ErrProviderNotFound = providers.ErrProviderNotFound ErrDeleteNotSupported = providers.ErrDeleteNotSupported )
Provider-construction errors are owned by the providers package; re-export the ones callers and tests assert on so they remain reachable from pkg/secrets.
var ( // ErrSecretNotDeclared indicates a secret name was used but not declared in the // component's secrets.vars (or an inherited declaration). ErrSecretNotDeclared = errors.New("secret is not declared") // ErrSecretMissing indicates a declared secret has no value in its backend. ErrSecretMissing = errors.New("secret is not initialized in its backend") // ErrUndeclaredKey indicates an imported/pushed key has no matching declaration. ErrUndeclaredKey = errors.New("key is not declared as a secret") // ErrNoBackend indicates a declaration references no backend (neither store nor sops). ErrNoBackend = errors.New("secret declaration has no backend (set `store:` or `sops:`)") // ErrAmbiguousBackend indicates a declaration references more than one backend. ErrAmbiguousBackend = errors.New("secret declaration must set exactly one of `store:` or `sops:`") // ErrKeygenUnsupported indicates the referenced vault's backend cannot generate a key. ErrKeygenUnsupported = errors.New("vault backend does not support key generation") // ErrInvalidSecretArgs indicates the !secret function received invalid arguments. ErrInvalidSecretArgs = errors.New("invalid !secret arguments") // ErrEmptyName indicates an empty secret name. ErrEmptyName = errors.New("secret name cannot be empty") // ErrScopeConflict indicates a declaration carries an explicit `scope` that conflicts with the // scope implied by its position (the one-way rule: an instance-declared secret can never be // stack-scoped, and a stack-level declaration can't be instance-scoped). ErrScopeConflict = errors.New("secret scope conflicts with declaration position") // ErrSecretNotOverridable indicates an attempt to set an instance-level value for a secret that // is stack-scoped at the targeted component. Overriding requires the instance to opt in by // declaring the secret under the component; otherwise the write is rejected (no silent shadow). ErrSecretNotOverridable = errors.New("secret is stack-scoped and not overridable at this instance") // ErrSopsCollision indicates two scopes resolve to a colliding SOPS file: distinct instances // sharing a file (no isolation), or a stack-scoped secret resolving per-component (not shared). ErrSopsCollision = errors.New("SOPS secret files collide across scopes") // ErrImportSourceStore indicates the import source store could not be determined: the // declaration is not store-backed and no explicit source store was given. ErrImportSourceStore = errors.New("import source store is required (the declaration has no `store:` to default to)") // ErrImportSourceRead indicates the source value could not be read from the source store // coordinate during an import. ErrImportSourceRead = errors.New("failed to read import source value") )
Sentinel errors for the secrets subsystem.
Functions ¶
func DetectSopsCollisions ¶
func DetectSopsCollisions(placements []SopsPlacement) error
DetectSopsCollisions returns an error if the placements violate the scope-vs-file invariants that keep stack and instance secrets safe:
- instance-scoped: two distinct (stack, component) instances must not resolve to the same file (a shared file means an "instance override" would silently overwrite another instance);
- stack-scoped: a (stack, secret) must resolve to a single file independent of component (a per-component file means the secret is not actually shared).
The derive-in-code default never trips these; it exists to catch a hand-written `spec.file` template that does not discriminate by component (or that discriminates a stack-scoped secret).
func ExtractDeclarations ¶
func ExtractDeclarations(componentSection map[string]any) map[string]Declaration
ExtractDeclarations reads the `secrets.vars` map from a resolved component section and returns the declared secrets keyed by name. Inheritance is already applied by the standard Atmos stack-merge pipeline, so this simply reads the merged map. Unknown/zero values are tolerated; a declaration with neither `store` nor `sops` is returned with an empty BackendName so callers can surface ErrNoBackend at use time.
func ExtractProviders ¶
ExtractProviders reads the `secrets.providers` map from a resolved component section. This lets SOPS providers be declared in a stack/component rather than only in atmos.yaml.
func Resolve ¶
func Resolve(atmosConfig *schema.AtmosConfiguration, input, currentStack string, stackInfo *schema.ConfigAndStacksInfo) (any, error)
Resolve resolves a `!secret NAME [| path ...] [| default ...]` expression to a value.
Behavior (in order):
- If the processing scope is an inspection command with masking enabled (stackInfo.SecretsMaskOnly), it returns the mask replacement WITHOUT retrieving the value from the backend — no provider call, no credentials required.
- Otherwise it looks up the declaration in the component section, resolves the backend provider, retrieves the value, applies the optional path/default modifiers, registers the value (recursively) with the I/O masker, and returns it.
func TagScope ¶
TagScope returns a copy of a `secrets:` section with the derived scope stamped onto every declaration under `secrets.vars`. Stamping happens by declaration position before the standard Atmos deep-merge, so "most-specific wins" resolves stack-vs-instance overrides for free (a component-level `instance` tag overrides an inherited stack-level `stack` tag) and ExtractDeclarations reads the resolved scope after the merge.
The input section is NOT mutated — the stack-level (global) section is shared across every component in a stack, so mutating it in place would race under concurrent stack processing.
A declaration carrying an explicit `scope` that conflicts with the positional scope returns ErrScopeConflict, enforcing the one-way rule: an instance-declared secret can never be stack-scoped, and a stack-level declaration can't be instance-scoped. An explicit `scope: global` is exempt — it is strictly more shared than either position implies, so it is honored wherever the declaration appears (typically a catalog fragment imported anywhere).
Types ¶
type BackendType ¶
type BackendType string
BackendType distinguishes a store-backed (track 1) declaration from a SOPS (track 2) one.
const ( // BackendStore is a store-backed secret (a `secret: true` store). It mirrors the // providers registry track key so the two never drift. BackendStore BackendType = BackendType(providers.TrackStore) // BackendSops is a SOPS file-backed secret. It mirrors the providers registry track key. BackendSops BackendType = BackendType(providers.TrackSops) )
type Declaration ¶
type Declaration struct {
// Name is the secret name (the key used by `!secret NAME`).
Name string
// Description is a human-readable description.
Description string
// BackendType selects the backend track (store vs sops).
BackendType BackendType
// BackendName is the referenced store name (track 1) or SOPS provider name (track 2).
BackendName string
// Reference is an optional backend-specific address for the secret (e.g. a 1Password
// `op://vault/item/field` reference). It may contain Go-template vars ({{ .atmos_stack }},
// {{ .atmos_component }}). When set it overrides Name as the backend key; backends that key
// off the declaration name (most stores, SOPS) ignore it.
Reference string
// Required marks the secret as required for validation.
Required bool
// Scope is the addressing level (stack vs instance). It is derived from declaration position
// during stack processing (top-level `secrets:` → stack; component `secrets:` → instance) and
// stamped onto the declaration; an empty Scope is treated as ScopeInstance.
Scope Scope
}
Declaration is a single declared secret resolved from a component's secrets.vars.
func LookupDeclaration ¶
func LookupDeclaration(componentSection map[string]any, name string) (Declaration, bool)
LookupDeclaration returns the declaration for a secret name from a component section.
func (*Declaration) IsStackScoped ¶
func (d *Declaration) IsStackScoped() bool
IsStackScoped reports whether the declaration is stored once per stack (shared by all instances).
type GenerableVault ¶
GenerableVault identifies a vault whose backend supports key generation (the registry-pattern keygen capability). Track is the backend track (e.g. "sops"); Name is the vault name.
type ImportSource ¶
type ImportSource struct {
// Store is the source store name. Empty selects the declaration's own `store:`.
Store string
// Stack is the source stack path segment (raw, not validated against real stacks).
Stack string
// Component is the source component path segment (raw; empty for stack-scoped legacy paths).
Component string
// Key is the source key. Empty selects the declaration name.
Key string
}
ImportSource identifies an existing store coordinate to adopt a value from. Stack and Component are raw source path segments (typically copied verbatim from a legacy `!store <store> <stack> <component> <key>` expression) — they need not name a real Atmos stack or component, and either may be empty to omit that segment from the source path.
type ResolveOptions ¶
type ResolveOptions struct {
// Path is an optional YQ-style path expression applied to a structured secret value.
Path string
// Default is an optional fallback value used when the secret is missing.
Default *string
}
ResolveOptions carries optional modifiers parsed from the !secret function or CLI flags.
type Scope ¶
Scope is the addressing level at which a secret value is stored (see providers.Scope). It is re-exported here so the secrets service and CLI can branch on a declaration's scope without importing the providers package directly.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the CRUD facade for the `atmos secret` CLI. It operates over declarations extracted from a component section and dispatches to the appropriate backend provider.
func NewService ¶
func NewService(atmosConfig *schema.AtmosConfiguration, stack, component string, componentSection map[string]any) *Service
NewService creates a Service scoped to a (stack, component) and its resolved component section (which carries the secrets.vars declarations after inheritance/merge).
func (*Service) Declarations ¶
func (s *Service) Declarations() []Declaration
Declarations returns the declared secrets for the service's scope, sorted by name.
func (*Service) DeleteAll ¶
DeleteAll removes every declared secret's value from its backend, returning the number of declarations processed. Delete is idempotent (it no-ops on values that are not initialized), so this also serves as a clean "reset" of a SOPS file's declared keys.
func (*Service) FileDependencies ¶
FileDependencies returns the distinct backing files this scope's file-based declared secrets (currently SOPS) resolve to, for `describe affected` to treat as implicit dependencies: a changed secret file then marks every component that consumes it. Secrets whose backend is not file-based (store-backed) contribute nothing. It is best-effort — declarations whose provider or path cannot be resolved are skipped rather than failing the whole computation. Results are de-duplicated and sorted.
func (*Service) GenerateKeyForVault ¶
func (s *Service) GenerateKeyForVault(v GenerableVault) (*providers.KeygenResult, error)
GenerateKeyForVault generates key material for a vault referenced by this scope and returns what the backend produced.
func (*Service) Get ¶
func (s *Service) Get(name string, opts ResolveOptions) (any, error)
Get retrieves a value for a declared secret, registering it with the masker.
func (*Service) ImportFromStore ¶
func (s *Service) ImportFromStore(name string, src ImportSource, dryRun bool) error
ImportFromStore copies an existing secret value from a source store coordinate into the declared secret's computed coordinate — terraform-import-style adoption that brings a value created outside the secrets subsystem (e.g. legacy `!store` usage) under management. The source value is read, registered with the masker, and written through the declaration's normal Set path (sensitivity flag, scope-derived coordinate); the source is never modified or deleted. With dryRun the source is read — proving it exists and is accessible — but nothing is written.
func (*Service) IsDeclared ¶
IsDeclared reports whether a key is declared as a secret in this scope.
func (*Service) Reset ¶
Reset overwrites a file-based provider's backing file (e.g. SOPS) with a clean, empty document for this scope, creating it if missing. It is a no-op for backends that are not file-based (they have no whole-file state to reset). Returns whether any provider was reset.
func (*Service) ScopeOf ¶
ScopeOf returns the resolved scope of a declared secret and whether it is declared. An undeclared name returns ("", false). A declaration with no explicit scope defaults to ScopeInstance.
func (*Service) SopsPlacements ¶
func (s *Service) SopsPlacements() []SopsPlacement
SopsPlacements returns the resolved SOPS file placement for each SOPS-backed declared secret in this service's scope. Non-SOPS (store-backed) secrets and unresolvable providers are skipped.
func (*Service) Status ¶
Status reports whether each declared secret is initialized in its backend. It never registers values with the masker (uses the backend status check, not Get).
func (*Service) Validate ¶
func (s *Service) Validate() ValidationResult
Validate checks that every required declared secret is initialized in its backend.
func (*Service) VaultsMissingKeys ¶
func (s *Service) VaultsMissingKeys() ([]GenerableVault, error)
VaultsMissingKeys returns the vaults referenced by this scope's declarations whose backend supports key generation but has no key material yet, so callers can offer to generate one. It is backend-agnostic: any provider implementing providers.KeyGenerator participates. Results are de-duplicated and sorted by name.
type SopsPlacement ¶
type SopsPlacement struct {
Stack string
Component string
Secret string //nolint:gosec // holds the secret's NAME (identifier), never its value.
Scope Scope
File string
}
SopsPlacement is a single SOPS-backed secret's resolved file location within a scope. It is the input to the collision lookup that guards the advanced `spec.file` template path (the derive-in- code default is collision-safe by construction, so it never produces violations here).
type Status ¶
type Status struct {
Declaration Declaration
Coordinate providers.Coordinate
Initialized bool
// Err holds any error encountered while checking status (e.g. access denied).
Err error
}
Status describes whether a declared secret is initialized in its backend.
type ValidationResult ¶
type ValidationResult struct {
// MissingRequired lists required secrets that are not initialized in their backend.
MissingRequired []Status
// Errored lists secrets whose status could not be determined (e.g. access denied).
Errored []Status
// All holds the full per-secret status set.
All []Status
}
ValidationResult summarizes the outcome of validating a scope's declared secrets.
func (ValidationResult) Valid ¶
func (r ValidationResult) Valid() bool
Valid reports whether all required secrets are initialized and no status errors occurred.