provisioning/adrs/adr-018-secretumvault-integration.ncl

87 lines
5.4 KiB
XML

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,
},
}