Help us shape the future of Go! Take the 2026 Go Developer Survey

secrets

package
v1.0.14 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

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

func MissingRefsError(p Provider, missing []SecretRef, storePath string) error

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

func ReadSecretFile(path string) (map[string]string, error)

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

func RenderK8sSecrets(p Provider, refs []SecretRef, namespace string) []map[string]any

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

func ResolvesValues(p Provider) bool

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

func ValidSecretKey(name string) bool

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

func ValidateDeclaredRefs(p Provider, refs []SecretRef, storePath string) error

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

func WriteSecretFile added in v0.1.45

func WriteSecretFile(path string, values map[string]string) error

WriteSecretFile writes the map back as YAML with sorted keys, 0600. Sorted so a re-write is a clean diff rather than map-iteration noise.

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

type DeclaredSecretKey struct {
	From  string
	Key   string
	Value string
}

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

type Layered interface {
	Layering() (sharedPath, localPath string, inherited, overridden int)
}

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

func NewPulledProvider(values map[string]string) Provider

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)
	// SharedPath is the same store resolved against the repo anchor (the
	// 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

func MissingDeclaredRefs(p Provider, refs []SecretRef) []SecretRef

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.

func (SecretRef) Key added in v0.1.46

func (r SecretRef) Key() string

Key returns the in-Secret key the ref reads, defaulting to EnvName.

Jump to

Keyboard shortcuts

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