Add Trivy vulnerability scanning for Docker images - #135
Conversation
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
maximthomas
left a comment
There was a problem hiding this comment.
praise: The scan is wired in carefully.
limit-severities-for-sarif: truenext toseverity: CRITICAL,HIGH, so the severity filter also applies to SARIF output.security-events: writeis granted only tobuild-docker/build-docker-alpine, withcontents: read, and the upload is guarded byif: ${{ always() && hashFiles('trivy-results.sarif') != '' }}.aquasecurity/trivy-actionis pinned by SHA (ed142fd…# v0.36.0), anddocker-scan.ymlskips the cron in forks while still allowingworkflow_dispatch.
issue (non-blocking): Once docker-scan.yml has run on master, every PR's Trivy check will end neutral with "2 configurations not found".
.github/workflows/docker-scan.yml:59, .github/workflows/build.yml:192, :267
trivy-image-latest / trivy-image-alpine are uploaded only on master, while PRs upload only trivy-build-* under the same tool. GitHub compares a PR against every configuration of that tool on the base branch. OpenDJ, which has the same setup, shows the result: the Trivy check-run on OpenDJ PR #1152 is neutral, titled "2 configurations not found", with the summary "Code scanning cannot determine the alerts introduced by this pull request, because 2 configurations present on refs/heads/master were not found". The PR check this change adds would show that warning on every PR from then on. At minimum, say so where the next reader will look:
# Scans the published Docker images for known vulnerabilities: new CVEs surface in
# already-released images (mostly via the base image), without any change in this repository.
# Its trivy-image-* categories exist only on master, so once it has run, every PR's
# "Trivy" code-scanning check concludes neutral with "2 configurations not found".question (non-blocking): Was the build-time scan meant to catch CVEs in Maven dependencies that a PR adds?
.github/workflows/build.yml:171-184, :246-259; Dockerfile:25-30, Dockerfile-alpine:27-34
The scanned image is built with VERSION=${{ env.release_version }}, and the Dockerfiles download openicf-$VERSION.zip from the latest GitHub release; the COPY OpenICF-java-framework/openicf-zip/target/*.zip line is commented out. The image therefore never contains the PR's jars, so a PR that adds a dependency with a fixable CRITICAL/HIGH CVE still gets a green Trivy check. Only Dockerfile and base-image changes are scanned. If catching such dependencies was the goal, this is a Major gap and needs a scan of the built ZIP, e.g. scan-type: fs over OpenICF-java-framework/openicf-zip/target in build-maven. If it was not, one comment line is enough:
- name: Scan image for vulnerabilities (Trivy)
# the image is built from the last release's ZIP (VERSION=release_version), so this
# scan covers the Dockerfile and the base image, not this commit's jars;
# trivy resolves the image from the local Docker daemon, so only the runner'ssuggestion (non-blocking): Make the scan step continue-on-error, so that a Trivy or registry outage does not fail the image smoke test.
.github/workflows/build.yml:171, :246
When there are findings the step exits 0 (no exit-code). It exits 1 when the trivy binary download fails, or when the trivy-db / trivy-java-db download (~923 MiB, fetched again on every run because of cache: false) fails on both mirror.gcr.io and ghcr.io. build-docker / build-docker-alpine are the only smoke test of the image, and they would then go red even though the image is fine. The upload's if: always() && hashFiles(...) already skips a missing report.
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
# a Trivy or DB-registry outage must not fail the image smoke test
continue-on-error: truesuggestion (non-blocking): Say that the published-image scan covers only linux/amd64. The alpine image ships a different JRE on linux/386.
.github/workflows/docker-scan.yml:41-46; Dockerfile-alpine:31
No platform is given, so Trivy pulls only the runner's linux/amd64 manifest. release.yml publishes alpine for 6 platforms, and Dockerfile-alpine:31 installs openjdk11-jre on 386 and openjdk25-jre on all the others. A CVE in the 386 JRE is therefore never reported. build.yml states its amd64-only limit; this file does not.
- name: Scan openidentityplatform/openicf:${{ matrix.tag }} (Trivy)
# trivy pulls only the runner's linux/amd64 manifest; the other published platforms
# (alpine on linux/386 ships openjdk11-jre instead of openjdk25-jre) are not scanned.Or: add a matrix entry for alpine on linux/386, with the step env TRIVY_PLATFORM: linux/386 and its own category (untested).
suggestion (non-blocking): Run docker-scan.yml once before merge. Nothing on this PR runs it.
.github/workflows/docker-scan.yml:19-22
Its only triggers are schedule and workflow_dispatch, and both need the file on the default branch. gh run list --workflow docker-scan.yml returns 404 upstream and on vharseko/OpenICF. The Docker Hub pull, the templated SARIF names, the hashFiles(format(...)) guard and the trivy-image-* upload will first run on the Monday after merge. OpenDJ's twin workflow has been green for 4 weeks, so the risk is low.
# after the file is on vharseko/OpenICF's default branch (or on upstream right after merge)
gh workflow run docker-scan.yml -R vharseko/OpenICF
gh run list -R vharseko/OpenICF --workflow docker-scan.yml --limit 1Pin: a green run that uploads two trivy-image-* analyses.
nitpick (non-blocking): The comment calls build.yml's scan a "gate", but it gates nothing.
.github/workflows/docker-scan.yml:42
build.yml sets no exit-code. On this PR, 2+2 findings were uploaded and Trivy still passed.
# unlike the build.yml scan, unfixed CVEs are reported too: surfacing them innitpick (non-blocking): The PR description promises a "Code scanning results / trivy-build-*" check; the actual check is Trivy.
.github/workflows/build.yml:185-192
GitHub creates one check-run per SARIF tool (Trivy, next to CodeQL), covering both trivy-build-* categories. A reviewer or a branch-protection rule looking for trivy-build-* will not find it.
7f2f021 to
6bc5403
Compare
|
@maximthomas the branch is rebased onto current master; the answers per point: "2 configurations not found" (issue). Confirmed on OpenDJ #1152. Documented in the header of Build-time scan and the PR's Maven dependencies (question). Yes, it should cover them — fixed rather than commented (1e5ec91). As in OpenDJ,
Published-image scan is linux/amd64 only (suggestion). Documented in Run "gate" (nitpick). Now "scan" (6bc5403). Check name in the PR description (nitpick). The description now names the |
Scans the freshly built images (default and alpine) in build.yml for fixable CRITICAL/HIGH CVEs and uploads SARIF to code scanning, and adds a weekly docker-scan workflow that scans the published openidentityplatform/openicf:latest/:alpine images. Mirrors OpenIdentityPlatform/OpenDJ#854, with aquasecurity/trivy-action pinned by commit SHA to match the pinning convention introduced in OpenIdentityPlatform#130.
… test build-docker and build-docker-alpine now wait for build-maven, take the openicf ZIP from its ubuntu-latest-11 artifact and uncomment the COPY in the Dockerfile, so the smoke test and the Trivy scan cover this commit's jars instead of the last release. The Dockerfiles download the release ZIP only when none was copied in, so release.yml is unchanged.
…he scan limits The build-time scan step is continue-on-error: findings never fail it, only a Trivy or trivy-db registry outage does. docker-scan.yml now states that it scans only the linux/amd64 manifest and that its master-only trivy-image-* categories leave every PR's Trivy check neutral, and no longer calls the build.yml scan a gate.
6bc5403 to
82fc1ce
Compare
|
@maximthomas the branch is rebased onto current Conflict in Commit hashes cited in my previous reply. After the rebase: 1e5ec91 → c4b8772 (build from the ZIP of the commit under test), 6bc5403 → 82fc1ce ( PR description. |
maximthomas
left a comment
There was a problem hiding this comment.
praise: The docker jobs now test and scan this commit, and the release path is unchanged.
Prepare Dockerfile+ theubuntu-latest-11artifact build the images from this commit's ZIP. The scan shows it:refs/pull/135/mergenow has alerts for the PR's ownbcprov-jdk18on1.84 andbc-fips2.1.2.- The
ls ./openicf-*.zipguard inDockerfile/Dockerfile-alpinekeepsrelease.yml's download path as it was.release.ymlpasses onlyVERSION, and its COPY stays commented. - Every round-1 point is addressed:
continue-on-erroron both scans, the amd64-only note, the "2 configurations not found" note, and theTrivycheck named in the description.
issue (non-blocking): cache: false also turns off setup-trivy's binary cache, so every job downloads Trivy anonymously from github.com. On this head the alpine scan never ran, but the job stayed green.
.github/workflows/build.yml:214, :313, .github/workflows/docker-scan.yml:55
trivy-action@ed142fd passes cache on to setup-trivy@3fb12ec (action.yaml:129-133). With cache: false, the binary is never restored from cache, and install.sh's checking GitHub for tag lookup runs in every job (token-setup-trivy only authenticates the checkout). In run 37051597083, build-docker-alpine logged crit unable to find 'v0.70.0'. continue-on-error kept the job green, no trivy-results.sarif was written, and the upload was skipped. refs/pull/135/merge has only trivy-build-default alerts, but the Trivy check passed. So 1 of 2 scans was lost on this head. The step comment says cache: false only keeps the DBs out of the cache.
- name: Install Trivy
# cached, unlike the DBs: trivy-action's `cache: false` would also skip the binary
# cache and leave an anonymous github.com release lookup in every run
continue-on-error: true
uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 # v0.2.6
with:
version: v0.70.0
cache: true
- name: Scan image for vulnerabilities (Trivy)
continue-on-error: true
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
skip-setup-trivy: true
# ...inputs as now, cache: false kept for the DBsNot run. The first run for each version still does the lookup. docker-scan.yml needs the same change.
suggestion (non-blocking): Let the docker jobs run when an unrelated build-maven leg fails.
.github/workflows/build.yml:126, :224
needs: build-maven with no if: waits on all 9 legs of the fail-fast: false matrix. A single red macOS, Windows or other-JDK leg then skips both docker jobs: the image build, the smoke test, the scan and the upload. In 5 of the 7 recent red Build runs, ubuntu-latest-11 was green and another leg failed (e.g. 36698693947 windows-26, 37002960094 windows-11). At BASE the docker jobs ran in those runs.
build-docker:
needs: build-maven
# run even when an unrelated matrix leg failed; the download below still
# fails if the ubuntu-latest-11 leg itself did
if: ${{ !cancelled() }}Not run. The jobs still wait for the slowest leg, because GitHub cannot wait on a single matrix leg.
suggestion (non-blocking): Note at the upload step that the docker jobs depend on the ubuntu-latest-11 artifact.
.github/workflows/build.yml:113-114, :143, :241
If "Re-run failed jobs" is used on a docker job after retention-days: 5, only that job re-runs, and download-artifact fails with "artifact not found". At BASE the same re-run worked.
# build-docker and build-docker-alpine download ubuntu-latest-11; a docker-job
# re-run after retention-days needs "Re-run all jobs"
name: ${{ matrix.os }}-${{ matrix.java }}nitpick (non-blocking): The comment on Get latest release version still says the Dockerfile's download URL needs the tag.
.github/workflows/build.yml:153-155, :251-253
With the COPY uncommented, the Dockerfile skips the download (the logs unzip only openicf-2.1.0-SNAPSHOT.zip). The tag now only names the local image.
# Authenticated: the anonymous api.github.com limit is per runner IP
# and, once hit, the empty answer left the metadata step with no tag.
# The tag only names the locally built image; the ZIP comes from build-maven.
# `|| true` keeps a failed lookup going to the `last release:` line, and `test -n` stops it.nitpick (non-blocking): The header says "every PR's" Trivy check reports 2 missing configurations. That holds only for a PR that uploads both trivy-build-* categories.
.github/workflows/docker-scan.yml:17-18
If a PR loses a scan (as on this head) or its docker jobs are skipped, it uploads fewer categories, and the count differs.
# Its trivy-image-* categories exist only on master, so once it has run, every PR that
# uploads both trivy-build-* categories gets a "Trivy" check that concludes neutral
# with "2 configurations not found".nitpick (non-blocking): linux/amd64 is go-containerregistry's default, not the runner's platform.
.github/workflows/docker-scan.yml:45-46
There is no pull step, and no --platform is passed, so a remote multi-arch index resolves to the hard-coded linux/amd64. On ubuntu-latest the result is the same; only the stated reason is wrong.
# already-released images is the point of this workflow. Trivy pulls only the
# linux/amd64 manifest (no --platform is passed); the other published platforms are not scanned…d build-maven failures trivy-action's cache: false also skipped setup-trivy's binary cache, so every job did an anonymous github.com release lookup; in run 37051597083 it failed and the alpine scan was silently lost. Trivy is now installed by its own setup-trivy step with the binary cached, while the DBs stay uncached. build-docker and build-docker-alpine run unless the run was cancelled, so a red leg other than ubuntu-latest-11 no longer skips the image smoke test and scan. Comments: the docker jobs' dependency on the ubuntu-latest-11 artifact, the release tag now only naming the local image, the linux/amd64 default in docker-scan.yml and which PRs get "2 configurations not found".
|
@maximthomas round 2 is addressed in 5ee0021, on top of the same base (
For the record: OpenDJ has the same configuration and has not hit this in its last ~30 Build runs — but there the scan is not Docker jobs skipped when an unrelated Re-run after Stale comment on "every PR's" in the linux/amd64 is go-containerregistry's default (nitpick). Fixed: the comment now says no |
maximthomas
left a comment
There was a problem hiding this comment.
praise: this round closes all of round 2's points, and the CI run at this head shows the fixes working.
- Both scans now run: code scanning has
trivy-build-defaultandtrivy-build-alpineanalyses for this head's merge commit (run 37187759719). At the previous head the alpine scan was lost. if: ${{ !cancelled() }}onbuild-docker/build-docker-alpine(.github/workflows/build.yml:131,:241) keeps the image smoke test running when an unrelated matrix leg fails. A superseded run still skips both jobs.- The separate
aquasecurity/setup-trivystep withcache: true(build.yml:203-210) skips the install-script checkout and the anonymous github.com tag lookup on a cache hit.
Adds Docker image vulnerability scanning with Trivy, mirroring OpenIdentityPlatform/OpenDJ#854.
build.yml—build-dockerandbuild-docker-alpinenow build the image from the ZIP of the commit under test, as OpenDJ does: they wait forbuild-maven, takeopenicf-*.zipfrom itsubuntu-latest-11artifact and uncomment theCOPYin the Dockerfile. Before, the image was built from the last release's ZIP (VERSION=release_version), so neither the smoke test nor a scan saw this commit's jars.Dockerfile/Dockerfile-alpinenow download the release ZIP only when none was copied in, sorelease.ymlis unchanged. They run unless the run was cancelled (if: ${{ !cancelled() }}), so a red matrix leg other thanubuntu-latest-11no longer skips the image smoke test and scan; a redubuntu-latest-11leg fails them at the artifact download. A docker-job re-run after the artifact's 5-day retention needs "Re-run all jobs".Both jobs scan the freshly built image (resolved from the local Docker daemon, so the runner's
linux/amd64manifest only) right after the Docker test step. Findings do not fail the build: the SARIF report is uploaded viacodeql-action/upload-sarif, so PRs get aTrivycode-scanning check next toCodeQL(one check-run per SARIF tool, covering both thetrivy-build-defaultandtrivy-build-alpinecategories), and the full list lives in the Security tab. The scan step iscontinue-on-error, so a Trivy or trivy-db registry outage does not fail the image smoke test. Only fixable CRITICAL/HIGH CVEs are reported (ignore-unfixed: truepluslimit-severities-for-sarif: true— without the latter the severity filter is silently dropped for SARIF output), and only the vulnerability scanner runs (scanners: vuln). The action's built-in ~1GB DB cache is disabled (cache: false) so it cannot evict them2-repositorycaches out of the repo's 10GB actions-cache quota. Because trivy-action passes that samecacheinput tosetup-trivy, which would then skip its binary cache and do an anonymous github.com release lookup in every job, Trivy is installed by a separateaquasecurity/setup-trivystep (version: v0.70.0,cache: true, alsocontinue-on-error) and trivy-action runs withskip-setup-trivy: true. The two docker jobs getsecurity-events: writenext tocontents: read; build.yml's workflow-levelpermissions:(from #130) stayscontents: readfor every other job.docker-scan.yml(new) — weekly cron (30 5 * * 1) +workflow_dispatchscan of the publishedopenidentityplatform/openicf:latestand:alpineimages: new CVEs surface in already-released images (mostly via the base image) without any change in this repository. Unlike the build-time scan, unfixed CVEs are reported too. Trivy is installed the same way (separatesetup-trivystep with the binary cached, here withoutcontinue-on-error). Only thelinux/amd64manifest is scanned (no--platformis passed, so go-containerregistry's default applies); the other published platforms are not (alpineonlinux/386shipsopenjdk11-jreinstead ofopenjdk25-jre). Reports are uploaded as SARIF with a separate category per tag (trivy-image-*, distinct from thetrivy-build-*categories inbuild.yml). The scheduled run is skipped in forks; manual runs are always allowed. Its triggers need the file on the default branch, so nothing on this PR runs it: it is to be started once by hand (gh workflow run docker-scan.yml) right after merge. Since thetrivy-image-*categories exist only on master, every PR that uploads bothtrivy-build-*categories will then get aTrivycheck that concludes neutral with "2 configurations not found" (as on OpenDJ).aquasecurity/trivy-action(v0.36.0) andaquasecurity/setup-trivy(v0.2.6, the same SHA trivy-action itself uses) are pinned by commit SHA, matching the pinning convention #130 introduced for third-party actions in this repo. Future false positives / accepted findings can be suppressed via a.trivyignorefile in the repository root or dismissed in the Security tab.