--- id: rapp-qonto-openbao-approle-bridge demand_source: reef-railiance/workplans/REEF-RAILIANCE-WP-0003-T01 consumer_repo: rapp-qonto credential_type: openbao-approle-kv status: built approved_by: Bernd Worsch approved_at: "2026-07-27" created: "2026-07-27" updated: "2026-08-22" --- # Construction plan: rapp-qonto OpenBao AppRole bridge ## 1. Demand `rapp-qonto` needs non-interactive, read-only delivery of the existing Binky Qonto API credential from `tenants/binky/qonto-api` into its dedicated Kubernetes namespace. External Secrets Operator should authenticate directly to OpenBao and mint short-lived tokens; neither the application nor a ClusterSecretStore should hold a long-lived OpenBao token. ## 2. Existing-structure survey Live OpenBao state was checked on 2026-07-27: - auth mounts include `kubernetes/`; its configured API endpoint is the cluster-local `https://10.43.0.1:443`; - existing Kubernetes roles are `external-secrets-activity-core`, `external-secrets-issue-core`, and `external-secrets-reuse-surface`; - policy `workload-kv-read-binky-qonto-api` already exists and is scoped to the existing tenant credential; - the `tenants/` KV v2 mount and `tenants/binky/qonto-api` value already exist; this plan does not create or read the value; - existing cluster stores use an interim static-token pattern, despite the Kubernetes roles existing; - `CCR-2026-0009` proposed a duplicate `workload-kv-read-qonto-assistant` policy and the transitional `qonto-assistant` namespace/name. Topology verification established that this OpenBao instance runs on Coulombcore while the consumer runs on railiance01. Its existing Kubernetes auth mount validates Coulombcore service-account tokens and cannot validate a railiance01 token. A cross-cluster Kubernetes auth mount would additionally require stable TokenReview API reachability and a reviewer identity across the cluster boundary. The existing read policy satisfies the requested data scope exactly. Reuse it; do not create the duplicate policy from CCR-2026-0009. Use a dedicated AppRole as a transitional bridge because it does not couple authentication to the wrong Kubernetes API. Retain same-cluster Kubernetes workload authentication as the target when secret custody and workload placement are unified. ## 3. Proposed changes | # | Action | Object | Reuse-vs-new rationale | |---|---|---|---| | 1 | reuse | policy `workload-kv-read-binky-qonto-api` | Already grants the exact required read scope; a second equivalent policy adds drift without isolation | | 2 | create | AppRole `external-secrets-rapp-qonto` | Dedicated non-human bridge; policy #1, token TTL 15 minutes, maximum TTL 30 minutes, bounded token uses | | 3 | deliver | Kubernetes Secret `external-secrets/openbao-rapp-qonto-approle` | Holds only the AppRole role ID and secret ID for ESO; created through the approved Mason delivery path, never committed or logged | | 4 | create | `ClusterSecretStore/openbao-rapp-qonto` | Uses AppRole #2 and secret #3 against the `tenants` KV v2 mount; namespace condition permits only `rapp-qonto` | | 5 | modify | `rapp-qonto` ExternalSecret | Map remote fields `API_USER` -> `QONTO_LOGIN` and `API_KEY` -> `QONTO_SECRET_KEY`; no values enter Git or logs | | 6 | retire | proposed `openbao-qonto-assistant` static-token store | Superseded before deployment; avoids a long-lived OpenBao token in a Kubernetes Secret | | 7 | propose | ops-warden catalog entry `rapp-qonto-workload-kv` | Pointer to the built AppRole lane, `warden_executes: false`, initially `draft` | | 8 | record | migration residual | Replace the AppRole bridge with same-cluster Kubernetes auth when OpenBao custody or workload placement is unified | No underlying Qonto secret is created, changed, printed, or rotated. ## 4. Review notes - **Naming:** steady-state `rapp-qonto` names replace the migration-only `qonto-assistant` names. - **TTL/scoping:** 15-minute tokens match existing External Secrets roles; maximum TTL is 30 minutes and token uses are bounded. The ClusterSecretStore condition restricts consumers to one namespace. - **Redundancy:** reuses the exact existing policy. This removes the duplicate policy proposed by CCR-2026-0009. - **Compaction:** the draft static-token Qonto store is retired before it is deployed. Existing unrelated stores are untouched. - **Consumer usability:** External Secrets performs authentication and delivery. The application consumes only a namespaced Kubernetes Secret. - **Posture:** the lane accesses a high-risk bank credential, but it is read-only and the runtime exposes no financial write operation. Production still requires egress, audit, revocation, and failure-domain gates. - **Credential custody:** the AppRole secret ID is a revocable bootstrap credential held only in a namespaced Kubernetes Secret accessible to ESO. It is not a Qonto credential and never enters Git, workplans, audit events, command output, or chat. - **Migration:** this bridge is intentionally transitional. A cross-cluster TokenReview dependency is rejected for now because it expands networking, reviewer-identity, and failure-domain coupling. ## 5. Executive summary **One-line ask:** allow External Secrets on railiance01 to use a dedicated, revocable AppRole bridge to deliver the existing Binky Qonto read credential to `rapp-qonto`, issuing only short-lived OpenBao tokens. **Who/what gets access:** a new AppRole named `external-secrets-rapp-qonto`. Its bootstrap identity is stored in `external-secrets/openbao-rapp-qonto-approle` and referenced only by the dedicated ClusterSecretStore. No application pod, human role, or unrelated store receives the identity. **To what:** read-only access through the existing `workload-kv-read-binky-qonto-api` policy to exactly `tenants/binky/qonto-api`. Store use is limited to ExternalSecret resources in the `rapp-qonto` namespace. **For how long:** each AppRole login receives a 15-minute OpenBao token with a 30-minute maximum and bounded uses. The revocable AppRole secret ID persists for ESO continuity until rotated or the bridge is retired; no OpenBao token persists in Kubernetes. **Blast radius if the identity is abused:** an attacker able to read the AppRole bootstrap Secret could mint bounded, short-lived tokens and read the Binky Qonto API credential. The OpenBao policy cannot read any sibling tenant secret. Revoke/rotate the AppRole secret ID immediately; rotate the downstream Qonto key too if it may have been read. **Cost to reverse:** delete one AppRole, its bootstrap Kubernetes Secret, one ClusterSecretStore, and the `rapp-qonto` ExternalSecret/derived Secret. The existing human Qonto lane and underlying credential remain intact. **Decision needed:** approve as proposed, reject, or send back to phase 1 with changes. Approval authorizes the structural access lane only, never disclosure or modification of the Qonto credential value. **Previous decision:** the Kubernetes-auth design was approved by Bernd Worsch on 2026-07-27. That approval does not authorize this revised AppRole design. ## 6. Build result Build attempted 2026-07-27 and stopped during verification. The OpenBao role was created, but the store login returned 403. Live topology inspection showed that `bao.coulomb.social` resolves to Coulombcore (`92.205.130.254`), while the ESO service account token belongs to railiance01 (`92.205.62.239`). The existing `kubernetes/` auth mount therefore validates a different cluster. The failed ClusterSecretStore, ExternalSecret, disposable namespace, and mis-targeted OpenBao role were removed without reading a secret value. No live object from the failed build remains. The revised phase-2 recommendation is alternative 2: use the dedicated AppRole bridge described above until the OpenBao/cluster boundary is unified. The revised plan was explicitly approved by Bernd Worsch on 2026-07-27 (`go on`). Phase 4 may proceed. ## 7. Revised build result Built and verified on 2026-07-27 through `ops_mason.executor.build_approle_kv_lane`: - reused policy `workload-kv-read-binky-qonto-api`, scoped to exactly `tenants/binky/qonto-api`; - created AppRole `external-secrets-rapp-qonto` with 15-minute token TTL, 30-minute maximum TTL, eight token uses, and a revocable secret ID; - delivered the role ID and secret ID directly into Kubernetes Secret `external-secrets/openbao-rapp-qonto-approle`; temporary local and remote delivery files were removed; - created namespace-restricted `ClusterSecretStore/openbao-rapp-qonto`; - synchronized `ExternalSecret/rapp-qonto` into `Secret/rapp-qonto-runtime`. Metadata-only live verification reported the store `Valid`, the ExternalSecret `SecretSynced`, and exactly two keys in the derived Secret. No credential value was read, printed, committed, or recorded. The bridge remains transitional. Replace it with same-cluster Kubernetes authentication when OpenBao custody and workload placement are unified.