Revise Qonto secret lane for cross-cluster topology
This commit is contained in:
parent
8d89611b22
commit
239776974b
2 changed files with 147 additions and 113 deletions
147
plans/rapp-qonto-openbao-approle-bridge.md
Normal file
147
plans/rapp-qonto-openbao-approle-bridge.md
Normal 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.
|
||||||
|
|
@ -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.
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue