ops-mason/plans/audit-core-openbao-runtime-custody.md
tegwick 8e41f85121 Build audit-core AppRole lane after founder approval
reuse_policy leaves external-secrets-audit-core intact so database
leases keep working. AppRole delivered to Kubernetes; interim static
ESO token retired.
2026-08-13 10:42:59 +02:00

146 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: audit-core-openbao-runtime-custody
demand_source: audit-core/workplans/AUDIT-WP-0005-T02
consumer_repo: audit-core
credential_type: openbao-approle-kv
status: built
approved_by: "Bernd Worsch"
approved_at: "2026-08-13"
created: "2026-08-13"
updated: "2026-08-13"
---
# Construction plan: audit-core OpenBao runtime custody
## 1. Demand
audit-core on railiance01 needs durable PostgreSQL custody and a sender
registry without the founder typing OpenBao tokens or running `bao kv put`.
The founders remaining acts are: approve this plan, and unseal OpenBao if
it is sealed. Mechanics belong to ops-mason (structure) and ops-warden
(routing / founder desk), not to an ad-hoc terminal session.
## 2. Existing-structure survey
Checked 2026-08-13 against live railiance01 and the routing catalog:
- **Database leases already exist.** `rapp-postgres` provisioned `audit_core`
with `audit_core_app` / `audit_core_migrate`, and OpenBao already vends
`database/creds/audit-core-runtime` and `database/creds/audit-core-migration`
through railiance-platform grants `rapp-postgres/audit-core-runtime` and
`rapp-postgres/audit-core-migration`. `warden route show
database-dynamic-credentials` points at that playbook. Do not create a
second database engine or a KV copy of a rotating lease.
- **ESO token already exists.** `external-secrets/openbao-audit-core-eso-token`
was minted 2026-08-13 with policy `external-secrets-audit-core` (read on
the two database cred paths and on `platform/data/workloads/audit-core/senders`).
ClusterSecretStore YAML is drafted; it is not yet applied.
- **KV senders path does not exist.** A founder `bao kv put` against
`platform/workloads/audit-core/senders` 403d because the workstation
`~/.vault-token` is not an operator identity. That is the failure this
plan exists to retire.
- **Catalog.** `database-dynamic-credentials` already covers the lease.
There is no `audit-core-senders` entry. `openbao-api-key` is the generic
template and must not become the concrete lane.
- **Auth topology.** Same as user-engine / rapp-qonto: railiance01 ESO
cannot use CoulombCore Kubernetes auth. AppRole is the transitional
bridge; same-cluster Kubernetes auth remains the target.
- **Does an existing lane already satisfy this?** The database half, yes —
bind to the existing OpenBao database roles. The sender-registry half,
no — there is no path, no AppRole, and no catalog entry. Reuse the
email-connect / user-engine *shape* (namespace-limited store, 15m
tokens, eight uses), do not widen those identities.
## 3. Proposed changes
| # | Action | Object | Reuse-vs-new rationale |
|---|---|---|---|
| 1 | reuse | OpenBao roles `audit-core-runtime` / `audit-core-migration` | Already vended; a KV copy of a lease recreates the delivery gap |
| 2 | reuse | policy `external-secrets-audit-core` | Already written; covers database creds + senders read |
| 3 | create | AppRole `external-secrets-audit-core` | Replace the long-lived ESO token with the same 15m/30m/8-use bridge as user-engine and rapp-qonto |
| 4 | deliver | Secret `external-secrets/openbao-audit-core-approle` | Mason write-only delivery of role_id/secret_id; never logged |
| 5 | create | ClusterSecretStore `openbao-audit-core` | Namespace-limited to `audit-core`; AppRole auth (token store is the interim until this builds) |
| 6 | reuse | ClusterSecretStore `openbao-audit-core-database` + ExternalSecret | Namespaced VaultDynamicSecret cannot read the ESO token in `external-secrets`; CSS can. One `dataFrom` extract per lease. Applied 2026-08-13, SecretSynced |
| 7 | create | empty KV path `platform/workloads/audit-core/senders` | Structure only. Values are **not** founder-pasted: first deploy mints sender tokens in-cluster into Secret `audit-core-senders`. A later wrap-migrate (user-engine pattern) can move them into this path without printing |
| 8 | propose | ops-warden catalog `audit-core-senders` | Pointer, `warden_executes: false`, starts `draft` |
| 9 | retire | founder `bao kv put` / token-prompt as the T02 delivery path | That is the error-prone interaction this demand forbids |
No sibling workload path is readable. Mason does not print, log, or return
sender tokens or database passwords.
## 4. Review notes
- **Naming:** `external-secrets-audit-core` / `openbao-audit-core` matches
email-connect and user-engine. Database grant names stay with rapp-postgres.
- **TTL/scoping:** 15-minute tokens, 30-minute max, eight uses. Store
condition is `audit-core` only.
- **Redundancy:** does not create a second database role or a KV cache of
the lease. Does not widen `openbao-email-connect` or
`openbao-user-engine-runtime`.
- **Compaction:** retires the attended `bao kv put` path as the way T02
completes. The already-minted static ESO token is interim and is revoked
after the AppRole store is Ready.
- **Secret values:** sender tokens are generated by us, not imported from a
vendor. Founder paste-once is the wrong desk for them. Generate in-cluster;
optional later wrap-migrate into the empty KV path.
- **Founder remaining acts:** approve this plan; unseal OpenBao if sealed.
No token paste, no `bao` command review.
## 5. Executive summary
**One-line ask:** approve letting ops-mason finish audit-cores OpenBao
lane (AppRole + namespace-limited store + empty senders path + catalog
pointer) so the receiver can run without you typing OpenBao tokens.
**Who gets access:** only External Secrets, through AppRole
`external-secrets-audit-core`. Application pods keep seeing mounted
files and `AUDIT_CORE_SENDERS`. Nothing else gains a new identity.
**To what:** read `database/creds/audit-core-runtime`,
`database/creds/audit-core-migration`, and (later)
`platform/workloads/audit-core/senders`. No sibling workload or tenant
path.
**For how long:** each AppRole login is 15 minutes, 30 minutes maximum,
eight uses. The AppRole secret ID lasts until rotated or replaced by
same-cluster Kubernetes auth.
**Blast radius if the credential leaks:** append to the audit trail and
read the sender registry; cannot rewrite or delete stored events (append-only
trigger). Revoke the AppRole secret ID, rotate sender tokens overlap-first,
and roll the receiver. Database leases expire on their own.
**Cost to reverse:** delete the AppRole, its bootstrap Secret, the
ClusterSecretStore, and the empty KV path. Database roles and the
`audit_core` database stay (owned by rapp-postgres). No other consumer
is on this identity.
**Decision:** approve, reject, or send back to phase 1.
Approval authorizes Mason build of structure and in-cluster sender
minting. It does not authorize printing any secret value.
## 6. Build result
Built 2026-08-13 after founder approve, via
`ops_mason.executor.build_approle_kv_lane` with `reuse_policy=True` so
the existing `external-secrets-audit-core` policy (database creds +
senders read) was not rewritten.
- reused policy `external-secrets-audit-core`;
- created AppRole `external-secrets-audit-core` (15m / 30m / 8 uses);
- delivered role-id/secret-id into Secret
`external-secrets/openbao-audit-core-approle`; local delivery dir shredded;
- switched ClusterSecretStores `openbao-audit-core` and
`openbao-audit-core-database` to AppRole auth; both `Valid`;
- ExternalSecrets remain `SecretSynced`; receiver `/readyz` still
`custody_class=archive`;
- deleted interim Secret `external-secrets/openbao-audit-core-eso-token`;
- catalog entry `audit-core-senders` already drafted in ops-warden
(`warden route find "audit-core senders" --all`).
Empty KV path `platform/workloads/audit-core/senders` was not written:
mason does not put values, and senders already live in-cluster. A later
wrap-migrate can create that path without a founder paste.
No secret value was printed, committed, or logged. The 15-minute
platform-admin child used for the build was revoked.