diff --git a/plans/rapp-qonto-openbao-approle-bridge.md b/plans/rapp-qonto-openbao-approle-bridge.md new file mode 100644 index 0000000..8e62fd2 --- /dev/null +++ b/plans/rapp-qonto-openbao-approle-bridge.md @@ -0,0 +1,147 @@ +--- +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: reviewed +approved_by: null +approved_at: null +created: "2026-07-27" +updated: "2026-07-27" +--- + +# 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 plan is reviewed but unapproved; phase 4 must not begin without a new +explicit approval. diff --git a/plans/rapp-qonto-openbao-kubernetes-lane.md b/plans/rapp-qonto-openbao-kubernetes-lane.md deleted file mode 100644 index 308b5f4..0000000 --- a/plans/rapp-qonto-openbao-kubernetes-lane.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -id: rapp-qonto-openbao-kubernetes-lane -demand_source: reef-railiance/workplans/REEF-RAILIANCE-WP-0003-T01 -consumer_repo: rapp-qonto -credential_type: openbao-kubernetes-kv -status: reviewed -approved_by: null -approved_at: null -created: "2026-07-27" -updated: "2026-07-27" ---- - -# Construction plan: rapp-qonto OpenBao Kubernetes lane - -## 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 to OpenBao -with Kubernetes workload identity; 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. - -The existing read policy satisfies the requested data scope exactly. Reuse it; -do not create the duplicate policy from CCR-2026-0009. A new authentication -role is still required because no existing role should gain Qonto access and -no existing Qonto lane is workload-authenticated. - -## 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 | Kubernetes auth role `external-secrets-rapp-qonto` | New identity binding only; bound to service account `external-secrets` in namespace `external-secrets`, policy #1, token TTL 15 minutes | -| 3 | create | `ClusterSecretStore/openbao-rapp-qonto` | Uses Vault Kubernetes auth role #2 and the `tenants` KV v2 mount; namespace condition permits only `rapp-qonto` | -| 4 | modify | `rapp-qonto` ExternalSecret | Map remote fields `API_USER` -> `QONTO_LOGIN` and `API_KEY` -> `QONTO_SECRET_KEY`; no values enter Git or logs | -| 5 | retire | proposed `openbao-qonto-assistant` static-token store | Superseded before deployment; avoids a long-lived OpenBao token in a Kubernetes Secret | -| 6 | propose | ops-warden catalog entry `rapp-qonto-workload-kv` | Pointer to the built Kubernetes-auth lane, `warden_executes: false`, initially `draft` | - -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. - The role binds only the ESO service account; 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. -- **Build executor extension:** ops-mason currently builds AppRole lanes only. - Phase 4 must add a narrowly tested Kubernetes-role operation with the same - approval refusal and metadata-only audit guarantees before applying this - plan. - -## 5. Executive summary - -**One-line ask:** allow External Secrets on railiance01 to deliver the existing -Binky Qonto read credential to `rapp-qonto` without storing a long-lived -OpenBao token in Kubernetes. - -**Who/what gets access:** a new OpenBao Kubernetes role named -`external-secrets-rapp-qonto`, usable only by the existing -`external-secrets` service account in the `external-secrets` namespace. No -application pod, human role, or unrelated service account receives OpenBao -login rights. - -**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 Kubernetes-auth login receives a 15-minute OpenBao -token. There is no delivered AppRole secret ID and no long-lived OpenBao token -stored in Kubernetes. The Kubernetes service-account identity remains valid -while the ESO deployment and role binding exist. - -**Blast radius if the identity is abused:** an attacker able to act as the ESO -service account and use this role could read the Binky Qonto API credential. -The OpenBao policy cannot read any sibling tenant secret. The downstream Qonto -key remains high-risk and must be rotated if exposed. - -**Cost to reverse:** delete one Kubernetes auth role, one ClusterSecretStore, -and the `rapp-qonto` ExternalSecret/derived Secret. The existing human Qonto -lane and underlying credential remain intact; no unrelated consumer changes. - -**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.