Scan the published image for known vulnerabilities
terdut-server was the only one of the three release-managed repos with no image scanning at all. riksdata and rd-web have had a scan-image job since they were set up; everything published here up to and including v0.9.3 went out without a CVE check. It scans the pushed image rather than a locally built one, for the same reason the siblings do: trivy cannot read a local image on this runner, since Talos has no docker socket and the dind sidecar shares no filesystem with the job. So it runs after image rather than gating it, and a red scan unpublishes nothing. What it means is narrower and worth stating plainly: do not bump the wrapper chart in Ryuvia/charts to that version. Checked before wiring it in rather than after. v0.9.3 scans clean at HIGH,CRITICAL with unfixed findings ignored, so this does not turn the pipeline red on arrival, and the same command exits 1 on an image that does have findings — a gate that cannot fail is not a gate. One platform is scanned, not both. The image is FROM scratch, so there are no OS packages and trivy sees a single target: the Go binary and its module graph. linux/amd64 and linux/arm64 are that same module set built for a different GOARCH, so a finding in one is a finding in both. On an image with a base layer that reasoning would not hold. Still no govulncheck and no gitleaks here, which riksdata and rd-web run in a separate CI job. This is the only security scanning terdut-server has. Claude-Session: https://claude.ai/code/session_01S7R4gWTz5wh5xCY4nCSJjN
This commit is contained in:
@@ -187,3 +187,37 @@ jobs:
|
|||||||
set -eu
|
set -eu
|
||||||
echo "$TOKEN" | helm registry login "$REGISTRY" -u niklas --password-stdin
|
echo "$TOKEN" | helm registry login "$REGISTRY" -u niklas --password-stdin
|
||||||
make helm-package helm-push VERSION="$REF_NAME"
|
make helm-package helm-push VERSION="$REF_NAME"
|
||||||
|
|
||||||
|
# Host mode, like image and chart: this needs a docker daemon to run trivy in, and a
|
||||||
|
# `container:` job would sit on the dind bridge with none.
|
||||||
|
#
|
||||||
|
# It scans the pushed image rather than a locally built one, because trivy cannot read a
|
||||||
|
# local image on this runner -- Talos has no docker socket and the dind sidecar shares no
|
||||||
|
# filesystem with the job -- so it pulls from the registry. That is also why this runs
|
||||||
|
# after `image` rather than gating it: a red scan does not unpublish anything.
|
||||||
|
#
|
||||||
|
# What a red scan means is therefore not "the release failed" but "do not bump the wrapper
|
||||||
|
# chart in Ryuvia/charts to this version". The image and chart are already published by
|
||||||
|
# the time this runs, and deliberately so -- this pipeline does not deploy.
|
||||||
|
#
|
||||||
|
# riksdata and rd-web have had this since they were set up; terdut-server went without any
|
||||||
|
# image scanning until 2026-09-02, so every release before v0.9.4 was published with no
|
||||||
|
# CVE check at all.
|
||||||
|
scan-image:
|
||||||
|
needs: image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
env:
|
||||||
|
REF_NAME: ${{ github.ref_name }}
|
||||||
|
run: git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" .
|
||||||
|
|
||||||
|
# Credentials are passed even though these packages are anonymously pullable -- that
|
||||||
|
# is a property of the personal namespace this publishes to, not something a release
|
||||||
|
# should depend on staying true.
|
||||||
|
- name: Scan the pushed image (trivy)
|
||||||
|
env:
|
||||||
|
TRIVY_USERNAME: niklas
|
||||||
|
TRIVY_PASSWORD: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
REF_NAME: ${{ github.ref_name }}
|
||||||
|
run: make security-image VERSION="$REF_NAME"
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ Config is `.release.conf` here plus `make release-vars`. The process itself live
|
|||||||
|
|
||||||
Two things about this repo specifically:
|
Two things about this repo specifically:
|
||||||
|
|
||||||
- **The pipeline has no image scan.** `.gitea/workflows/release.yaml` runs `test`,
|
- **The image is scanned after it is published, not before.** `scan-image` runs trivy
|
||||||
`binaries`, `image` and `chart`. A green release run is not evidence the image is
|
against the pushed image, because trivy cannot read a locally built one on this runner.
|
||||||
CVE-clean, and a release note must not imply it is.
|
A red scan therefore unpublishes nothing — it means: do not bump the wrapper chart in
|
||||||
|
`Ryuvia/charts` to this version. Added 2026-09-02; every release up to and including
|
||||||
|
v0.9.3 was published with no CVE check at all.
|
||||||
- **The wrapper chart has two `tag:` lines** — the app image and the python backup sidecar —
|
- **The wrapper chart has two `tag:` lines** — the app image and the python backup sidecar —
|
||||||
so `chart-bump` needs `--image "$IMAGE"` to know which one moves.
|
so `chart-bump` needs `--image "$IMAGE"` to know which one moves.
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ CHART_VERSION := $(shell echo "$(VERSION)" | sed 's/^v//')
|
|||||||
PLATFORMS ?= linux/amd64,linux/arm64
|
PLATFORMS ?= linux/amd64,linux/arm64
|
||||||
BUILDX_BUILDER ?= terdut
|
BUILDX_BUILDER ?= terdut
|
||||||
|
|
||||||
|
TRIVY_VERSION := 0.73.0
|
||||||
|
|
||||||
# --pull, not --no-cache: refresh the base image without discarding the layer cache.
|
# --pull, not --no-cache: refresh the base image without discarding the layer cache.
|
||||||
DOCKER_BUILD_FLAGS ?= --pull
|
DOCKER_BUILD_FLAGS ?= --pull
|
||||||
|
|
||||||
@@ -113,7 +115,7 @@ HELM_ISOLATED = HELM_REPOSITORY_CONFIG=$(CURDIR)/.helm-repos.yaml
|
|||||||
.PHONY: require-version
|
.PHONY: require-version
|
||||||
require-version:
|
require-version:
|
||||||
@test "$(VERSION)" != "dev" || \
|
@test "$(VERSION)" != "dev" || \
|
||||||
(echo "refusing to publish VERSION=dev — pass VERSION=vX.Y.Z (the workflow passes the tag)" && exit 1)
|
(echo "VERSION=dev names no release — pass VERSION=vX.Y.Z (the workflow passes the tag)" && exit 1)
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: ## Build the image for this host only, without pushing (local check / CI smoke)
|
build: ## Build the image for this host only, without pushing (local check / CI smoke)
|
||||||
@@ -166,3 +168,29 @@ binaries: require-version ## Cross-compile the release binaries into dist/
|
|||||||
|
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
release: push helm-package helm-push ## Publish image + chart (the workflow's one call)
|
release: push helm-package helm-push ## Publish image + chart (the workflow's one call)
|
||||||
|
|
||||||
|
## --- security ---
|
||||||
|
|
||||||
|
# Scans the pushed image, not a local one: trivy cannot read a locally built image on the
|
||||||
|
# runner -- Talos has no docker socket, and the dind sidecar shares no filesystem with the
|
||||||
|
# job -- so it pulls from the registry. Same reason riksdata and rd-web scan after pushing.
|
||||||
|
#
|
||||||
|
# It cannot gate a deploy, because this pipeline does not deploy. A red scan means: do not
|
||||||
|
# bump the wrapper chart in Ryuvia/charts to this version.
|
||||||
|
#
|
||||||
|
# The image is FROM scratch, so there are no OS packages to scan and trivy sees exactly one
|
||||||
|
# target -- the Go binary and its module graph. That also makes scanning a single platform
|
||||||
|
# sufficient here: linux/amd64 and linux/arm64 are the same modules built for a different
|
||||||
|
# GOARCH, so a CVE in one is a CVE in both. On an image with a base layer that would not
|
||||||
|
# hold and both platforms would need scanning.
|
||||||
|
#
|
||||||
|
# riksdata and rd-web also run govulncheck and gitleaks in a `security` CI job. This repo
|
||||||
|
# does not, so this target is the only security scanning it has.
|
||||||
|
.PHONY: security-image
|
||||||
|
security-image: require-version ## Scan the pushed image for CVEs (needs VERSION)
|
||||||
|
@# The named volume persists trivy's vulnerability DB between runs; without it every
|
||||||
|
@# scan re-downloads the whole database.
|
||||||
|
docker run --rm -e TRIVY_USERNAME -e TRIVY_PASSWORD \
|
||||||
|
-v trivy-cache:/root/.cache/trivy \
|
||||||
|
docker.io/aquasec/trivy:$(TRIVY_VERSION) image --severity HIGH,CRITICAL \
|
||||||
|
--ignore-unfixed --exit-code 1 $(IMAGE):$(VERSION)
|
||||||
|
|||||||
@@ -709,8 +709,10 @@ by pushing a tag.
|
|||||||
|
|
||||||
Two things the release process needs to know about this repo:
|
Two things the release process needs to know about this repo:
|
||||||
|
|
||||||
- **The pipeline has no image scan**, unlike riksdata and rd-web. A green release run is not
|
- **The image scan runs after publishing**, like riksdata's and rd-web's: trivy cannot read
|
||||||
evidence the image is CVE-clean.
|
a locally built image on this runner, so it pulls the pushed one. A red `scan-image` means
|
||||||
|
do not bump the wrapper chart to that version — it cannot unpublish anything. The image is
|
||||||
|
`FROM scratch`, so trivy sees exactly one target, the Go binary and its module graph.
|
||||||
- **The wrapper chart's `values.yaml` has two `tag:` lines** — the app image and the python
|
- **The wrapper chart's `values.yaml` has two `tag:` lines** — the app image and the python
|
||||||
backup sidecar — so `chart-bump` is given `--image` to say which one moves.
|
backup sidecar — so `chart-bump` is given `--image` to say which one moves.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user