diff --git a/.github/workflows/publish-images.yml b/.github/workflows/publish-images.yml index ddd1c90e..69246d32 100644 --- a/.github/workflows/publish-images.yml +++ b/.github/workflows/publish-images.yml @@ -265,7 +265,10 @@ jobs: base_version="$(grep -m1 '^FROM postgres:' images/postgres/Dockerfile | sed -E 's#^FROM postgres:([^@[:space:]]+).*#\1#')" ;; restic) - base_version="$(grep -m1 '^FROM restic/restic:' images/restic/Dockerfile | sed -E 's#^FROM restic/restic:([^@[:space:]]+).*#\1#')" + # restic is compiled from source in images/restic/Dockerfile (the + # upstream restic/restic image is no longer the base), so the base + # version comes from the pinned source tag ARG. + base_version="$(grep -m1 '^ARG RESTIC_VERSION=' images/restic/Dockerfile | cut -d= -f3)" ;; *) echo "No version scheme defined for image '${{ matrix.image.name }}'" >&2 diff --git a/.trivyignore b/.trivyignore index a9f84853..57f46911 100644 --- a/.trivyignore +++ b/.trivyignore @@ -18,181 +18,3 @@ # Rules: only IDs reported by the scan may be listed, each ID at most once, # and exp must be a valid date at most 90 days out. An expired exception # stops being ignored and fails the daily scan again. - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2025-61726 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2025-61729 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2025-68121 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-111943 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-25679 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-27145 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-32280 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-32281 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-32283 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-33811 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-33814 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-33818 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-39820 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-39821 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-39822 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-39836 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-42499 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-42504 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-56853 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-56858 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-56859 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-56860 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-56862 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-78667 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-78669 exp:2027-01-23 - -# gosu 1.19 in the pinned postgres:18 base is built with go1.24.6; -# patched in the next upstream base image digest (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-97031 exp:2027-01-23 - -# restic 0.19.1 (the latest upstream release, digest-pinned) is built with -# github.com/klauspost/compress v1.18.6; patched in the next restic -# release (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-63209 exp:2027-01-26 - -# restic 0.19.1 (the latest upstream release, digest-pinned) is built with -# golang.org/x/crypto v0.52.0; patched in the next restic release -# (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-56854 exp:2027-01-26 - -# restic 0.19.1 (the latest upstream release, digest-pinned) is built with -# golang.org/x/net v0.55.0 and Go stdlib v1.26.4; patched in the next -# restic release (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-46600 exp:2027-01-26 - -# restic 0.19.1 (the latest upstream release, digest-pinned) is built with -# golang.org/x/text v0.37.0 (CVE-2026-56851); patched in the next restic -# release (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-56851 exp:2027-01-26 - -# restic 0.19.1 (the latest upstream release, digest-pinned) is built with -# golang.org/x/text v0.37.0 (CVE-2026-56852); patched in the next restic -# release (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-56852 exp:2027-01-26 - -# restic 0.19.1 (the latest upstream release, digest-pinned) is built with -# google.golang.org/grpc v1.81.1 (CVE-2026-84304); patched in the next -# restic release (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-84304 exp:2027-01-26 - -# restic 0.19.1 (the latest upstream release, digest-pinned) is built with -# google.golang.org/grpc v1.81.1 (CVE-2026-84445); patched in the next -# restic release (refresh the pin) -# owner:@ronaldhensbergen -CVE-2026-84445 exp:2027-01-26 - -# restic 0.19.1 (the latest upstream release, digest-pinned) is built with -# google.golang.org/grpc v1.81.1 (GHSA-hrxh-6v49-42gf); patched in the next -# restic release (refresh the pin) -# owner:@ronaldhensbergen -GHSA-hrxh-6v49-42gf exp:2027-01-26 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f01ea43..464fa79f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,22 @@ The format is based on Keep a Changelog. ### Fixed +- Cleared the 34 `.trivyignore` exceptions listed by the trivyignore audit + (#1001) by fixing the findings at the source in the rebuilt images instead + of ignoring them: the `postgres` image now pins the current `postgres:18` + digest (also in `modules/warehouse/postgres/module.yaml`, + `docker-compose.yml`, and `.cds/haven-dev-noinit.yml`) and replaces the + base image's prebuilt `gosu` binary — which no `postgres:18` tag rebuilds + with a fixed Go toolchain — with a build from the pinned gosu 1.20 tag + compiled with Go 1.27.3; the `restic` image now compiles restic 0.19.1 (the + latest upstream release) from source with Go 1.27.3 and the fixed module + versions (`go-jose/v4` 4.1.6, `klauspost/compress` 1.18.7, `x/crypto` + 0.57.0, `x/net` 0.60.0, `x/text` 0.42.0, `grpc` 1.83.2) on a pinned Alpine + base instead of using the vulnerable prebuilt `restic/restic` image; both + source tarballs are verified against pinned sha256 hashes in the builder + stages. Both rebuilt images scan clean of HIGH/CRITICAL findings with + Trivy v0.75.0. + - Fixed `cds security` false positives on the reference production profile (#979): the secret-value rules CDS-SEC-001/003/033 no longer flag values that are entirely a renderer reference (`config.passwordFrom`, diff --git a/Makefile b/Makefile index 0bdd11bc..ab020252 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ docker-build: echo "Building $$dockerfile in directory $$dir..."; \ context="$$dir"; \ case "$$dockerfile" in \ - ./images/dagster/*|./images/superset/*|./images/postgres/*) context="." ;; \ + ./images/dagster/*|./images/superset/*|./images/dbt/*|./images/dlt/*|./images/postgres/*|./images/restic/*) context="." ;; \ esac; \ docker build -f "$$dockerfile" "$$context" || exit 1; \ done diff --git a/images/postgres/Dockerfile b/images/postgres/Dockerfile index 5456c98e..aac8caf5 100644 --- a/images/postgres/Dockerfile +++ b/images/postgres/Dockerfile @@ -13,10 +13,37 @@ # The pgdg apt repository is already configured in the upstream postgres # image; the pgbackrest package version is pinned for reproducible builds. -FROM postgres:18@sha256:32ca0af8e77bfb8c6610c488e4691f83f972a3e9e64d3b02facf3ab111ad5500 +# gosu build stage: the upstream postgres image ships a prebuilt gosu binary +# compiled with an old Go toolchain, and no postgres:18 tag rebuilds it, so +# the Go stdlib CVEs against that binary are not fixable by a base digest +# bump alone (#1001). Build gosu from the pinned upstream tag with the +# current Go toolchain instead; the result is a static binary +# (CGO_ENABLED=0). The source tarball is verified against the pinned +# sha256 below (review hardening on #1041); module dependencies would be +# covered by the Go checksum database, but gosu has none. +FROM golang:1.27.3-alpine@sha256:d200d2dd76b03a99b52651fd1325aff7dcd35422c35c01bac56571b0507f3deb AS gosu +ARG GOSU_VERSION=1.20 +# sha256 of https://codeload.github.com/tianon/gosu/tar.gz/refs/tags/${GOSU_VERSION} +# (verified stable across downloads). No Renovate datasource exists for +# codeload tarball hashes, so bump this ARG together with GOSU_VERSION — +# the build fails closed on a mismatch. +ARG GOSU_TARBALL_SHA256=207a2045e890d3ca3326027c5f585733085960b401a49dbc00e3bb628c054cf8 +RUN mkdir -p /src \ + && wget -qO /tmp/gosu.tar.gz "https://codeload.github.com/tianon/gosu/tar.gz/refs/tags/${GOSU_VERSION}" \ + && echo "${GOSU_TARBALL_SHA256} /tmp/gosu.tar.gz" | sha256sum -c - \ + && tar xz --strip-components=1 -C /src -f /tmp/gosu.tar.gz \ + && rm /tmp/gosu.tar.gz \ + && cd /src \ + && CGO_ENABLED=0 go build -trimpath -o /go/bin/gosu . + +FROM postgres:18@sha256:979bccfde35445db618c65107d27d08c6c21339d8dc3ad4c830c6633947be0e4 USER root +# Replace the base image's prebuilt gosu with the toolchain-current build +# from the stage above (see the comment on that stage). +COPY --from=gosu /go/bin/gosu /usr/local/bin/gosu + # Pull in Debian security fixes for the base image's OS packages before # installing pgbackrest, mirroring images/superset/base/Dockerfile: the # pinned upstream digest can lag behind trixie-security, and the Trivy diff --git a/images/postgres/README.md b/images/postgres/README.md index 041a56e9..8ccc4ebf 100644 --- a/images/postgres/README.md +++ b/images/postgres/README.md @@ -4,6 +4,12 @@ PostgreSQL image with [pgBackRest](https://pgbackrest.org/) for the built-in backup capability of the `warehouse/postgres` module (issue #665). - Base: `postgres:18`, digest-pinned (Renovate keeps the pin current). +- Replaces the base image's prebuilt `gosu` binary with a build from the + pinned gosu tag compiled with the current Go toolchain: the upstream + image downloads a prebuilt binary that no `postgres:18` tag rebuilds, so + its Go stdlib CVEs are only fixable by compiling gosu here (#1001). The + source tarball is verified against the pinned `GOSU_TARBALL_SHA256` + during the build (bump it together with `GOSU_VERSION`). - Adds the `pgbackrest` package from the pgdg apt repository baked into the upstream image, version-pinned for reproducible builds. - `cds-pgbackrest-archive` — PostgreSQL `archive_command` handler. Archives WAL diff --git a/images/restic/Dockerfile b/images/restic/Dockerfile index 979703ca..fd1e1d8a 100644 --- a/images/restic/Dockerfile +++ b/images/restic/Dockerfile @@ -11,11 +11,44 @@ # Repository location and S3 settings come from the CDS_BACKUP_* # environment variables rendered by the module; the repository is always # client-side encrypted with RESTIC_PASSWORD from the profile's secrets. +# +# restic is compiled from source instead of using the upstream +# restic/restic image: 0.19.1 is still the latest upstream release, and its +# published binary is built with vulnerable Go modules and an old stdlib, +# so no upstream image refresh can clear those findings (#1001). Building +# the pinned release tag with the current Go toolchain and the fixed module +# versions below does. The versions are the fixed versions from the Trivy +# advisories (x/net v0.60.0 requires x/crypto >= v0.57.0 and x/text +# >= v0.42.0); Renovate tracks them via the regex manager in renovate.json. + +FROM golang:1.27.3-alpine@sha256:d200d2dd76b03a99b52651fd1325aff7dcd35422c35c01bac56571b0507f3deb AS restic-build +ARG RESTIC_VERSION=0.19.1 +# sha256 of https://codeload.github.com/restic/restic/tar.gz/refs/tags/v${RESTIC_VERSION} +# (verified stable across downloads). No Renovate datasource exists for +# codeload tarball hashes, so bump this ARG together with RESTIC_VERSION — +# the build fails closed on a mismatch. Module deps fetched by go get/go mod +# tidy are covered by the Go checksum database. +ARG RESTIC_TARBALL_SHA256=bb9b1a19040744d26d8a79be029d4e6b189c45ccc9d8831d7fe367d3c33df725 +WORKDIR /src +RUN wget -qO /tmp/restic.tar.gz "https://codeload.github.com/restic/restic/tar.gz/refs/tags/v${RESTIC_VERSION}" \ + && echo "${RESTIC_TARBALL_SHA256} /tmp/restic.tar.gz" | sha256sum -c - \ + && tar xz --strip-components=1 -f /tmp/restic.tar.gz \ + && rm /tmp/restic.tar.gz \ + && go get github.com/go-jose/go-jose/v4@v4.1.6 \ + github.com/klauspost/compress@v1.18.7 \ + golang.org/x/crypto@v0.57.0 \ + golang.org/x/net@v0.60.0 \ + golang.org/x/text@v0.42.0 \ + google.golang.org/grpc@v1.83.2 \ + && go mod tidy \ + && CGO_ENABLED=0 go build -trimpath -o /restic ./cmd/restic -FROM restic/restic:0.19.1@sha256:136600b6ff6843d61d355f7f71f460a166429f35de6fd11b568fece3c9a4d510 +FROM alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a USER root +COPY --from=restic-build /restic /usr/bin/restic + # Pull in Alpine security fixes for the base image's OS packages before # creating the user, mirroring images/postgres/Dockerfile: the pinned # upstream digest can lag behind the fixed packages, and the Trivy scan diff --git a/images/restic/README.md b/images/restic/README.md index b2782003..02122705 100644 --- a/images/restic/README.md +++ b/images/restic/README.md @@ -3,8 +3,15 @@ Restic image for the generic file/volume backup module (`modules/backup/restic`, issue #668). -- Base: `restic/restic:0.19.1`, digest-pinned (Renovate keeps the pin - current). +- Base: `alpine:3.24`, digest-pinned (Renovate keeps the pin current). restic + itself is compiled from source in a builder stage: `RESTIC_VERSION` pins + the upstream release tag (0.19.1 is still the latest release) and the + `go get` line pins the fixed Go module versions from the Trivy advisories + against the published 0.19.1 binary, compiled with the current Go + toolchain (#1001); Renovate tracks the ARG and the module pins. The source + tarball is verified against the pinned `RESTIC_TARBALL_SHA256` during the + build (bump it together with `RESTIC_VERSION`). Using the prebuilt + `restic/restic` image instead would reintroduce those findings. - Adds a dedicated non-root `restic` user (uid 10000) and the repository / source mount points (`/var/lib/restic-repo`, `/backup`) with the right ownership, so a fresh named volume inherits uid/gid 10000 on first use diff --git a/modules/warehouse/postgres/module.yaml b/modules/warehouse/postgres/module.yaml index c9cf1c4a..f629d766 100644 --- a/modules/warehouse/postgres/module.yaml +++ b/modules/warehouse/postgres/module.yaml @@ -328,7 +328,7 @@ spec: # production-only pieces below replace these fields in production # profiles (production: mapping form; see apply_production_gates). postgres: - image: postgres:18@sha256:32ca0af8e77bfb8c6610c488e4691f83f972a3e9e64d3b02facf3ab111ad5500 + image: postgres:18@sha256:979bccfde35445db618c65107d27d08c6c21339d8dc3ad4c830c6633947be0e4 user: postgres init: true read_only: true diff --git a/renovate.json b/renovate.json index b32c6c31..85452b45 100644 --- a/renovate.json +++ b/renovate.json @@ -9,6 +9,42 @@ ] }, "customManagers": [ + { + "customType": "regex", + "description": "Track the gosu source tag compiled in images/postgres/Dockerfile to replace the postgres base image's prebuilt gosu binary (upstream publishes unprefixed tags like 1.20, hence the loose versioning); when the tag changes, bump GOSU_TARBALL_SHA256 in the same file — no datasource covers codeload tarball hashes, so the sha256 is manual", + "matchStrings": [ + "ARG GOSU_VERSION=(?\\S+)" + ], + "depNameTemplate": "tianon/gosu", + "datasourceTemplate": "docker", + "versioningTemplate": "loose", + "managerFilePatterns": [ + "/^images/postgres/Dockerfile$/" + ] + }, + { + "customType": "regex", + "description": "Track the restic source tag compiled in images/restic/Dockerfile (restic is built from source with patched Go modules; the upstream restic/restic image is no longer used as a base); when the tag changes, bump RESTIC_TARBALL_SHA256 in the same file — no datasource covers codeload tarball hashes, so the sha256 is manual", + "matchStrings": [ + "ARG RESTIC_VERSION=(?\\S+)" + ], + "depNameTemplate": "restic/restic", + "datasourceTemplate": "docker", + "managerFilePatterns": [ + "/^images/restic/Dockerfile$/" + ] + }, + { + "customType": "regex", + "description": "Track the Go module versions pinned in the `go get` line of images/restic/Dockerfile (the fixed versions from the Trivy advisories against the restic 0.19.1 binary)", + "matchStrings": [ + "(?[\\w./-]+)@(?v[\\w.-]+)" + ], + "datasourceTemplate": "golang", + "managerFilePatterns": [ + "/^images/restic/Dockerfile$/" + ] + }, { "customType": "regex", "description": "Track pip pins in Dagster entrypoint script", diff --git a/tests/test_image_digest_check.py b/tests/test_image_digest_check.py index 584668c3..ceb91f6f 100644 --- a/tests/test_image_digest_check.py +++ b/tests/test_image_digest_check.py @@ -42,13 +42,13 @@ def __exit__(self, *exc): class ParsePinnedReferenceTest(unittest.TestCase): def test_parses_tag_and_digest(self): parsed = parse_pinned_reference( - "postgres:18@sha256:32ca0af8e77bfb8c6610c488e4691f83f972a3e9e64d3b02facf3ab111ad5500" + "postgres:18@sha256:979bccfde35445db618c65107d27d08c6c21339d8dc3ad4c830c6633947be0e4" ) self.assertEqual( parsed, ( "postgres:18", - "sha256:32ca0af8e77bfb8c6610c488e4691f83f972a3e9e64d3b02facf3ab111ad5500", + "sha256:979bccfde35445db618c65107d27d08c6c21339d8dc3ad4c830c6633947be0e4", ), )