Back to path
AdvancedLedgerline · Project 8 of 12 ~6h· 5 milestones

Prove what you ship: sign and verify the image

Continues from the last build: The runtime image is minimal, non-root and scanned in the pipeline.

Last rung you got the image down to a distroless, non-root, Trivy-clean container.

Cosign keyless signing (Sigstore OIDC)SBOM attestations (CycloneDX predicate)Signature and identity verification gatesGitHub Actions least-privilege permissionsPinning third-party actions to commit SHAsOWASP CI/CD Top 10 threat mappingSoftware supply chain security fundamentals

What you'll build

The pipeline signs every passing image with Cosign using keyless OIDC, attaches the rung 6 SBOM as a signed CycloneDX attestation, and gates deploy behind cosign verify and cosign verify-attestation checks that reject anything unsigned or signed by the wrong identity, proven by a failed deploy attempt against a tampered image. The pipeline itself is hardened: every third-party action is pinned to a commit SHA, GITHUB_TOKEN carries only the permissions each job needs, and the workflow has a written map against the OWASP CI/CD Top 10, closing CICD-SEC-9 (Improper Artifact Integrity Validation) for good.

See how we teach, before you sign up

You don't just get code dumped on you. Every starter file and every solution is explained line-by-line, in plain English. Here's one real file from this project:

.github/workflows/supply-chain.ymlyaml
name: supply-chain

on:
  workflow_run:
    workflows: ["ci"]
    types: [completed]

permissions:
  contents: read

jobs:
  sign:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write
    env:
      IMAGE_REF: ghcr.io/${{ github.repository }}
    steps:
      - name: Install cosign
        uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.7.0

      # TODO: resolve the built image digest from the ci workflow's output
      # TODO: cosign sign --yes "${IMAGE_REF}@<digest>"

  attest-sbom:
    needs: sign
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write
    steps:
      # TODO: syft "${IMAGE_REF}@<digest>" -o cyclonedx-json > sbom.cdx.json
      # TODO: cosign attest --yes --type cyclonedx --predicate sbom.cdx.json "${IMAGE_REF}@<digest>"
      - run: echo "attest-sbom placeholder"

  verify-before-deploy:
    needs: attest-sbom
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      # TODO: cosign verify --certificate-identity <identity> --certificate-oidc-issuer <issuer> "${IMAGE_REF}@<digest>"
      # TODO: cosign verify-attestation --type cyclonedx ... "${IMAGE_REF}@<digest>"
      - run: echo "verify-before-deploy placeholder"

Reading this file

  • workflows: ["ci"]Chains this workflow off the existing rung 7 ci.yml completing, so signing only ever happens after that pipeline's own gates pass.
  • if: ${{ github.event.workflow_run.conclusion == 'success' }}Hard stop: if ci.yml failed or was cancelled, nothing in this workflow signs or ships anything.
  • id-token: writePresent only on the two jobs that actually call cosign sign or cosign attest, not on verify-before-deploy which only reads.
  • # TODO: cosign sign --yes "${IMAGE_REF}@<digest>"You fill this in during milestone 2, using the real digest output rather than the literal placeholder.

Starting skeleton wired to run after the existing ci workflow completes. Fill in the TODOs following milestones 2 through 4.

That's 1 of 8 explained code blocks in this single project.

The build, milestone by milestone

  1. 1

    Name the threat: tampering and a poisoned registry

    5 guided steps

    Every control you add this rung exists to answer one question precisely: how do you know the container running right now is the one your pipeline built and scanned? If you cannot state the threat, you cannot tell whether Cosign, attestations, or the verify gate actually close it, or just look like they do.

  2. 2

    Sign every passing image with Cosign keyless OIDC

    5 guided steps

    A signature over the image digest is the first half of integrity. If it is missing, anyone with registry write access can silently swap the image and nothing downstream notices. Keyless signing removes the classic supply chain weak point, a signing key sitting in a secrets store waiting to be leaked.

  3. 3

    Bind the SBOM to the image as a signed attestation

    5 guided steps

    An SBOM you cannot verifiably connect to a specific running image is just paperwork. Anyone, an auditor, an incident responder, or your future self at 2am, needs to be able to ask the registry 'what is really in the image with this digest' and get an answer backed by a signature, not by trust that the right file got uploaded to the right place.

  4. 4

    Refuse to deploy anything that is not verifiably yours

    6 guided steps

    A signature that nothing ever checks is decoration. The property you actually want, only signed, correctly-identified images run in production, only exists once you have a gate that fails closed. Proving it by trying to sneak an unsigned image past it is the only way to know the gate is real and not just present in the YAML.

  5. 5

    Pin and protect the pipeline that signs everything

    5 guided steps

    A floating tag like actions/checkout@v4 can be repointed by the action's maintainer, or by an attacker who compromises that maintainer's account, to a different commit at any time, and your workflow would silently run different code on the next push. A default GITHUB_TOKEN often carries write access your job never needs. Both are exactly the kind of pipeline-level compromise that would let someone forge a 'legitimately' signed, tampered image, defeating everything else this rung built.

What's inside when you start

3 starter files, ready to clone
5 guided milestones
5 full reference solutions
8 code blocks explained line-by-line
5 "is it working?" checks
6 interview questions it prepares you for

You'll walk away with

A sign job in the pipeline that keylessly signs every built image by digest with Cosign after the scan gate passes
The rung 6 SBOM attached to each image as a signed CycloneDX attestation, queryable with cosign verify-attestation
A verify-before-deploy gate that checks both signature and certificate identity, with a documented proof run showing it rejects an unsigned test image
Every third-party action in the workflow pinned to a full commit SHA, with least-privilege permissions blocks on every job
An updated security/threat-model.md with Tampering and Spoofing rows for the supply chain, mapped to CICD-SEC-9 and CICD-SEC-6
A short written map of this workflow's jobs against the OWASP CI/CD Top 10

This is portfolio-grade. Build it free.

Sign up to unlock every milestone step-by-step, the code skeletons, full reference solutions, and checkable tasks, with your progress saved as you build.

Start building