secrets

package
v1.233.0 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
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

View Source
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.

View Source
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")

	// ErrSelectorUnresolved indicates a declaration's `store:`/`sops:` value (or the SOPS provider it
	// names) is a YAML-function selector (for example `!aws.cloudformation.output ...`) that could not
	// be resolved to a concrete backend. Only the declaration that needs the backend fails; sibling
	// declarations with literal backends are unaffected.
	ErrSelectorUnresolved = errors.New("secret backend selector could not be resolved")

	// ErrSelectorEvaluatorUnavailable indicates a backend selector was found but no selector
	// evaluator (credentials plus component evaluation context) is available in this code path, such
	// as credential-free listing.
	ErrSelectorEvaluatorUnavailable = errors.New("backend selectors are not evaluated here (they require credentials)")

	// ErrSelectorResult indicates a backend selector evaluated to something other than a backend name.
	ErrSelectorResult = errors.New("selector did not evaluate to a backend name")

	// 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

func ExtractProviders(componentSection map[string]any) map[string]any

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 IsSelector

func IsSelector(value string) bool

IsSelector reports whether a declaration's `store:`/`sops:` value is a YAML-function selector (such as `!aws.cloudformation.output ...`) that must be evaluated before it names a backend.

func Resolve

func Resolve(atmosConfig *schema.AtmosConfiguration, input, currentStack string, stackInfo *schema.ConfigAndStacksInfo, extra ...Option) (any, error)

Resolve resolves a `!secret NAME [| path ... | raw] [| default ...]` expression to a value.

Behavior (in order):

  1. It validates that the secret is declared in the component section.
  2. 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.
  3. Otherwise it 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.

A declaration whose `store:`/`sops:` is a YAML-function selector (for example `!aws.cloudformation.output ...`) is resolved lazily here, only for the secret being read, using the evaluator supplied through WithSelectorEvaluator.

func TagScope

func TagScope(section map[string]any, scope Scope) (map[string]any, error)

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
	// PositionScope is the position-derived scope recorded when an explicit `scope` was a template
	// or YAML function validated only after rendering. Empty otherwise; see ScopeConflict.
	PositionScope 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).

func (*Declaration) ScopeConflict

func (d *Declaration) ScopeConflict() error

ScopeConflict returns ErrScopeConflict when a rendered explicit scope violates the one-way rule against the declaration's position (an instance-declared secret can never be stack-scoped, and a stack-level declaration can't be instance-scoped; `global` is exempt). It is nil when the scope was validated during stack processing (PositionScope empty) or is consistent.

type FileDependencySet

type FileDependencySet struct {
	// Files are the exact backing files of declarations that resolved.
	Files []string
	// Folders are directories under which a change may affect the scope, because a declaration's
	// backend selector could not be resolved and its file is therefore unknown.
	Folders []string
	// Unresolved lists each declaration that contributed to Folders, so callers can report it.
	Unresolved []UnresolvedFileDependency
}

FileDependencySet is the result of ResolveFileDependencies: the files a scope's file-based secrets are known to live in, plus the conservative locations for declarations whose backend selector could not be resolved.

type GenerableVault

type GenerableVault struct {
	Track string
	Name  string
}

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 Option

type Option func(*options)

Option configures a Service or a single Resolve call.

func WithSelectorEvaluator

func WithSelectorEvaluator(evaluator SelectorEvaluator) Option

WithSelectorEvaluator supplies the evaluator used to resolve backend selectors lazily, only for the declarations that are actually used. Results are memoized per path.

type ResolveOptions

type ResolveOptions struct {
	// Path is an optional YQ-style path expression applied to a structured secret value.
	Path string
	// Raw requests the original textual payload instead of the store's structured value.
	Raw bool
	// 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

type Scope = providers.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 SelectorEvaluator

type SelectorEvaluator func(path []string, raw any) (any, error)

SelectorEvaluator evaluates the raw value found at path inside a component section (for example ["secrets", "vars", "API_KEY", "store"]) and returns the evaluated value. It is how a declaration whose `store:`, `sops:` or provider definition is a YAML-function selector (such as `!aws.cloudformation.output <component> <stack> <output>`) is turned into a concrete backend.

The evaluator is supplied by the caller because evaluation needs the stack-processing and authentication machinery that pkg/secrets must not depend on: the `atmos secret` CLI resolves a selector with its authenticated scope, and the `!secret` YAML function resolves it with the processing context of the component being described.

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, opts ...Option) *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) Delete

func (s *Service) Delete(name string) error

Delete removes a declared secret's value from its backend.

func (*Service) DeleteAll

func (s *Service) DeleteAll() (int, error)

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

func (s *Service) FileDependencies() []string

FileDependencies returns the distinct backing files this scope's file-based declared secrets (currently SOPS) resolve to. It is the exact-file part of ResolveFileDependencies; callers that must not under-report changes (`describe affected`) use ResolveFileDependencies, which also returns the conservative folders for selector-backed declarations that could not be resolved. 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

func (s *Service) IsDeclared(name string) bool

IsDeclared reports whether a key is declared as a secret in this scope.

func (*Service) Reset

func (s *Service) Reset() (bool, error)

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) ResolveFileDependencies

func (s *Service) ResolveFileDependencies() FileDependencySet

ResolveFileDependencies returns the files this scope's file-based declared secrets depend on, for `describe affected` to treat as implicit dependencies: a changed secret file marks every component that consumes it. Secrets whose backend is not file-based (store-backed) contribute nothing, and other declarations that cannot be resolved are skipped.

A SOPS declaration whose backend selector cannot be resolved (no evaluator, which is the case for credential-free `describe affected`, or an undeployed producer) is never skipped silently. Its file is unknown, so the possible locations of the provider definitions it could name are returned as Folders (and Files) and the declaration is listed in Unresolved: any change there may affect the scope. When the service has a selector evaluator and the selector resolves, the exact file is returned instead. Results are de-duplicated and sorted.

func (*Service) ScopeOf

func (s *Service) ScopeOf(name string) (Scope, bool)

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) Set

func (s *Service) Set(name string, value any) error

Set stores a value for a declared secret.

func (*Service) SopsPlacements

func (s *Service) SopsPlacements() ([]SopsPlacement, error)

SopsPlacements returns the resolved SOPS file placement for each SOPS-backed declared secret in this service's scope. Non-SOPS (store-backed) secrets are skipped.

A SOPS declaration whose backend is a selector that cannot be resolved (no evaluator, or the producer is not deployed) is never skipped silently: its file is unknown, so a collision with another instance cannot be ruled out. The returned error wraps ErrSelectorUnresolved and names every such declaration with its component and stack; the placements that did resolve are still returned so callers can report collisions among them as well.

func (*Service) Status

func (s *Service) Status(verify bool) []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) and never decrypts.

Listing is credential-free by default. A provider whose existence check is local (no network, no auth, no decryption — e.g. SOPS, which reads cleartext key names) is always checked. A non-local provider (a remote store) is checked only when verify is true; otherwise its status is reported as Unknown so that `atmos secret list` needs no authenticated identity. Callers that require an authoritative answer for remote backends (e.g. `secret validate`, or `secret list --verify`) pass verify=true and must have wired the store auth resolver.

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
	// Unknown is true when the secret's initialization state was not checked because doing so
	// would require contacting a remote backend (network + credentials), and verification was not
	// requested. Listing is credential-free by default: local backends (e.g. SOPS) are always
	// checked, but remote stores are reported as Unknown unless `--verify` is passed. When Unknown
	// is true, Initialized is meaningless.
	Unknown bool
	// Unresolved is true when the declaration's backend is a YAML-function selector that was not
	// evaluated (credential-free listing). It implies Unknown; Reason explains how to resolve it.
	Unresolved bool
	// Reason is a human-readable explanation for an Unknown or errored status (empty otherwise).
	Reason string
	// 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 UnresolvedFileDependency

type UnresolvedFileDependency struct {
	// Declaration is the secret name.
	Declaration string
	// Field is where the selector sits: `sops` for the declaration's own backend, or
	// `providers.<name>` for a selector inside the named provider definition.
	Field string
	// Selector is the unresolved selector text.
	Selector string
	// Locations are the files and folders that stand in for the unknown file.
	Locations providers.FileLocations
	// Err is why the selector was not resolved.
	Err error
}

UnresolvedFileDependency describes one SOPS declaration whose backend selector could not be resolved, and the conservative locations that stand in for its unknown file.

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.

Directories

Path Synopsis
Package deferred composes secret lookups with their deferred dependencies.
Package deferred composes secret lookups with their deferred dependencies.
Package providers implements the secret backend providers for the Atmos secrets subsystem.
Package providers implements the secret backend providers for the Atmos secrets subsystem.

Jump to

Keyboard shortcuts

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