Correct whynot credential tenant path

This commit is contained in:
tegwick 2026-06-28 01:00:12 +02:00
parent ad47a136f7
commit eb24e04b71
10 changed files with 67 additions and 48 deletions

View file

@ -3,9 +3,9 @@ kind: credential-change-request
schema_version: 1 schema_version: 1
request_type: workload-kv-read request_type: workload-kv-read
title: whynot-design npm publish token lane title: whynot-design npm publish token lane
status: approved status: proposed
created: '2026-06-27' created: '2026-06-27'
updated: '2026-06-27' updated: '2026-06-28'
requester: requester:
agent: ops-warden agent: ops-warden
message_id: fe5b1696-8956-4bd5-9d6f-dbde1901a076 message_id: fe5b1696-8956-4bd5-9d6f-dbde1901a076
@ -26,15 +26,22 @@ review:
decision: approved decision: approved
comment: 'State Hub decision 250669d0-8475-4527-9624-cd072249f9a9: APPROVE: scoped comment: 'State Hub decision 250669d0-8475-4527-9624-cd072249f9a9: APPROVE: scoped
path and confirmed binding are acceptable' path and confirmed binding are acceptable'
- at: '2026-06-27T22:54:20+00:00'
reviewer: bernd.worsch
decision: scope_corrected_requires_review
comment: Corrected tenant from whynot-design to coulomb per operator clarification.
The previous approval covered platform/workloads/whynot-design/whynot-design/npm-publish
and must not be reused for the corrected platform/workloads/coulomb/whynot-design/npm-publish
scope.
target: target:
domain: financials domain: financials
tenant: whynot-design tenant: coulomb
workload: whynot-design workload: whynot-design
environment: production environment: production
purpose: npm package publishing through ops-warden caller-scoped fetch/exec purpose: npm package publishing through ops-warden caller-scoped fetch/exec
openbao: openbao:
mount: platform mount: platform
kv_path: platform/workloads/whynot-design/whynot-design/npm-publish kv_path: platform/workloads/coulomb/whynot-design/npm-publish
fields: fields:
- NPM_AUTH_TOKEN - NPM_AUTH_TOKEN
policy_name: workload-kv-read-whynot-design-npm-publish policy_name: workload-kv-read-whynot-design-npm-publish
@ -90,7 +97,6 @@ state_hub:
related_workplan_id: RAILIANCE-WP-0006 related_workplan_id: RAILIANCE-WP-0006
ops_warden_reply_message_id: b175c561-7858-43f5-a309-949b0dede1b4 ops_warden_reply_message_id: b175c561-7858-43f5-a309-949b0dede1b4
ops_warden_batch_message_id: fe5b1696-8956-4bd5-9d6f-dbde1901a076 ops_warden_batch_message_id: fe5b1696-8956-4bd5-9d6f-dbde1901a076
decision_id: 250669d0-8475-4527-9624-cd072249f9a9 superseded_decision_id: 250669d0-8475-4527-9624-cd072249f9a9
decision_api_url: http://127.0.0.1:8000/decisions/250669d0-8475-4527-9624-cd072249f9a9 superseded_decision_resolved_at: '2026-06-27T22:04:32.956077Z'
decision_dashboard_url: http://127.0.0.1:3000/decisions superseded_decision_reason: tenant/workload scope corrected before secret provisioning
decision_resolved_at: '2026-06-27T22:04:32.956077Z'

View file

@ -137,12 +137,16 @@ Default pattern:
workload namespace. workload namespace.
3. Reference that Kubernetes Secret from the Deployment, Job, or CronJob. 3. Reference that Kubernetes Secret from the Deployment, Job, or CronJob.
Path convention: Path convention for workload credential custody:
```text ```text
platform/workloads/<namespace>/<service-account>/<secret-name> platform/workloads/<tenant-or-org>/<workload>/<secret-purpose>
``` ```
Kubernetes namespace and service-account bounds belong in the OpenBao auth role
or External Secrets binding, not in the tenant segment unless the namespace is
itself the approved workload identity.
Use CSI-mounted files only for workloads that need file references, sharper Use CSI-mounted files only for workloads that need file references, sharper
mount boundaries, or refresh behavior that should not rewrite application mount boundaries, or refresh behavior that should not rewrite application
manifests. Do not use the OpenBao injector in the current deployment. manifests. Do not use the OpenBao injector in the current deployment.

View file

@ -103,7 +103,7 @@ A reviewer should see a concise rendered proposal:
Request: whynot-design npm publish token lane Request: whynot-design npm publish token lane
Type: workload-kv-read Type: workload-kv-read
Mount/path/field: Mount/path/field:
platform/workloads/whynot-design/whynot-design/npm-publish platform/workloads/coulomb/whynot-design/npm-publish
NPM_AUTH_TOKEN NPM_AUTH_TOKEN
Policy: Policy:
workload-kv-read-whynot-design-npm-publish workload-kv-read-whynot-design-npm-publish

View file

@ -395,7 +395,7 @@ tenant contract):
Path convention: Path convention:
```text ```text
platform/workloads/<namespace>/<service-account>/<secret-name> platform/workloads/<tenant-or-org>/<workload>/<secret-purpose>
platform/object-storage/<consumer> platform/object-storage/<consumer>
platform/databases/<consumer> platform/databases/<consumer>
platform/operators/<purpose> platform/operators/<purpose>

View file

@ -25,8 +25,10 @@ Ops-warden batch follow-up:
| Item | Value | | Item | Value |
| --- | --- | | --- | --- |
| ops-warden catalog id | `whynot-design-npm-publish` | | ops-warden catalog id | `whynot-design-npm-publish` |
| Tenant/org | `coulomb` |
| Workload/project | `whynot-design` |
| KV mount | `platform` | | KV mount | `platform` |
| OpenBao CLI path | `platform/workloads/whynot-design/whynot-design/npm-publish` | | OpenBao CLI path | `platform/workloads/coulomb/whynot-design/npm-publish` |
| Secret field | `NPM_AUTH_TOKEN` | | Secret field | `NPM_AUTH_TOKEN` |
| Front-door readiness | `template`, `resolvable=false` until CCR verification | | Front-door readiness | `template`, `resolvable=false` until CCR verification |
| Read policy | `workload-kv-read-whynot-design-npm-publish` | | Read policy | `workload-kv-read-whynot-design-npm-publish` |
@ -45,7 +47,7 @@ bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read
Expected OpenBao fetch shape: Expected OpenBao fetch shape:
```bash ```bash
bao kv get -field=NPM_AUTH_TOKEN platform/workloads/whynot-design/whynot-design/npm-publish bao kv get -field=NPM_AUTH_TOKEN platform/workloads/coulomb/whynot-design/npm-publish
``` ```
Expected ops-warden exec shape after activation: Expected ops-warden exec shape after activation:
@ -63,8 +65,8 @@ logging it.
The source policy grants only: The source policy grants only:
```text ```text
read platform/data/workloads/whynot-design/whynot-design/npm-publish read platform/data/workloads/coulomb/whynot-design/npm-publish
read platform/metadata/workloads/whynot-design/whynot-design/npm-publish read platform/metadata/workloads/coulomb/whynot-design/npm-publish
``` ```
It does not grant write, delete, patch, sudo, auth, sibling workload, or parent It does not grant write, delete, patch, sudo, auth, sibling workload, or parent
@ -121,7 +123,7 @@ and service account.
An approved operator must create or confirm the secret with: An approved operator must create or confirm the secret with:
```text ```text
path: platform/workloads/whynot-design/whynot-design/npm-publish path: platform/workloads/coulomb/whynot-design/npm-publish
field: NPM_AUTH_TOKEN field: NPM_AUTH_TOKEN
``` ```
@ -129,14 +131,14 @@ In the OpenBao UI, open the `platform` KV engine and create or edit the secret
at: at:
```text ```text
workloads/whynot-design/whynot-design/npm-publish workloads/coulomb/whynot-design/npm-publish
``` ```
For policies and API checks, the same KV-v2 secret is addressed as: For policies and API checks, the same KV-v2 secret is addressed as:
```text ```text
platform/data/workloads/whynot-design/whynot-design/npm-publish platform/data/workloads/coulomb/whynot-design/npm-publish
platform/metadata/workloads/whynot-design/whynot-design/npm-publish platform/metadata/workloads/coulomb/whynot-design/npm-publish
``` ```
The OpenBao UI path does not include the `data/` or `metadata/` segment. Those The OpenBao UI path does not include the `data/` or `metadata/` segment. Those
@ -169,7 +171,7 @@ Send ops-warden only these pointers:
```text ```text
catalog id: whynot-design-npm-publish catalog id: whynot-design-npm-publish
mount: platform mount: platform
path: platform/workloads/whynot-design/whynot-design/npm-publish path: platform/workloads/coulomb/whynot-design/npm-publish
field: NPM_AUTH_TOKEN field: NPM_AUTH_TOKEN
oidc login: bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read oidc login: bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read
policy: workload-kv-read-whynot-design-npm-publish policy: workload-kv-read-whynot-design-npm-publish

View file

@ -4,10 +4,10 @@
# path used by ops-warden's caller-scoped access lane. It does not grant list # path used by ops-warden's caller-scoped access lane. It does not grant list
# access to sibling workloads or mutation capabilities. # access to sibling workloads or mutation capabilities.
path "platform/data/workloads/whynot-design/whynot-design/npm-publish" { path "platform/data/workloads/coulomb/whynot-design/npm-publish" {
capabilities = ["read"] capabilities = ["read"]
} }
path "platform/metadata/workloads/whynot-design/whynot-design/npm-publish" { path "platform/metadata/workloads/coulomb/whynot-design/npm-publish" {
capabilities = ["read"] capabilities = ["read"]
} }

View file

@ -19,7 +19,7 @@ Applies source-owned OpenBao workload KV read-lane policies.
Current lane: Current lane:
- policy: workload-kv-read-whynot-design-npm-publish - policy: workload-kv-read-whynot-design-npm-publish
- path: platform/workloads/whynot-design/whynot-design/npm-publish - path: platform/workloads/coulomb/whynot-design/npm-publish
- field: NPM_AUTH_TOKEN - field: NPM_AUTH_TOKEN
The script reads an OpenBao operator token from OPENBAO_TOKEN_FILE or an The script reads an OpenBao operator token from OPENBAO_TOKEN_FILE or an
@ -131,7 +131,7 @@ Remaining live steps:
1. Confirm the whynot-design KeyCape/NetKingdom bound claim or service account. 1. Confirm the whynot-design KeyCape/NetKingdom bound claim or service account.
2. Create auth/netkingdom/role/whynot-design-workload-kv-read with only the 2. Create auth/netkingdom/role/whynot-design-workload-kv-read with only the
workload-kv-read-whynot-design-npm-publish policy. workload-kv-read-whynot-design-npm-publish policy.
3. Provision platform/workloads/whynot-design/whynot-design/npm-publish with 3. Provision platform/workloads/coulomb/whynot-design/npm-publish with
field NPM_AUTH_TOKEN through approved OpenBao/operator custody. field NPM_AUTH_TOKEN through approved OpenBao/operator custody.
4. Run positive and negative fetch verification without printing the token. 4. Run positive and negative fetch verification without printing the token.
NEXT NEXT

View file

@ -50,7 +50,7 @@ class CredentialChangeTests(unittest.TestCase):
ccr, _errors, warnings = credential_change.validate_ccr(self.sample) ccr, _errors, warnings = credential_change.validate_ccr(self.sample)
rendered = credential_change.render_summary(ccr, warnings) rendered = credential_change.render_summary(ccr, warnings)
self.assertIn("whynot-design npm publish token lane", rendered) self.assertIn("whynot-design npm publish token lane", rendered)
self.assertIn("platform/workloads/whynot-design/whynot-design/npm-publish", rendered) self.assertIn("platform/workloads/coulomb/whynot-design/npm-publish", rendered)
self.assertIn("whynot-design-npm-publish", rendered) self.assertIn("whynot-design-npm-publish", rendered)
self.assertIn("readiness: template resolvable=False", rendered) self.assertIn("readiness: template resolvable=False", rendered)
self.assertIn("approve | deny | needs_changes", rendered) self.assertIn("approve | deny | needs_changes", rendered)
@ -58,18 +58,15 @@ class CredentialChangeTests(unittest.TestCase):
def test_status_payload_marks_template_not_resolvable(self) -> None: def test_status_payload_marks_template_not_resolvable(self) -> None:
ccr, _errors, warnings = credential_change.validate_ccr(self.sample) ccr, _errors, warnings = credential_change.validate_ccr(self.sample)
payload = credential_change.status_payload(ccr, warnings) payload = credential_change.status_payload(ccr, warnings)
self.assertTrue(payload["apply_allowed"]) self.assertFalse(payload["apply_allowed"])
self.assertFalse(payload["frontdoor_resolvable"]) self.assertFalse(payload["frontdoor_resolvable"])
self.assertEqual(payload["access_frontdoor"]["readiness"], "template") self.assertEqual(payload["access_frontdoor"]["readiness"], "template")
self.assertEqual(payload["access_frontdoor"]["catalog_id"], "whynot-design-npm-publish") self.assertEqual(payload["access_frontdoor"]["catalog_id"], "whynot-design-npm-publish")
self.assertEqual(payload["apply_blockers"], []) self.assertEqual(payload["apply_blockers"], ["apply requires status approved, got proposed"])
self.assertEqual(payload["warnings"], []) self.assertEqual(payload["warnings"], [])
self.assertEqual( self.assertIsNone(payload["state_hub"]["decision_id"])
payload["state_hub"]["decision_id"],
"250669d0-8475-4527-9624-cd072249f9a9",
)
self.assertIn( self.assertIn(
"front door requires CCR status active, got approved", "front door requires CCR status active, got proposed",
payload["frontdoor_blockers"], payload["frontdoor_blockers"],
) )
self.assertIn("front door is marked resolvable=false", payload["frontdoor_blockers"]) self.assertIn("front door is marked resolvable=false", payload["frontdoor_blockers"])
@ -94,6 +91,11 @@ class CredentialChangeTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmp: with tempfile.TemporaryDirectory() as tmp:
copied = Path(tmp) / self.sample.name copied = Path(tmp) / self.sample.name
shutil.copy2(self.sample, copied) shutil.copy2(self.sample, copied)
copied_ccr = credential_change.load_yaml(copied)
copied_ccr.setdefault("state_hub", {})[
"decision_id"
] = "250669d0-8475-4527-9624-cd072249f9a9"
credential_change.dump_yaml(copied, copied_ccr)
original = credential_change.state_hub_decision_status original = credential_change.state_hub_decision_status
try: try:
credential_change.state_hub_decision_status = lambda _ccr, _url: { credential_change.state_hub_decision_status = lambda _ccr, _url: {
@ -144,7 +146,7 @@ class CredentialChangeTests(unittest.TestCase):
rendered, rendered,
) )
self.assertIn( self.assertIn(
"# bao kv put platform/workloads/whynot-design/whynot-design/npm-publish", "# bao kv put platform/workloads/coulomb/whynot-design/npm-publish",
rendered, rendered,
) )
self.assertIn("NPM_AUTH_TOKEN=<enter-through-approved-custody>", rendered) self.assertIn("NPM_AUTH_TOKEN=<enter-through-approved-custody>", rendered)
@ -199,7 +201,7 @@ class CredentialChangeTests(unittest.TestCase):
def test_generated_policy_is_narrow(self) -> None: def test_generated_policy_is_narrow(self) -> None:
ccr, _errors, _warnings = credential_change.validate_ccr(self.sample) ccr, _errors, _warnings = credential_change.validate_ccr(self.sample)
policy = credential_change.generated_policy_hcl(ccr) policy = credential_change.generated_policy_hcl(ccr)
self.assertIn('path "platform/data/workloads/whynot-design/whynot-design/npm-publish"', policy) self.assertIn('path "platform/data/workloads/coulomb/whynot-design/npm-publish"', policy)
self.assertNotIn("*", policy) self.assertNotIn("*", policy)
self.assertNotIn("delete", policy) self.assertNotIn("delete", policy)

View file

@ -123,12 +123,16 @@ Kubernetes workloads, use External Secrets Operator to materialize OpenBao
values as Kubernetes Secrets. Do not use the OpenBao injector in the current values as Kubernetes Secrets. Do not use the OpenBao injector in the current
deployment. deployment.
Runtime path convention: Runtime path convention for workload credential custody:
```text ```text
platform/workloads/<namespace>/<service-account>/<secret-name> platform/workloads/<tenant-or-org>/<workload>/<secret-purpose>
``` ```
Kubernetes namespace and service-account bounds belong in the auth role or
External Secrets binding unless the namespace is itself the approved workload
identity.
ArgoCD repository credentials are operator credentials, not workload secrets, ArgoCD repository credentials are operator credentials, not workload secrets,
and should live under: and should live under:

View file

@ -52,11 +52,10 @@ whynot-design.
## Proposed Contract ## Proposed Contract
Use the existing workload convention documented in `docs/openbao.md` and Use the workload credential convention documented in `docs/openbao.md`:
`docs/argocd-gitops.md`:
```text ```text
platform/workloads/<namespace>/<service-account>/<secret-name> platform/workloads/<tenant-or-org>/<workload>/<secret-purpose>
``` ```
For this lane, the proposed non-secret contract is: For this lane, the proposed non-secret contract is:
@ -64,9 +63,11 @@ For this lane, the proposed non-secret contract is:
| Item | Proposed value | | Item | Proposed value |
| --- | --- | | --- | --- |
| KV mount | `platform` | | KV mount | `platform` |
| CLI path | `platform/workloads/whynot-design/whynot-design/npm-publish` | | Tenant/org | `coulomb` |
| KV-v2 policy data path | `platform/data/workloads/whynot-design/whynot-design/npm-publish` | | Workload/project | `whynot-design` |
| KV-v2 policy metadata path | `platform/metadata/workloads/whynot-design/whynot-design/npm-publish` | | CLI path | `platform/workloads/coulomb/whynot-design/npm-publish` |
| KV-v2 policy data path | `platform/data/workloads/coulomb/whynot-design/npm-publish` |
| KV-v2 policy metadata path | `platform/metadata/workloads/coulomb/whynot-design/npm-publish` |
| Secret field | `NPM_AUTH_TOKEN` | | Secret field | `NPM_AUTH_TOKEN` |
| OpenBao read policy | `workload-kv-read-whynot-design-npm-publish` | | OpenBao read policy | `workload-kv-read-whynot-design-npm-publish` |
| OIDC auth mount | `netkingdom` unless KeyCape compatibility requires `keycape` | | OIDC auth mount | `netkingdom` unless KeyCape compatibility requires `keycape` |
@ -78,7 +79,7 @@ The expected caller-facing read shape is:
```bash ```bash
bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read
bao kv get -field=NPM_AUTH_TOKEN platform/workloads/whynot-design/whynot-design/npm-publish bao kv get -field=NPM_AUTH_TOKEN platform/workloads/coulomb/whynot-design/npm-publish
``` ```
The command shape is illustrative only. Verification must avoid printing the The command shape is illustrative only. Verification must avoid printing the
@ -109,7 +110,7 @@ Acceptance:
ops-warden signing smoke. ops-warden signing smoke.
**2026-06-27:** Reviewed the unread ops-warden request and existing **2026-06-27:** Reviewed the unread ops-warden request and existing
`platform/workloads/<namespace>/<service-account>/<secret-name>` convention. `platform/workloads/<tenant-or-org>/<workload>/<secret-purpose>` convention.
Captured the proposed `whynot-design` npm publish lane above with no secret Captured the proposed `whynot-design` npm publish lane above with no secret
values. values.
@ -129,7 +130,7 @@ the selected `npm-publish` path.
Acceptance: Acceptance:
- A policy file under `openbao/policies/` defines read access to the exact - A policy file under `openbao/policies/` defines read access to the exact
`platform/data/workloads/whynot-design/whynot-design/npm-publish` path. `platform/data/workloads/coulomb/whynot-design/npm-publish` path.
- Metadata/list capabilities are only as broad as needed for the caller and - Metadata/list capabilities are only as broad as needed for the caller and
ops-warden fetch UX. ops-warden fetch UX.
- The policy grants no write, delete, patch, sudo, auth, or unrelated workload - The policy grants no write, delete, patch, sudo, auth, or unrelated workload
@ -140,7 +141,7 @@ Acceptance:
**2026-06-27:** Added the concrete policy artifact at **2026-06-27:** Added the concrete policy artifact at
`openbao/policies/workload-kv-read-whynot-design-npm-publish.hcl`. It grants `openbao/policies/workload-kv-read-whynot-design-npm-publish.hcl`. It grants
only `read` on the exact KV-v2 data and metadata paths for only `read` on the exact KV-v2 data and metadata paths for
`platform/workloads/whynot-design/whynot-design/npm-publish`; it does not grant `platform/workloads/coulomb/whynot-design/npm-publish`; it does not grant
write/delete/list/sudo/auth or sibling workload access. Added write/delete/list/sudo/auth or sibling workload access. Added
`scripts/openbao-apply-workload-kv-lanes.sh`, `scripts/openbao-apply-workload-kv-lanes.sh`,
`make openbao-workload-kv-lanes-dry-run`, and `make openbao-workload-kv-lanes-dry-run`, and
@ -195,7 +196,7 @@ publish token.
Acceptance: Acceptance:
- The path exists at - The path exists at
`platform/workloads/whynot-design/whynot-design/npm-publish`. `platform/workloads/coulomb/whynot-design/npm-publish`.
- The field is named exactly `NPM_AUTH_TOKEN`. - The field is named exactly `NPM_AUTH_TOKEN`.
- The token value is entered through an approved operator/OpenBao path and is - The token value is entered through an approved operator/OpenBao path and is
never written to Git, State Hub, chat, prompts, shell history, or workplan never written to Git, State Hub, chat, prompts, shell history, or workplan