|
168 | 168 | "node-type": "service", |
169 | 169 | "name": "Kubelet", |
170 | 170 | "description": "Node-local kubelet performing HTTP liveness (/healthz) and readiness (/readyz) probes against the controller." |
| 171 | + }, |
| 172 | + { |
| 173 | + "unique-id": "service-workload-kubernetes-api", |
| 174 | + "node-type": "service", |
| 175 | + "name": "Workload Cluster Kubernetes API Server", |
| 176 | + "description": "API server of the CAPI-managed workload cluster (e.g. k0smotron-hosted k0s). Distinct trust boundary from the management cluster API: accepts its own tokens, its own RBAC, and owns the Node objects plus the 5spot-system namespace that backs emergency reclaim.", |
| 177 | + "interfaces": [ |
| 178 | + { |
| 179 | + "unique-id": "workload-kube-api-https", |
| 180 | + "definition-url": "https://calm.finos.org/release/1.2/prototype/interface/http-endpoint.json", |
| 181 | + "config": { |
| 182 | + "port": 6443, |
| 183 | + "protocol": "HTTPS" |
| 184 | + } |
| 185 | + } |
| 186 | + ] |
| 187 | + }, |
| 188 | + { |
| 189 | + "unique-id": "service-reclaim-agent", |
| 190 | + "node-type": "service", |
| 191 | + "name": "5-Spot Reclaim Agent", |
| 192 | + "description": "Node-side DaemonSet on the workload cluster that watches /proc for processes matching ScheduledMachine.spec.killIfCommands and, on match, writes reclaim annotations onto its own Node object. Runs in-pod with hostPID=true and host /proc mounted read-only; consumes its per-node ConfigMap via a kube API watcher scoped by field selector metadata.name=reclaim-agent-<NODE_NAME> in namespace 5spot-system. Distinct field manager (5spot-reclaim-agent) attributes every write.", |
| 193 | + "controls": { |
| 194 | + "least-privilege": { |
| 195 | + "description": "Agent holds a namespaced Role in 5spot-system granting get,list,watch on ConfigMaps only, plus the kubelet node-scoped token for patching its own Node's annotations. No cluster-wide writes.", |
| 196 | + "requirements": [ |
| 197 | + { |
| 198 | + "requirement-url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final", |
| 199 | + "config": { |
| 200 | + "framework": "NIST SP 800-53 Rev. 5", |
| 201 | + "controls": ["AC-3", "AC-6"], |
| 202 | + "evidence": "deploy/node-agent/rbac.yaml" |
| 203 | + } |
| 204 | + } |
| 205 | + ] |
| 206 | + }, |
| 207 | + "container-hardening": { |
| 208 | + "description": "Explicit pod-level securityContext, read-only root filesystem, host /proc mounted read-only. Architectural-necessity Trivy suppressions (KSV-0010/0012/0020/0021/0023/0105/0118/0121) are documented with rationale in .trivyignore.", |
| 209 | + "requirements": [ |
| 210 | + { |
| 211 | + "requirement-url": "https://csrc.nist.gov/pubs/sp/800/190/final", |
| 212 | + "config": { |
| 213 | + "framework": "NIST SP 800-190", |
| 214 | + "controls": ["CM-6", "SI-7"], |
| 215 | + "evidence": "deploy/node-agent/daemonset.yaml" |
| 216 | + } |
| 217 | + } |
| 218 | + ] |
| 219 | + } |
| 220 | + } |
| 221 | + }, |
| 222 | + { |
| 223 | + "unique-id": "data-asset-reclaim-request-annotation", |
| 224 | + "node-type": "data-asset", |
| 225 | + "name": "Reclaim Request Annotation", |
| 226 | + "description": "Ephemeral annotation triple on a workload Node written by the reclaim agent and read by the 5-Spot controller: 5spot.finos.org/reclaim-requested=\"true\", /reclaim-reason, /reclaim-requested-at. Cleared by the controller as step 6 of the 7-step emergency-reclaim ordering contract." |
| 227 | + }, |
| 228 | + { |
| 229 | + "unique-id": "data-asset-reclaim-agent-configmap", |
| 230 | + "node-type": "data-asset", |
| 231 | + "name": "Per-Node Reclaim Agent ConfigMap", |
| 232 | + "description": "Per-node ConfigMap named reclaim-agent-<node-name> in namespace 5spot-system on the workload cluster. Controller-projected from ScheduledMachine.spec.killIfCommands via server-side apply with field manager 5spot-controller-reclaim-agent; consumed by the agent via a kube runtime watcher scoped by field selector. Empty killIfCommands deletes the ConfigMap; 404-on-delete is benign by contract." |
171 | 233 | } |
172 | 234 | ], |
173 | 235 |
|
|
190 | 252 | }, |
191 | 253 | { |
192 | 254 | "unique-id": "rel-workload-cluster-contains-node", |
193 | | - "description": "When a schedule is active, the physical node becomes a worker in the CAPI-managed workload cluster.", |
| 255 | + "description": "When a schedule is active, the physical node becomes a worker in the CAPI-managed workload cluster. The workload cluster also hosts its own API server and, where ScheduledMachine.spec.killIfCommands is set, the 5-Spot reclaim agent as a DaemonSet.", |
194 | 256 | "relationship-type": { |
195 | 257 | "deployed-in": { |
196 | 258 | "container": "ecosystem-workload-cluster", |
197 | | - "nodes": ["network-physical-node"] |
| 259 | + "nodes": [ |
| 260 | + "network-physical-node", |
| 261 | + "service-workload-kubernetes-api", |
| 262 | + "service-reclaim-agent" |
| 263 | + ] |
198 | 264 | } |
199 | 265 | } |
200 | 266 | }, |
|
331 | 397 | "nodes": ["data-asset-scheduledmachine-cr"] |
332 | 398 | } |
333 | 399 | } |
| 400 | + }, |
| 401 | + { |
| 402 | + "unique-id": "rel-controller-workload-kube-api", |
| 403 | + "description": "Controller reaches into the workload cluster API to project the per-node reclaim-agent ConfigMap (server-side apply), stamp the 5spot.finos.org/reclaim-agent=enabled label onto backing Nodes, watch Node updates for the reclaim-requested annotation, issue best-effort drains, and clear the reclaim annotations as step 6 of the emergency-reclaim contract.", |
| 404 | + "relationship-type": { |
| 405 | + "connects": { |
| 406 | + "source": { "node": "service-five-spot-controller" }, |
| 407 | + "destination": { |
| 408 | + "node": "service-workload-kubernetes-api", |
| 409 | + "interfaces": ["workload-kube-api-https"] |
| 410 | + } |
| 411 | + } |
| 412 | + }, |
| 413 | + "protocol": "HTTPS", |
| 414 | + "controls": { |
| 415 | + "audit-attribution": { |
| 416 | + "description": "All controller writes to the workload cluster API use the distinct field manager '5spot-controller-reclaim-agent' so audit logs attribute projection writes separately from the main reconciler. Supports non-repudiation in regulated-banking review.", |
| 417 | + "requirements": [ |
| 418 | + { |
| 419 | + "requirement-url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final", |
| 420 | + "config": { |
| 421 | + "framework": "NIST SP 800-53 Rev. 5", |
| 422 | + "controls": ["AU-2", "AU-10"], |
| 423 | + "evidence": "src/reconcilers/helpers.rs::reconcile_reclaim_agent_provision" |
| 424 | + } |
| 425 | + } |
| 426 | + ] |
| 427 | + } |
| 428 | + } |
| 429 | + }, |
| 430 | + { |
| 431 | + "unique-id": "rel-reclaim-agent-workload-kube-api", |
| 432 | + "description": "Reclaim agent uses the workload cluster API for two scoped operations: a field-selector-scoped watch on its own per-node ConfigMap in 5spot-system, and a merge-patch on its own Node's annotations via the kubelet node-scoped token on process match.", |
| 433 | + "relationship-type": { |
| 434 | + "connects": { |
| 435 | + "source": { "node": "service-reclaim-agent" }, |
| 436 | + "destination": { |
| 437 | + "node": "service-workload-kubernetes-api", |
| 438 | + "interfaces": ["workload-kube-api-https"] |
| 439 | + } |
| 440 | + } |
| 441 | + }, |
| 442 | + "protocol": "HTTPS", |
| 443 | + "controls": { |
| 444 | + "node-scoped-credentials": { |
| 445 | + "description": "Agent writes to Node objects only via the kubelet node-scoped token (patch nodes/<self>). ConfigMap reads are gated by a namespaced Role/RoleBinding in 5spot-system granting get,list,watch only. No cluster-wide API access.", |
| 446 | + "requirements": [ |
| 447 | + { |
| 448 | + "requirement-url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final", |
| 449 | + "config": { |
| 450 | + "framework": "NIST SP 800-53 Rev. 5", |
| 451 | + "controls": ["AC-6"], |
| 452 | + "evidence": "deploy/node-agent/rbac.yaml" |
| 453 | + } |
| 454 | + } |
| 455 | + ] |
| 456 | + } |
| 457 | + } |
| 458 | + }, |
| 459 | + { |
| 460 | + "unique-id": "rel-reclaim-configmap-stored-in-workload-api", |
| 461 | + "description": "The per-node reclaim-agent ConfigMap is persisted in the workload cluster API server's etcd, namespace 5spot-system.", |
| 462 | + "relationship-type": { |
| 463 | + "composed-of": { |
| 464 | + "container": "service-workload-kubernetes-api", |
| 465 | + "nodes": ["data-asset-reclaim-agent-configmap"] |
| 466 | + } |
| 467 | + } |
| 468 | + }, |
| 469 | + { |
| 470 | + "unique-id": "rel-reclaim-annotation-on-node", |
| 471 | + "description": "The reclaim-request annotation triple lives on the physical Node's metadata and is the agent→controller signalling surface for emergency reclaim.", |
| 472 | + "relationship-type": { |
| 473 | + "composed-of": { |
| 474 | + "container": "network-physical-node", |
| 475 | + "nodes": ["data-asset-reclaim-request-annotation"] |
| 476 | + } |
| 477 | + } |
334 | 478 | } |
335 | 479 | ], |
336 | 480 |
|
|
424 | 568 | ] |
425 | 569 | } |
426 | 570 | } |
| 571 | + }, |
| 572 | + { |
| 573 | + "unique-id": "flow-emergency-reclaim", |
| 574 | + "name": "Emergency Reclaim (process-match eject)", |
| 575 | + "description": "A matched process on a workload node triggers an ASAP eject that skips graceful drain. The 7-step ordering contract is load-bearing: spec.schedule.enabled=false (step 5) must be written BEFORE the annotation clear (step 6) so a crash between them is replay-safe. Full narrative in docs/src/concepts/emergency-reclaim.md.", |
| 576 | + "transitions": [ |
| 577 | + { |
| 578 | + "sequence-number": 1, |
| 579 | + "relationship-unique-id": "rel-reclaim-agent-workload-kube-api", |
| 580 | + "description": "Agent detects a process matching spec.killIfCommands via /proc scan and PATCHes the reclaim annotation triple (5spot.finos.org/reclaim-requested=\"true\", /reclaim-reason, /reclaim-requested-at) onto its own Node via the kubelet node-scoped token. Field manager: 5spot-reclaim-agent." |
| 581 | + }, |
| 582 | + { |
| 583 | + "sequence-number": 2, |
| 584 | + "relationship-unique-id": "rel-controller-workload-kube-api", |
| 585 | + "description": "Controller Node watch fires. check_emergency_reclaim reads the annotation, transitions status.phase = EmergencyRemove, and emits Event Reason: EmergencyReclaim with the annotation's reason string.", |
| 586 | + "direction": "destination-to-source" |
| 587 | + }, |
| 588 | + { |
| 589 | + "sequence-number": 3, |
| 590 | + "relationship-unique-id": "rel-controller-workload-kube-api", |
| 591 | + "description": "Best-effort kubectl drain --grace-period=0 --force --disable-eviction against the workload cluster, bounded by EMERGENCY_DRAIN_TIMEOUT_SECS=60. Failure is log-and-continue — the eject has already been committed to." |
| 592 | + }, |
| 593 | + { |
| 594 | + "sequence-number": 4, |
| 595 | + "relationship-unique-id": "rel-capi-core-reconciles-machine", |
| 596 | + "description": "Controller deletes the CAPI Machine on the management cluster — no PDB respect, no graceful shutdown. CAPI propagates deletion to providers and the physical node leaves the workload cluster." |
| 597 | + }, |
| 598 | + { |
| 599 | + "sequence-number": 5, |
| 600 | + "relationship-unique-id": "rel-controller-kube-api", |
| 601 | + "description": "Controller PATCHes ScheduledMachine spec.schedule.enabled=false (the loop-breaker: without this, the next schedule window re-adds the node and the agent re-fires forever) and emits Event Reason: EmergencyReclaimDisabledSchedule. Load-bearing: must run BEFORE step 6." |
| 602 | + }, |
| 603 | + { |
| 604 | + "sequence-number": 6, |
| 605 | + "relationship-unique-id": "rel-controller-workload-kube-api", |
| 606 | + "description": "Controller PATCHes the Node to null all three reclaim annotations. Best-effort: a crash between steps 5 and 6 is replay-safe because the agent re-annotates on the next poll cycle if the matched process is still running." |
| 607 | + }, |
| 608 | + { |
| 609 | + "sequence-number": 7, |
| 610 | + "relationship-unique-id": "rel-controller-kube-api", |
| 611 | + "description": "Controller transitions ScheduledMachine status.phase = Disabled. Re-enable is a manual operator action (kubectl patch ... spec.schedule.enabled=true)." |
| 612 | + } |
| 613 | + ], |
| 614 | + "controls": { |
| 615 | + "ordering-idempotence": { |
| 616 | + "description": "The load-bearing ordering of steps 5 (disable-schedule PATCH) before 6 (annotation-clear PATCH) guarantees crash-recovery idempotence. A crash after step 6 but before step 7 is caught by the PHASE_EMERGENCY_REMOVE match arm in reconcile(), which finishes the transition to Disabled on the next reconcile.", |
| 617 | + "requirements": [ |
| 618 | + { |
| 619 | + "requirement-url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final", |
| 620 | + "config": { |
| 621 | + "framework": "NIST SP 800-53 Rev. 5", |
| 622 | + "controls": ["SI-7", "AU-2"], |
| 623 | + "evidence": "src/reconcilers/helpers.rs::handle_emergency_remove, docs/src/concepts/emergency-reclaim.md" |
| 624 | + } |
| 625 | + } |
| 626 | + ] |
| 627 | + } |
| 628 | + } |
427 | 629 | } |
428 | 630 | ], |
429 | 631 |
|
|
0 commit comments