Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions docker/cve-scanner/Dockerfile
Original file line number Diff line number Diff line change
@@ -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 <registry>/octopusdeploy/calamari-cve-scanner:trivy0.74.0-grype0.117.0 \
# docker/cve-scanner
# docker push <registry>/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 = "<the docker feed>"
# image = "<registry>/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.
36 changes: 36 additions & 0 deletions docker/cve-scanner/README.md
Original file line number Diff line number Diff line change
@@ -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.