ops-mason/plans/user-engine-openbao-runtime-custody.md

6.3 KiB

id demand_source consumer_repo credential_type status approved_by approved_at created updated
user-engine-openbao-runtime-custody net-kingdom/workplans/NK-WP-0023-T05 net-kingdom openbao-approle-kv built Bernd Worsch 2026-07-30 2026-07-30 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.

6. Build result

Built on 2026-07-30 exactly as approved:

  • created KV path platform/workloads/user-engine/runtime;
  • created policy workload-kv-read-user-engine-runtime;
  • created AppRoles external-secrets-user-engine and external-secrets-sso-user-engine;
  • delivered each bootstrap identity directly into its Kubernetes Secret;
  • created two namespace-restricted ClusterSecretStores and three ExternalSecrets while preserving existing application Secret names;
  • migrated the two existing values in a wrapped process without printing or staging them;
  • rehearsed coordinated rotation, waited for all three derived Secrets, and returned both dependent Deployments to Ready;
  • verified exact-path read, sibling-path deny, matching derived provisioner tokens, and a live post-rotation identity reconciliation.

No new secret value requires founder paste-once provisioning. No credential value was written to Git, logs, State Hub, or chat. The two executor audit records are in audit/build-log.jsonl; no ops-warden catalog entry is needed because this is an OpenBao/External Secrets runtime lane rather than an SSH certificate route.