Documentation
¶
Overview ¶
Package keystore provides named key-material management for GoBricks applications: RSA key pairs and raw symmetric secrets (HMAC/CMAC keys, HKDF input).
Material is loaded at startup from files or base64-encoded values (typically injected via environment variables for Kubernetes/EKS deployments). Once loaded, the store is read-only and safe for concurrent access. Each entry is exactly one of an RSA pair, a symmetric secret, or a password-protected PKCS#12 bundle — a mixed entry is rejected by the config layer at startup (structural detection, no explicit discriminator).
Configuration ¶
Keys are configured in YAML under the "keystore" section:
keystore:
secretminlength: 32 # default 32; a set value can only raise it (ADR-095)
keys:
signing:
public:
file: "certs/signing_public.der" # Local dev
private:
value: "${SIGNING_PRIVATE_KEY_BASE64}" # EKS (base64-encoded DER)
mac-key:
secret:
value: "${MAC_KEY_BASE64}" # base64 raw key material
vts:
pkcs12:
file: "certs/vts.p12" # or value: base64 of the bundle
password:
env: "VTS_P12_PASSWORD" # variable NAME; or file: path
Usage ¶
Register the module before modules that need keys:
if err := fw.RegisterModule(keystore.NewModule()); err != nil {
log.Fatal(err)
}
if err := fw.RegisterModule(&myapp.JWEModule{}); err != nil {
log.Fatal(err)
}
Access keys via ModuleDeps (nil-check for fail-fast if keys are required):
func (m *Module) Init(deps *app.ModuleDeps) error {
if deps.KeyStore == nil {
return fmt.Errorf("KeyStore required but not configured")
}
m.keyStore = deps.KeyStore
return nil
}
privKey, err := m.keyStore.PrivateKey("signing")
Secret returns a defensive copy of symmetric key material; the caller owns the slice and may zeroize it after use:
macKey, err := m.keyStore.Secret("mac-key")
Index ¶
Constants ¶
const ( RoleTagJoseRoute = "jose-route" RoleTagSeal = "seal" )
Role tags a startup resolution carries: which framework feature asked the store for an entry. HTTP jose (a route policy) and payload sealing (a seal-tagged event type) must never share a kid — one key serving two protocols widens what a compromise of either reaches — so the store remembers the tag of every startup resolution and the app WARNs once per entry seen under both (#1306, ADR-097). Warn only: an enforced prefix partition was rejected as breaking shipped HTTP surface. Runtime (per-message) resolutions never record a tag. The app reads the log once route registration is complete, so a NEW startup resolution path must run before that point (app.prepareRuntime) or its entries go unreported.
Variables ¶
This section is empty.
Functions ¶
func CompareGenerations ¶ added in v0.63.0
func CompareGenerations(a, b Generation) int
CompareGenerations orders two generations of one family by version, the order FamilyEnumerator guarantees. Versions are canonical decimal, so a shorter digit string is the smaller integer and equal lengths compare lexically — no integer parse, no overflow ceiling on the digit count.
Types ¶
type DualRoleReporter ¶ added in v0.63.0
DualRoleReporter is the optional door the app reads after registration: every entry resolved under more than one role tag, mapped to the sorted tags it was seen under. Plain map so the app (which keystore imports) can name the method without importing this package.
type FamilyEnumerator ¶ added in v0.63.0
type FamilyEnumerator interface {
// Generations returns the provisioned generations of logical, ascending by
// version, or an empty slice when none is provisioned. Never an error: an
// unknown family is simply empty.
Generations(logical string) []Generation
}
FamilyEnumerator lists the provisioned generations of a Logical kid. The keystore's store implements it; consumers type-assert app.KeyStore to reach it, so the app.KeyStore interface itself is unchanged. The result IS the accept set: provisioning key material is the sole trust act (#1306).
type Generation ¶ added in v0.63.0
type Generation struct {
// Logical is the family name the sealing declaration carries.
Logical string
// Version is the generation marker without the hyphen, e.g. "v2".
Version string
// Role is what the entry's material permits.
Role Role
}
Generation is one provisioned key of a Logical kid's family: the entry named <Logical>-<Version> and the role its material grants.
func ActiveGeneration ¶ added in v0.63.0
func ActiveGeneration(store FamilyEnumerator, active map[string]string, logical string) (Generation, error)
ActiveGeneration resolves the producer's Activation for one Logical kid: which provisioned generation seals new traffic. active is the messaging.seal.active selector (Logical kid -> "v<N>"), already shape-checked by config.Validate; store is the keystore's family index.
- no provisioned generation: error naming the family, selector or not;
- one provisioned, no selector: that one is active;
- several provisioned, no selector: error — startup never guesses;
- selector present: it must name a provisioned generation, else an error naming the selector value.
The caller (the component owning sealing declarations) calls it once per Logical kid it resolves, sign and encrypt alike, at startup. active is the bare map rather than config.SealConfig by decision: the keystore stays decoupled from a messaging-owned config type, and the grammar re-check below covers a map that never passed config.Validate.
func (Generation) Kid ¶ added in v0.63.0
func (g Generation) Kid() string
Kid is the full entry name, e.g. "svc-payments-sign-v2" — the value that travels on the wire and the name the store's accessors take.
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
Module implements the GoBricks app.Module interface for named key-material management. It loads named RSA key pairs and raw symmetric secrets at startup and provides them to other modules via deps.KeyStore.
Register before modules that need keys:
if err := fw.RegisterModule(keystore.NewModule()); err != nil {
log.Fatal(err)
}
if err := fw.RegisterModule(&myapp.JWEModule{}); err != nil {
log.Fatal(err)
}
func (*Module) Init ¶
func (m *Module) Init(deps *app.ModuleDeps) error
Init implements app.Module. Loads all configured key material (RSA pairs and symmetric secrets) and validates it. Fails fast on any error.
type Role ¶ added in v0.63.0
type Role uint8
Role is the material an entry holds, which decides what a sealing side can do with a generation: verify/encrypt (public only), sign/decrypt (private present), or MAC (symmetric secret).
type RoleRecorder ¶ added in v0.63.0
type RoleRecorder interface {
RecordResolution(entry, role string)
}
RoleRecorder is the optional door a startup resolver uses to tag an entry. The keystore module's store implements it; a test double may too.