CCR-2026-0029/0030: activity-core ops_run worker token paths (proposed)

ACTIVITY-WP-0039-T02. Add read on the two exact worker-token paths to
workload-kv-read-activity-core-eso, one CCR per worker identity. Adds
scripts/openbao-policy-sync.sh, a silent attended policy writer that refuses
undeclared live drift and verifies on readback. Not applied; CCRs await
platform-operator and activity-core-owner approval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 150322@bnt-lap001
Assistant-Session: 16a7b788-374e-4915-a1df-fc87ffd9a5e4
This commit is contained in:
codex 2026-09-23 20:00:40 +02:00
parent 915a3cacd9
commit 15184aaa01
8 changed files with 413 additions and 2 deletions

View file

@ -22,6 +22,7 @@
| workplan | RPF-WP-0043 | active | — | workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md | | workplan | RPF-WP-0043 | active | — | workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md |
| workplan | RPF-WP-0044 | active | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | | workplan | RPF-WP-0044 | active | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
| workplan | RPF-WP-0045 | active | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md | | workplan | RPF-WP-0045 | active | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md |
| workplan | RPF-WP-0046 | active | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0015-T01 | done | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | | task | RPF-WP-0015-T01 | done | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md |
| task | RPF-WP-0015-T02 | wait | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | | task | RPF-WP-0015-T02 | wait | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md |
| task | RPF-WP-0015-T03 | wait | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | | task | RPF-WP-0015-T03 | wait | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md |
@ -85,3 +86,9 @@
| task | RPF-WP-0045-T04 | done | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md | | task | RPF-WP-0045-T04 | done | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md |
| task | RPF-WP-0045-T05 | cancel | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md | | task | RPF-WP-0045-T05 | cancel | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md |
| task | RPF-WP-0045-T06 | wait | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md | | task | RPF-WP-0045-T06 | wait | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md |
| task | RPF-WP-0046-T01 | done | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0046-T02 | done | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0046-T03 | wait | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0046-T04 | wait | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0046-T05 | wait | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0046-T06 | todo | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |

View file

@ -0,0 +1,101 @@
id: CCR-2026-0029
kind: credential-change-request
schema_version: 1
request_type: workload-kv-read
title: activity-core ops_run worker token for rein-aharness@railiance01 via the activity-core-eso store
status: proposed
created: '2026-09-23'
updated: '2026-09-23'
requester:
agent: activity-core
message_id: e87bdefc
reason: ACTIVITY-WP-0039-T02. secrets-engine (SECRETS-WP-0009-T03,
SECRETS-WP-0011-T04) asked that the ops_run worker tokens move from a
hand-generated value into OpenBao custody and be delivered by the
existing openbao-activity-core ClusterSecretStore.
review:
required: true
required_approvers:
- platform-operator
- activity-core-owner
comments:
- at: '2026-09-23'
reviewer: claude-code
decision: metadata_review_pending_owner_approval
comment: Both paths already hold values (founder-attended mint 2026-09-23,
ACTIVITY-WP-0039-T03). The consuming ExternalSecret
activity-core/actcore-ops-run-worker-tokens (activity-core
k8s/railiance/15-externalsecret-worker-tokens.yaml, not yet applied)
reads field token at exactly these two paths through store
openbao-activity-core. That store is on Kubernetes auth role
activity-core-eso (RPF-WP-0045). The change adds two exact data-read
paths to that role's policy. There is no wildcard, no metadata or list,
and no new role.
target:
domain: financials
tenant: activity-core
workload: activity-core-ops-run-worker-rein-aharness-railiance01
environment: production
purpose: Deliver the bearer token of ops_run queue worker rein-aharness@railiance01 to the
activity-core runtime Secret through External Secrets. The companion lane is
CCR-2026-0030 (the other worker identity, same policy change).
openbao:
mount: platform
kv_path: platform/workloads/activity-core/ops-run-workers/rein-aharness-railiance01
fields:
- token
policy_name: workload-kv-read-activity-core-eso
policy_file: openbao/policies/workload-kv-read-activity-core-eso.hcl
auth:
method: kubernetes
mount: kubernetes
role: activity-core-eso
bound_claims:
service_account_names:
- activity-core-eso
service_account_namespaces:
- activity-core
bound_claims_confirmed: true
policies:
- workload-kv-read-activity-core-eso
access_frontdoor:
type: external-secrets
catalog_id: activity-core-ops-run-worker-rein-aharness-railiance01
readiness: pending-review
resolvable: false
delivery:
surface: external-secrets
target: ExternalSecret actcore-ops-run-worker-tokens merges ACTIVITY_CORE_WORKER_TOKEN
into Secret actcore-runtime-secret in namespace activity-core.
risk:
classification: high
notes:
- Each token authenticates one queue worker identity to activity-core. A
leaked token lets its holder claim ops_run work as that worker.
- Read is limited to the two exact paths. Other ops-run-workers paths stay
unreadable.
- A read-only lane for the metered worker's path is being catalogued
separately by secrets-engine. It is not granted by this CCR.
verification:
positive:
- ExternalSecret actcore-ops-run-worker-tokens reaches SecretSynced without
printing values.
negative:
- The activity-core-eso role cannot read a sibling path under
platform/data/workloads/activity-core/ops-run-workers/.
- Metadata and list on ops-run-workers are denied.
activation_conditions:
- Policy applied with platform-admin authority through
scripts/openbao-policy-sync.sh, guarded by the prior declared digest
41f4278c3f62ff879575e62ef52071feaeb794fabd05868cb3ee40608cfd4785.
- Values provisioned directly in OpenBao (done, ACTIVITY-WP-0039-T03).
evidence: []
lifecycle:
deactivate: Remove the two path blocks from workload-kv-read-activity-core-eso
and re-apply it.
rotate: activity-core re-mints the worker token at its path. ESO syncs it
within 1h, and the queue accepts the new token per ACTIVITY_CORE_WORKERS.
compromised: Re-mint the affected worker token, force-sync the
ExternalSecret, and record blast-radius notes.
state_hub:
workplan_id: ACTIVITY-WP-0039

View file

@ -0,0 +1,101 @@
id: CCR-2026-0030
kind: credential-change-request
schema_version: 1
request_type: workload-kv-read
title: activity-core ops_run worker token for rein-aharness-metered@railiance01 via the activity-core-eso store
status: proposed
created: '2026-09-23'
updated: '2026-09-23'
requester:
agent: activity-core
message_id: e87bdefc
reason: ACTIVITY-WP-0039-T02. secrets-engine (SECRETS-WP-0009-T03,
SECRETS-WP-0011-T04) asked that the ops_run worker tokens move from a
hand-generated value into OpenBao custody and be delivered by the
existing openbao-activity-core ClusterSecretStore.
review:
required: true
required_approvers:
- platform-operator
- activity-core-owner
comments:
- at: '2026-09-23'
reviewer: claude-code
decision: metadata_review_pending_owner_approval
comment: Both paths already hold values (founder-attended mint 2026-09-23,
ACTIVITY-WP-0039-T03). The consuming ExternalSecret
activity-core/actcore-ops-run-worker-tokens (activity-core
k8s/railiance/15-externalsecret-worker-tokens.yaml, not yet applied)
reads field token at exactly these two paths through store
openbao-activity-core. That store is on Kubernetes auth role
activity-core-eso (RPF-WP-0045). The change adds two exact data-read
paths to that role's policy. There is no wildcard, no metadata or list,
and no new role.
target:
domain: financials
tenant: activity-core
workload: activity-core-ops-run-worker-rein-aharness-metered-railiance01
environment: production
purpose: Deliver the bearer token of ops_run queue worker rein-aharness-metered@railiance01 to the
activity-core runtime Secret through External Secrets. The companion lane is
CCR-2026-0029 (the other worker identity, same policy change).
openbao:
mount: platform
kv_path: platform/workloads/activity-core/ops-run-workers/rein-aharness-metered-railiance01
fields:
- token
policy_name: workload-kv-read-activity-core-eso
policy_file: openbao/policies/workload-kv-read-activity-core-eso.hcl
auth:
method: kubernetes
mount: kubernetes
role: activity-core-eso
bound_claims:
service_account_names:
- activity-core-eso
service_account_namespaces:
- activity-core
bound_claims_confirmed: true
policies:
- workload-kv-read-activity-core-eso
access_frontdoor:
type: external-secrets
catalog_id: activity-core-ops-run-worker-rein-aharness-metered-railiance01
readiness: pending-review
resolvable: false
delivery:
surface: external-secrets
target: ExternalSecret actcore-ops-run-worker-tokens merges ACTIVITY_CORE_WORKER_TOKEN_METERED
into Secret actcore-runtime-secret in namespace activity-core.
risk:
classification: high
notes:
- Each token authenticates one queue worker identity to activity-core. A
leaked token lets its holder claim ops_run work as that worker.
- Read is limited to the two exact paths. Other ops-run-workers paths stay
unreadable.
- A read-only lane for the metered worker's path is being catalogued
separately by secrets-engine. It is not granted by this CCR.
verification:
positive:
- ExternalSecret actcore-ops-run-worker-tokens reaches SecretSynced without
printing values.
negative:
- The activity-core-eso role cannot read a sibling path under
platform/data/workloads/activity-core/ops-run-workers/.
- Metadata and list on ops-run-workers are denied.
activation_conditions:
- Policy applied with platform-admin authority through
scripts/openbao-policy-sync.sh, guarded by the prior declared digest
41f4278c3f62ff879575e62ef52071feaeb794fabd05868cb3ee40608cfd4785.
- Values provisioned directly in OpenBao (done, ACTIVITY-WP-0039-T03).
evidence: []
lifecycle:
deactivate: Remove the two path blocks from workload-kv-read-activity-core-eso
and re-apply it.
rotate: activity-core re-mints the worker token at its path. ESO syncs it
within 1h, and the queue accepts the new token per ACTIVITY_CORE_WORKERS.
compromised: Re-mint the affected worker token, force-sync the
ExternalSecret, and record blast-radius notes.
state_hub:
workplan_id: ACTIVITY-WP-0039

View file

@ -1,7 +1,7 @@
# RPF-WP-0045: ESO delivery for ClusterSecretStore openbao-activity-core via # RPF-WP-0045: ESO delivery for ClusterSecretStore openbao-activity-core via
# Kubernetes auth role activity-core-eso (SA activity-core/activity-core-eso). # Kubernetes auth role activity-core-eso (SA activity-core/activity-core-eso).
# Exactly the four KV paths the four activity-core ExternalSecrets read; data # Exactly the KV paths the activity-core ExternalSecrets read; data read only,
# read only, no metadata/list/write. ESO also needs lookup-self and revoke-self. # no metadata/list/write. ESO also needs lookup-self and revoke-self.
path "platform/data/workloads/activity-core/llm-connect/llm-connect-provider-secrets" { path "platform/data/workloads/activity-core/llm-connect/llm-connect-provider-secrets" {
capabilities = ["read"] capabilities = ["read"]
@ -19,6 +19,16 @@ path "platform/data/workloads/railiance/backup/offsite-lane" {
capabilities = ["read"] capabilities = ["read"]
} }
# CCR-2026-0029 and CCR-2026-0030 / ACTIVITY-WP-0039-T02: ops_run queue worker tokens, one path
# per worker identity, read by ExternalSecret actcore-ops-run-worker-tokens.
path "platform/data/workloads/activity-core/ops-run-workers/rein-aharness-railiance01" {
capabilities = ["read"]
}
path "platform/data/workloads/activity-core/ops-run-workers/rein-aharness-metered-railiance01" {
capabilities = ["read"]
}
path "auth/token/lookup-self" { path "auth/token/lookup-self" {
capabilities = ["read"] capabilities = ["read"]
} }

4
scripts/openbao-policy-sync.sh Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
# Silent child for the governed attended login; results go to the receipt only.
set -euo pipefail
exec python3 "$(dirname "$0")/openbao_policy_sync.py" "$@" >/dev/null 2>&1

116
scripts/openbao_policy_sync.py Executable file
View file

@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""Silent contained write of one declared ACL policy, guarded against drift.
Writes openbao/policies/<name>.hcl to sys/policy/<name> only when the live
rules still equal the previously declared version (--expect-live-sha256). If
the live rules already equal the file, nothing is written. Any other live
state is refused, so an undeclared live edit is never overwritten. Readback
must match. One non-secret receipt; no output.
python3 scripts/openbao-attended-exec.py -- scripts/openbao-policy-sync.sh \\
--policy <name> --expect-live-sha256 <sha> --receipt <absolute file>
"""
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path
import re
import subprocess
import sys
POLICY_DIR = Path(__file__).resolve().parents[1] / 'openbao/policies'
NAME = re.compile(r'^[a-z0-9][a-z0-9-]{1,80}$')
class Refused(Exception):
pass
def digest(text):
return hashlib.sha256(text.strip().encode('utf-8')).hexdigest()
def require_attended():
if Path.home().parent.name != '.warden-attended-login' or os.getenv('BAO_TOKEN') or os.getenv('VAULT_TOKEN'):
raise Refused('attended_envelope_required')
def read_live(name):
result = subprocess.run(['bao', 'read', '-format=json', 'sys/policy/' + name],
capture_output=True, check=True, timeout=30)
return json.loads(result.stdout)['data']['rules']
def write_live(name, path):
subprocess.run(['bao', 'policy', 'write', name, str(path)],
capture_output=True, check=True, timeout=30)
def sync(name, expect, read=read_live, write=write_live):
path = POLICY_DIR / (name + '.hcl')
declared = digest(path.read_text(encoding='utf-8'))
live = digest(read(name))
if live == declared:
return {'status': 'already_current', 'changed': False, 'live_sha256': live}
if live != expect:
raise Refused('live_policy_drifted')
write(name, path)
after = digest(read(name))
if after != declared:
raise Refused('readback_mismatch')
return {'status': 'applied', 'changed': True, 'previous_sha256': live, 'live_sha256': after}
def write_receipt(path, name, **body):
fd = os.open(Path(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
body = {
'schema': 'railiance-platform.openbao-policy-sync.v1',
'observed_at': datetime.now(timezone.utc).isoformat(),
'policy_name': name, 'credential_values_emitted': False, **body,
}
with os.fdopen(fd, 'w', encoding='utf-8') as handle:
json.dump(body, handle, indent=2, sort_keys=True)
handle.write('\n')
def classify(error):
if isinstance(error, Refused):
return str(error)
if isinstance(error, subprocess.CalledProcessError):
return 'bao_write_failed' if 'policy' in error.cmd else 'bao_read_failed'
return 'contained_operation_failed'
def parse(argv):
opts, args = {}, list(argv)
while args:
if args[0] in ('--policy', '--expect-live-sha256', '--receipt') and len(args) > 1:
opts[args[0]] = args[1]
args = args[2:]
else:
raise SystemExit(2)
if set(opts) != {'--policy', '--expect-live-sha256', '--receipt'} or not NAME.match(opts['--policy']):
raise SystemExit(2)
if not (POLICY_DIR / (opts['--policy'] + '.hcl')).is_file():
raise SystemExit(2)
return opts['--policy'], opts['--expect-live-sha256'], opts['--receipt']
def main(argv):
name, expect, receipt = parse(argv)
try:
require_attended()
result = sync(name, expect)
write_receipt(receipt, name, declared_sha256=digest((POLICY_DIR / (name + '.hcl')).read_text()), **result)
return 0
except Exception as error:
try:
write_receipt(receipt, name, status=classify(error), changed=False)
except Exception:
pass
return 1
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -0,0 +1,65 @@
import importlib.util
import json
from pathlib import Path
import pytest
spec = importlib.util.spec_from_file_location(
'sync', Path(__file__).resolve().parents[1] / 'scripts/openbao_policy_sync.py')
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
NAME = 'workload-kv-read-activity-core-eso'
DECLARED = (m.POLICY_DIR / (NAME + '.hcl')).read_text()
def store(initial):
live = {'rules': initial}
writes = []
def write(name, path):
writes.append(name)
live['rules'] = Path(path).read_text()
return live, writes, (lambda name: live['rules']), write
def test_applies_when_live_is_the_expected_prior_version():
live, writes, read, write = store('path "old" {}')
result = m.sync(NAME, m.digest('path "old" {}'), read, write)
assert result['status'] == 'applied' and writes == [NAME]
assert m.digest(live['rules']) == m.digest(DECLARED)
def test_already_current_writes_nothing():
_, writes, read, write = store(DECLARED)
assert m.sync(NAME, 'unused', read, write)['status'] == 'already_current'
assert writes == []
def test_refuses_undeclared_drift():
_, writes, read, write = store('path "someone-else" {}')
with pytest.raises(m.Refused, match='live_policy_drifted'):
m.sync(NAME, m.digest('path "old" {}'), read, write)
assert writes == []
def test_readback_mismatch_is_refused():
live = {'rules': 'path "old" {}'}
with pytest.raises(m.Refused, match='readback_mismatch'):
m.sync(NAME, m.digest('path "old" {}'), lambda n: live['rules'], lambda n, p: None)
def test_new_paths_are_exact_and_read_only():
for worker in ('rein-aharness-railiance01', 'rein-aharness-metered-railiance01'):
block = 'path "platform/data/workloads/activity-core/ops-run-workers/%s" {\n capabilities = ["read"]\n}' % worker
assert block in DECLARED
assert 'ops-run-workers/*' not in DECLARED and '"list"' not in DECLARED
def test_refuses_outside_envelope_and_bad_args(tmp_path, monkeypatch):
monkeypatch.setenv('HOME', str(tmp_path))
receipt = tmp_path / 'r.json'
assert m.main(['--policy', NAME, '--expect-live-sha256', 'x', '--receipt', str(receipt)]) == 1
assert json.loads(receipt.read_text())['status'] == 'attended_envelope_required'
with pytest.raises(SystemExit):
m.parse(['--policy', '../etc', '--expect-live-sha256', 'x', '--receipt', 'r'])

View file

@ -10,6 +10,7 @@ topic_slug: railiance
created: "2026-09-23" created: "2026-09-23"
updated: "2026-09-23" updated: "2026-09-23"
related: [RPF-WP-0045, RPF-WP-0037, RPF-WP-0036] related: [RPF-WP-0045, RPF-WP-0037, RPF-WP-0036]
state_hub_workstream_id: "cdb309e0-adb4-58aa-9c98-5ea270780e8d"
--- ---
## Problem ## Problem
@ -56,6 +57,7 @@ Design:
id: RPF-WP-0046-T01 id: RPF-WP-0046-T01
status: done status: done
priority: high priority: high
state_hub_task_id: "25592a83-d2e5-5aa2-b23e-5d563a621f19"
``` ```
`argocd/platform-addons/eso-token-renewer/` holds `renew.py`, the `argocd/platform-addons/eso-token-renewer/` holds `renew.py`, the
@ -73,6 +75,7 @@ AppProject is applied by hand, so the git change alone is not live. Tests:
id: RPF-WP-0046-T02 id: RPF-WP-0046-T02
status: done status: done
priority: high priority: high
state_hub_task_id: "a318792f-82ba-568b-8633-fba3513aa978"
``` ```
The script is `scripts/openbao-eso-db-token-periodic-attended.sh`. It is The script is `scripts/openbao-eso-db-token-periodic-attended.sh`. It is
@ -87,6 +90,7 @@ parity with the CronJob mounts is tested.
id: RPF-WP-0046-T03 id: RPF-WP-0046-T03
status: wait status: wait
priority: high priority: high
state_hub_task_id: "211bc548-34af-5f3a-ab2c-bf29d33f97ad"
``` ```
This step is `ADMINISTER @ realm:kubernetes/railiance01`, `activation=APPROVED`, This step is `ADMINISTER @ realm:kubernetes/railiance01`, `activation=APPROVED`,
@ -109,6 +113,7 @@ ExternalSecrets are `SecretSynced`.
id: RPF-WP-0046-T04 id: RPF-WP-0046-T04
status: wait status: wait
priority: high priority: high
state_hub_task_id: "28726b1b-3777-5945-859b-9e563b12fee6"
``` ```
1. Apply `argocd/railiance01/bootstrap/02-railiance-platform-addons-project.yaml` 1. Apply `argocd/railiance01/bootstrap/02-railiance-platform-addons-project.yaml`
@ -125,6 +130,7 @@ priority: high
id: RPF-WP-0046-T05 id: RPF-WP-0046-T05
status: wait status: wait
priority: high priority: high
state_hub_task_id: "acf49bee-e40a-533c-b1f0-4152c989211d"
``` ```
The replaced 768h tokens still expire around 2026-10-25T17:00Z, and they The replaced 768h tokens still expire around 2026-10-25T17:00Z, and they
@ -144,6 +150,7 @@ ready.
id: RPF-WP-0046-T06 id: RPF-WP-0046-T06
status: todo status: todo
priority: medium priority: medium
state_hub_task_id: "2f528e56-ebf0-547e-8d4f-3cb34c348d46"
``` ```
- rapp-postgres: the 768h mint scripts (`apply-*-eso-token*.sh`) would undo - rapp-postgres: the 768h mint scripts (`apply-*-eso-token*.sh`) would undo