LPsLux Proposals
Key Management
LP-7326

Secrets

Draft

A KMSSecret CRD names a KMS scope and a target Secret; an operator pulls the values from KMS over the native binary protocol and writes them into the k8s Secret — never plaintext in the manifest.

Category
Core
Created
2026-06-24

Abstract

A secret lives in KMS. A pod needs it as a Kubernetes Secret. A KMSSecret custom resource is the bridge: it names a KMS scope to read and a Secret to write, and an operator reconciles the second from the first. The manifest carries the reference, never the value — the secret material never appears in git, in a CR, or in any plaintext field.

This is the read side of machine identity (LP-7110): the CR's credentialsRef points at the machine credentials that authorize the KMS scope; KMS (LP-7336, M-Chain custody LP-7100) holds the material.

Invariant

A KMSSecret declares a KMS scope and a target Secret. The operator's only job is to make the target equal the scope. The secret value transits KMS → operator → k8s Secret; it is never written in the CR.

Why (first principles)

The alternative — a Secret checked into a manifest — puts the value in git, in CI logs, in every clone. The only safe place for the value is KMS; the only thing safe to commit is a pointer to it. So the CR is a pointer: group secrets.lux.network, version v1alpha1, kind KMSSecret, holding a scope (projectSlug/envSlug/secretsPath) and a target (managedSecretReference).

Reconciliation is convergence, not a one-shot copy. The operator re-reads the scope on an interval and rewrites the target, so a rotated secret in KMS becomes a rotated Secret in the cluster without a redeploy. The loop is the whole design — declare the desired equality once, let the operator hold it.

The transport is the native binary protocol, not HTTP. In-cluster the operator dials KMS over luxfi/zap on port 9999 — "no HTTP, no auth tokens, no REST round-trips" — so the value crosses one binary hop between two in-cluster components and lands in the Secret, never serialized through a REST surface.

Enforcement

The kind is fixed. The operator reconciles exactly secrets.lux.network/v1alpha1, resource kmssecrets (kms/cmd/kms-operator/main.go:43-45).

The loop is four steps, stated at the top of the command and run on RESYNC_INTERVAL: list KMSSecret CRs, dial KMS at the CR's host over ZAP, list then get every secret in the scope, write or update the named k8s Secret (kms/cmd/kms-operator/main.go:1-11, :79-85). One reconcile reads authentication.universalAuth.secretsScope (envSlug/secretsPath), pulls each value over ZAP, and creates or updates the managedSecretReference Secret with that data (kms/cmd/kms-operator/main.go:117-198).

The value is never in the CR. The reconcile reads from the ZAP client and writes data into the Secret it creates; nothing in the spec carries secret material — only credentialsRef (the machine identity, a reference to another Secret), the scope, and the target. The target defaults to the CR's own namespace when secretNamespace is omitted (kms/cmd/kms-operator/main.go:132-134); the written Secret is labelled app.kubernetes.io/managed-by: lux-kms-operator and back-linked to its source CR (kms/cmd/kms-operator/main.go:161-172).

Copyright and related rights waived via CC0.