All notable changes to the 5spot project will be documented in this file.
The format is based on the regulated environment requirements:
- Author attribution is MANDATORY for all entries
- Changes are logged in reverse chronological order
- Each entry must include impact assessment
Author: Erick Bourgeois
src/crd.rs: Addedprovider_id: Option<String>(serialized asproviderID) toScheduledMachineStatus. Replaced the thinLocalObjectReference { name }node reference with a newNodeRef { apiVersion, kind, name, uid }struct mirroring CAPI'sMachine.status.nodeRef. Removed the now-unusedLocalObjectReferencetype.src/crd_tests.rs: Added 6 TDD cases covering providerID round-trip, fullnodeRefdeserialization, optionaluid, serialization omission, old-shape rejection, andNodeRefround-trip.src/bin/crddoc.rs: Documented newproviderIDandnodeRefstatus fields.deploy/crds/scheduledmachine.yaml: Regenerated from the updated Rust types.docs/reference/api.md: Regenerated to reflect new status schema.
Phase 1 of the event-driven watches + status enrichment roadmap. Surfacing providerID and a full Node reference (with UID) on ScheduledMachine.status lets operators correlate a scheduled machine to a specific VM and Node from kubectl get sm -o jsonpath=..., without manual lookups across CAPI Machines and the Node API. This is the schema foundation that Phase 2 (reconciler populates the fields) and Phases 3–4 (event-driven watches on CAPI Machine and Node) build upon.
- Breaking change —
status.nodeRefshape changed from{ name }to{ apiVersion, kind, name, uid }. Existing CRs with the old shape must clearstatus.nodeRefbefore rollout, or the controller will report deserialization errors on that field. - Requires cluster rollout — CRD must be re-applied alongside the new controller image.
- Config change only
- Documentation only
Author: Daniel Guns
Dockerfile: Base image bumped fromgcr.io/distroless/cc-debian12:nonroot(glibc 2.36) togcr.io/distroless/cc-debian13:nonroot(glibc 2.41).github/workflows/build.yaml: Pinned Linux x86_64 runner fromubuntu-latesttoubuntu-24.04for CI stabilityCargo.lock: Updated transitive dependencyrustls-webpkifrom0.103.11to0.103.12
ubuntu-latest now resolves to Ubuntu 24.04 (glibc 2.39), producing binaries that require GLIBC_2.39 at runtime. The previous cc-debian12 base only provides glibc 2.36, causing a hard crash at container startup. Bumping to cc-debian13 (glibc 2.41) resolves the mismatch. Runners are explicitly pinned to ubuntu-24.04 so CI doesn't break silently when ubuntu-latest moves to 26.04. Additionally, two CVEs in rustls-webpki 0.103.11 (RUSTSEC-2026-0098, RUSTSEC-2026-0099) were patched by bumping to 0.103.12. Fixes issue #17.
- Breaking change
- Requires cluster rollout — new base image
- Config change only
- Documentation only
Author: Erick Bourgeois
.github/workflows/build.yaml: Changed Cosign signing step condition fromgithub.event_name == 'release'togithub.event_name != 'pull_request'
Main-branch images are tagged latest and main-YYYY-MM-DD and may be deployed to staging. Signing them allows cosign verify to work on staging images, not just production releases. PR images remain unsigned — they are ephemeral, tagged pr-{number}, and not deployed anywhere.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
.github/workflows/build.yaml: Addeddocker/login-action@v3step to theattestjob beforeactions/attest-build-provenance@v2
push-to-registry: true in actions/attest-build-provenance pushes the attestation bundle as an OCI artifact to GHCR, which requires registry credentials. Each job runs in a fresh environment — the Docker login performed by firestoned/github-actions/docker/setup-docker in the docker job does not carry over to the attest job.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
.github/workflows/build.yaml: New consolidated workflow replacing the three separate files; triggers onpull_request,pushto main, andrelease: published; usesif:at job and step level to gate event-specific behaviour.github/workflows/pr.yaml: Deleted.github/workflows/main.yaml: Deleted.github/workflows/release.yaml: Deleted
Three workflows shared the same build matrix, env vars, and most job logic, requiring the same fix to be applied in three places (e.g. the linker override, the attest job). A single file is easier to maintain and gives a complete picture of CI behaviour in one place.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Key design decisions:
extract-versionserves as the quality gate: it runs after all checks that apply to the current event (verify-commits,license-check,format) and all downstream jobs depend on it.- Docker metadata uses three separate
docker/metadata-actionsteps gated byif:;docker/build-push-actionconcatenates all outputs and filters empty lines.- Cosign signing, Docker SBOM generation,
sign-artifacts, SLSA provenance, andupload-release-assetsare guarded byif: github.event_name == 'release'.testandformat/clippyare guarded byif: github.event_name == 'pull_request'.trivyis guarded byif: github.event_name != 'pull_request'.- Artifact retention: PR/push = 1 day (two upload steps); release = default.
Author: Erick Bourgeois
.github/workflows/pr.yaml: Addedid: docker_buildto docker build step; addedoutputs:block to docker job (per-variant digests); addedexport-digeststep; addedattestjob depending ondockerandextract-version.github/workflows/main.yaml: Same additions to docker job and newattestjob.github/workflows/release.yaml: Same additions todocker-releasejob and newattestjob (depends ondocker-release)
GitHub's actions/attest-build-provenance generates a signed SLSA provenance attestation stored natively in GitHub Artifact Attestations and optionally pushed to the OCI registry alongside the image. This is queryable with gh attestation verify and complements the existing Cosign signatures in the release workflow. Requires the matrix digest-export pattern to pass per-image digests from a matrix job to a downstream job.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
.github/workflows/docs.yaml: New workflow — builds MkDocs documentation (includingmake docswhich runscargo run --bin crddoc) and deploys to GitHub Pages on push to main; runs link checks on PRs
The project has a full MkDocs documentation site under docs/ but no automated build or publishing pipeline. This workflow closes that gap by building on every relevant change, checking for broken links on PRs, and publishing to GitHub Pages on every merge to main.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Note: GitHub Pages must be enabled in the repository settings (
Settings → Pages → Source: GitHub Actions) for the deploy job to succeed. Thepoetry.lockfile should be committed after the firstpoetry installrun to improve cache efficiency and build reproducibility.
Author: Erick Bourgeois
.github/workflows/pr.yaml: AddedCARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: ccandCARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: ccto the top-levelenv:block.github/workflows/main.yaml: Same.github/workflows/release.yaml: Same
.cargo/config.toml specifies linker = "x86_64-unknown-linux-gnu-gcc" and linker = "aarch64-unknown-linux-gnu-gcc" for the respective targets — Homebrew cross-compilers needed when a macOS developer uses cargo build --target <linux-triple> locally (the Makefile fallback path). On Linux CI runners, cargo build --release with no explicit target resolves to the native triple (e.g., x86_64-unknown-linux-gnu on ubuntu-latest), which picks up the same override and fails because the cross-compiler is not installed on GitHub Actions runners. CARGO_TARGET_*_LINKER environment variables take precedence over config.toml, restoring cc (the system linker) in CI without modifying the config file.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
.github/workflows/pr.yaml: Replacedfirestoned/github-actions/rust/setup-rust-build@v1.3.6+build-binary@v1.3.6+generate-sbom@v1.3.6withdtolnay/rust-toolchain@stable+cargo build --release+cargo-cyclonedx; switched ARM64 build toubuntu-24.04-armnative runner; updated artifact paths fromtarget/$target/release/totarget/release/; fixedlicense-id: "MIT"→"Apache-2.0".github/workflows/main.yaml: Same build and license-check changes.github/workflows/release.yaml: Same build and license-check changes; replacedsetup-rust-buildinpackage-deploy-manifestsjob withdtolnay/rust-toolchain@stable
The firestoned/github-actions/rust/build-binary@v1.3.6 action internally sets -C linker=x86_64-unknown-linux-gnu-gcc, which is not installed on GitHub Actions ubuntu-latest runners, causing all build jobs to fail. Native cargo build --release on arch-appropriate runners eliminates cross-compilation entirely. License-id was stale after the FINOS Apache-2.0 migration.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
Cargo.toml: Addedkube-lease-manager = "0.11"dependencysrc/constants.rs: AddedDEFAULT_LEASE_NAME,DEFAULT_LEASE_DURATION_SECS,DEFAULT_LEASE_RENEW_DEADLINE_SECS,DEFAULT_LEASE_RETRY_PERIOD_SECS,DEFAULT_LEASE_GRACE_SECS,DEFAULT_LEASE_NAMESPACEconstantssrc/reconcilers/scheduled_machine.rs: Addedis_leader: Arc<AtomicBool>toContext(defaults totruefor backward-compatible single-instance mode); added leader guard inreconcile_guarded— non-leaders returnAction::await_change()immediatelysrc/main.rs: Addedenable_leader_election,lease_name,lease_namespace,lease_duration_secs,lease_renew_deadline_secsCLI args; when enabled, setsis_leader = falseat startup and spawns a backgroundkube-lease-managertask that flipsis_leaderon acquisition/lossdeploy/deployment/deployment.yaml: FixedPOD_NAME→CONTROLLER_POD_NAMEenv var (aligns withContext::newand leader election holder identity)src/reconcilers/scheduled_machine_tests.rs: Added 2 TDD tests —test_context_new_defaults_is_leader_to_trueandtest_reconcile_guarded_awaits_change_when_not_leaderdocs/src/operations/configuration.md: Added all leader election env vars, CLI args, Leader Election section, Lease RBAC rules
Basel III HA (P2-4): a single-replica controller is a single point of failure. With ENABLE_LEADER_ELECTION=true and replicas: 2, only the lease holder reconciles resources. Standby replicas react within one LEASE_DURATION_SECONDS window on leader failure. Context::is_leader defaults to true so existing single-replica deployments continue without any config change.
- Breaking change
- Requires cluster rollout — set
ENABLE_LEADER_ELECTION=trueandreplicas: 2; RBAC forleasesalready inclusterrole.yaml - Config change only
- Documentation only
Author: Erick Bourgeois
.github/ISSUE_TEMPLATE/bug_report.yml: Added# Copyright (c) 2025 Erick Bourgeois, finos+# SPDX-License-Identifier: Apache-2.0header.github/ISSUE_TEMPLATE/feature_request.yml: Same.github/ISSUE_TEMPLATE/meeting_minutes.yml: Same.github/ISSUE_TEMPLATE/support_question.yml: Same
Supply-chain provenance and automated license scanning (NIST SA-4) require SPDX headers on all project-owned files. The three workflow files and both composite actions already had headers from P2-10; these four issue templates were the remaining .github/ YAML files without them. dco.yml was intentionally left untouched — it is managed by FINOS and carries an explicit "Do not edit" notice.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
src/constants.rs: AddedMAX_RECONCILE_RETRIES: u32 = 10constantsrc/reconcilers/scheduled_machine.rs: Addedretry_counts: Arc<Mutex<HashMap<String, u32>>>toContext; updatedContext::newto initialise it; updatedreconcile_guardedto clear the retry count on successful reconciliationsrc/reconcilers/helpers.rs: Addedcompute_backoff_secs(retry_count: u32) -> u64(pure, capped exponential); replaced fixed-delayerror_policywith retry-count-aware implementation that increments the per-resource counter and computesERROR_REQUEUE_SECS * 2^ncapped atMAX_BACKOFF_SECSsrc/reconcilers/helpers_tests.rs: Added 5 TDD tests forcompute_backoff_secs(base, doubling, cap at retry 4, cap at MAX_RECONCILE_RETRIES, large count)
Basel III HA resilience (P2-5): a fixed 30 s retry interval can cause thundering-herd pressure when many resources fail simultaneously. Bounded exponential back-off distributes retry load while ensuring eventual recovery. Retry counts are cleared on success so transient failures do not permanently elevate delay. Aligns with NIST SI-2 flaw remediation by limiting retry storms.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
src/reconcilers/scheduled_machine.rs: Addedgenerate_reconcile_id()— derives a short correlation ID from the resource's UID last segment + nanosecond hex timestamp; refactoredreconcile_scheduled_machineintoreconcile_guardedwrapped in atracing::info_span!carryingreconcile_id,resource, andnamespace— every log line in a reconciliation now carries these fields in JSON output (NIST AU-3 / SOX §404 P2-3)src/reconcilers/scheduled_machine_tests.rs: Added 5 TDD tests forgenerate_reconcile_id()covering non-empty output, UID-last-segment prefix, hex timestamp suffix, unknown-fallback when no UID, and uniqueness across callssrc/crd.rs: Addedcondition_status_schema()and wired it toCondition.statusvia#[schemars(schema_with = "...")]— constrains the CRD field toenum: [True, False, Unknown](NIST CM-5 / P2-7)src/crd_tests.rs: Added 5 TDD tests forCondition.statusschema enum: constraint exists, all three values present, and runtimeCondition::new()still accepts string status unchangeddeploy/crds/scheduledmachine.yaml: Regenerated —Condition.statusnow hasenum: [True, False, Unknown]in the CRD OpenAPI schemadocs/reference/api.md: Regenerated to reflect schema change
- P2-3: Every reconciliation now emits a unique
reconcile_idon all log lines via atracingspan, enabling full end-to-end correlation in a SIEM or log aggregation platform. Closes the NIST AU-3 / SOX §404 correlation ID gap. - P2-7: The
Condition.statusfield previously accepted any string; the CRD schema now enforces the Kubernetes-standardTrue/False/Unknownenum as required by NIST CM-5 configuration change control. Runtime behaviour is unchanged — the constraint is schema-only.
- Breaking change
- Requires cluster rollout — CRD must be reapplied (
kubectl apply -f deploy/crds/scheduledmachine.yaml); existing CRs with valid status values are unaffected - Config change only
- Documentation only
Author: Erick Bourgeois
docs/src/security/index.md: New security section landing page — security posture at a glance table, compliance mapping summary, and links to sub-pagesdocs/src/security/admission-validation.md: Comprehensive user-facing guide for theValidatingAdmissionPolicycovering: VAP vs. webhook comparison table, Mermaid admission flow sequence diagram, full 13-rule reference table with per-rule detail and examples, deployment instructions, rollout strategy (Audit → Deny → AuditAndDeny), four concrete kubectl test examples, namespace scoping guidance, and Kubernetes version compatibility tabledocs/mkdocs.yml: AddedSecuritytop-level nav section (between Advanced Topics and Developer Guide) containing Overview, Admission Validation, and Threat Model pages
The ValidatingAdmissionPolicy deployed in the previous entry had no user-facing documentation. Operators need to know what is validated, how to deploy it, how to do a safe rollout, and how to test it. The new Security section also surfaces the threat model in the main navigation — previously it existed only in the repo but was not reachable from the docs site.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
[2026-04-08 14:00] - Phase 2 (P2-6/P2-8/P2-9/P2-10): eviction correctness, JSON logging, supply-chain provenance
Author: Erick Bourgeois
src/reconcilers/helpers.rs: Fixed P2-6 —evict_pod429 PDB-blocked arm now returnsErr(ReconcilerError::CapiError(...))instead of silently returningOk(()); log level raised frominfotowarn; doc comment updated to remove the incorrect "429 is not an error" statementsrc/reconcilers/helpers_tests.rs: Added 5 TDD mock API tests forevict_podcovering: success (200), already-deleted (404 → Ok), PDB-blocked (429 → CapiError), server error (500 → CapiError), and forbidden (403 → CapiError)src/main.rs: Wired P2-8 — added--log-formatCLI arg mapped toRUST_LOG_FORMATenv var (default"json"); tracing subscriber now uses.json()layer forjsonand plain text layer fortext/anything elsedeploy/deployment/deployment.yaml: ChangedRUST_LOG_FORMATdefault from"text"to"json"so production pods emit structured JSON for SIEM ingestionsrc/**/*.rs(all 18 files): Added P2-10 SPDX supply-chain provenance headers to every Rust source file:// Copyright (c) 2025 Erick Bourgeois, RBC Capital Markets // SPDX-License-Identifier: Apache-2.0
- P2-6: A PDB-blocked eviction (HTTP 429) was silently treated as success, causing the drain loop to believe the pod was evicted when it wasn't — a data-integrity bug that could leave a node non-empty. Now propagated as
CapiErrorso the caller can decide to retry or abort. - P2-8: Structured JSON logging is required for SIEM ingestion and NIST AU-3 compliance;
textformat was only appropriate for local development. - P2-9:
cargo-audit 0.22.0was already running viafirestoned/github-actions/rust/security-scan@v1.3.6on all PRs and main — no code change required, marked ✅ in roadmap. - P2-10: SPDX headers enable automated license scanning and supply-chain provenance tracking per NIST SA-4.
- Breaking change
- Requires cluster rollout —
RUST_LOG_FORMAT=jsondefault; existing log parsers expecting plain text must be updated - Config change only
- Documentation only
Author: Erick Bourgeois
deploy/admission/validatingadmissionpolicy.yaml:ValidatingAdmissionPolicywith 13 CEL validation rules covering:clusterNamenon-empty;gracefulShutdownTimeout/nodeDrainTimeoutduration format (^\d+[smh]$);cronXORdaysOfWeek/hoursOfDaymutual exclusivity;daysOfWeekday-name/range item format;hoursOfDayhour/range item format;bootstrapSpec/infrastructureSpecapiVersion namespaced-group requirement; bootstrap/infrastructure provider API group allowlist (mirrorsALLOWED_BOOTSTRAP_API_GROUPS/ALLOWED_INFRASTRUCTURE_API_GROUPSinsrc/constants.rs);bootstrapSpec.kind/infrastructureSpec.kindnon-emptydeploy/admission/validatingadmissionpolicybinding.yaml:ValidatingAdmissionPolicyBindingwithvalidationActions: [Deny]applied cluster-wide
docs/roadmaps/compliance-sox-basel3-nist.md: Marked P3-4, CM-5, and all CRD schema validation gaps as resolved; updated gap table and compliance control mappingdocs/roadmaps/project-roadmap-2026.md: Updated Phase 3.1 Admission Webhooks → Admission Validation; checked off all implemented rules; noted future mutating webhook and reference-existence check as separate itemsdocs/src/security/threat-model.md: Updated Deployment-Layer Controls table to reflect VAP deployed (was a recommendation)
ValidatingAdmissionPolicy (Kubernetes ≥ 1.26) enforces spec constraints at API-server admission time without requiring a separate webhook server, TLS certificate, or additional binary. Closes the NIST CM-5 gap: invalid specs that previously reached the reconciler are now rejected before being persisted to etcd.
- Breaking change
- Requires cluster rollout — apply
deploy/admission/manifests; requires Kubernetes ≥ 1.26 (alpha), ≥ 1.28 (beta), ≥ 1.30 (GA) - Config change only
- Documentation only
Author: Erick Bourgeois
src/reconcilers/helpers.rs: Expanded thin one-liner docs on all remaining functions —add_finalizer,handle_deletion,handle_kill_switch,check_grace_period_elapsed,update_phase_with_last_schedule,update_phase_with_grace_period,bootstrap_resource_name,infrastructure_resource_name,machine_resource_name,create_dynamic_resource,parse_api_version,remove_machine_from_cluster,should_evict_pod,evict_pod, anderror_policy— with full///docs covering purpose, behaviour details, and# Errorssectionssrc/bin/crdgen.rs: Replaced//comment header with//!module doc explaining purpose, usage, and regeneration requirement; added///onmain()with# Panicsnotesrc/bin/crddoc.rs: Added//!module doc explaining purpose, usage, and implementation note about static-println generation vs schema-driven approach; added///onmain()
All public items and binary entry points now have complete rustdoc coverage to satisfy the project's documentation standard (CLAUDE.md §Code Comments) and to provide clear in-IDE guidance for future contributors.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
[2026-04-08 00:03] - Phase 2 (P2-1/P2-2): Kubernetes Event audit trail and before/after phase logging
Author: Erick Bourgeois
src/reconcilers/scheduled_machine.rs: AddedRecorderfield toContextstruct (created fromReporterwith controller name and pod name); removed separateclient/recorderargs from allupdate_phase*call sites — now pass&ctxdirectlysrc/reconcilers/helpers.rs: Addedbuild_phase_transition_event()pure function that constructs aKubeEventfrom phase transition parameters (WarningforError/Terminated,Normalotherwise); updatedupdate_phase(),update_phase_with_last_schedule(), andupdate_phase_with_grace_period()to accept&Context(replacing separate&Client+&Recorderparams), logfrom → tophase transition atINFOlevel, and publish an immutable Kubernetes Event via the recorder (best-effort — failures emitWARNbut do not abort the transition)deploy/deployment/rbac/clusterrole.yaml: Addedevents.k8s.io/eventscreate+patch rule alongside the existing core""events rule (kube-rs Recorder uses theevents.k8s.io/v1API)src/reconcilers/helpers_tests.rs: Added 7 unit tests forbuild_phase_transition_event()covering Normal/Warning event types, note format, unknown from-phase fallback, action field, and reason field
P2-1 and P2-2 from the SOX/Basel III/NIST compliance roadmap. Every machine phase transition now writes an immutable Kubernetes Event visible via kubectl describe scheduledmachine <name>, providing an auditable record of state changes required by SOX §404 (immutable audit trail) and NIST AU-2/AU-3 (event recording and audit record content). Before/after logging closes the gap against AU-3 by making the previous phase explicit in each log line.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
src/reconcilers/scheduled_machine.rs: Replaced 7unwrap()calls withok_or_elseerror propagation in all phase handlers (reconcile_inner,handle_pending_phase,handle_active_phase,handle_shutting_down_phase,handle_inactive_phase,handle_disabled_phase,handle_error_phase) — NIST SI-3, P1-1src/reconcilers/helpers.rs: Replaced 3unwrap()calls withok_or_elseerror propagation inadd_finalizer,handle_deletion, andhandle_kill_switch— NIST SI-3, P1-1src/metrics.rs: Replaced 11.expect()panics with graceful fallback pattern using private helper functions (fallback_counter_vec,fallback_gauge,fallback_gauge_vec,fallback_histogram_vec); metrics initialization failures now log a warning and continue rather than crash — NIST SI-3, P1-2deploy/deployment/networkpolicy.yaml: Created Kubernetes NetworkPolicy implementing NIST SC-7 boundary protection — ingress restricted to Prometheus scrape (port 8080, monitoring namespace only) and kubelet probes (port 8081); egress restricted to DNS (port 53) and Kubernetes API server (port 6443) — NIST SC-7, P1-3src/main.rs: Explicitly appliedK8S_API_TIMEOUT_SECSconstant tokube::Configread_timeoutandwrite_timeoutfields to enforce connection timeouts against Kubernetes API server — Basel III operational resilience, P1-4
Phase 1 of the SOX/Basel III/NIST SP 800-53 compliance remediation roadmap (docs/roadmaps/compliance-sox-basel3-nist.md). All unwrap()/expect() calls in production code paths represent potential uncontrolled panics that violate NIST SI-3 (Malicious Code Protection) and operational resilience requirements. The NetworkPolicy enforces least-privilege network access per NIST SC-7. Explicit API timeouts align with Basel III operational resilience requirements for bounded failure modes.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
docs/src/concepts/schedules.md: Converted cron field reference ASCII art toflowchart LRMermaid diagram (5 labelled field nodes)
Project standard requires all diagrams to use Mermaid. This was the last remaining ASCII diagram across all docs.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
docs/src/security/threat-model.md: Converted Section 2 system overview ASCII art toflowchart TBMermaid diagram; converted Section 4 trust boundaries text block toflowchart LRMermaid diagram
Project standard is Mermaid for all diagrams (consistent with architecture.md and other docs/src files).
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
docs/src/security/threat-model.md: New STRIDE threat model covering all controller components, trust boundaries, threat actors, 30+ threats with likelihood/impact ratings, full mitigations matrix, and 6 residual risk items with remediation guidance
Regulatory requirement in a banking environment: all security-significant components must have a documented threat model traceable to identified controls. This also captures the rationale behind the security hardening changes made in the same session.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
.github/workflows/pr.yaml: Pull Request CI — lint, test, Linux binary builds, Docker build/push, security scan.github/workflows/main.yaml: Main branch CI/CD — builds, Docker push (latest + date tags), security scan, Trivy container scan.github/workflows/release.yaml: Release workflow — versioned Docker images with Cosign signing, SLSA provenance, binary signing, deploy manifest packaging, release asset upload
Establish baseline CI/CD pipeline for the 5-spot operator using the same firestoned GitHub Actions patterns as bindy. Linux-only builds (x86_64 + ARM64) since this is a Kubernetes operator.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Erick Bourgeois
src/crd.rs: Removednamespacefield fromEmbeddedResource— bootstrap and infrastructure resources are now always created in the ScheduledMachine's own namespace, preventing cross-namespace attackssrc/crd.rs: Addedtimezone_schema()withmaxLength: 64and character-class pattern constraint to block log injection via the timezone fieldsrc/reconcilers/helpers.rs: Fixed integer overflow inparse_duration()— now useschecked_muland rejects durations exceeding 24 hours (MAX_DURATION_SECS)src/reconcilers/helpers.rs: Addedvalidate_labels()— rejects label/annotation keys using reserved prefixes (kubernetes.io/,k8s.io/,cluster.x-k8s.io/,5spot.finos.org/) before merging into CAPI Machine resourcessrc/reconcilers/helpers.rs: Addedvalidate_api_group()— enforces an allowlist of permitted API groups for bootstrap and infrastructure embedded resources; blocks core Kubernetes APIs (v1,rbac.authorization.k8s.io/v1, etc.)src/reconcilers/helpers.rs: Wrappedremove_machine_from_clusterintokio::time::timeoutinsidehandle_deletion— finalizer cleanup now has a hard 10-minute deadline, preventing indefinite namespace deletion blockssrc/reconcilers/scheduled_machine.rs: AddedValidationErrorandTimeoutErrorvariants toReconcilerErrorsrc/constants.rs: AddedMAX_DURATION_SECS,MAX_TIMEZONE_LEN,FINALIZER_CLEANUP_TIMEOUT_SECS,RESERVED_LABEL_PREFIXES,ALLOWED_BOOTSTRAP_API_GROUPS,ALLOWED_INFRASTRUCTURE_API_GROUPSdeploy/deployment/rbac/clusterrole.yaml: Narrowedk0smotron.ioresources from wildcard to explicit list (k0sworkerconfigs,remotemachinesand their/statussubresources)src/reconcilers/helpers_tests.rs: New test file — 25 security-focused tests covering overflow protection, reserved label rejection, and API group allowlist enforcementsrc/crd_tests.rs,src/reconcilers/scheduled_machine_tests.rs: Removednamespace: NonefromEmbeddedResourcetest fixtures (field removed)
Comprehensive security audit identified: cross-namespace resource creation via user-controlled namespace overrides, integer overflow in duration parsing, label injection into CAPI resources, unbounded apiVersion/kind inputs, and missing finalizer cleanup timeouts. These are now all addressed to meet zero-trust security requirements for a regulated banking environment.
- Breaking change —
EmbeddedResource.namespacefield removed from CRD schema (existing CRs with this field: Kubernetes ignores unknown fields, no action required) - Requires cluster rollout — CRDs must be regenerated (
regen-crdsskill) before deploying - Config change only
- Documentation only
Author: Erick Bourgeois
- Created
.claude/directory with SKILL.md and CHANGELOG.md - Adopted skills-based workflow from bindy project
- Updated documentation structure for better organization
Standardize project instructions and skills across projects, improving consistency and making procedures reusable and discoverable.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Unknown
scripts/install-cloud-init.sh: Linux-only script to convert VMDK→raw, mount LVM with conflict-safe handling, chroot to installcloud-initandopen-vm-tools, optional initramfs rebuild, raw→streamOptimized VMDK, and import as vSphere template viagovc.
Enable automated preparation and deployment of a cloud-init-enabled RHEL image on a VMware VM. Credentials and vSphere target configuration are provided via environment variables to avoid storing secrets in code.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Unknown
scripts/install-cloud-init.sh: Replaced fragilegovc vm.info-based existence check with robustgovc find -type m -name <name>logic; iterates over matched inventory paths, converts templates to VMs when needed, and destroys them before import.
govc vm.info can return exit code 0 with no output, leading to false positives. Using govc find and inspecting inventory paths provides reliable detection of existing VMs/templates with the target name and avoids confusing "not found" errors.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only
Author: Unknown
scripts/install-cloud-init.sh: UseLVM_SYSTEM_DIRto isolate loop device LVM metadata to a separate directory (/tmp/lvm-loop-$$); use temporary VG name (vg00_loop) if host has same VG name to avoid device-mapper conflicts in/dev/mapper/.
Device-mapper device names in /dev/mapper/ are global at the kernel level, even with isolated LVM metadata via LVM_SYSTEM_DIR. If both host and loop device have vg00 with LVs named root, var, etc., device-mapper refuses to create duplicate devices ("Device or resource busy"). By using vgimportclone -n vg00_loop when a conflict exists, we give the loop device VG a unique name for device-mapper while keeping metadata isolated. No rename needed after deactivation since the isolated metadata directory is simply deleted.
- Breaking change
- Requires cluster rollout
- Config change only
- Documentation only