Solutions — Security Scanning & Image Signing
Lesson 05 · Cloud Infrastructure & DevOps · reference solutions
Task 1 solution — .trivyignore
Create a .trivyignore file in the repository root. One CVE ID per line. Comments (lines starting with #) are supported:
# CVE in libc6, no fix available upstream as of 2024-12
CVE-2024-12345
Reference the file in the Trivy workflow step via trivyignores:
- name: Trivy scan
uses: aquasecurity/trivy-action@0.24.0
with:
image-ref: ${{ matrix.service }}:ci-${{ github.sha }}
severity: HIGH,CRITICAL
exit-code: 1
ignore-unfixed: true
trivyignores: .trivyignore
Common mistake
Placing .trivyignore next to the Dockerfile but invoking Trivy from the repository root. Trivy looks for .trivyignore relative to its working directory, not relative to the image context. If your workflow runs from the repo root (default), put .trivyignore at the repo root. If you need a different path, use the trivyignores input to specify an absolute or relative path explicitly.
Task 2 solution — sign on tag pushes
Two changes to security.yml: add tags: ['v*'] to the trigger, and update the image tags: in the build step to include github.ref_name:
on:
push:
branches: [main]
tags: ['v*']
Inside the build step, update the tags: input:
- name: Build & push ${{ matrix.service }}
id: build
uses: docker/build-push-action@v5
with:
context: ./cloud-devops/sample-app/${{ matrix.service }}
push: true
tags: |
ghcr.io/${{ github.repository }}/${{ matrix.service }}:${{ github.ref_name }}
ghcr.io/${{ github.repository }}/${{ matrix.service }}:${{ github.sha }}
cache-from: type=gha,scope=${{ matrix.service }}
cache-to: type=gha,mode=max,scope=${{ matrix.service }}
Common mistake
Using github.sha instead of github.ref_name for the human-readable tag. On a tag push, github.ref_name evaluates to the tag name (e.g., v0.1.0). On a branch push, it evaluates to the branch name (e.g., main). Using only github.sha produces a valid and signed image, but users have no way to pull it by version number — they would need to look up the SHA first.
Task 3 solution — verification job
Add a verify job to security.yml that runs after sign:
verify:
runs-on: ubuntu-latest
needs: [sign]
strategy:
matrix:
service: [api, worker]
steps:
- uses: sigstore/cosign-installer@v3
- name: Verify signature
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign verify \
--certificate-identity-regexp "https://github.com/${{ github.repository }}/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/${{ github.repository }}/${{ matrix.service }}:${{ github.sha }}
Common mistake
Running cosign verify without --certificate-identity-regexp. This verifies that the signature is cryptographically valid (the cert chain leads to Fulcio) but does not check who signed it. An attacker with a valid GitHub identity on any repository could produce a signature that passes this check. The --certificate-identity-regexp flag pins verification to your specific repository and workflow path, preventing substitution attacks.
Closing tip: Lesson 06 turns these signed images into a deploy step — pushing to a target environment with secret-scoped credentials.