Revise Qonto secret lane for cross-cluster topology

This commit is contained in:
tegwick 2026-07-27 02:37:26 +02:00
parent 8d89611b22
commit 239776974b
2 changed files with 147 additions and 113 deletions

View file

@ -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.

View file

@ -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.