diff --git a/plans/user-engine-openbao-runtime-custody.md b/plans/user-engine-openbao-runtime-custody.md new file mode 100644 index 0000000..b20c359 --- /dev/null +++ b/plans/user-engine-openbao-runtime-custody.md @@ -0,0 +1,99 @@ +--- +id: user-engine-openbao-runtime-custody +demand_source: net-kingdom/workplans/NK-WP-0023-T05 +consumer_repo: net-kingdom +credential_type: openbao-approle-kv +status: proposed +created: "2026-07-30" +updated: "2026-07-30" +--- + +# Construction plan: user-engine OpenBao runtime custody + +## 1. Demand + +The live portal edge marker and identity-provisioner service token are +bootstrap Kubernetes Secrets without an authoritative rotation source. +`user-engine` and `identity-provisioner` need the same runtime behavior with +OpenBao as authority and External Secrets Operator as delivery mechanism. + +## 2. Existing-structure survey + +- `Secret/user-engine-runtime` in namespace `user-engine` contains only + `proxy-secret`. +- `Secret/identity-provisioner-client` in namespace `user-engine` and + `Secret/identity-provisioner-token` in namespace `sso` carry the two copies + of the provisioner service token. +- The applications already consume only Secret references; no code or + Deployment interface change is required. +- The established `rapp-qonto` bridge proves that railiance01 External Secrets + must currently use AppRole against the OpenBao instance on CoulombCore; + railiance01 service-account tokens cannot use that instance's Kubernetes + auth mount. +- Existing activity-core stores and policies are unrelated and must not be + widened or reused across this identity boundary. + +## 3. Proposed changes + +| # | Action | Object | Rationale | +|---|---|---|---| +| 1 | create | KV path `platform/workloads/user-engine/runtime` | One authoritative workload path with fields `PROXY_SECRET` and `PROVISIONER_TOKEN` | +| 2 | create | policy `workload-kv-read-user-engine-runtime` | Read and metadata access only to the exact KV v2 path | +| 3 | create | AppRoles `external-secrets-user-engine` and `external-secrets-sso-user-engine` | Separate revocable bootstrap identities for the two consuming namespaces | +| 4 | deliver | AppRole bootstrap Secrets in `external-secrets` | Write-only Mason delivery; 15-minute tokens, 30-minute maximum, eight uses | +| 5 | create | namespace-restricted ClusterSecretStores for `user-engine` and `sso` | Prevent either identity from becoming a general cross-namespace store | +| 6 | create | three ExternalSecrets targeting the existing Secret names | Preserve application interfaces while changing authority | +| 7 | migrate | existing values into the KV path through a wrapped platform-admin process | Read source and write destination in one process; never return either value | +| 8 | verify | metadata, rollout, positive/negative authentication, and rotation rehearsal | Record field names, readiness, denial, and rollout only | +| 9 | retire | manual Secret-copy/bootstrap procedure | ExternalSecret becomes the sole writer and rotation path | + +The migration does not mint a new application capability. It moves two +existing values into governed custody. Same-cluster Kubernetes auth remains +the target after OpenBao moves to reef-railiance. + +## 4. Review and optimization + +- Separate AppRoles avoid widening one namespace identity across `sso` and + `user-engine`. +- Both policies resolve to one exact path, avoiding duplicate values and + divergent rotation. +- Existing Secret names and keys remain stable, limiting rollout risk. +- A wrapped migration process is required because printing, downloading, or + staging either value would create a new disclosure path. +- Reversal deletes the ExternalSecrets/stores/AppRoles and restores the + protected pre-migration Secret snapshots during the bounded rollback window. +- Rotation changes the OpenBao fields, waits for all three ExternalSecrets, + and rolls the provisioner and portal together; failed synchronization stops + before either Deployment changes. + +## 5. Executive summary + +**One-line ask:** approve moving the existing user-engine edge marker and +provisioner token into one exact OpenBao workload path, delivered by two +namespace-specific, revocable External Secrets AppRoles. + +**Who gets access:** only External Secrets through +`external-secrets-user-engine` and `external-secrets-sso-user-engine`. +Application pods continue to receive only their existing namespaced Secrets. + +**To what:** read-only access to exactly +`platform/workloads/user-engine/runtime`; no sibling workload or tenant path. + +**For how long:** each AppRole login receives a 15-minute token with a +30-minute maximum and eight uses. AppRole secret IDs persist for ESO continuity +until rotated or same-cluster Kubernetes auth replaces the bridge. + +**Blast radius:** compromise permits impersonating the internal provisioner +client and/or the portal trusted marker, but grants no direct OpenBao sibling +read, directory administrator password, user password, MFA seed, or platform +role. Revoke both AppRole secret IDs, rotate both runtime values, and roll the +two workloads. + +**Cost to reverse:** delete three ExternalSecrets, two stores, two AppRoles, +their bootstrap Secrets, and one exact policy/path; restore the protected +pre-migration Kubernetes Secret snapshots. No database or identity record +changes. + +**Decision needed:** approve as proposed, reject, or return with changes. +Approval authorizes custody migration and bounded rotation rehearsal, never +printing or otherwise disclosing either value.