How do you know the image running in production was built by YOUR pipeline and not tampered with? Answer: cryptographic signing. This lesson teaches keyless signing with Sigstore/cosign — the industry standard.

The Supply Chain Problem

Without signing: anyone with registry push access can replace your image Attacker pushes malicious image with YOUR tag → K8s pulls it → you're compromised Real attacks: SolarWinds, Codecov, ua-parser-js — all supply chain compromises

How Keyless Signing Works

Signing (in CI pipeline) GitHub Actions OIDC identity token Sigstore/Fulcio Issues short-lived cert cosign sign Signs image digest Rekor (log) Records in transparency log Verification (at deploy time) cosign verify "Was this signed by github.com/my-org?" ✅ Verified Image came from our pipeline
Keyless: no keys to manage. GitHub's OIDC identity proves who built the image. Sigstore records the proof in a public transparency log.
Keyless signing means no private keys to store, rotate, or leak. The pipeline's OIDC identity (same mechanism as Azure auth) IS the proof of provenance. The signature says: "This image was built by workflow X in repo Y on branch Z."

🏋️ Sign Images in Your Pipeline

  sign:
    name: ✍️ Sign Image
    needs: build
    runs-on: ubuntu-latest
    permissions:
      id-token: write   # Required for keyless signing
    steps:
      - uses: sigstore/cosign-installer@v3

      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - run: az acr login --name ${{ vars.ACR_NAME }}

      - name: Sign image (keyless)
        run: |
          cosign sign --yes \
            ${{ vars.ACR_LOGIN_SERVER }}/cicd-mastery-api@${{ needs.build.outputs.digest }}
          echo "✅ Image signed!"

Verify before deploy:

      - name: Verify signature
        run: |
          cosign verify \
            --certificate-identity-regexp "https://github.com/YOUR_ORG/.*" \
            --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
            ${{ vars.ACR_LOGIN_SERVER }}/cicd-mastery-api@${{ needs.build.outputs.digest }}

Enforce in Kubernetes: Admission Control

Signing is useless if unsigned images can still deploy. Use a policy engine to BLOCK unsigned images:

# Kyverno policy (alternative: Sigstore Policy Controller)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-signature
      match:
        resources:
          kinds: [Pod]
          namespaces: [production]
      verifyImages:
        - imageReferences: ["acrcicdmastery*.azurecr.io/*"]
          attestors:
            - entries:
                - keyless:
                    issuer: "https://token.actions.githubusercontent.com"
                    subject: "https://github.com/YOUR_ORG/*"
Defense in depth: Sign in CI (proves origin) + verify at admission (blocks unsigned). Even if someone gets registry push access, they can't deploy unsigned images to production. Only YOUR pipeline can.

🧠 Recall Check

  1. What does "keyless" mean in cosign keyless signing?
  2. What proves WHO built the image in keyless signing?
  3. Why sign by digest (@sha256:...) instead of tag?
  4. What prevents an unsigned image from deploying to production?
Reveal answers
  1. No long-lived private key to manage. Instead, the signer's OIDC identity (from GitHub Actions) is used to get a short-lived certificate from Fulcio. The cert is the proof.
  2. The OIDC identity token from GitHub Actions, which contains: repo name, branch, workflow path. This is embedded in the signing certificate.
  3. Tags are mutable (someone can push a different image with the same tag). Digests are content-addressed hashes — they uniquely identify exactly ONE image. Sign the digest = sign the exact bytes.
  4. An admission controller (Kyverno, Sigstore Policy Controller) that intercepts Pod creation and verifies the image signature before allowing it to run.

Next lesson: Policy-as-Code — enforcing organizational rules (no root containers, resource limits required) via OPA Gatekeeper.