kms

package
v1.801.462 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Overview

Package kms is secret custody: your org's secrets sealed at rest, plus threshold signing.

Secrets are read and written over /v1/kms; signing is done by the MPC ring.

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

Index

Constants

View Source
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

View Source
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.

View Source
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.

View Source
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.

View Source
var ErrSignUnavailable = errors.New("kms: signing unavailable — MPC backend not configured (set CLOUD_KMS_MPC_ADDR and CLOUD_KMS_MPC_VAULT_ID)")

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

func Mount(app cloud.Router, deps cloud.Deps) error

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

func ValidSegment(s string, max int) bool

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

func ValidSubpath(p string) bool

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

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

func New(cfg Config, log luxlog.Logger) (*Client, error)

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

func (c *Client) Close() error

Close releases the embedded store. Safe to call once at shutdown.

func (*Client) Delete

func (c *Client) Delete(path, name, env string) error

Delete removes a secret. Returns ErrSecretNotFound verbatim for a 404 mapping.

func (*Client) DeleteSecret added in v1.801.350

func (c *Client) DeleteSecret(ctx context.Context, ref string) error

DeleteSecret is the KMSClient shape of Delete: it forgets one secret addressed by a flat ref, which is how a peer on the internal plane names the same record this process holds by (path, name, env).

func (*Client) Find added in v1.801.408

func (c *Client) Find(path, env string) ([]SecretMeta, error)

Find returns the metadata of every secret in the store under path, across every environment when env is empty. It is what an audit, a rotation or a migration asks; Client.List is what the credential broker asks.

path is a subtree ROOT and recursive, so a caller enumerating an org sees the whole org. It does not require the master key: nothing is decrypted.

func (*Client) Get

func (c *Client) Get(path, name, env string) ([]byte, error)

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

func (c *Client) GetSecret(ctx context.Context, ref string) ([]byte, error)

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

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

func (c *Client) Names(path, env string) ([]string, error)

Names is List reduced to the one field the credential broker needs, and is the half of credz.Source that Get does not already satisfy. It exists so credz can enumerate an app's scope without importing this package's SecretMeta — the broker deliberately knows nothing about a secret beyond its name.

func (*Client) Put

func (c *Client) Put(path, name, env string, value []byte) error

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

func (c *Client) PutSecret(ctx context.Context, ref string, value []byte) error

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

func (c *Client) Ready() bool

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

func (c *Client) Sign(ctx context.Context, keyRef string, payload []byte) ([]byte, error)

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

func (c *Client) SigningConfigured() bool

SigningConfigured reports whether an MPC backend is wired. Sign fails closed when false — no signature is fabricated.

type Config

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

	// Durable is the deployment's HA-durability factory, handed in rather than
	// read off cloud.Deps: this client is a DEPENDENCY of that value, built by
	// BuildDeps before it exists, so it cannot ask the deps set for anything.
	// nil ⇒ local-only, which is what a test wants and what a deployment with no
	// object store gets anyway.
	Durable *org.Durability

	// 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

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.

Jump to

Keyboard shortcuts

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