diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f0ced25d..4f7a84108 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -110,6 +110,8 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v7 with: + # 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 }} retention-days: 5 path: | @@ -123,7 +125,14 @@ jobs: OpenICF-xml-connector/target/*.jar !**/*-sources.jar 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() }} runs-on: 'ubuntu-latest' + permissions: + contents: read + security-events: write services: registry: image: registry:2 @@ -133,11 +142,22 @@ jobs: - uses: actions/checkout@v7 with: fetch-depth: 0 + - name: Download artifacts + uses: actions/download-artifact@v7 + with: + name: ubuntu-latest-11 + - name: Prepare Dockerfile + # build the image from the ZIP of this commit, not from the last release + shell: bash + run: | + sed -i -E '/^#COPY OpenICF/s/^#//' ./Dockerfile + grep '^COPY OpenICF-java-framework/openicf-zip/target/' ./Dockerfile + ls -l OpenICF-java-framework/openicf-zip/target/*.zip # 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 is what the Dockerfile's releases/download URL needs, and that - # URL is upstream's, so a fork build asks upstream too. `|| true` keeps a - # failed lookup going to the `last release:` line, and `test -n` stops it. + # 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. - name: Get latest release version shell: bash env: @@ -147,6 +167,7 @@ jobs: echo "last release: $release_version" test -n "$release_version" echo "release_version=$release_version" >> "$GITHUB_ENV" + echo "image_repository=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV" - name: Docker meta id: meta uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 @@ -179,8 +200,49 @@ 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: 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) + # 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. + # Findings do not fail the step; a Trivy or trivy-db registry outage does, and + # must not fail the image smoke test above + continue-on-error: true + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + skip-setup-trivy: true + 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: + 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() }} runs-on: 'ubuntu-latest' + permissions: + contents: read + security-events: write services: registry: image: registry:2 @@ -190,11 +252,22 @@ jobs: - uses: actions/checkout@v7 with: fetch-depth: 0 + - name: Download artifacts + uses: actions/download-artifact@v7 + with: + name: ubuntu-latest-11 + - name: Prepare Dockerfile + # build the image from the ZIP of this commit, not from the last release + shell: bash + run: | + sed -i -E '/^#COPY OpenICF/s/^#//' ./Dockerfile-alpine + grep '^COPY OpenICF-java-framework/openicf-zip/target/' ./Dockerfile-alpine + ls -l OpenICF-java-framework/openicf-zip/target/*.zip # 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 is what the Dockerfile's releases/download URL needs, and that - # URL is upstream's, so a fork build asks upstream too. `|| true` keeps a - # failed lookup going to the `last release:` line, and `test -n` stops it. + # 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. - name: Get latest release version shell: bash env: @@ -204,6 +277,7 @@ jobs: echo "last release: $release_version" test -n "$release_version" echo "release_version=$release_version" >> "$GITHUB_ENV" + echo "image_repository=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV" - name: Docker meta id: meta uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 @@ -237,3 +311,37 @@ 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: 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) + # 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. + # Findings do not fail the step; a Trivy or trivy-db registry outage does, and + # must not fail the image smoke test above + continue-on-error: true + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + skip-setup-trivy: true + 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..38f91b511 --- /dev/null +++ b/.github/workflows/docker-scan.yml @@ -0,0 +1,72 @@ +# 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. +# 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". +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/OpenICF' + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + tag: [ 'latest', 'alpine' ] + steps: + - uses: actions/checkout@v7 + - 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 + uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 # v0.2.6 + with: + version: v0.70.0 + cache: true + - name: Scan openidentityplatform/openicf:${{ matrix.tag }} (Trivy) + # unlike the build.yml scan, unfixed CVEs are reported too: surfacing them in + # 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 (alpine on linux/386 ships openjdk11-jre instead of openjdk25-jre) + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + skip-setup-trivy: true + image-ref: openidentityplatform/openicf:${{ 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 }} diff --git a/Dockerfile b/Dockerfile index e97f23376..5e68dd5ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,11 +22,13 @@ ARG VERSION WORKDIR /opt +# build.yml uncomments the COPY to build the image from the ZIP of the commit under test; +# without it the openicf-$VERSION.zip of the release is downloaded #COPY OpenICF-java-framework/openicf-zip/target/*.zip ./ RUN apt-get update \ && apt-get install -y --no-install-recommends curl unzip \ - && bash -c 'if [ ! -z "$VERSION" ] ; then rm -rf ./*.zip ; curl -L https://github.com/OpenIdentityPlatform/OpenICF/releases/download/$VERSION/openicf-$VERSION.zip --output openicf-$VERSION.zip ; fi' \ + && bash -c 'if [ ! -z "$VERSION" ] && ! ls ./openicf-*.zip >/dev/null 2>&1 ; then curl -L https://github.com/OpenIdentityPlatform/OpenICF/releases/download/$VERSION/openicf-$VERSION.zip --output openicf-$VERSION.zip ; fi' \ && unzip openicf-*.zip && rm -rf *.zip \ && apt-get remove -y --purge unzip \ && rm -rf /var/lib/apt/lists/* \ diff --git a/Dockerfile-alpine b/Dockerfile-alpine index 8b2573965..c54ba1da3 100644 --- a/Dockerfile-alpine +++ b/Dockerfile-alpine @@ -24,13 +24,15 @@ ARG TARGETARCH WORKDIR /opt +# build.yml uncomments the COPY to build the image from the ZIP of the commit under test; +# without it the openicf-$VERSION.zip of the release is downloaded #COPY OpenICF-java-framework/openicf-zip/target/*.zip ./ RUN apk add --update --no-cache --virtual builddeps curl unzip \ && apk upgrade --update --no-cache \ && if [ "$TARGETARCH" = "386" ]; then JDK=openjdk11-jre; else JDK=openjdk25-jre; fi \ && apk add bash "$JDK" \ - && bash -c 'if [ ! -z "$VERSION" ] ; then rm -rf ./*.zip ; curl -L https://github.com/OpenIdentityPlatform/OpenICF/releases/download/$VERSION/openicf-$VERSION.zip --output openicf-$VERSION.zip ; fi' \ + && bash -c 'if [ ! -z "$VERSION" ] && ! ls ./openicf-*.zip >/dev/null 2>&1 ; then curl -L https://github.com/OpenIdentityPlatform/OpenICF/releases/download/$VERSION/openicf-$VERSION.zip --output openicf-$VERSION.zip ; fi' \ && unzip openicf-*.zip && rm -rf *.zip \ && apk del unzip \ && addgroup -S $USER \