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
How Keyless Signing Works
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
- What does "keyless" mean in cosign keyless signing?
- What proves WHO built the image in keyless signing?
- Why sign by digest (
@sha256:...) instead of tag? - What prevents an unsigned image from deploying to production?
Reveal answers
- 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.
- The OIDC identity token from GitHub Actions, which contains: repo name, branch, workflow path. This is embedded in the signing certificate.
- 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.
- 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.