let d = import "adr-defaults.ncl" in d.make_adr { id = "adr-018", title = "SecretumVault — Dynamic Secrets Complementary to SOPS+Age", status = 'Accepted, date = "2026-01-08", context = "The platform manages two distinct classes of secrets: (1) static gitops secrets — API keys, TLS certs, SSH keys committed as encrypted SOPS+Age files; (2) dynamic runtime secrets — temporary database passwords, short-lived tokens, auto-rotating credentials. SOPS+Age handles class 1 well but has no concept of TTL, auto-rotation, or access audit trails. HashiCorp Vault handles class 2 but is Go binary (not Rust-native), uses BSL license (not permissive), and uses HCL policies (incompatible with Cedar authorization model).", decision = "SecretumVault provides dynamic runtime secrets management. It is embedded in the platform as a Rust-native library (path dependency `../../../Development/secretumvault`) with Cedar policy enforcement, SurrealDB-backed storage, and filesystem backend for solo mode (age key at `${data_dir}/vault/master.age`). SOPS+Age remains for static gitops secrets. SecretumVault complements, not replaces, SOPS.", rationale = [ { claim = "Rust-native: zero subprocess overhead, same authorization model as the platform", detail = "SecretumVault is linked as a Rust library. No subprocess, no gRPC, no network hop for secret retrieval. Cedar policies for secrets are evaluated by the same Cedar engine used for infrastructure authorization — one policy model, one audit trail.", }, { claim = "HashiCorp Vault rejected: BSL license and HCL policies incompatible with platform constraints", detail = "BSL license restricts commercial use without a subscription. HCL policies require maintaining a separate policy language alongside Cedar. Neither aligns with the platform's Rust/Cedar/Nickel stack.", }, { claim = "SOPS+Age remains authoritative for static gitops secrets", detail = "Static secrets (API keys in config, TLS certs) are already gitops-managed via SOPS. Replacing SOPS would break the gitops workflow and add migration risk. SecretumVault handles only runtime-dynamic secrets.", }, ], consequences = { positive = [ "Dynamic credential TTL and auto-rotation for database passwords and tokens", "Cedar-gated secret access with audit trail — compliance-ready", "Solo mode uses filesystem age key — no infrastructure dependency for single-operator deployments", "Consistent authorization model: Cedar governs both infrastructure operations and secret access", ], negative = [ "HA deployment requires 3-node Raft cluster — additional infrastructure for production", "Age key at `${data_dir}/vault/master.age` is the only bootstrap credential — requires chmod 600 and secure backup", "Local path dependency — requires secretumvault repo checkout alongside provisioning", ], }, alternatives_considered = [ { option = "HashiCorp Vault", why_rejected = "BSL license incompatible with open distribution. Go binary introduces subprocess overhead. HCL policies require a second policy language alongside Cedar. No Rust-native integration.", }, { option = "Extend SOPS+Age to handle dynamic secrets", why_rejected = "SOPS is a file encryption tool, not a secrets engine. TTL, auto-rotation, and audit trails are not concepts SOPS is designed for. Extending it would be a reimplementation of a secrets engine with worse UX.", }, ], constraints = [ { id = "sops-and-vault-complementary", claim = "SOPS+Age handles static gitops secrets; SecretumVault handles runtime dynamic secrets — no overlap", scope = "platform/secretumvault/", severity = 'Hard, check = { tag = 'Grep, pattern = "secretumvault", paths = ["platform/secretumvault/"], must_be_empty = false }, rationale = "Mixing the two systems creates ambiguity about which is authoritative for a given secret class. Clear separation prevents accidental migration of static secrets into the vault.", }, { id = "cedar-governs-vault-access", claim = "All SecretumVault read operations must be authorized via Cedar policies — no bypass path", scope = "platform/secretumvault/src/", severity = 'Hard, check = { tag = 'Grep, pattern = "cedar|authorize", paths = ["platform/secretumvault/"], must_be_empty = false }, rationale = "Cedar is the single authorization point for the platform. Vault access bypassing Cedar creates an unaudited path to secrets.", }, { id = "vault-age-key-permissions", claim = "The bootstrap age key at vault/master.age must be chmod 600 and must be the only file-based credential", scope = "platform/secretumvault/src/solo.rs", severity = 'Hard, check = { tag = 'NuCmd, cmd = "rg 'master.age' --include='*.rs' platform/ | grep -v '0o600\\|0600'", expect_exit = 1 }, rationale = "The age key is the bootstrap trust anchor. If it is world-readable, the entire vault is compromised.", }, ], related_adrs = ["adr-014-solid-enforcement", "adr-015-solo-mode-architecture"], ontology_check = { decision_string = "SecretumVault provides dynamic runtime secrets with Cedar authorization; SOPS+Age remains for static gitops secrets; complementary, not competing", invariants_at_risk = ["solid-boundaries"], verdict = 'Safe, }, }