@@ -9,6 +9,217 @@ The format is based on the regulated environment requirements:
99
1010---
1111
12+ ## [ 2026-06-10 11:30] - ADR 0005: remove spec.kata.destPath — fixed host path, H-1 gate closed
13+
14+ ** Author:** Erick Bourgeois
15+
16+ ### Changed
17+ - ` docs/adr/0005-remove-kata-destpath-fixed-host-path.md ` : NEW — remove the
18+ user-configurable ` destPath ` entirely rather than allowlist it; the drop-in
19+ destination is the compile-time constant ` /etc/k0s/containerd.d/kata.toml `
20+ (the real k0s containerd import dir per docs.k0sproject.io/stable/runtime —
21+ the previous ` container.d ` default was a typo k0s never read). Index updated.
22+ - ` src/crd.rs ` : ` KataConfig.dest_path ` field, its schema fn, and its default
23+ removed; ` deny_unknown_fields ` makes a lingering ` destPath ` a hard admission
24+ error. Spec docs updated.
25+ - ` src/constants.rs ` : new ` KATA_CONFIG_DEST_PATH ` + ` KATA_CONFIG_DEST_BASE ` ;
26+ ` KATA_CONFIG_APPLIED_ANNOTATION ` doc rewritten (bare hash, no path).
27+ - ` src/kata_config_agent.rs ` : NEW ` confine_dest_path ` — fail-closed
28+ defense-in-depth resolver (` /etc/k0s/ ` slash-aware prefix + ` .toml ` suffix,
29+ lexical ` .. ` /` . ` /empty-segment rejection, ` canonicalize(parent) ` must stay
30+ under the canonicalized base — defeats symlinked-directory escapes) run
31+ before EVERY host write and tear-down unlink; ` KataRef.dest_path ` removed
32+ (legacy annotations with the field still parse, path ignored);
33+ ` AppliedRecord ` /` parse_applied_record ` deleted — the applied annotation is a
34+ bare content hash a forged value cannot weaponize.
35+ - ` src/bin/kata_config_agent.rs ` : deliver + tear-down both target the fixed
36+ constant through ` confine_dest_path ` ; tear-down no longer needs a recorded
37+ path (always reconciles the fixed path to absent); ` host_path ` helper gone.
38+ - ` src/reconcilers/helpers.rs ` : ref-annotation builder no longer emits
39+ ` destPath ` — no Node annotation carries a host path.
40+ - ` deploy/kata-config-agent/daemonset.yaml ` : hostPath mount narrowed from ` / `
41+ to ` /etc/k0s ` (` type: Directory ` fail-fast); comments rewritten.
42+ - Tests (all four suites): fixtures drop ` dest_path ` ; new coverage — schema has
43+ no destPath property, serde rejects a lingering destPath, legacy-annotation
44+ tolerance, 10 ` confine_dest_path ` tests (traversal, symlink escape, prefix
45+ sibling ` /etc/k0s.evil ` , non-toml, relative, curdir), ref-annotation
46+ carries-no-path assertions. CRDs + api.md regenerated (crddoc kata section
47+ rewritten).
48+ - ` src/labels.rs ` → ` src/labels_tests.rs ` : inline ` mod tests ` body moved out —
49+ the last violation of the separate-` _tests.rs ` rule; ` .claude/CLAUDE.md `
50+ hardened to state the rule is exception-free for ALL targets including
51+ ` src/bin/*.rs ` , with only the three-line ` #[path] ` declaration allowed in
52+ source files.
53+ - Docs: concepts page (annotation-contract table, diagrams, security table,
54+ ruled-out list), operator guide (fixed-path prereqs/steps/troubleshooting),
55+ scheduled-machine spec row, example manifest — all rewritten for the fixed
56+ path; ` docs/architecture/calm/architecture.json ` ` host-path-containment `
57+ control + agent/flow descriptions updated (` make calm-validate ` clean,
58+ diagrams re-rendered).
59+
60+ ### Why
61+ Closes the H-1 latent-High gate from the 2026-06-09 security roadmap, stronger
62+ than originally specified: with the agent running privileged on the host, a
63+ CRD-supplied path was a node-root-via-CRD vector (write any host file, unlink
64+ any host file via a forged applied annotation). Per operator decision the
65+ capability is removed, not validated — no path-shaped attacker input exists in
66+ the contract at all, and the writable host surface shrinks to /etc/k0s.
67+
68+ ### Impact
69+ - [x] Breaking change (pre-release: any SM setting kata.destPath now fails admission)
70+ - [ ] Requires cluster rollout
71+ - [ ] Config change only
72+ - [ ] Documentation only
73+
74+ ## [ 2026-06-10 09:30] - Agent pod-security exception boundary VAP (ADR 0004)
75+
76+ ** Author:** Erick Bourgeois
77+
78+ ### Changed
79+ - ` docs/adr/0004-agent-pod-security-exception-boundary-vap.md ` : new ADR — the
80+ privileged node agents need a PSA/Gatekeeper/Kyverno exemption for
81+ ` 5spot-system ` on the workload cluster; since Kubernetes admission is
82+ conjunctive (no VAP can override another engine's deny), the exemption lives
83+ in the baseline engine and we ship a deny-by-default compensating VAP behind
84+ it. Index updated in ` docs/adr/README.md ` .
85+ - ` deploy/admission/agent-pod-security-policy.yaml ` : new ` ValidatingAdmissionPolicy `
86+ (` 5spot-agent-pod-security ` , k8s ≥ 1.30, ` failurePolicy: Fail ` ) — identity-pinned
87+ allowlist over pods in ` 5spot-system ` : ` hostPID ` /` hostPath ` /explicit-root
88+ restricted to the two agent ServiceAccounts; ` privileged ` to the kata agent only;
89+ hostPath clamped per agent (kata: ` / ` ; reclaim: ` /proc ` , ` /etc/machine-id ` );
90+ capability adds clamped to ` NET_ADMIN ` on the reclaim agent;
91+ ` hostNetwork ` /` hostIPC ` denied for everyone; compensating controls
92+ (readOnlyRootFilesystem on privileged containers, pod seccomp RuntimeDefault)
93+ made mandatory; ephemeral containers may never be risky. Header documents the
94+ PSA/Gatekeeper/Kyverno exemption recipes this policy pairs with.
95+ - ` deploy/admission/agent-pod-security-binding.yaml ` : binding scoped to
96+ ` 5spot-system ` via ` namespaceSelector ` , ` validationActions: [Deny] ` .
97+ - ` docs/architecture/calm/architecture.json ` : ` agent-pod-security-boundary `
98+ control added to the workload-cluster API server node (NIST AC-6/CM-5);
99+ ` make calm-validate ` clean, diagrams re-rendered.
100+ - ` docs/src/security/admission-validation.md ` : new "Agent Pod-Security Exception
101+ Boundary" section (posture table, conjunctive-admission explanation, per-engine
102+ exemption recipes); deployment list/commands extended to the fourth policy pair.
103+ - ` docs/src/security/threat-model.md ` : new threat E5 (abuse of the namespace-wide
104+ exemption) marked mitigated by ADR 0004; mitigations-summary row added.
105+
106+ ### Why
107+ The agents' privileged posture (ADR 0003) cannot pass a pod-security baseline,
108+ and the only exemption mechanisms those engines offer are namespace-wide. An
109+ unguarded exemption would let anything in ` 5spot-system ` run privileged. The
110+ paired deny-by-default VAP makes the "only our two agents, only their exact
111+ posture" claim machine-enforced and auditable. Policy logic verified by
112+ simulating all 12 CEL validations against the real DaemonSet pod templates
113+ (admitted) and 9 rogue/escalation scenarios (each denied by the intended rule).
114+
115+ ### Impact
116+ - [ ] Breaking change
117+ - [ ] Requires cluster rollout
118+ - [x] Config change only (new admission manifests; operator applies with the
119+ baseline-engine exemption as a paired deployment)
120+ - [ ] Documentation only
121+
122+ ## [ 2026-06-09 21:30] - Kata config delivery Phase 5 — agent metrics, integration test, docs, security pass
123+
124+ ** Author:** Erick Bourgeois
125+
126+ ### Changed
127+ - ` src/metrics.rs ` : six new agent metrics — ` fivespot_kata_config_writes_total ` ,
128+ ` …_deletes_total ` , ` …_drift_corrected_total ` , ` …_restarts_total ` ,
129+ ` …_sync_errors_total ` , and the ` …_last_sync_timestamp_seconds ` staleness gauge —
130+ plus their recorder fns and a shared ` serve_metrics(port) ` (the controller's
131+ private metrics server moved here so both binaries serve one implementation).
132+ - ` src/metrics_tests.rs ` : 6 new tests covering every recorder (counter increments,
133+ drift/write coupling, unchanged-tick gauge-only semantics), serialized on a
134+ lock because the kata counters are label-less process-globals.
135+ - ` src/kata_config_agent.rs ` + ` _tests.rs ` : new pure ` is_drift_correction `
136+ classifier (a rewrite of already-applied content is drift, not a rollout) + 4 tests.
137+ - ` src/bin/kata_config_agent.rs ` : ` --metrics-port ` (env ` METRICS_PORT ` , default
138+ 8080), metrics server spawned in loop mode, and recording wired into
139+ ` reconcile_once ` (write/delete/unchanged/restart) and the retry path (errors).
140+ - ` src/main.rs ` : ` run_metrics_server ` replaced by ` metrics::serve_metrics ` .
141+ - ` tests/integration_kata_config.rs ` : NEW — pins the ADR 0002 annotation contract
142+ (controller patch builder ↔ agent parser, field-for-field), runs the full
143+ host-file lifecycle (provision → restart-once → drift-heal-without-restart →
144+ rollout-restart → tear-down) against a tempdir with a counting executor, and
145+ adds an ` --ignored ` real-Node round-trip mirroring ` integration_emergency_reclaim.rs ` .
146+ - ` src/reconcilers/mod.rs ` : re-export the two kata patch builders for the test.
147+ - ` deploy/kata-config-agent/daemonset.yaml ` : ` prometheus.io/* ` scrape annotations,
148+ named ` metrics ` containerPort 8080, ` METRICS_PORT ` env.
149+ - ` deploy/kata-config-agent/kustomization.yaml ` : NEW — ` kubectl apply -k ` entry
150+ point mirroring ` deploy/node-agent/ ` .
151+ - ` examples/scheduledmachine-kata.yaml ` : NEW — full ` spec.kata ` example with the
152+ source-ConfigMap shape in a comment.
153+ - ` .trivyignore ` : justify KSV-0001 (allowPrivilegeEscalation cannot be false with
154+ privileged: true — API server rejects the combination) and KSV-0003/0004
155+ (drop: [ ALL] is a kernel no-op under privileged); ` trivy config deploy/ ` now 0 findings.
156+ - Docs: NEW ` docs/src/concepts/kata-config-delivery.md ` (architecture, mermaid
157+ delivery chain + sequence, restart-guard table, security posture) and NEW
158+ ` docs/src/guides/kata-config.md ` (operator walkthrough + troubleshooting);
159+ ` spec.kata ` row in ` concepts/scheduled-machine.md ` ; agent metrics table in
160+ ` operations/monitoring.md ` ; nav entries (new Guides section) in ` mkdocs.yml ` ;
161+ feature bullet in ` README.md ` .
162+ - Stale-comment sweep: ` src/bin/crddoc.rs ` emitted the pre-pivot ` kataConfigRef: `
163+ field name into the generated API example — fixed and ` make crddoc ` re-run;
164+ pre-pivot "per-node ConfigMap projection" comments rewritten in
165+ ` src/constants.rs ` , ` src/reconcilers/helpers.rs ` , ` src/reconcilers/scheduled_machine.rs ` .
166+ - ` docs/architecture/calm/architecture.json ` : agent node gains its ` /metrics `
167+ HTTP interface; least-privilege control text corrected to get-only
168+ ConfigMap/Secret RBAC (KSV-0113); data-asset wording aligned to the per-tick
169+ GET (no watcher). ` make calm-validate ` clean, diagrams re-rendered.
170+
171+ ### Why
172+ Closes Phase 5 (final phase) of the per-node Kata config delivery roadmap:
173+ observability for the node agent, an integration test pinning the single
174+ load-bearing controller↔agent contract, user-facing concept + operator docs,
175+ and a clean security scan with every privileged-posture finding justified.
176+
177+ ### Impact
178+ - [ ] Breaking change
179+ - [ ] Requires cluster rollout
180+ - [ ] Config change only
181+ - [x] Documentation only (agent/manifests not yet released)
182+
183+ ## [ 2026-06-09 19:30] - Kata config delivery Phase 4 — nsenter host k0s-service restart
184+
185+ ** Author:** Erick Bourgeois
186+
187+ ### Changed
188+ - ` src/kata_config_agent.rs ` : add the Phase 4 restart-orchestration units (ADR
189+ 0003 ) — ` nsenter_restart_argv ` (builds `nsenter -t 1 -m -u -i -n -p -- systemctl
190+ restart <service >` ), the ` RestartExecutor` trait, ` AppliedRecord` ({destPath,
191+ hash} carried in the ` kata-config-applied ` annotation) + ` parse_applied_record ` ,
192+ ` intended_hash_for ` , ` needs_restart ` , and ` restart_if_needed ` . ` ABSENT_HASH_MARKER `
193+ marks the torn-down state.
194+ - ` src/kata_config_agent_tests.rs ` : 14 new tests — exact nsenter command line,
195+ ` AppliedRecord ` serde round-trip + garbage→None, ` intended_hash_for ` per outcome,
196+ the ` needs_restart ` guard (no-prior / stale / match), and ` restart_if_needed `
197+ short-circuit / once / error-propagation via a counting ` RestartExecutor ` fake.
198+ - ` src/bin/kata_config_agent.rs ` : wire the restart into ` reconcile_once ` — after a
199+ sync, when the on-host content hash differs from the last-applied hash, record the
200+ new ` AppliedRecord ` ** before** restarting via the concrete ` NsenterRestartExecutor ` .
201+ ` read_node_state ` /` record_applied ` now use the compact ` {destPath, hash} ` record
202+ (replacing the bare-destPath value); unparseable records degrade to "not applied"
203+ (re-apply + restart, the safe direction).
204+ - ` docs/adr/0003-*.md ` : refine the applied annotation to the ` {destPath, hash} ` record.
205+ - ` docs/architecture/calm/architecture.json ` : align the agent node, the workload-API
206+ relationship, and the delivery-flow step prose to the compact record; `make
207+ calm-validate` clean, ` make calm-diagrams` re-rendered.
208+
209+ ### Why
210+ Completes the load-bearing piece of per-node kata delivery: after writing the
211+ drop-in the agent must bounce the host k0s service so containerd reloads it.
212+ Done in-pod via ` nsenter ` into host PID 1 (ADR 0003). The applied-hash record,
213+ written before the restart, is the restart-loop guard — the restart SIGKILLs the
214+ agent, and on the next loop a matching hash makes it a no-op (single-cycle
215+ convergence). Drift correction rewrites the file but does not re-restart.
216+
217+ ### Impact
218+ - [ ] Breaking change
219+ - [ ] Requires cluster rollout
220+ - [ ] Config change only
221+ - [x] Documentation only (agent/manifests not yet released)
222+
12223## [ 2026-06-09 18:45] - Tighten kata-config-agent secret RBAC to get-only (KSV-0113)
13224
14225** Author:** Erick Bourgeois
0 commit comments