diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml index 1197a54..3329cf3 100644 --- a/.github/workflows/build-container.yml +++ b/.github/workflows/build-container.yml @@ -34,7 +34,7 @@ jobs: tags: devshell-dsc:${{ github.sha }} # Only inject the secret when it exists – empty values are ignored secrets: | # inject read token - gh_read_token=${{ env.GH_READ_TOKEN }} + ${{ env.GH_READ_TOKEN != '' && format('gh_read_token={0}', env.GH_READ_TOKEN) }} sbom: mode=max # max-mode SBOM for Scout provenance: mode=max # max-mode provenance for Scout push: false diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 891c673..dee4f97 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -51,7 +51,7 @@ jobs: builder: ${{ steps.buildx.outputs.name }} context: . secrets: | # inject read token - gh_read_token=${{ env.GH_READ_TOKEN }} + ${{ env.GH_READ_TOKEN != '' && format('gh_read_token={0}', env.GH_READ_TOKEN) }} # If we use build cache we might not be able bump to latest version installed by Dockerfile when pushing a new tag # cache-from: "type=registry,ref=ghcr.io/viscalyx/devshell-dsc:buildcache" # cache-to: "type=registry,ref=ghcr.io/viscalyx/devshell-dsc:buildcache,mode=max" diff --git a/.github/workflows/scout-pr.yml b/.github/workflows/scout-pr.yml new file mode 100644 index 0000000..0fb163b --- /dev/null +++ b/.github/workflows/scout-pr.yml @@ -0,0 +1,93 @@ +name: Container security (Docker Scout) + +on: + pull_request: + branches: [ main ] + paths: + - '**/Dockerfile' # rebuild only when something container‑related changes + - '.github/workflows/**' + +jobs: + scout: + runs-on: ubuntu-latest + permissions: # required for PR comments + Code‑scanning upload + contents: read + pull-requests: write + security-events: write + packages: write # allow pushing to GHCR + + env: + IMAGE_NAME: ${{ github.repository }} + TAG: pr-${{ github.event.number }} + + steps: + - uses: actions/checkout@v4 + + - name: Set up Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + with: + driver: docker-container + driver-opts: | + network=host + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: docker.io + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ secrets.GHCR_USERNAME }} # GitHub username + password: ${{ secrets.GHCR_TOKEN }} # PAT with packages:write scope + + - name: Build & push PR and baseline images to GHCR + uses: docker/build-push-action@v6 + env: + GH_READ_TOKEN: ${{ secrets.GH_READ_TOKEN }} # provide token for provenance + with: + builder: ${{ steps.buildx.outputs.name }} # use configured Buildx builder + secrets: | # inject read token + ${{ env.GH_READ_TOKEN != '' && format('gh_read_token={0}', env.GH_READ_TOKEN) }} + context: . + platforms: | + linux/amd64 + linux/arm64 + tags: | + ghcr.io/${{ github.repository }}:${{ env.TAG }} + sbom: mode=max # max-mode SBOM for Scout + provenance: mode=max # max-mode provenance for Scout + push: true # push both tags to GHCR + + - name: Scan image with Docker Scout + id: scout + uses: docker/scout-action@v1 + with: + # dockerhub-user: ${{ secrets.DOCKERHUB_USERNAME }} # Docker Hub user for Scout auth + # dockerhub-password: ${{ secrets.DOCKERHUB_TOKEN }} # Docker Hub token for Scout auth + # registry-user: ${{ secrets.GHCR_USERNAME }} # GHCR user for fetching PR image + # registry-password: ${{ secrets.GHCR_TOKEN }} # GHCR token for fetching PR image + command: cves,recommendations,compare # list CVEs and compare to prod baseline + image: ghcr.io/${{ github.repository }}:${{ env.TAG }} + organization: ${{ github.repository_owner }} # namespace for baseline pull + # --- comparison baseline --------------------------------------- + to: docker.io/${{ github.repository }}:latest # pull baseline from Docker Hub + # --- what should make the step fail ----------------------------- + only-severities: critical,high # we tolerate ≤ medium + exit-code: true # fail if *any* matching CVE left + exit-on: vulnerability # fail if comparison adds new vulnerabilities + only-fixed: true # ignore unfixed issues + # --- UX helpers -------------------------------------------------- + sarif-file: scout-results.sarif # upload to Security tab + write-comment: true # nice PR comment + summary: true + github-token: ${{ secrets.GITHUB_TOKEN }} # token for PR comment + + - name: Upload SARIF to GitHub code-scanning + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: scout-results.sarif diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21b1b42..02b3ace 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,6 +100,63 @@ docker compose build --no-cache dev Max-mode attestations are automatically enabled in CI/CD workflows for production builds and security scanning. However, developers can test these locally to verify Docker Scout integration or troubleshoot attestation-related issues. +### Prerequisites for Local Attestation Testing + +- Docker BuildKit enabled (automatically enabled in modern Docker versions) +- Docker Scout CLI (optional, for local scanning) + +### Building with Max-Mode Attestations + +To build locally with the same attestation settings used in production: + +```sh +# Basic build with max-mode attestations +DOCKER_BUILDKIT=1 docker build \ + --sbom=mode=max \ + --provenance=mode=max \ + -t devshell:dsc . +``` + +```sh +# No-cache build with max-mode attestations +DOCKER_BUILDKIT=1 docker build \ + --no-cache \ + --sbom=mode=max \ + --provenance=mode=max \ + -t devshell:dsc . +``` + +### Testing with Docker Scout Locally + +If you have Docker Scout CLI installed, you can test the security scanning locally: + +```sh +# Build with attestations +DOCKER_BUILDKIT=1 docker build \ + --sbom=mode=max \ + --provenance=mode=max \ + -t devshell:dsc . + +# Scan for vulnerabilities +docker scout cves devshell:dsc + +# Compare with latest published image (requires Docker Hub access) +docker scout compare devshell:dsc --to viscalyx/devshell-dsc:latest +``` + +> [!NOTE] +> Max-mode attestations add additional build time and storage overhead. They are primarily useful for: +> +> - Testing the full CI/CD security pipeline locally +> - Debugging attestation-related issues +> - Verifying Docker Scout integration before pushing changes +> +> For regular development and testing, the standard build commands without attestations are sufficient. + +## Publishing + +Max-mode attestations are automatically enabled in CI/CD workflows for production builds and security scanning. However, developers can test these locally to verify Docker Scout integration or troubleshoot attestation-related issues. + Max-mode attestations add additional build time and storage overhead. They are primarily useful for: - Testing the full CI/CD security pipeline locally diff --git a/cspell.json b/cspell.json index d35ddc8..2965f41 100644 --- a/cspell.json +++ b/cspell.json @@ -28,6 +28,9 @@ "sarif", "shellcheck", "viscalyx", - "viscalyxbot" + "viscalyxbot", + "sarif", + "cves", + "buildkit" ] }