diff --git a/docker/cve-scanner/Dockerfile b/docker/cve-scanner/Dockerfile new file mode 100644 index 000000000..8b9e28668 --- /dev/null +++ b/docker/cve-scanner/Dockerfile @@ -0,0 +1,49 @@ +# Execution container for the Calamari CVE scan runbook. +# +# OPTIONAL. The scan works without this image: scan.sh installs Trivy and Grype at +# runtime when they are not on PATH. This exists to make those versions pinned and +# reviewable instead. +# +# Why it matters for a scheduled scan: the runbook alerts when the reported CVE set +# changes. With unpinned scanners, a scanner upgrade and a genuine new CVE produce the +# same signal - "something appeared" - and the difference is what decides whether anyone +# needs to act. Pinning means a change in the diff is a change in the world, and scanner +# upgrades become deliberate commits to this file. +# +# Build and push (adjust the registry to wherever the runbook's feed points): +# docker build -t /octopusdeploy/calamari-cve-scanner:trivy0.74.0-grype0.117.0 \ +# docker/cve-scanner +# docker push /octopusdeploy/calamari-cve-scanner:trivy0.74.0-grype0.117.0 +# +# Then point the runbook's scan step at it, in +# Modern-Deployments/.octopus/calamari-cve-scanning/runbooks/scan-cves.ocl: +# +# container { +# feed = "" +# image = "/octopusdeploy/calamari-cve-scanner:trivy0.74.0-grype0.117.0" +# } +# +# scan.sh needs no change. It auto-detects both binaries on PATH and skips the install. + +FROM octopusdeploy/worker-tools:6.4.0-ubuntu.22.04 + +# Versions observed in the first green runbook run, 2026-08-18. Bump deliberately, and +# expect the next scan after a bump to report differences that are the scanner's doing +# rather than Calamari's. +ARG TRIVY_VERSION=0.74.0 +ARG GRYPE_VERSION=0.117.0 + +USER root + +RUN set -eux; \ + curl -sSfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \ + | sh -s -- -b /usr/local/bin "v${TRIVY_VERSION}"; \ + curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh \ + | sh -s -- -b /usr/local/bin "v${GRYPE_VERSION}"; \ + trivy --version; \ + grype version + +# The vulnerability databases are deliberately NOT baked in. They change daily, and a +# stale database bundled into an image is exactly the failure this scan exists to catch: +# it would report a clean result because nobody was looking, not because nothing is wrong. +# Both tools download the current database on first use in each run. diff --git a/docker/cve-scanner/README.md b/docker/cve-scanner/README.md new file mode 100644 index 000000000..53b8bdaee --- /dev/null +++ b/docker/cve-scanner/README.md @@ -0,0 +1,36 @@ +# Pinned scanner image for the CVE scan runbook + +Optional. Nothing depends on this yet. + +## What it is for + +`scripts/scan-calamari-cves/scan.sh` installs Trivy and Grype at runtime when they are not +already on `PATH`. That works, and it is why the runbook needs no image today. It has one +weakness that only matters once the scan runs unattended: + +**An unpinned scanner makes an upgrade indistinguishable from a discovery.** The runbook's +whole job is to alert when the reported CVE set changes. If the scanner silently moves to a +new version between runs, findings can appear or vanish because the tool changed rather than +because Calamari or the world did — and that difference is the entire signal. + +Pinning the versions in an image makes scanner upgrades explicit commits to the `Dockerfile`, +reviewable like any other dependency bump. + +## Adopting it + +1. Build and push the image (see the header of the `Dockerfile` for the exact commands). +2. Point the runbook's scan step at it — a two-line change to `container { … }` in + `Modern-Deployments/.octopus/calamari-cve-scanning/runbooks/scan-cves.ocl`. +3. Nothing else changes. `scan.sh` detects both binaries on `PATH` and skips its install + step automatically, so the same script keeps working locally with Docker and in the + runbook without them. + +## Deliberate omission: the vulnerability databases + +The databases are not baked in. They change daily — this repo's own README records a CVE +going from absent to present in the audit source within a single day. A stale database +frozen into an image would produce a quiet, clean-looking result for the same reason an +end-of-life runtime does: nobody is looking, not nothing is wrong. + +Both tools fetch the current database at run time. On Octopus Cloud dynamic workers there is +no cache between runs, so this download happens every run regardless of the image.