From 81b7222c0d8fd6b45ea8e34741e063f83eed2493 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Fri, 18 Sep 2026 18:03:19 +0300 Subject: [PATCH 1/4] Add Trivy vulnerability scanning for Docker images - build.yml: scan the freshly built default and alpine images after the docker tests and upload the SARIF report to code scanning (fixable CRITICAL/HIGH only); the two docker jobs get security-events: write - docker-scan.yml: weekly (and manual) scan of the published openidentityplatform/openidm:latest and :alpine images, unfixed CVEs included, reported under separate trivy-image-* categories Ported from OpenIdentityPlatform/OpenDJ#854. --- .github/workflows/build.yml | 52 +++++++++++++++++++++++++++ .github/workflows/docker-scan.yml | 59 +++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 .github/workflows/docker-scan.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 10c27101b..7e2e3c0e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -297,6 +297,9 @@ jobs: echo "No errors or exceptions detected in openidm logs" build-docker: runs-on: 'ubuntu-latest' + permissions: + contents: read + security-events: write # upload the Trivy scan results to code scanning services: registry: image: registry:2 @@ -311,6 +314,7 @@ jobs: run: | export git_version_last="$(curl -i -o - --silent https://api.github.com/repos/OpenIdentityPlatform/OpenIDM/releases/latest | grep -m1 "\"name\"" | cut -d\" -f4)" ; echo "last release: $git_version_last" echo "release_version=$git_version_last" >> $GITHUB_ENV + echo "image_repository=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV - name: Docker meta id: meta uses: docker/metadata-action@v6 @@ -343,8 +347,33 @@ jobs: docker run --rm -it -d --memory="1g" --name=test localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }} timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test | grep -q \"healthy\"; do sleep 10; done' docker logs test + - name: Scan image for vulnerabilities (Trivy) + # trivy resolves the image from the local Docker daemon, so only the runner's + # linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from + # evicting the m2-repository caches out of the repo's 10GB actions-cache quota + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: localhost:5000/${{ env.image_repository }}:${{ env.release_version }} + format: sarif + output: trivy-results.sarif + severity: CRITICAL,HIGH + limit-severities-for-sarif: true + ignore-unfixed: true + scanners: vuln + cache: false + - name: Upload Trivy report to GitHub Security + uses: github/codeql-action/upload-sarif@v4 + # upload even if a preceding step failed, but not without a report to upload + if: ${{ always() && hashFiles('trivy-results.sarif') != '' }} + with: + sarif_file: trivy-results.sarif + # distinct from the docker-scan.yml categories, which track the published images + category: trivy-build-default build-docker-alpine: runs-on: 'ubuntu-latest' + permissions: + contents: read + security-events: write # upload the Trivy scan results to code scanning services: registry: image: registry:2 @@ -359,6 +388,7 @@ jobs: run: | export git_version_last="$(curl -i -o - --silent https://api.github.com/repos/OpenIdentityPlatform/OpenIDM/releases/latest | grep -m1 "\"name\"" | cut -d\" -f4)" ; echo "last release: $git_version_last" echo "release_version=$git_version_last" >> $GITHUB_ENV + echo "image_repository=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV - name: Docker meta id: meta uses: docker/metadata-action@v6 @@ -392,3 +422,25 @@ jobs: docker run --rm -it -d --memory="1g" --name=test localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}-alpine timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test | grep -q \"healthy\"; do sleep 10; done' docker logs test + - name: Scan image for vulnerabilities (Trivy) + # trivy resolves the image from the local Docker daemon, so only the runner's + # linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from + # evicting the m2-repository caches out of the repo's 10GB actions-cache quota + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: localhost:5000/${{ env.image_repository }}:${{ env.release_version }}-alpine + format: sarif + output: trivy-results.sarif + severity: CRITICAL,HIGH + limit-severities-for-sarif: true + ignore-unfixed: true + scanners: vuln + cache: false + - name: Upload Trivy report to GitHub Security + uses: github/codeql-action/upload-sarif@v4 + # upload even if a preceding step failed, but not without a report to upload + if: ${{ always() && hashFiles('trivy-results.sarif') != '' }} + with: + sarif_file: trivy-results.sarif + # distinct from the docker-scan.yml categories, which track the published images + category: trivy-build-alpine diff --git a/.github/workflows/docker-scan.yml b/.github/workflows/docker-scan.yml new file mode 100644 index 000000000..8bbc394f2 --- /dev/null +++ b/.github/workflows/docker-scan.yml @@ -0,0 +1,59 @@ +# The contents of this file are subject to the terms of the Common Development and +# Distribution License (the License). You may not use this file except in compliance with the +# License. +# +# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the +# specific language governing permission and limitations under the License. +# +# When distributing Covered Software, include this CDDL Header Notice in each file and include +# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL +# Header, with the fields enclosed by brackets [] replaced by your own identifying +# information: "Portions copyright [year] [name of copyright owner]". +# +# Copyright 2026 3A Systems, LLC. + +# 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. +name: Docker Scan + +on: + schedule: + - cron: '30 5 * * 1' + workflow_dispatch: + +permissions: + contents: read + +jobs: + scan: + # Do not run the scheduled scan in forks; manual runs are always allowed. + if: github.event_name == 'workflow_dispatch' || github.repository == 'OpenIdentityPlatform/OpenIDM' + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + tag: [ 'latest', 'alpine' ] + steps: + - uses: actions/checkout@v6 + - name: Scan openidentityplatform/openidm:${{ matrix.tag }} (Trivy) + # unlike the build.yml gate, unfixed CVEs are reported too: surfacing them in + # already-released images is the point of this workflow + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: openidentityplatform/openidm:${{ matrix.tag }} + format: sarif + output: trivy-${{ matrix.tag }}.sarif + severity: CRITICAL,HIGH + limit-severities-for-sarif: true + scanners: vuln + cache: false + - name: Upload report to GitHub Security + uses: github/codeql-action/upload-sarif@v4 + # upload even if a preceding step failed, but not without a report to upload + if: ${{ always() && hashFiles(format('trivy-{0}.sarif', matrix.tag)) != '' }} + with: + sarif_file: trivy-${{ matrix.tag }}.sarif + category: trivy-image-${{ matrix.tag }} From e7b9a87a36a03c4a1c1e20da3e4ed9ea523dc810 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Sat, 3 Oct 2026 09:15:49 +0300 Subject: [PATCH 2/4] Build the docker images from the commit's zip for the Trivy scan - build-docker / build-docker-alpine: need build-maven, download the ubuntu-latest-25 openidm zip and uncomment the Dockerfile COPY; drop the VERSION build-arg, with which the Dockerfiles replace the copied zip with the latest release, so the docker test and the scan cover this commit's own dependencies - continue-on-error on the Trivy scan and SARIF upload steps, so a scanner download failure does not fail the docker jobs - docker-scan.yml: note that only the linux/amd64 manifest is scanned --- .github/workflows/build.yml | 36 +++++++++++++++++++++++++++---- .github/workflows/docker-scan.yml | 3 ++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e2e3c0e8..ac7adf79a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -297,6 +297,7 @@ jobs: echo "No errors or exceptions detected in openidm logs" build-docker: runs-on: 'ubuntu-latest' + needs: build-maven permissions: contents: read security-events: write # upload the Trivy scan results to code scanning @@ -309,6 +310,18 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 + - name: Download OpenIDM zip artifact + uses: actions/download-artifact@v8 + with: + name: ubuntu-latest-25 + path: openidm-zip/target + - name: Prepare Dockerfile + # build the image from this commit's zip instead of downloading the latest release, + # so the docker test and the Trivy scan cover this commit's own dependencies + shell: bash + run: | + sed -i -E '/^#COPY openidm-zip/s/^#//' ./Dockerfile + grep -q '^COPY openidm-zip/target/openidm-' ./Dockerfile - name: Get latest release version shell: bash run: | @@ -335,8 +348,7 @@ jobs: with: context: . file: ./Dockerfile - build-args: | - VERSION=${{ env.release_version }} + # no VERSION build-arg: with it the Dockerfile replaces the copied zip with the release platforms: linux/amd64, linux/arm64/8, linux/ppc64le, linux/s390x, linux/riscv64 #, linux/arm/v7 push: true tags: ${{ steps.meta.outputs.tags }} @@ -351,6 +363,7 @@ jobs: # trivy resolves the image from the local Docker daemon, so only the runner's # linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from # evicting the m2-repository caches out of the repo's 10GB actions-cache quota + continue-on-error: true # a trivy binary or DB download failure must not fail the docker test job uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: localhost:5000/${{ env.image_repository }}:${{ env.release_version }} @@ -363,6 +376,7 @@ jobs: cache: false - name: Upload Trivy report to GitHub Security uses: github/codeql-action/upload-sarif@v4 + continue-on-error: true # upload even if a preceding step failed, but not without a report to upload if: ${{ always() && hashFiles('trivy-results.sarif') != '' }} with: @@ -371,6 +385,7 @@ jobs: category: trivy-build-default build-docker-alpine: runs-on: 'ubuntu-latest' + needs: build-maven permissions: contents: read security-events: write # upload the Trivy scan results to code scanning @@ -383,6 +398,18 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 + - name: Download OpenIDM zip artifact + uses: actions/download-artifact@v8 + with: + name: ubuntu-latest-25 + path: openidm-zip/target + - name: Prepare Dockerfile + # build the image from this commit's zip instead of downloading the latest release, + # so the docker test and the Trivy scan cover this commit's own dependencies + shell: bash + run: | + sed -i -E '/^#COPY openidm-zip/s/^#//' ./Dockerfile-alpine + grep -q '^COPY openidm-zip/target/openidm-' ./Dockerfile-alpine - name: Get latest release version shell: bash run: | @@ -410,8 +437,7 @@ jobs: with: context: . file: ./Dockerfile-alpine - build-args: | - VERSION=${{ env.release_version }} + # no VERSION build-arg: with it the Dockerfile replaces the copied zip with the release platforms: linux/amd64, linux/arm64/8, linux/s390x, linux/ppc64le, linux/riscv64 push: true tags: ${{ steps.meta.outputs.tags }} @@ -426,6 +452,7 @@ jobs: # trivy resolves the image from the local Docker daemon, so only the runner's # linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from # evicting the m2-repository caches out of the repo's 10GB actions-cache quota + continue-on-error: true # a trivy binary or DB download failure must not fail the docker test job uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: localhost:5000/${{ env.image_repository }}:${{ env.release_version }}-alpine @@ -438,6 +465,7 @@ jobs: cache: false - name: Upload Trivy report to GitHub Security uses: github/codeql-action/upload-sarif@v4 + continue-on-error: true # upload even if a preceding step failed, but not without a report to upload if: ${{ always() && hashFiles('trivy-results.sarif') != '' }} with: diff --git a/.github/workflows/docker-scan.yml b/.github/workflows/docker-scan.yml index 8bbc394f2..3fbe09a99 100644 --- a/.github/workflows/docker-scan.yml +++ b/.github/workflows/docker-scan.yml @@ -40,7 +40,8 @@ jobs: - uses: actions/checkout@v6 - name: Scan openidentityplatform/openidm:${{ matrix.tag }} (Trivy) # unlike the build.yml gate, unfixed CVEs are reported too: surfacing them in - # already-released images is the point of this workflow + # already-released images is the point of this workflow; no platform is set, so + # only the linux/amd64 manifest of each multi-arch tag is scanned uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: openidentityplatform/openidm:${{ matrix.tag }} From 9fc0c7c3bcedc85a387d967de4dfc3477233cb4d Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Mon, 5 Oct 2026 10:57:34 +0300 Subject: [PATCH 3/4] Fail the docker test on a release image and run it despite unrelated matrix failures - build-docker / build-docker-alpine: the Docker test checks that the image's bundles carry the version of this commit's zip, so a restored VERSION build-arg (the Dockerfiles then replace the copied zip with the release) fails the step instead of scanning the release again - if: !cancelled() on both docker jobs: they consume only the ubuntu-latest-25 artifact, so a red leg elsewhere in the build-maven matrix no longer skips the docker test and the trivy-build-* upload; a red ubuntu-latest-25 leg fails the artifact download - docker-scan.yml: the build.yml scan is not a gate, findings never fail the build --- .github/workflows/build.yml | 12 ++++++++++++ .github/workflows/docker-scan.yml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac7adf79a..f1be15494 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -298,6 +298,9 @@ jobs: build-docker: runs-on: 'ubuntu-latest' needs: build-maven + # the zip comes from the ubuntu-latest-25 leg alone: a red leg elsewhere in the matrix must not skip + # the docker test and the scan; a red ubuntu-latest-25 leg fails the artifact download below + if: ${{ !cancelled() }} permissions: contents: read security-events: write # upload the Trivy scan results to code scanning @@ -359,6 +362,9 @@ jobs: docker run --rm -it -d --memory="1g" --name=test localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }} timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test | grep -q \"healthy\"; do sleep 10; done' docker logs test + # the image must carry this commit's bundles, not a downloaded release (as with a VERSION build-arg) + ver=$(basename openidm-zip/target/openidm-*.zip .zip); ver=${ver#openidm-} + docker exec test ls /opt/openidm/bundle | grep -q -- "-${ver}.jar" - name: Scan image for vulnerabilities (Trivy) # trivy resolves the image from the local Docker daemon, so only the runner's # linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from @@ -386,6 +392,9 @@ jobs: build-docker-alpine: runs-on: 'ubuntu-latest' needs: build-maven + # the zip comes from the ubuntu-latest-25 leg alone: a red leg elsewhere in the matrix must not skip + # the docker test and the scan; a red ubuntu-latest-25 leg fails the artifact download below + if: ${{ !cancelled() }} permissions: contents: read security-events: write # upload the Trivy scan results to code scanning @@ -448,6 +457,9 @@ jobs: docker run --rm -it -d --memory="1g" --name=test localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}-alpine timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test | grep -q \"healthy\"; do sleep 10; done' docker logs test + # the image must carry this commit's bundles, not a downloaded release (as with a VERSION build-arg) + ver=$(basename openidm-zip/target/openidm-*.zip .zip); ver=${ver#openidm-} + docker exec test ls /opt/openidm/bundle | grep -q -- "-${ver}.jar" - name: Scan image for vulnerabilities (Trivy) # trivy resolves the image from the local Docker daemon, so only the runner's # linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from diff --git a/.github/workflows/docker-scan.yml b/.github/workflows/docker-scan.yml index 3fbe09a99..72fb0e876 100644 --- a/.github/workflows/docker-scan.yml +++ b/.github/workflows/docker-scan.yml @@ -39,7 +39,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Scan openidentityplatform/openidm:${{ matrix.tag }} (Trivy) - # unlike the build.yml gate, unfixed CVEs are reported too: surfacing them in + # unlike the build.yml scan (ignore-unfixed: true), unfixed CVEs are reported too: surfacing them in # already-released images is the point of this workflow; no platform is set, so # only the linux/amd64 manifest of each multi-arch tag is scanned uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 From 699a7486095d230e70cd13aff414506c895985e0 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Mon, 5 Oct 2026 11:58:33 +0300 Subject: [PATCH 4/4] Capture the bundle listing before grepping it in the docker test "ls | grep -q" under pipefail fails the step with exit 141 whenever grep exits on the first match before ls has finished writing. --- .github/workflows/build.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f1be15494..63e51946f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -364,7 +364,9 @@ jobs: docker logs test # the image must carry this commit's bundles, not a downloaded release (as with a VERSION build-arg) ver=$(basename openidm-zip/target/openidm-*.zip .zip); ver=${ver#openidm-} - docker exec test ls /opt/openidm/bundle | grep -q -- "-${ver}.jar" + # no "ls | grep -q": grep exits on the first match, ls dies of SIGPIPE and pipefail fails the step (141) + bundles=$(docker exec test ls /opt/openidm/bundle) + grep -q -- "-${ver}.jar" <<< "$bundles" - name: Scan image for vulnerabilities (Trivy) # trivy resolves the image from the local Docker daemon, so only the runner's # linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from @@ -459,7 +461,9 @@ jobs: docker logs test # the image must carry this commit's bundles, not a downloaded release (as with a VERSION build-arg) ver=$(basename openidm-zip/target/openidm-*.zip .zip); ver=${ver#openidm-} - docker exec test ls /opt/openidm/bundle | grep -q -- "-${ver}.jar" + # no "ls | grep -q": grep exits on the first match, ls dies of SIGPIPE and pipefail fails the step (141) + bundles=$(docker exec test ls /opt/openidm/bundle) + grep -q -- "-${ver}.jar" <<< "$bundles" - name: Scan image for vulnerabilities (Trivy) # trivy resolves the image from the local Docker daemon, so only the runner's # linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from