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 is absent from BOTH `present` (the provider's resolved view, which may layer another checkout's store) and the file at path. It never overwrites: an existing value — even an empty one — is left alone, because replacing an encryption key destroys the data it protects. 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.