diff --git a/docs/credential-lane-designs/state-hub-preflight-activation.md b/docs/credential-lane-designs/state-hub-preflight-activation.md index 18bba2e..b222942 100644 --- a/docs/credential-lane-designs/state-hub-preflight-activation.md +++ b/docs/credential-lane-designs/state-hub-preflight-activation.md @@ -24,6 +24,12 @@ cross-namespace store references. Coding-agent data AND metadata are denied. State Hub chart diff together. Confirm the dedicated SA binding and record CCR approval from the user's task authorization. Keep the lane non-resolvable. 2. Commit/push the reviewed source in both repositories before live apply. + Verify the selected kubeconfig reaches kube-system UID + `a553c742-0115-43d4-99a4-a5ca56fe0786` using a metadata-only namespace GET. + The writer enforces this identity before OpenBao access. The workstation's + default config uses a local port forward; if that listener is unavailable, + establish the approved cluster access path before starting attended login. + Do not substitute another cluster's context to make the command succeed. 3. Run the silent writer through the contained attended login envelope: ```sh diff --git a/history/2026-09-05-preflight-signing-activation-readiness.md b/history/2026-09-05-preflight-signing-activation-readiness.md new file mode 100644 index 0000000..67a80f2 --- /dev/null +++ b/history/2026-09-05-preflight-signing-activation-readiness.md @@ -0,0 +1,23 @@ +# Signing activation readiness — RPF-WP-0035-T04 + +The user requested completion of T04. Concurrent session work landed platform +commit `5d28893` and prepared State Hub chart wiring and STATE-WP-0088. Avoid +overlapping live activation with that session; no credential mutation or API +outage was performed during this review. + +Read-only SSH checks confirmed kube-system UID +`a553c742-0115-43d4-99a4-a5ca56fe0786`, one ready State Hub API replica, image +`main-cdff3b7`, and absence of ExternalSecret `state-hub-rename-preflight`. +The default workstation kubeconfig used localhost port 16443, whose listener +refused connection. The activation runbook now requires verified cluster access +before login, and the writer refuses a different cluster before OpenBao access +or key generation. A regression test covers this refusal. + +Credential routing selected `openbao-platform-admin-login`, verdict +`founder_required`, identity act `oidc_login`. This requires attended operator +OIDC/MFA through the contained Warden envelope. It cannot be substituted with +root or workload authority. No new login was initiated while the other session's +activation ownership remained unconfirmed. + +T04 remains in progress: live custody, ESO delivery, signed preflight, and +fenced rotation/invalidation/recovery evidence remain outstanding. diff --git a/scripts/state_hub_preflight_lane.py b/scripts/state_hub_preflight_lane.py index 6a0a02d..c36e30b 100644 --- a/scripts/state_hub_preflight_lane.py +++ b/scripts/state_hub_preflight_lane.py @@ -19,6 +19,7 @@ FIELD = 'REPOSITORY_RENAME_PREFLIGHT_SECRET' POLICY = 'workload-kv-read-state-hub-rename-preflight' ROLE = 'state-hub-rename-preflight-eso' SA = 'state-hub-preflight-eso' +CLUSTER_UID = 'a553c742-0115-43d4-99a4-a5ca56fe0786' class LaneError(Exception): @@ -68,6 +69,12 @@ def approved_contract(): return expected, policy +def assert_cluster(kube): + namespace = data(command(kube + ['get', 'namespace', 'kube-system', '-o', 'json'])) + if namespace.get('metadata', {}).get('uid') != CLUSTER_UID: + raise LaneError('primary_cluster_identity_mismatch') + + def assert_fenced(kube): deployment = data(command(kube + ['-n', 'state-hub', 'get', 'deployment', 'state-hub', '-o', 'json'])) pods = data(command(kube + ['-n', 'state-hub', 'get', 'pods', '-l', 'app=state-hub', '-o', 'json'])) @@ -147,10 +154,11 @@ def verify_access(kube, receipt): def run(args, receipt): role, policy = approved_contract() + kube = ['kubectl', '--kubeconfig', args.kubeconfig] + assert_cluster(kube) identity = data(bao(['token', 'lookup', '-format=json']))['data'] if 'platform-admin' not in identity['policies'] or 'root' in identity['policies']: raise LaneError('attended_platform_admin_required') - kube = ['kubectl', '--kubeconfig', args.kubeconfig] if args.action == 'verify': verify_access(kube, receipt) receipt['status'] = 'custody_verified_pending_eso_and_api_acceptance' diff --git a/tests/test_state_hub_preflight_lane.py b/tests/test_state_hub_preflight_lane.py index 9423bc0..1eb0272 100644 --- a/tests/test_state_hub_preflight_lane.py +++ b/tests/test_state_hub_preflight_lane.py @@ -69,6 +69,7 @@ class SigningLaneTests(unittest.TestCase): args = SimpleNamespace(action='rotate', expected_version=1, kubeconfig='/fixture') result = SimpleNamespace(stdout=json.dumps({'data': {'policies': ['platform-admin']}}).encode()) with patch.object(lane, 'approved_contract', return_value=({}, '')), \ + patch.object(lane, 'assert_cluster'), \ patch.object(lane, 'bao', return_value=result), \ patch.object(lane, 'assert_fenced', side_effect=lane.LaneError('fence')), \ patch.object(lane.secrets, 'token_hex') as generate: @@ -76,6 +77,18 @@ class SigningLaneTests(unittest.TestCase): lane.run(args, {}) generate.assert_not_called() + def test_wrong_cluster_precedes_any_openbao_access_or_generation(self): + args = SimpleNamespace(action='provision', expected_version=0, kubeconfig='/fixture') + response = SimpleNamespace(stdout=json.dumps({'metadata': {'uid': 'other-cluster'}}).encode()) + with patch.object(lane, 'approved_contract', return_value=({}, '')), \ + patch.object(lane, 'command', return_value=response), \ + patch.object(lane, 'bao') as access, \ + patch.object(lane.secrets, 'token_hex') as generate: + with self.assertRaisesRegex(lane.LaneError, 'primary_cluster_identity_mismatch'): + lane.run(args, {}) + access.assert_not_called() + generate.assert_not_called() + def test_unapproved_key_format_or_policy_does_not_pass_contract(self): import tempfile, yaml ccr = copy.deepcopy(self.ccr) diff --git a/workplans/RPF-WP-0035-credential-lane-implementation.md b/workplans/RPF-WP-0035-credential-lane-implementation.md index 1081f4a..4db15ab 100644 --- a/workplans/RPF-WP-0035-credential-lane-implementation.md +++ b/workplans/RPF-WP-0035-credential-lane-implementation.md @@ -122,3 +122,11 @@ access checks and a non-mutating signed preflight pass; every API replica uses the accepted version; rotation/invalidation and recovery are evidenced. No repository rename is part of S3 lane acceptance. If demand is withdrawn, record the owning decision and cancel this task explicitly rather than provision it. + +2026-09-05 continuation: verified the live primary cluster identity and healthy +single API replica; the signing ExternalSecret is still absent. Added a writer +guard against wrong-cluster kubeconfigs before any OpenBao access. The default +workstation kubeconfig's local port-forward listener was unavailable. Activation +still needs the contained attended OIDC/MFA login and the acceptance evidence +above; source preparation is not live completion. See +`history/2026-09-05-preflight-signing-activation-readiness.md`.