diff --git a/.github/actions/image-cve-gate/action.yml b/.github/actions/image-cve-gate/action.yml new file mode 100644 index 000000000..4689ead27 --- /dev/null +++ b/.github/actions/image-cve-gate/action.yml @@ -0,0 +1,62 @@ +name: Image CVE gate +description: >- + Scan the push-by-digest blobs of a freshly built image and fail on a fixable + critical. Runs before the merge job tags them, so a rejected build stays + unreferenced: no build tag, no :latest, no descriptor bump, no chart. + +inputs: + image: + description: Image repository without a tag, e.g. ghcr.io/constructorfabric/insight-toolbox. + required: true + registry-username: + description: Registry user for pulling the digests under scan. + required: true + registry-password: + description: Registry token for pulling the digests under scan. + required: true + digests-dir: + description: Directory holding one empty file per pushed digest, named by the digest. + default: /tmp/digests + severity: + description: Severities that fail the build. + default: CRITICAL + trivy-image: + description: Trivy image, pinned by digest. + default: aquasec/trivy:0.72.0@sha256:cffe3f5161a47a6823fbd23d985795b3ed72a4c806da4c4df16266c02accdd6f + +runs: + using: composite + steps: + - shell: bash + env: + IMAGE: ${{ inputs.image }} + DIGESTS_DIR: ${{ inputs.digests-dir }} + SEVERITY: ${{ inputs.severity }} + TRIVY_IMAGE: ${{ inputs.trivy-image }} + TRIVY_USERNAME: ${{ inputs.registry-username }} + TRIVY_PASSWORD: ${{ inputs.registry-password }} + run: | + set -euo pipefail + shopt -s nullglob + digests=("$DIGESTS_DIR"/*) + if [ ${#digests[@]} -eq 0 ]; then + echo "::error::no digests in $DIGESTS_DIR — nothing was scanned" + exit 1 + fi + for f in "${digests[@]}"; do + ref="${IMAGE}@sha256:$(basename "$f")" + echo "::group::$ref" + docker run --rm \ + -e TRIVY_USERNAME -e TRIVY_PASSWORD \ + -v "$GITHUB_WORKSPACE:/work" -w /work \ + -v /tmp/trivy-cache:/root/.cache \ + "$TRIVY_IMAGE" image \ + --scanners vuln \ + --severity "$SEVERITY" \ + --pkg-types os,library \ + --ignore-unfixed \ + --exit-code 1 \ + --no-progress \ + "$ref" + echo "::endgroup::" + done diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index ed080549b..48dfabc8e 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -382,6 +382,15 @@ jobs: tags: | type=raw,value=${{ needs.changes.outputs.build_tag }} type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + - name: Fail on a fixable critical before any tag points here + uses: ./.github/actions/image-cve-gate + with: + image: ${{ env.IMAGE_PREFIX }}/insight-analytics + registry-username: ${{ github.actor }} + registry-password: ${{ secrets.GITHUB_TOKEN }} - name: Create multi-arch manifest and push working-directory: /tmp/digests run: | @@ -487,6 +496,15 @@ jobs: tags: | type=raw,value=${{ needs.changes.outputs.build_tag }} type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + - name: Fail on a fixable critical before any tag points here + uses: ./.github/actions/image-cve-gate + with: + image: ${{ env.IMAGE_PREFIX }}/insight-authenticator + registry-username: ${{ github.actor }} + registry-password: ${{ secrets.GITHUB_TOKEN }} - name: Create multi-arch manifest and push working-directory: /tmp/digests run: | @@ -592,6 +610,15 @@ jobs: tags: | type=raw,value=${{ needs.changes.outputs.build_tag }} type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + - name: Fail on a fixable critical before any tag points here + uses: ./.github/actions/image-cve-gate + with: + image: ${{ env.IMAGE_PREFIX }}/insight-gateway + registry-username: ${{ github.actor }} + registry-password: ${{ secrets.GITHUB_TOKEN }} - name: Create multi-arch manifest and push working-directory: /tmp/digests run: | @@ -711,6 +738,15 @@ jobs: tags: | type=raw,value=${{ needs.changes.outputs.build_tag }} type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + - name: Fail on a fixable critical before any tag points here + uses: ./.github/actions/image-cve-gate + with: + image: ${{ env.IMAGE_PREFIX }}/insight-identity-resolution + registry-username: ${{ github.actor }} + registry-password: ${{ secrets.GITHUB_TOKEN }} - name: Create multi-arch manifest and push working-directory: /tmp/digests run: | @@ -839,6 +875,15 @@ jobs: tags: | type=raw,value=${{ needs.changes.outputs.build_tag }} type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + - name: Fail on a fixable critical before any tag points here + uses: ./.github/actions/image-cve-gate + with: + image: ${{ env.IMAGE_PREFIX }}/insight-front + registry-username: ${{ github.actor }} + registry-password: ${{ secrets.GITHUB_TOKEN }} - name: Create multi-arch manifest and push working-directory: /tmp/digests run: | @@ -1009,6 +1054,15 @@ jobs: tags: | type=raw,value=${{ needs.changes.outputs.build_tag }} type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + - name: Fail on a fixable critical before any tag points here + uses: ./.github/actions/image-cve-gate + with: + image: ${{ env.IMAGE_PREFIX }}/insight-toolbox + registry-username: ${{ github.actor }} + registry-password: ${{ secrets.GITHUB_TOKEN }} - name: Create multi-arch manifest and push working-directory: /tmp/digests run: | @@ -1270,6 +1324,15 @@ jobs: tags: | type=raw,value=${{ needs.changes.outputs.build_tag }} type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + - name: Fail on a fixable critical before any tag points here + uses: ./.github/actions/image-cve-gate + with: + image: ${{ env.IMAGE_PREFIX }}/${{ matrix.entry.name }} + registry-username: ${{ github.actor }} + registry-password: ${{ secrets.GITHUB_TOKEN }} - name: Create multi-arch manifest and push working-directory: /tmp/digests run: | diff --git a/.github/workflows/trivy-images.yml b/.github/workflows/trivy-images.yml index 58e00b239..a860e19f1 100644 --- a/.github/workflows/trivy-images.yml +++ b/.github/workflows/trivy-images.yml @@ -142,3 +142,12 @@ jobs: sarif_file: trivy-image.sarif # Per-image category: one analysis per image, so alerts never overwrite each other. category: trivy-image:${{ matrix.image }} + + - name: Fail on a fixable critical + # Last, and reusing the scan's JSON, so the summary and the SARIF upload still run. + env: + TRIVY_IMAGE: aquasec/trivy:0.72.0@sha256:cffe3f5161a47a6823fbd23d985795b3ed72a4c806da4c4df16266c02accdd6f + run: | + set -euo pipefail + docker run --rm -v "${{ github.workspace }}:/work" "$TRIVY_IMAGE" convert \ + --severity CRITICAL --exit-code 1 --format table /work/trivy-image.json