Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ jobs:
permissions:
contents: read
packages: write
id-token: write # keyless cosign signing via OIDC
steps:
- name: Download digests
uses: actions/download-artifact@v8
Expand Down Expand Up @@ -202,3 +203,54 @@ jobs:
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/${{ github.repository }}@sha256:%s ' *)
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Sign the published image (keyless)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cosign sign runs on PR events too: finalize has no event filter (if: always() && needs.build.result == 'success'), so every PR signs its pr-N image. Keyless cosign records each signature (with commit/actor identity) permanently in the public Rekor transparency log and leaves orphan .sig tags in GHCR for throwaway images. Gate the sign step (or job) with if: github.event_name != 'pull_request'; the PR-run smoke test of the cosign path can be done once rather than on every future PR.

run: |
tag=$(jq -cr '.tags[0]' <<< "$DOCKER_METADATA_OUTPUT_JSON")
digest=$(docker buildx imagetools inspect "$tag" --format '{{.Manifest.Digest}}')
cosign sign --yes "ghcr.io/${{ github.repository }}@${digest}"

# Build the runtime image and scan it for OS/dependency vulnerabilities.
# Non-blocking for now (exit-code 0): findings surface in the Security tab.
# Flip exit-code to 1 to gate once the baseline is clean.
image-scan:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: image-scan has no needs: build, so it races the build job that populates cache-to: type=gha. The PR description says it reuses the gha cache, but on a cold branch run the scan does a full cold build. Add needs: build (or accept the cold build and drop the reuse claim).

name: Image Scan
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build image (amd64) for scanning
uses: docker/build-push-action@v7
with:
context: .
file: docker/Dockerfile
platforms: linux/amd64
load: true
tags: postguard-business:scan
cache-from: type=gha
# Run Trivy from its official image rather than the GitHub Action, which
# had a supply-chain compromise advisory (GHSA-69fq-xp46-6x23).
- name: Trivy vulnerability scan
run: |
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD:/work" \
aquasec/trivy:latest image \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aquasec/trivy:latest is unpinned. The step comment (lines 237-238) justifies avoiding the Trivy GitHub Action due to a supply-chain compromise (GHSA-69fq-xp46-6x23), but pulling the mutable :latest tag reintroduces the same supply-chain exposure. Pin to a digest or an explicit version.

--severity HIGH,CRITICAL \
--ignore-unfixed \
--format sarif \
--output /work/trivy-results.sarif \
--exit-code 0 \
postguard-business:scan
- name: Upload Trivy results

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Upload Trivy results uses if: always(), but if the scan build or the Trivy step fails, trivy-results.sarif is never produced and the upload step errors, turning the image-scan check red even though the job is meant to be non-blocking. Guard with e.g. if: hashFiles('trivy-results.sarif') != ''.

if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
category: trivy
Loading