Documentation
¶
Overview ¶
Package secrets resolves declared secret REFERENCES (which live in git: EnvVar.secret_ref / secret_key) to secret VALUES (which never live in git) for one environment.
A secret has two halves:
- a non-sensitive REFERENCE — the env-var NAME, the k8s Secret name, and the key within it. KCL projects this into the manifest as a `secretKeyRef`. It is reproducible and version-controlled.
- a sensitive VALUE — obtained at resolve time from a per-env PROVIDER. Never in git, never in KCL render output.
This package owns the VALUE side. KCL only emits the provider DECLARATION (type + path); all value resolution happens here in Go so secrets never enter the KCL renderer.
Provider kinds:
- "file" (dev/local): forge reads a gitignored YAML file — a flat map of env-var name to value. Local clusters only. This is the scaffolded default; see fileProvider for why it replaced the dotenv.
- "dotenv": REMOVED. It injected the whole file into every host service, so values never had to be declared. NewProvider returns a hard error naming `forge secret migrate`.
- "hosted": values live in the env's hosted control plane (write-only API) and are materialized in-cluster by it. forge never sees them; `forge secret set/list/unset` write/list through the control plane.
- "external" (prod/staging): forge never sees values. k8s references pre-existing Secrets (External Secrets Operator / sealed); host & external runtimes obtain secrets via workload identity / ambient env. forge only validates the secretKeyRef wiring (it can't, and so does not, validate the values themselves).
The package is intentionally decoupled from internal/cli to avoid an import cycle (cli depends on secrets, not the reverse). It reuses only the stdlib for its own file reads — no cycle risk.
Index ¶
- func EnsureGenerated(path string, specs map[string]GenerateSpec, wanted []string, ...) ([]string, error)
- func MissingRefsError(p Provider, missing []SecretRef, storePath string) error
- func ReadSecretFile(path string) (map[string]string, error)
- func RenderDeclaredSecrets(declared []DeclaredSecret, dot Provider, env, namespace string) ([]map[string]any, error)
- func RenderK8sSecrets(p Provider, refs []SecretRef, namespace string) []map[string]any
- func ResolvesValues(p Provider) bool
- func ValidSecretKey(name string) bool
- func ValidateDeclaredRefs(p Provider, refs []SecretRef, storePath string) error
- func WriteSecretFile(path string, values map[string]string) error
- type DeclaredSecret
- type DeclaredSecretKey
- type GenerateSpec
- type Layered
- type Provider
- type ProviderConfig
- type SecretRef
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func EnsureGenerated ¶ added in v0.1.45
func EnsureGenerated(path string, specs map[string]GenerateSpec, wanted []string, present map[string]string) ([]string, error)
EnsureGenerated mints a value for every key in `wanted` that has a generate spec and no value in EITHER `present` (the provider's resolved view, which may layer another checkout's store) or the file at path. It never overwrites a value, because replacing an encryption key destroys the data it protects. An EMPTY slot is not a value: `forge generate` scaffolds every sensitive key as "" so the store lists what wants one, and skipping those would leave a fresh project's generated keys unminted forever. The store is written 0600 only when something was generated. It returns the generated key NAMES, never values.
func MissingRefsError ¶ added in v0.1.46
MissingRefsError is ValidateDeclaredRefs' refusal for refs already found missing: the aligned list and the command that fixes it.
func ReadSecretFile ¶ added in v0.1.45
ReadSecretFile loads the YAML secret file: a flat mapping of env-var name to value.
Values are decoded as strings, so `PORT: 8080` and `DEBUG: true` yield "8080" and "true" rather than a type error — an env var is a string, and failing on an unquoted number would be a papercut with no upside. Nested maps and sequences ARE an error: they cannot become an env var, so accepting them silently would leave a value that looks set but never arrives.
func RenderDeclaredSecrets ¶
func RenderDeclaredSecrets(declared []DeclaredSecret, dot Provider, env, namespace string) ([]map[string]any, error)
RenderDeclaredSecrets builds k8s Secret manifests from a RenderedSecrets provider's declared Secrets. Each declared key resolves to its value:
- from="file": resolve Key from the env's secret store `dot`. A key with no value is an error (listed per Secret/key) — the value can't be rendered.
- from="literal": use the inline Value, but ONLY when env is dev/e2e. A literal in any other env is a hard error (the trust-safe gate).
Returns the Secret manifests (one per declared Secret) in deterministic order, or a single aggregated error listing every problem. namespace is stamped on each Secret's metadata.
func RenderK8sSecrets ¶
RenderK8sSecrets builds k8s Secret manifests (as []map[string]any, ready to marshal to YAML/JSON) from the resolved values, grouping refs by SecretName; each Secret's stringData[SecretKey] = resolved(EnvName). Only Kind "file" produces output; "external"/"none" return nil (prod references pre-existing Secrets). Skips refs whose value doesn't resolve (ValidateDeclaredRefs is the gate for those). Deterministic ordering (sorted Secret names + keys) for stable diffs.
func ResolvesValues ¶ added in v0.1.45
ResolvesValues reports whether p holds real values forge can validate declarations against and inject: a file store, or a LOCAL env's pulled hosted store. external / none / a persistent hosted env resolve nothing.
Declared as an unexported marker rather than a Kind() comparison because a hosted provider may or may not hold values (local vs persistent env), so the kind name alone cannot answer it.
func ValidSecretKey ¶ added in v0.1.45
ValidSecretKey reports whether name is usable as an env-var name: [A-Za-z_][A-Za-z0-9_]*. Shared with the CLI so `forge secret set` rejects the same names the loader would.
func ValidateDeclaredRefs ¶
ValidateDeclaredRefs returns a single fail-fast error listing every declared ref the provider cannot supply. For Kind "file": each EnvName must be present in All(). For "external"/"none": returns nil (forge cannot see those values).
Types ¶
type DeclaredSecret ¶
type DeclaredSecret struct {
Name string
Keys map[string]DeclaredSecretKey
}
DeclaredSecret is one explicitly-declared k8s Secret a RenderedSecrets provider renders: a Secret NAME and a map of in-Secret key -> value SOURCE. Mirrors the cli RenderedSecretEntity (the secrets package stays decoupled from cli, so cli maps its entity -> this).
type DeclaredSecretKey ¶
DeclaredSecretKey is the value source for one key in a DeclaredSecret. From is "file" (resolve Key from the secret store) or "literal" (inline Value, gated to dev/e2e by RenderDeclaredSecrets).
type GenerateSpec ¶ added in v0.1.45
type GenerateSpec struct {
Bytes int `json:"bytes"`
Encoding string `json:"encoding"`
Prefix string `json:"prefix,omitempty"`
}
GenerateSpec mirrors KCL's forge.GeneratedSecret: how to mint a store value that is pure random key material.
func (GenerateSpec) Generate ¶ added in v0.1.45
func (g GenerateSpec) Generate() (string, error)
Generate returns Prefix + Bytes of crypto-random data in the spec's encoding.
type Layered ¶ added in v0.1.45
Layered is implemented by a provider that merged more than one store. Declared at the CONSUMER (the CLI prints the notice) rather than exported as a wide provider interface — Provider stays three methods.
type Provider ¶
type Provider interface {
Kind() string // "file" | "external" | "hosted" | "none"
// Resolve returns the value for an env var by NAME (the key in the
// store == the EnvVar.name). ok=false
// when this provider has no value for name.
Resolve(name string) (value string, ok bool)
// All returns every value the provider can supply, keyed by name.
// file: the whole store. external/none: nil.
All() map[string]string
}
Provider resolves declared secret references to values for one env.
func NewProvider ¶
func NewProvider(cfg *ProviderConfig) (Provider, error)
NewProvider builds a Provider. cfg==nil -> a noop provider (Kind "none", All nil, Resolve always !ok) so callers need no nil checks. file: loads the store now; a MISSING file is a non-fatal file: loads the store now; a MISSING file is a non-fatal empty provider (so validation, not load, reports the missing keys with the `forge secret set` fix) — BUT an unreadable/invalid store is an error.
func NewPulledProvider ¶ added in v0.1.45
NewPulledProvider wraps the values a LOCAL environment's control plane served. Kind is "hosted" (that is where the values live); it resolves them.
type ProviderConfig ¶
type ProviderConfig struct {
Type string // "file" | "external"
Path string // secret-store file (already resolved to an absolute/project path by caller)
// primary checkout) when Path is inside a LINKED git worktree. It is
// the BASE layer: Path overrides it key by key. Empty on the primary
// checkout, outside a repo, or when it would equal Path.
//
// See layeredProvider for why this is a merge and not a fallback.
SharedPath string
}
ProviderConfig is the cli-decoupled view of the KCL secret_provider entity. (cli maps KCLEntities.SecretProvider -> this.)
type SecretRef ¶
type SecretRef struct {
EnvName string
SecretName string
SecretKey string
// Optional exempts this ref from the missing-value check, and it is set
// ONLY from the proto's `optional: true` on a sensitive config field.
//
// The default — a declared secret must have a value — is the whole point
// of the pre-flight, so this is deliberately not inferrable from anything
// forge can observe about the environment. An unset credential and a
// deliberately-absent one look identical in the store; the annotation is
// the author saying which it is.
Optional bool
}
SecretRef is a declared reference extracted from the entities: the env-var NAME (== the secret's filename in the store), the k8s Secret NAME, and the key within that Secret. SecretKey defaults to EnvName when empty (matches the KCL _env_source lambda).
func MissingDeclaredRefs ¶ added in v0.1.46
MissingDeclaredRefs lists the declared, non-optional refs a value-resolving provider cannot supply, one per env-var name, sorted by name. Nil when there are none, or when the provider cannot see values (external/none), so a read-only caller can REPORT what is missing where ValidateDeclaredRefs refuses.