Skip to content

Commit 6f6a74d

Browse files
authored
Documentation updates and added cargo deny and dependabot (#24)
Signed-off-by: Erick Bourgeois <erick@jeb.ca>
1 parent a2b9cc0 commit 6f6a74d

13 files changed

Lines changed: 557 additions & 80 deletions

File tree

.claude/CHANGELOG.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,147 @@ The format is based on the regulated environment requirements:
99

1010
---
1111

12+
## [2026-04-19 17:15] - Add Dependabot config for Actions, Cargo, and Docker
13+
14+
**Author:** Erick Bourgeois
15+
16+
### Changed
17+
- `.github/dependabot.yml` (new): SPDX-headered Dependabot v2 config with three ecosystems:
18+
- **`github-actions`** — weekly Monday 09:00 `America/Toronto`, limit 10 PRs. Grouped update `actions-routine` bundles low-risk first-party + tooling bumps (`actions/*`, `docker/*`, `github/codeql-action`, `softprops/action-gh-release`, `anchore/sbom-action`, `aquasecurity/trivy-action`) into one PR. Security-sensitive actions (`sigstore/*`, `EmbarkStudios/cargo-deny-action`, `ossf/*`) are left ungrouped so each one opens as an individual PR for per-change review. `dtolnay/rust-toolchain` explicitly ignored (branch-tracking ref, no tags — re-pinned manually on quarterly cadence).
19+
- **`cargo`** — weekly Monday, limit 5 PRs. Groups patch + minor bumps; majors open individually. cargo-audit + cargo-deny CI gates block regressions.
20+
- **`docker`** — weekly Monday, limit 3 PRs. Picks up `FROM ...@sha256:...` digest bumps once Dockerfiles adopt digest pinning (currently no-op; future-proof).
21+
- Commit-message prefix: `ci` for Actions, `chore` for Cargo and Docker. Labels applied to every PR for easy filtering.
22+
23+
### Why
24+
The previous commit SHA-pinned all external GitHub Actions across the workflows, which is correct for supply-chain hygiene but creates stagnation risk — pins do not auto-update, so known-vulnerable action versions can sit in CI indefinitely. Dependabot solves the stagnation side of the trade-off: it opens a PR per new release with the full changelog diff, and the same CI gates (Semgrep, Trivy, cargo-deny, Scorecard) that guard any other PR guard the bump itself. This closes the loop between "pin everything" and "keep pins current" without inviting humans to cowboy-update SHAs.
25+
26+
### Impact
27+
- [ ] Breaking change
28+
- [ ] Requires cluster rollout
29+
- [x] Config change only (Dependabot)
30+
- [ ] Documentation only
31+
32+
### Operational note
33+
- **PR volume:** expect 1–3 Actions PRs and 1–2 Cargo PRs per week initially, tapering as the tree stabilizes. The `actions-routine` group keeps the routine noise in one PR.
34+
- **Review rule of thumb:** security-sensitive bumps (sigstore, cargo-deny, ossf) → read the full release notes before merging. Routine Actions group → verify Scorecard Pinned-Dependencies still passes, spot-check the diff for any behaviour change flags, merge if CI is green.
35+
- **Future-proof Docker:** `Dockerfile` and `Dockerfile.chainguard` use tag-based `FROM` lines today. If we move to digest pinning (`FROM image@sha256:...`), Dependabot starts opening PRs for base-image digest bumps automatically — no further config change needed.
36+
37+
---
38+
39+
## [2026-04-19 17:00] - Pin all GitHub Actions to full commit SHAs
40+
41+
**Author:** Erick Bourgeois
42+
43+
### Changed
44+
- `.github/workflows/build.yaml`, `calm.yaml`, `calm-test.yaml`, `docs.yaml` and `.github/actions/prepare-docker-binaries/action.yaml`: replaced every `@<tag>` reference with `@<40-char-sha> # <tag>` for all external actions. 77 replacements across 5 files covering `actions/*`, `docker/*`, `sigstore/cosign-installer`, `anchore/sbom-action`, `aquasecurity/trivy-action`, `EmbarkStudios/cargo-deny-action`, `softprops/action-gh-release`, `github/codeql-action/upload-sarif`, `dtolnay/rust-toolchain`, and all `firestoned/github-actions/*` sub-actions. `scorecard.yaml` was already SHA-pinned and is unchanged.
45+
- `.github/workflows/build.yaml`: the SLSA reusable workflow at `slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0` stays on a semver tag — pinning to SHA would break SLSA provenance verification because `slsa-verifier` validates against approved released versions. Added an inline multi-line comment explaining this so future reviewers don't "fix" it by SHA-pinning.
46+
- `aquasecurity/trivy-action` was previously pinned to the nonexistent tag `0.28.0` (missing `v` prefix — the tag is `v0.28.0`). Bumped to the current latest `v0.35.0` at SHA `57a97c7e7821a5776cebc9bb87c984fa69cba8f1`.
47+
- `dtolnay/rust-toolchain@stable` resolved to SHA `29eef336d9b2848a0b548edc03f92a220660cdb8` with an inline comment noting it is a moving ref that should be re-pinned quarterly to keep Rust current.
48+
49+
### Why
50+
Closes the OpenSSF Scorecard `Pinned-Dependencies` finding across the entire CI surface. Unpinned action tags are a supply-chain risk: a compromised or silently-force-pushed tag would silently execute attacker code in our workflows with access to `GITHUB_TOKEN`, `SNYK_TOKEN`-equivalent secrets, and GHCR push permissions. SHA pinning freezes behaviour to a specific reviewed commit; Dependabot can later send PRs to bump pins with a full diff.
51+
52+
### Impact
53+
- [ ] Breaking change
54+
- [ ] Requires cluster rollout
55+
- [x] Config change only (CI/CD workflow)
56+
- [ ] Documentation only
57+
58+
### Operational note
59+
- **Dependabot for Actions is strongly recommended** as a follow-up so pins don't rot. Add `.github/dependabot.yml` with a `github-actions` ecosystem entry; Dependabot will open one PR per outdated action SHA with a changelog link.
60+
- **Re-pin cadence:** security-sensitive actions (`sigstore/*`, `ossf/*`, `EmbarkStudios/cargo-deny-action`) should be bumped within a week of a new release. Build tooling (`docker/*`, `actions/*`) is less time-sensitive. `dtolnay/rust-toolchain` should be bumped quarterly to keep the Rust toolchain current.
61+
- All pin comments include the resolved semver tag (e.g. `# v4.3.1`) so a human reader can see at a glance what version is in use without clicking through to the SHA.
62+
63+
---
64+
65+
## [2026-04-19 17:30] - Phase 5 docs finish: regenerate stale api.md (mdBook source)
66+
67+
**Author:** Erick Bourgeois
68+
69+
### Changed
70+
- `docs/src/reference/api.md`: regenerated via `make crddoc`. Now includes the Phase 1 status-schema additions — `providerID`, extended `nodeRef` (with `uid`, `apiVersion`, `kind` alongside `name`), `machineRef`, `bootstrapRef`, `infrastructureRef`, and refreshed condition/observedGeneration docstrings. Previously this file was stale; Phase 1's regen wrote only to `docs/reference/api.md` (a legacy path referenced by `.claude/SKILL.md`), not to the mdBook source path that actually renders on the doc site (`docs/src/reference/api.md`, per the `Makefile` `crddoc` target).
71+
72+
### Why
73+
The Phase 5 checklist in the roadmap required "`docs/src/reference/api.md` — regenerated by `regen-api-docs` skill." Spot-checking after the earlier Phase 5 commit revealed that the canonical Makefile target output path and the SKILL.md instruction disagreed, and the mdBook source copy was still on the pre-Phase-1 schema. Regenerating closes that gap so the doc site reflects what consumers actually get on `kubectl get scheduledmachine -o yaml`.
74+
75+
### Impact
76+
- [ ] Breaking change
77+
- [ ] Requires cluster rollout
78+
- [ ] Config change only
79+
- [x] Documentation only
80+
81+
### Operational note
82+
- **SKILL.md drift:** `.claude/SKILL.md` currently instructs `cargo run --bin crddoc > docs/reference/api.md`, which writes to the wrong path. The `Makefile` `crddoc` target is correct. A small follow-up should reconcile the skill instructions with the Makefile so future regens aren't silently written to the wrong path.
83+
84+
---
85+
86+
## [2026-04-19 16:30] - Phase 5 docs: watch-topology diagram in architecture.md
87+
88+
**Author:** Erick Bourgeois
89+
90+
### Changed
91+
- `docs/src/concepts/architecture.md`: New top-level section "Watch Topology" with a Mermaid diagram showing the primary `ScheduledMachine` watch plus the two new secondary watches on CAPI `Machine` (label-filtered) and core `Node` (cluster-wide) feeding into pure reverse-mapper functions (`machine_to_scheduled_machine`, `node_to_scheduled_machines`) that enqueue the owning `ScheduledMachine`. Updated the "Controller" component-detail bullet list to enumerate the three watches.
92+
- `docs/roadmaps/5spot-event-driven-watches-and-status-enrichment.md` (copy at `~/dev/roadmaps/`): status header updated to `Phase 5 ✅` — roadmap closed.
93+
94+
### Why
95+
Closes Phase 5 of the event-driven-watches roadmap. Until this commit, the concepts doc described a controller that only watched its own CR; readers had no way to understand why a Node cordon now triggers an immediate reconcile instead of waiting for the next requeue. The watch-topology diagram makes the event-driven claim concrete and auditable.
96+
97+
### Impact
98+
- [ ] Breaking change
99+
- [ ] Requires cluster rollout
100+
- [ ] Config change only
101+
- [x] Documentation only
102+
103+
---
104+
105+
## [2026-04-19 16:00] - Replace misleading Snyk claim with real OSS security tooling (Semgrep + Trivy config + cargo-deny)
106+
107+
**Author:** Erick Bourgeois
108+
109+
### Changed
110+
- `.github/workflows/build.yaml`: Three new PR + push-to-main jobs (release events skip these — the release pipeline relies on scans that already ran on main):
111+
- `semgrep-sast` — runs `semgrep scan` in the `returntocorp/semgrep` container with the community `p/rust`, `p/security-audit`, `p/secrets`, and `p/owasp-top-ten` rulesets; `--metrics=off`; uploads SARIF to Code Scanning under the `semgrep` category. No token required.
112+
- `iac-scan``aquasecurity/trivy-action@0.28.0` with `scan-type: config` against the repo root (picks up `deploy/**/*.yaml` + `Dockerfile*` + `Dockerfile.chainguard`); uploads SARIF under the `trivy-iac` category.
113+
- `cargo-deny``EmbarkStudios/cargo-deny-action@v2` running `check --all-features` against the new `deny.toml`.
114+
- `deny.toml` (new, repo root): SPDX-headered cargo-deny config. License allow-list covers Apache-2.0, MIT, BSD-2/3-Clause, ISC, Unicode-DFS-2016, Unicode-3.0, Zlib, CC0-1.0, MPL-2.0, OpenSSL. Advisories: `yanked = "deny"`. Bans: `multiple-versions = "warn"` (to avoid breaking CI on transitive pulls we do not control), `wildcards = "deny"`. Sources: only official crates.io registry, no unknown git URLs.
115+
- `README.md`: Removed the misleading `Snyk` SAST badge (we never actually ran Snyk). Added badges for **Semgrep**, **Trivy** (Container + IaC), **cargo-deny**, **cargo-audit**, **Cosign**, and **SLSA** — each reflecting tooling that actually runs in the pipeline.
116+
- `docs/architecture/calm/architecture.json`: The `supply-chain-scanning` control description now reads: "Repository is scanned by Semgrep OSS (SAST), Trivy (container image + IaC config), cargo-audit + cargo-deny (RustSec advisories, license allow-list, source restrictions), and Gitleaks (secrets); OpenSSF Scorecard publishes supply-chain posture; SPDX license identifiers on source files." Old wording referenced Snyk and Aqua which were never actually wired up.
117+
118+
### Why
119+
The README's Security & Compliance section and the CALM architecture JSON both claimed "Snyk (SAST)" but `rg -i snyk` returned zero hits outside those two strings — the repo has never had Snyk configured. That is a compliance posture lie for a project in a regulated banking context. This commit closes the three real gaps with free OSS tooling: SAST (Semgrep), IaC misconfig scanning (Trivy config), and dependency license/advisory enforcement (cargo-deny). All three are token-free, run on every PR, and write SARIF to the existing Code Scanning dashboard — same surface consumers already use for Scorecard and Trivy container results.
120+
121+
### Impact
122+
- [ ] Breaking change
123+
- [ ] Requires cluster rollout
124+
- [x] Config change only (CI/CD workflow + cargo-deny config)
125+
- [ ] Documentation only
126+
127+
### Operational note
128+
- **First run of `cargo-deny` may fail** if an existing transitive dep carries a license not in the allow-list. Treat the first failure as signal: either add the license to `deny.toml` (with a justification comment) or file an issue to swap the dep. Do not weaken `unknown-registry = "deny"` — that is a supply-chain boundary, not a nuisance.
129+
- **Semgrep findings initially expected**: the first run will surface Rust and OWASP-style findings that were never triaged. Review in Code Scanning → filter by tool `semgrep` → triage-or-suppress with justification (Semgrep supports `// nosemgrep: rule-id — reason` inline comments).
130+
- **Trivy IaC findings**: scope is the `deploy/` manifests plus both Dockerfiles. Findings you cannot fix (e.g., a base-image constraint) can be suppressed via a `.trivyignore` file at repo root with a comment.
131+
- All three jobs run as `needs: [verify-commits]` so they parallelize with `extract-version`/`build` and do not serialize the critical path.
132+
133+
---
134+
135+
## [2026-04-19 15:45] - Add OpenSSF Scorecard badge to README
136+
137+
**Author:** Erick Bourgeois
138+
139+
### Changed
140+
- `README.md`: Added `[![OpenSSF Scorecard]...]` badge as the first entry in the "Security & Compliance" section, linking to `https://scorecard.dev/viewer/?uri=github.com/finos/5-spot`.
141+
142+
### Why
143+
The `scorecard.yaml` workflow already publishes results to the OpenSSF REST API (`api.securityscorecards.dev`) and to GitHub Code Scanning, but without a visible badge the score is invisible to anyone reading the README. The badge is the canonical consumer-facing signal that the project runs Scorecard and auto-updates with each `scorecard.yaml` run.
144+
145+
### Impact
146+
- [ ] Breaking change
147+
- [ ] Requires cluster rollout
148+
- [ ] Config change only
149+
- [x] Documentation only
150+
151+
---
152+
12153
## [2026-04-19 15:30] - Sign binaries and generate SLSA provenance on push-to-main (not only on release)
13154

14155
**Author:** Erick Bourgeois

.claude/SKILL.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,15 @@ kubectl apply -f deploy/crds/scheduledmachine.yaml
6767

6868
**Steps:**
6969
```bash
70-
# Regenerate API reference from CRD types
71-
cargo run --bin crddoc > docs/reference/api.md
70+
# Regenerate API reference from CRD types.
71+
# Prefer the Makefile target — it writes to the mdBook source path that
72+
# actually renders on the doc site.
73+
make crddoc
74+
# Equivalent:
75+
# cargo run --bin crddoc > docs/src/reference/api.md
7276
```
7377

74-
**Verification:** `docs/reference/api.md` reflects the current CRD schema.
78+
**Verification:** `docs/src/reference/api.md` reflects the current CRD schema.
7579

7680
---
7781

@@ -184,7 +188,7 @@ For each of the following files, check the YAML examples and field descriptions
184188
against `src/crd.rs` (source of truth) and `examples/*.yaml` (reference examples):
185189

186190
1. `docs/src/installation/quickstart.md` — YAML example in "Create Your First ScheduledMachine"
187-
2. `docs/reference/api.md` — all Spec Fields, Status Fields, and the top-level example
191+
2. `docs/src/reference/api.md` — all Spec Fields, Status Fields, and the top-level example (auto-generated — regenerate with `make crddoc`, do not hand-edit)
188192
3. `docs/src/advanced/capi-integration.md` — Bootstrap/Infrastructure sections and provider examples
189193
4. `docs/src/concepts/scheduled-machine.md` — field tables (types, defaults, required flags)
190194
5. Any other `.md` file under `docs/` that contains a YAML snippet with `kind: ScheduledMachine`
@@ -304,7 +308,7 @@ done
304308
- [ ] `examples/*.yaml` updated to match new schema
305309
- [ ] `docs/` documentation updated
306310
- [ ] `kubectl apply --dry-run=client -f examples/` passes
307-
- [ ] `cargo run --bin crddoc > docs/reference/api.md` run (LAST)
311+
- [ ] `make crddoc` run (LAST) — writes to `docs/src/reference/api.md` (mdBook source)
308312

309313
### If `src/reconcilers/` was modified:
310314
- [ ] Reconciliation flow diagrams updated in `docs/`

.github/actions/prepare-docker-binaries/action.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ runs:
88
using: composite
99
steps:
1010
- name: Download all binary artifacts
11-
uses: actions/download-artifact@v4
11+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
1212
with:
1313
path: ./artifacts
1414
pattern: 5spot-linux-*

.github/dependabot.yml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright (c) 2025 Erick Bourgeois, firestoned
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
# Dependabot keeps SHA-pinned GitHub Actions, Rust crates, and Docker base
5+
# images current. Each ecosystem opens PRs against main on the schedule below.
6+
# PRs include the full release-notes diff and the new SHA, so reviewers can
7+
# verify what changed before merging.
8+
#
9+
# Docs: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference
10+
11+
version: 2
12+
13+
updates:
14+
# ─────────────────────────────────────────────────────────────────────────
15+
# GitHub Actions — re-pin SHAs when upstream cuts a new release.
16+
# Covers every workflow under .github/workflows AND the composite actions
17+
# under .github/actions. Dependabot rewrites both the SHA and the trailing
18+
# `# vX.Y.Z` comment automatically.
19+
# ─────────────────────────────────────────────────────────────────────────
20+
- package-ecosystem: "github-actions"
21+
directory: "/"
22+
schedule:
23+
interval: "weekly"
24+
day: "monday"
25+
time: "09:00"
26+
timezone: "America/Toronto"
27+
open-pull-requests-limit: 10
28+
commit-message:
29+
prefix: "ci"
30+
include: "scope"
31+
labels:
32+
- "dependencies"
33+
- "github-actions"
34+
groups:
35+
# One PR per week bundling routine first-party / tooling bumps.
36+
actions-routine:
37+
applies-to: version-updates
38+
patterns:
39+
- "actions/*"
40+
- "docker/*"
41+
- "github/codeql-action"
42+
- "softprops/action-gh-release"
43+
- "anchore/sbom-action"
44+
- "aquasecurity/trivy-action"
45+
# Security-sensitive actions get their own PRs so each one is reviewed
46+
# individually. Keep sigstore/ossf/cargo-deny out of grouped updates.
47+
# (Ungrouped entries always open as individual PRs.)
48+
ignore:
49+
# dtolnay/rust-toolchain tracks the `stable` branch and has no tags, so
50+
# Dependabot would see no "new version" to propose. It is re-pinned by
51+
# hand on the quarterly cadence documented in build.yaml.
52+
- dependency-name: "dtolnay/rust-toolchain"
53+
54+
# ─────────────────────────────────────────────────────────────────────────
55+
# Rust crates (Cargo.lock) — keep transitive deps patched against RustSec
56+
# advisories. The cargo-audit + cargo-deny CI jobs will block merges that
57+
# regress the advisory status, so Dependabot can only land a PR that passes
58+
# those gates.
59+
# ─────────────────────────────────────────────────────────────────────────
60+
- package-ecosystem: "cargo"
61+
directory: "/"
62+
schedule:
63+
interval: "weekly"
64+
day: "monday"
65+
time: "09:00"
66+
timezone: "America/Toronto"
67+
open-pull-requests-limit: 5
68+
commit-message:
69+
prefix: "chore"
70+
include: "scope"
71+
labels:
72+
- "dependencies"
73+
- "rust"
74+
groups:
75+
cargo-patch-and-minor:
76+
applies-to: version-updates
77+
update-types:
78+
- "patch"
79+
- "minor"
80+
81+
# ─────────────────────────────────────────────────────────────────────────
82+
# Docker base images — Chainguard and Distroless both publish rolling tags,
83+
# so what Dependabot actually catches here are digest bumps in the
84+
# Dockerfile / Dockerfile.chainguard pinned `FROM image@sha256:…` lines.
85+
# If no digest pins exist yet, these entries are a no-op until one is added.
86+
# ─────────────────────────────────────────────────────────────────────────
87+
- package-ecosystem: "docker"
88+
directory: "/"
89+
schedule:
90+
interval: "weekly"
91+
day: "monday"
92+
time: "09:00"
93+
timezone: "America/Toronto"
94+
open-pull-requests-limit: 3
95+
commit-message:
96+
prefix: "chore"
97+
include: "scope"
98+
labels:
99+
- "dependencies"
100+
- "docker"

0 commit comments

Comments
 (0)