1.12 is the consensus-native KMS: it constructs a ConsensusAuthorizer at start and refuses to run without one — 'consensus validator authority is empty (refusing to boot fail-open)'. The refusal is right; there is no open mode by design. But nothing provides this pod the authority snapshot the operator is meant to drop at KMS_CONSENSUS_FILE, so the upgrade crash-looped and took the Zoo KMS down for ~90 minutes until it was rolled back. Adopting 1.12 needs the consensus authority provisioned first. Pinning the generation that runs is not a preference here — it is the difference between a KMS and a CrashLoopBackOff. Co-authored-by: Hanzo Dev <[email protected]>
120 lines
5.5 KiB
YAML
120 lines
5.5 KiB
YAML
# Zoo KMS — Zoo's OWN Go-native KMS server (crypto-sovereign).
|
|
# Namespace: zoo-kms (do-sfo3-zoo-k8s). Mirrors the PROVEN lux-kms-go pattern.
|
|
#
|
|
# Image: ghcr.io/zooai/kms:1.11.8 — Zoo-branded rebrand of luxfi/kms:1.11.8, the
|
|
# EXACT version proven-live in lux-kms-go (the pure-Go Hanzo/Lux KMS). White-
|
|
# labelled to the Zoo brand by domain (kms.zoo.network) and org (KMS_ORG=zoo).
|
|
# Patch-pin (never :latest/major). 1.11.10 mandates a consensus-authorizer that
|
|
# fail-closes without KMS_CONSENSUS_VALIDATORS/OPERATORS (crash) — a fleet
|
|
# upgrade owned by the KMS-issuer/consensus workstream; v1.11.8 = secrets-only
|
|
# parity with Lux today.
|
|
#
|
|
# Secrets surface (HTTP :8080) is the active path (kms-operator + Go callers):
|
|
# Org -> path -> env -> secret, IAM-JWT gated, ZapDB (Badger) at rest.
|
|
#
|
|
# PARITY WITH LUX (verified live 2026-07-14): the reference lux-kms-go/kms-0 runs
|
|
# SECRETS-ONLY — MPC_VAULT_ID="" and ZAP_PORT=0. It stores secrets with ZapDB
|
|
# at-rest encryption (encryption-key-b64); it does NOT do MPC-backed signing, and
|
|
# its lux-mpc ring is a SEPARATE sovereign primitive not wired into the KMS.
|
|
# Zoo is deployed at EXACT parity: own KMS + own zoo-mpc ring, secrets-only today.
|
|
# Enabling true MPC-backed signing (populate MPC_VAULT_ID with the Zoo DKG vault
|
|
# id + ZAP_PORT>0) requires KMS_CONSENSUS_VALIDATORS/OPERATORS authority or kmsd
|
|
# fail-closes — the SAME gate that blocks Lux. Staged, owner-go.
|
|
#
|
|
# Bootstrap secrets (kms-secrets, in-cluster only — generated by deploy.sh, NEVER
|
|
# committed; do NOT lose encryption-key-b64 or the ZapDB store is unreadable):
|
|
# master-key-b64 random 32B (SecretStore envelope)
|
|
# encryption-key-b64 random 32B (ZapDB Badger at-rest encryption)
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: StatefulSet
|
|
metadata:
|
|
name: kms
|
|
namespace: zoo-kms
|
|
labels: { app: kms, brand: zoo }
|
|
spec:
|
|
serviceName: kms
|
|
replicas: 1
|
|
selector: { matchLabels: { app: kms } }
|
|
template:
|
|
metadata: { labels: { app: kms, brand: zoo } }
|
|
spec:
|
|
imagePullSecrets:
|
|
- name: ghcr-zooai
|
|
containers:
|
|
- name: kms
|
|
# PINNED BACK from 1.12.9, which cannot boot here. 1.12 is the
|
|
# consensus-native generation: it builds a ConsensusAuthorizer at start
|
|
# and REFUSES to run without one —
|
|
# kms: consensus authorizer init failed: consensus validator authority
|
|
# is empty (refusing to boot fail-open)
|
|
# That refusal is correct (no open mode, fail closed), but nothing on
|
|
# this pod provides the authority snapshot the operator is meant to drop
|
|
# at KMS_CONSENSUS_FILE, so 1.12.9 crash-looped for 90 minutes with the
|
|
# Zoo KMS down. Moving to 1.12 needs the authority provisioned FIRST;
|
|
# until then this stays on the generation that runs.
|
|
image: ghcr.io/zooai/kms:1.11.8
|
|
imagePullPolicy: IfNotPresent
|
|
ports:
|
|
- { name: http, containerPort: 8080, protocol: TCP }
|
|
env:
|
|
- { name: KMS_DATA_DIR, value: /data/kms }
|
|
- { name: KMS_LISTEN, value: ":8080" }
|
|
- { name: KMS_ORG, value: "zoo" }
|
|
- { name: KMS_BRAND_NAME, value: "Zoo" }
|
|
# secrets-only parity with Lux (see header). Staged for MPC-backing.
|
|
- { name: ZAP_PORT, value: "0" }
|
|
- { name: MPC_VAULT_ID, value: "" }
|
|
# IAM — shared Casdoor, single-issuer (hanzo.id), Zoo apps as audience.
|
|
# Zoo-branded login face id.zoo.network resolves to the same IAM; the
|
|
# branded-endpoint swap is coordinated with the KMS-issuer work (agent C).
|
|
- { name: IAM_ENDPOINT, value: "https://hanzo.id" }
|
|
- { name: IAM_TOKEN_PATH, value: "/v1/iam/oauth/token" }
|
|
- { name: KMS_IAM_URL, value: "https://hanzo.id" }
|
|
- { name: KMS_EXPECTED_ISSUER, value: "https://hanzo.id" }
|
|
- { name: IAM_KEYS_URL, value: "https://hanzo.id/.well-known/jwks" }
|
|
# White-label the console OIDC SSO to the Zoo brand: KMS_IAM_OWNER=zoo
|
|
# makes client_id default to "zoo-kms" (NEVER the luxfi default "lux-kms").
|
|
- { name: KMS_IAM_OWNER, value: "zoo" }
|
|
- { name: KMS_OIDC_CLIENT_ID, value: "zoo-kms" }
|
|
- name: KMS_OIDC_CLIENT_SECRET
|
|
valueFrom: { secretKeyRef: { name: zoo-kms-oidc, key: client-secret } }
|
|
- name: KMS_STATE_SECRET
|
|
valueFrom: { secretKeyRef: { name: zoo-kms-oidc, key: state-secret } }
|
|
- name: KMS_MASTER_KEY_B64
|
|
valueFrom: { secretKeyRef: { name: kms-secrets, key: master-key-b64 } }
|
|
- name: KMS_ENCRYPTION_KEY_B64
|
|
valueFrom: { secretKeyRef: { name: kms-secrets, key: encryption-key-b64 } }
|
|
livenessProbe:
|
|
httpGet: { path: /healthz, port: http }
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 10
|
|
readinessProbe:
|
|
httpGet: { path: /healthz, port: http }
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
resources:
|
|
requests: { cpu: 50m, memory: 64Mi }
|
|
limits: { cpu: 500m, memory: 512Mi }
|
|
volumeMounts:
|
|
- { name: data, mountPath: /data/kms }
|
|
volumeClaimTemplates:
|
|
- metadata: { name: data }
|
|
spec:
|
|
accessModes: [ReadWriteOnce]
|
|
# Retain so an accidental PVC delete never nukes the Zoo KMS store.
|
|
storageClassName: do-block-storage-retain
|
|
resources: { requests: { storage: 5Gi } }
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: kms
|
|
namespace: zoo-kms
|
|
labels: { app: kms, brand: zoo }
|
|
spec:
|
|
type: ClusterIP
|
|
selector: { app: kms }
|
|
ports:
|
|
- { name: http, port: 80, targetPort: http, protocol: TCP }
|