Zion Boggan zionboggan.com ↗

readme + cosign sign/verify screenshot

388f52c   Zion Boggan committed on May 12, 2026 (1 month ago)
README.md +87 -0
@@ -0,0 +1,87 @@
+# CI/CD Supply Chain Security
+
+This picks up where the [secure CI/CD pipeline](../secure-cicd-pipeline) leaves off.
+That repo proves the *source* is clean. This one proves the *artifact* is - that the
+container image a cluster is about to run was built by my pipeline, hasn't been
+tampered with since, and ships with a verifiable bill of materials.
+
+The mechanism is Sigstore. The pipeline signs every image keylessly with Cosign
+using the workflow's OIDC identity, attaches an SBOM as a signed attestation, and a
+Kyverno policy refuses to admit anything to the cluster that can't produce both.
+
+```mermaid
+flowchart LR
+ build[build image] --> sbom[syft SBOM]
+ sbom --> scan[grype scan<br/>fail on high]
+ scan --> sign[cosign sign<br/>keyless OIDC]
+ sign --> attest[cosign attest<br/>SBOM]
+ attest --> verify[cosign verify<br/>in-pipeline]
+ verify --> admit[Kyverno admission<br/>at deploy time]
+```
+
+## Keyless signing
+
+There is no private key to manage or leak. Cosign requests a short-lived
+certificate from Fulcio bound to the GitHub Actions OIDC identity
+(`https://github.com/<owner>/<repo>`), signs, and logs the signature to the Rekor
+transparency log. Verification checks the certificate identity and issuer rather
+than a key you have to rotate.
+
+That is why the workflow asks for `id-token: write` - without it there's no OIDC
+token to exchange for the signing certificate.
+
+Cosign signing and verification, and what happens the moment a signed artifact is
+modified - the signature is rejected:
+
+![Cosign sign and verify](docs/screenshots/01-cosign-sign-verify.png)
+
+> The screenshot uses a local key pair to show the sign/verify mechanics offline;
+> the pipeline itself signs keylessly with the GitHub OIDC identity (no stored key).
+
+## What the pipeline does
+
+| Stage | Tool | Output |
+|-------|------|--------|
+| build | buildx + build-push-action | image pushed to GHCR by digest, with provenance + SBOM attestations |
+| scan | syft + grype | SPDX SBOM artifact; build fails on a high/critical CVE |
+| sign | cosign | keyless signature in Rekor |
+| attest | cosign | signed SPDX attestation attached to the image |
+| verify | cosign | signature + attestation checked before the run is called good |
+
+Everything operates on the image **digest**, never a mutable tag, so the thing that
+gets signed and verified is exactly the thing that was built.
+
+## Enforcing it at deploy time
+
+`policy/kyverno-verify-images.yaml` is a `ClusterPolicy` in `Enforce` mode that:
+
+1. requires a Cosign signature from this repo's GitHub identity, and
+2. requires an SPDX SBOM attestation from the same identity,
+
+for any image under `ghcr.io/zionboggan/*`. It also rewrites tags to digests on
+admission (`mutateDigest: true`) so a pod can't be pinned to a tag that later moves.
+
+Apply it:
+
+```bash
+kubectl apply -f policy/kyverno-verify-images.yaml
+```
+
+A signed image from the pipeline is admitted; an arbitrary `nginx:latest` is
+rejected. `policy/test/pods.yaml` has one of each for `kyverno apply` to test
+against, which is what the `admission-policy-check` workflow runs on PRs that touch
+the policy.
+
+## Verify an image by hand
+
+```bash
+OWNER=zionboggan ./policy/verify.sh ghcr.io/zionboggan/cicd-supply-chain-security@sha256:<digest>
+```
+
+## Notes
+
+Keyless is the right default for CI - nothing to store, everything in the
+transparency log. If you needed an air-gapped or offline verifier you'd switch to a
+key pair (or a KMS key) and Cosign supports that, but for a GitHub-hosted pipeline
+the OIDC identity is the cleaner trust anchor. More in
+[docs/supply-chain.md](docs/supply-chain.md).
docs/screenshots/01-cosign-sign-verify.png +0 -0
Binary file not shown