From be1b9aa680ba15daa589f3d3f431116ebc43f52d Mon Sep 17 00:00:00 2001 From: codex Date: Tue, 20 Oct 2026 22:42:19 +0200 Subject: [PATCH] CUST-WP-0073: railiance01 cutover steps 2-4 evidence; fix step-9 verifier identity and subresource probes Co-Authored-By: Claude Opus 5.5 --- .../credential-boundary-r1/SHA256SUMS | 2 +- .../verify_agent_denials.py | 15 ++++++++------ ...026-10-20-railiance01-cutover-steps-2-4.md | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 docs/evidence/2026-10-20-railiance01-cutover-steps-2-4.md diff --git a/docs/changes/CUST-WP-0073/credential-boundary-r1/SHA256SUMS b/docs/changes/CUST-WP-0073/credential-boundary-r1/SHA256SUMS index 40a6e05..4d2f53a 100644 --- a/docs/changes/CUST-WP-0073/credential-boundary-r1/SHA256SUMS +++ b/docs/changes/CUST-WP-0073/credential-boundary-r1/SHA256SUMS @@ -6,4 +6,4 @@ aa99050e3c446faf773a60701e5d1215f90ebca66a20edcb95975611e8683a5f original-revis c3815aea14fe0b1ba6e8b5e4b339a82496adb511a2ea587d38c797c9513e86f4 source-pins.json 3077831ba2efdfe53ec643c4a925801850edeecf9f1f52a43d12c3ae923da4e5 test_negative_verifier.py 88da0faf33b532acbec16e5f28acfb87278ec66328effb97ff9404891a818b65 validation.json -7f8cfb9340873acf508072f19e8476603a61a329041e381f0464000f2e5d795f verify_agent_denials.py +d3ab986707bc5f7f26bed15189734a13735c44e0f0c56c9e1fda13d61d3bed9f verify_agent_denials.py diff --git a/docs/changes/CUST-WP-0073/credential-boundary-r1/verify_agent_denials.py b/docs/changes/CUST-WP-0073/credential-boundary-r1/verify_agent_denials.py index f3c5c89..67c615a 100644 --- a/docs/changes/CUST-WP-0073/credential-boundary-r1/verify_agent_denials.py +++ b/docs/changes/CUST-WP-0073/credential-boundary-r1/verify_agent_denials.py @@ -9,12 +9,15 @@ import os import pwd import subprocess -IDENTITY = 'system:serviceaccount:custodian-agents:custodian-codex' +IDENTITY = 'system:serviceaccount:custodian-agents:custodian-agent' +# Subresources must use --subresource: `can-i get pods/log` asks about a pod +# named "log", which the curated read role (get pods) answers yes. DENIALS = [ ('get', 'secrets'), ('list', 'secrets'), ('watch', 'secrets'), - ('get', 'configmaps'), ('get', 'pods'), ('get', 'pods/log'), - ('create', 'pods/exec'), ('create', 'pods/attach'), ('create', 'pods'), - ('patch', 'deployments.apps'), ('create', 'serviceaccounts/token'), + ('get', 'configmaps'), ('get', 'pods --subresource=log'), + ('create', 'pods --subresource=exec'), ('create', 'pods --subresource=attach'), + ('create', 'pods --subresource=portforward'), ('create', 'pods'), + ('patch', 'deployments.apps'), ('create', 'serviceaccounts --subresource=token'), ('patch', 'applications.argoproj.io'), ] @@ -63,13 +66,13 @@ def probe(kubectl, context, account, inventory): return {'status': 'refused_wrong_identity', 'passed': False} checks = {} for verb, resource in DENIALS: - code, output = run(base + ['can-i', verb, resource, '--all-namespaces']) + code, output = run(base + ['can-i', verb, *resource.split(), '--all-namespaces']) checks[f'all-namespaces:{verb}:{resource}'] = code == 1 and output.strip() == b'no' # An all-namespaces denial can coexist with a namespaced RoleBinding grant. # Probe each namespace supplied in the separately supervised complete inventory. for namespace in inventory['namespaces']: for verb, resource in DENIALS: - code, output = run(base + ['can-i', verb, resource, '--namespace', namespace]) + code, output = run(base + ['can-i', verb, *resource.split(), '--namespace', namespace]) checks[f'{namespace}:{verb}:{resource}'] = code == 1 and output.strip() == b'no' for verb, resource in CLUSTER_DENIALS: code, output = run(base + ['can-i', verb, resource]) diff --git a/docs/evidence/2026-10-20-railiance01-cutover-steps-2-4.md b/docs/evidence/2026-10-20-railiance01-cutover-steps-2-4.md new file mode 100644 index 0000000..c392226 --- /dev/null +++ b/docs/evidence/2026-10-20-railiance01-cutover-steps-2-4.md @@ -0,0 +1,20 @@ +# railiance01 cutover steps 2–4 — 2026-10-20 (attended, CUST-WP-0073) + +Supervisor: Bernd Worsch (present; held a separate recovery SSH session during step 4). + +- **Step 3, RCLUSTER-WP-0019**: check run showed exactly backup dir, config.yaml, unit 644→600, restart. + Applied: unit backup `/root/k3s-kubeconfig-mode-backup/k3s.service.b5398c385430`; result + "admin kubeconfig root:root 0600; API ready"; node Ready. Six non-running pods pre-date the + restart (21–165 days old). Playbook fix `aaa92d4` (check-mode backup copy). +- **Step 4, RAIL-HO-WP-0014**: check run showed 7 changes, tegwick principals unchanged. Applied; + fresh `tegwick` login OK afterwards; `custodian-agt` uid 1001, only own group; principal `agt-custodian`. + Goss initially failed 1/33: `/etc/rancher/k3s/registries.yaml` was root 0644 since 2026-05-02 and holds + a registry login (frozen Gitea, removal tracked in RAPPS-WP-0016). With founder approval chmod 0600 + root:root; goss 33/33 pass. Persisted in RCLUSTER-WP-0019 playbook `36f733c`. + Note: that registry credential was world-readable on the node for ~5.5 months. +- **Step 2, RPF-WP-0062**: already synced by another session (`5a8cd4d`); app pinned at `567d2c6`, + Synced/Healthy. Impersonated `can-i` as the SA: create/get on pods log/exec/attach/portforward no, + nodes/proxy no, configmaps no, serviceaccounts token no, create deployments no; list pods and + Argo applications yes. Secret probes deferred to step 9 (workstation guard blocks the keyword). + `kubectl auth can-i get pods/log` without `--subresource` returns a misleading yes (pod named "log"); + step-9 verifier fixed accordingly (identity custodian-agent, curated `get pods` allowed).