the-custodian/docs/changes/CUST-WP-0073/existing-k3s-host-r1/PROPOSAL.md
codex 9f1ea088ef
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Reject generated-only commits / generated-commit-guard (push) Successful in 10s
Pin existing k3s hardening candidate and runtime review gates
2026-10-19 14:02:59 +02:00

5.7 KiB

Existing Railiance01 k3s service hardening — review addendum

Part of CUST73-CREDENTIAL-BOUNDARY-R1, next review revision; original revision-2 packet remains unchanged. No execution approval, mutation or restart occurred. This is the existing-host mode change only, not Custodian assignment admission.

Observed target and exact proposed change

Existing SSH configuration resolves railiance01 to 92.205.62.239 as tegwick, without proxy/local commands. The read used that existing access, no certificate issuance and no sudo. Warden ssh-cert-host-access route was checked first.

Sanitized observation refreshed at remote UTC 2026-10-19T12:00:53.721961+00:00, with local transport start/finish timestamps separately retained. Strict SSH host-key checking was required. The expected IPv4 is present on the host; observation.json binds hostname and hashed machine-id. These are captured facts, not timeless target assertions; rerun identity checks before any approval/execution.

k3s is loaded/active/running, main PID935, fragment /etc/systemd/system/k3s.service, no drop-ins, no pending daemon reload. Its loaded ExecStart and fragment each have exactly one mode644 flag. The fragment is regular root:root0644, device2049/inode7875, SHA256: b5398c385430ce225faadc1f3724597fe4b05dd999a04b8abbca64d4b0d68c0e. The default config.yaml and config.yaml.d are absent. Admin kubeconfig metadata is root:root0644, device2049/inode786464; its contents were not read. The service.env file is root:root0600 and inaccessible without privilege; only its metadata was read, not contents or a hash.

The exact proposed unit transformation changes the single mode argument from 644 to 600, preserving every other byte and total length. Two bytes change. The privately computed candidate SHA256 is: 866ea58bfab00215e5bd3352287ebb0230d4ddb7dd3ed4b1cd853e74c6698d7d. No candidate file was written on the host and raw unit content was never emitted. unit_transform.py provides the pure guarded transform, with pinned original and candidate hashes, root owner/mode/device/inode checks, O_NOFOLLOW, link-count and concurrent-read checks. It has no write/reload/restart code. Five tests cover exact change and refusal on drift, absent/duplicate flag or wrong candidate.

Execution sequence to include in owner-reviewed privileged revision

Owner: railiance-cluster for unit/install config, railiance-infra for account recovery. The narrow approved executor must:

  1. Verify fresh host identity, service state, original fragment identity/hash, no new drop-ins/config paths, and no pending reload. Refuse drift; do not auto-rebase this proposal onto another service revision.
  2. Retain private owner-only predecessor bytes and metadata outside agent reach. Confirm separate attended console/admin recovery and single-node headroom.
  3. After the protected-environment gate below, generate the candidate in private root staging and require exact candidate digest, root ownership and mode0644. Recheck the original object before atomic replacement. Converge only the admin kubeconfig owner/group/mode to root:root0600 without reading its content.
  4. Run systemctl daemon-reload; verify loaded mode600 and no unrelated fragment or drop-in change. Restart only k3s in the approved interruption window. No reinstall, package upgrade, generic bootstrap or kubeconfig export.
  5. Verify active/running service, fresh mode600 ExecStart, kubeconfig root:root0600 after readiness, cluster API readiness through attended admin access and no workload rollout regression. Receipts expose only booleans/state and pinned hashes. Do not capture kubectl error dumps, unit/env/config text or credentials.
  6. Keep future-installer source correction from the original packet coupled to this change, so a future installer does not reintroduce644 or controller export.

Explicit remaining gate

The protected /etc/systemd/system/k3s.service.env cannot be read by the current non-sudo observation path. No privilege escalation or content retrieval is requested here. The owner must independently return only nonsecret predicates: whether alternate config selection or K3S_KUBECONFIG_MODE is defined, whether it can conflict with the selected launch change, and whether additional config/drop-in sources exist. Do not return values or raw environment. Unknown means refusal, not an assumption that the default config absence covers every launch input. Fresh exact action, execution identity, expected interruption bound, validation and recovery must be bound into the supervisor-approved revision before use.

Abort and recovery

Before any write: a failed predicate aborts with no effect. After fragment replacement but before service restart: the owner can CAS-restore the private preimage only if the candidate digest is still exact, then daemon-reload; retain kubeconfig0600 and do not restart the old mode644 service. This rollback returns the reviewed predecessor unit, not a claim that credential separation succeeded.

After restart: prefer attended diagnosis with mode600 retained. Do not unconditionally restore644 or expose admin credentials to recover readiness. If restoring the old launch is necessary, stop all agent admission and obtain explicit supervisor disposition acknowledging mode644 regeneration; use retained console access, exact candidate/preimage CAS and immediate post-start0600 convergence. This exceptional fallback is not authorized by this preparation. Preserve failure evidence and re-establish persistent600 before agent admission.

No change to old SSH principals, workstation kubeconfigs, native provider credentials or actual interactive assignment isolation is established here. Estimated extra infrastructure allocation is zero; restart can interrupt the single control plane. Monetary loss is unquantified; autopilot remains disabled.