Documentation
¶
Overview ¶
Package kms embeds luxfi/kms in-process inside the unified Hanzo Cloud binary per HIP-0106 ("all Go embeds in cloud"), replacing the legacy Infisical fork.
It has two faces, both backed by the SAME embedded luxfi/kms library:
KMSClient — the in-process cloud.KMSClient (GetSecret/PutSecret/Sign) other
subsystems call via deps.KMS. No RPC, no external DB. Built once by
the factory this package registers (init, mount.go), filled into
deps.KMS by build.go's BuildDeps before MountAll, and reused by Mount.
/v1/kms/* — the secrets-manager REST surface the KMS console (kms.hanzo.ai)
calls, mounted onto cloud's Fiber app: JWT-gated, org-scoped
secrets CRUD + a real health probe + the SPA admin config (mount.go).
STORAGE — sealed secrets persist to PER-ORG SQLite (store.go): each org's secrets live in ITS OWN encrypted file {CLOUD_DATA_DIR}/orgs/{org}/kms.db via the canonical cloud.OrgDB → cek seam, mirroring clients/finance. This REPLACES the single embedded ZapDB KV, whose exclusive OS lock pinned cloud to replicas=1: a per-org SQLite file has no single-opener lock, so different pods can serve different tenants and cloud scales horizontally (consistent-hash org→pod; see the package report). Two layers of at-rest protection, both rooted in the SAME env-only master key: (1) each secret is sealed with an AES-256-GCM envelope (store.Seal: a fresh per-secret DEK wrapped by the master key) BEFORE it reaches SQLite — plaintext never touches disk; (2) cek opens each file SQLCipher-encrypted under a per-db DEK wrapped by the master key (defense in depth). No PostgreSQL, no external DB, no ZapDB.
BOOTSTRAP — cloud hosting the secret store is a chicken-and-egg: cloud cannot fetch its OWN master key from the KMS it hosts. The 32-byte master key is injected by the operator via a K8s Secret env (CLOUD_KMS_MASTER_KEY_REF, base64 of 32 bytes) and read ONLY from env — never from the store, never logged, never persisted in plaintext. When the master key is absent the subsystem mounts in a fail-closed HEALTH-ONLY mode: /v1/kms/health reports 503, every secret op returns a clear "master key not configured" error, and the store is backed by an EPHEMERAL in-memory KV (never an unencrypted on-disk one), so a later keyed boot opens a clean encrypted store rather than a bricked one. Never a silent insecure default.
SIGN — luxfi/kms's Sign is MPC-backed (threshold signing via the MPC daemon). cloud does not co-host the MPC cluster; Sign therefore fails CLOSED with a clear error whenever the MPC backend is not configured (CLOUD_KMS_MPC_ADDR / CLOUD_KMS_MPC_VAULT_ID unset). A signature is NEVER fabricated.
SECURITY — the REST surface (mount.go) reuses cloud's ONE auth boundary (SanitizeIdentity in serve.go establishes the validated principal; handlers read c.Org()/c.IsAdmin()) rather than a parallel JWT stack.
LAYERING — the library face (Client/New here in kms.go) is the cloud-free CLIENT core: the types.KMSClient impl + sealed store access, importing only cloud/types. The REST face (mount.go) imports cloud to mount /v1/kms/* and register the subsystem. build.go does NOT import this package; it receives the embedded-client constructor via cloud.RegisterKMSClientFactory (init, mount.go), so deps.KMS is built by BuildDeps before MountAll with no cloud⇄kms import cycle — the same inversion cloud already uses to mount every subsystem it never imports.
mount.go exposes the embedded luxfi/kms secrets-manager as /v1/kms/* on the unified Hanzo Cloud binary (HIP-0106) — the REST face of this package.
It re-declares luxfi/kms's REST surface (cmd/kms is package main with no mountable handler) on cloud's Fiber app, backed by the SAME embedded SecretStore the in-process cloud.KMSClient uses (Client, this package, handed through deps.KMS), and gated by cloud's ONE auth boundary (SanitizeIdentity → c.Org()/c.IsAdmin()) — never a parallel JWT stack.
GET /v1/kms/health — real probe (503 in health-only mode); public GET /v1/kms/config — SPA runtime config; public GET /v1/kms/orgs/:org/secrets — list a path's secret metadata; JWT, org-scoped GET /v1/kms/orgs/:org/secrets/+ — read one secret value; JWT, org-scoped POST /v1/kms/orgs/:org/secrets — upsert a secret (sealed); JWT, org-scoped DELETE /v1/kms/orgs/:org/secrets/+ — delete a secret; JWT, org-scoped
ORG SCOPING — {org} must equal the caller's validated org (c.Org()); a global admin (c.IsAdmin()) may act on any org. The org is folded into the store PATH as /orgs/{org}{subpath}, so one org can never address another org's records. This mirrors clients/paas and clients/admin.
Index ¶
- Constants
- Variables
- func Mount(app cloud.Router, deps cloud.Deps) error
- func ValidSegment(s string, max int) bool
- func ValidSubpath(p string) bool
- type Client
- func (c *Client) Close() error
- func (c *Client) Delete(path, name, env string) error
- func (c *Client) Get(path, name, env string) ([]byte, error)
- func (c *Client) GetSecret(ctx context.Context, ref string) ([]byte, error)
- func (c *Client) List(path, env string) ([]SecretMeta, error)
- func (c *Client) Put(path, name, env string, value []byte) error
- func (c *Client) PutSecret(ctx context.Context, ref string, value []byte) error
- func (c *Client) Ready() bool
- func (c *Client) Sign(ctx context.Context, keyRef string, payload []byte) ([]byte, error)
- func (c *Client) SigningConfigured() bool
- type Config
- type SecretMeta
Constants ¶
const ( MaxNameLen = maxNameLen MaxEnvLen = maxEnvLen )
MaxSegmentLens exposes the name/env bounds so the HTTP subsystem can produce specific 400 messages using the same limits the store methods enforce.
Variables ¶
var ErrInvalidKey = errors.New("kms: invalid secret coordinate (name/env must be non-empty, within length bounds, and contain no '/', NUL, or control characters)")
ErrInvalidKey is returned when a secret coordinate (name/env) contains a byte that would smuggle structure into the store key (a '/', NUL, or control char) or is out of length bounds. Enforced in ONE place — the store-access methods — so every entry point (the HTTP subsystem AND the in-process KMSClient facade) keys clean, unambiguous records.
var ErrMasterKeyMissing = errors.New("kms: master key not configured (operator must inject CLOUD_KMS_MASTER_KEY_REF)")
ErrMasterKeyMissing is the fail-closed error every secret op returns when no master key is configured. It is honest (mirrors the DisabledKMS pattern): the caller knows the operator must inject CLOUD_KMS_MASTER_KEY_REF.
var ErrSecretNotFound = kmsstore.ErrSecretNotFound
ErrSecretNotFound is re-exported so the REST subsystem can map a missing secret to a 404 without importing luxfi/kms's store package directly.
ErrSignUnavailable is the fail-closed error Sign returns when the MPC backend is not configured. Signing is threshold-MPC-backed; cloud never fabricates a signature.
Functions ¶
func Mount ¶
Mount wires /v1/kms/* onto app. The concrete-client cast (deps.KMS → *Client), deps.IAMIssuer and the conditional (health-only) route set make this a direct construction (cloud.NewBase), not cloud.Mount.
func ValidSegment ¶ added in v1.786.32
ValidSegment reports whether s is a valid single store-key segment (name or env): non-empty, within max bytes, and free of '/', NUL, and ASCII control characters. Exported so the HTTP subsystem reuses the exact same rule.
func ValidSubpath ¶ added in v1.786.32
ValidSubpath reports whether p is a valid store subpath: '/'-separated non-empty segments, none of which is "." or ".." or contains a control char, within the length bound. An empty/"/"-only path is valid (the org/collection root). Exported so the HTTP subsystem reuses the exact same rule.
Types ¶
type Client ¶ added in v1.786.32
type Client struct {
// contains filtered or unexported fields
}
Client is the in-process cloud.KMSClient backed by the embedded luxfi/kms SecretStore. GetSecret/PutSecret seal/open through the AES-256-GCM envelope; Sign fails closed (MPC is never co-hosted here).
The zero value is not usable; construct with New.
func New ¶ added in v1.786.32
New opens the embedded KMS store under cfg.DataDir and returns the in-process Client. A malformed or missing master key is NOT fatal: the store still opens (so health can report + list metadata) but the Client runs in health-only mode where every secret op fails closed with ErrMasterKeyMissing. A bad DataDir / store-open failure IS fatal (the subsystem cannot serve at all).
The master key is decoded from base64, validated to be exactly 32 bytes, and held in memory only. It is never logged and never written to the store.
func (*Client) Close ¶ added in v1.786.32
Close releases the embedded store. Safe to call once at shutdown.
func (*Client) Delete ¶ added in v1.786.32
Delete removes a secret. Returns ErrSecretNotFound verbatim for a 404 mapping.
func (*Client) Get ¶ added in v1.786.32
Get reads a sealed secret at (path, name, env) and returns the opened plaintext. Fails closed with ErrMasterKeyMissing when no master key is set.
func (*Client) GetSecret ¶ added in v1.786.32
GetSecret resolves a flat ref to (path, name, env), reads the sealed record from the store, and returns the AES-256-GCM-opened plaintext. Fails closed with ErrMasterKeyMissing when no master key is configured.
ref grammar (see parseRef): "name" | "path/name" | "path/name@env". A bare name resolves to (path="/", name, env="default").
func (*Client) List ¶ added in v1.786.32
func (c *Client) List(path, env string) ([]SecretMeta, error)
List returns the metadata (never ciphertext/plaintext) of the secrets at a path/env. It does not require the master key: nothing sensitive is decrypted.
func (*Client) Put ¶ added in v1.786.32
Put seals value under a fresh per-secret DEK (wrapped by the master key) and upserts it. Plaintext is sealed before it reaches the store — never stored raw. Fails closed with ErrMasterKeyMissing when no master key is set.
func (*Client) PutSecret ¶ added in v1.786.32
PutSecret seals value under a fresh per-secret DEK (wrapped by the master key) and upserts it into the store. Plaintext is never persisted. Fails closed with ErrMasterKeyMissing when no master key is configured.
func (*Client) Ready ¶ added in v1.786.32
Ready reports whether the Client can perform secret ops (a valid master key is configured). Used to fail closed uniformly across the KMSClient + REST paths, and surfaced to build.go for the boot log.
func (*Client) Sign ¶ added in v1.786.32
Sign is threshold-MPC-backed in luxfi/kms and cloud does not co-host the MPC cluster, so it fails closed with ErrSignUnavailable unless an MPC backend is explicitly configured. It NEVER returns a fabricated signature.
When an MPC backend IS configured the caller should route signing to the dedicated MPC/keys deployment (deps.KMS ZAP RPC); in-process co-hosting of the MPC signer is intentionally out of scope for the application-tier binary.
func (*Client) SigningConfigured ¶ added in v1.786.32
SigningConfigured reports whether an MPC backend is wired. Sign fails closed when false — no signature is fabricated.
type Config ¶ added in v1.786.32
type Config struct {
DataDir string // CLOUD_DATA_DIR; the store opens under {DataDir}/kms
MasterKeyB64 string // base64 of the 32-byte master key (CLOUD_KMS_MASTER_KEY_REF)
MPCAddr string // MPC daemon host:port(,...) — CLOUD_KMS_MPC_ADDR
MPCVaultID string // MPC vault id — CLOUD_KMS_MPC_VAULT_ID
// ReadOnly opens the store in reader mode: mutations fail closed so a replica
// never forks the authoritative writer's state, and a reader with no restored
// store under {DataDir}/orgs fails closed at New rather than serving nothing.
// Set by the reader HA role. Unlike the former ZapDB store, per-org SQLite is
// RO-shareable over WAL, so a reader CAN open the files locally — the reader
// no longer needs to reverse-proxy KMS to the writer (see the package report).
ReadOnly bool
}
Config is the embedded KMS configuration resolved from cloud.Config + env by New. All fields are optional: an empty MasterKeyB64 yields the fail-closed health-only mode; empty MPC fields make Sign fail closed.
type SecretMeta ¶ added in v1.786.32
type SecretMeta struct {
Name string `json:"name"`
Path string `json:"path"`
Env string `json:"env"`
Scheme string `json:"scheme"`
}
SecretMeta is a secret's non-sensitive descriptor (never any ciphertext or plaintext), returned by List for the console's secret browser.