Chapter 14 — Hands-on: a supply-chain-hardened pipeline
Brief
Build one end-to-end pipeline for a SecureShop service that is hardened at every stage from Part 3: IaC scanned and policy-gated, CI locked down with OIDC and least privilege, artefacts signed with provenance and an SBOM, and a cluster that refuses anything not built the approved way. Then attack it and confirm each gate holds.
Setup
* A repo for one service (e.g. `orders-api`) with a Dockerfile and
Terraform for its infra (an ECR repo, an IAM role, an S3 bucket).
* A `kind` cluster with Kyverno (or policy-controller) installed.
* GitHub Actions (or GitLab CI) with OIDC to your sandbox cloud account.
* Tools: checkov/trivy, conftest, syft, grype, cosign, slsa-github-
generator (or equivalent), Argo CD (or manual `kubectl apply` of
digest-pinned manifests).
Tasks
1. IaC GATE
[ ] `terraform plan -out` -> `show -json` -> checkov + trivy config
+ 3 custom conftest policies (no public admin ports, mandatory
customer-CMK encryption, no `iam:PassRole` Resource "*").
[ ] State in S3 (SSE-KMS) + DynamoDB lock; state bucket policy
restricts to the CI role.
[ ] Provider lockfile committed; modules pinned to versions/SHAs.
[ ] A scheduled drift-check workflow.
2. CI HARDENING
[ ] `permissions:` least-privilege in every workflow; `id-token:
write` only on the release job.
[ ] All actions pinned to commit SHAs; `harden-runner` with an
egress allowlist.
[ ] OIDC to a cloud role whose trust pins
`sub: repo:<org>/<repo>:environment:production`. Prove a PR
branch cannot assume it.
[ ] Branch protection + CODEOWNERS on workflow files, Dockerfile,
and build scripts; a gated `production` environment.
[ ] No static cloud keys anywhere; secret scanning + push
protection on.
3. BUILD INTEGRITY
[ ] Build the image; generate SBOM (syft, SPDX); scan (grype/trivy,
fail on CRITICAL with an expiring exception file).
[ ] `cosign sign` (keyless) + `cosign attest` the SBOM + SLSA
provenance (slsa-github-generator).
[ ] Push BY DIGEST to ECR; never deploy a tag.
4. DEPLOY VERIFICATION
[ ] Kyverno `verifyImages` policy in `Enforce`: signature from the
exact release-workflow identity + SLSA provenance whose source
repo == this repo + image from the approved registry + by
digest. Applies to all containers incl. init/sidecar.
[ ] GitOps (Argo CD) or a deploy job updates the manifest to the
new digest; cluster admits it.
5. ATTACK IT (document each result)
[ ] `docker push` an unsigned `orders-api:evil` and try to run it
-> DENIED (no signature).
[ ] Sign an image with a DIFFERENT identity (your personal cosign
keyless login) -> DENIED (wrong `--certificate-identity`).
[ ] Open a PR editing the `Makefile`/build script to exfil the OIDC
token -> CI requires maintainer approval to run; the push role
isn't assumable from a PR ref anyway.
[ ] Add `cidr_blocks=["0.0.0.0/0"]` on port 22 in Terraform -> PR
blocked by conftest.
[ ] Bump a dependency to a known-CVE version -> grype fails the
build.
Deliverable
~/sec-lab/adv/reports/p3-pipeline.md: the pipeline diagram with every gate
labelled, the config (workflows, policies, Terraform) in the repo, and the
five attack attempts each with the command and the "DENIED/BLOCKED" evidence.
Plus: "which single gate would you keep if you could only keep one, and why".
Definition of done
- A bad Terraform plan is blocked pre-merge by policy-as-code.
- CI uses OIDC (no static cloud keys), least-privilege tokens, SHA-pinned actions, and a gated prod environment; a PR branch cannot assume the deploy role.
- Every released image has a keyless signature, an SBOM attestation, and SLSA provenance, and is deployed by digest.
- The cluster refuses an unsigned image and an image signed by the wrong
identity, in
Enforcemode, for all container types. - All five attack attempts are blocked, with evidence.
Rubric (/100)
IaC scanning + policy-as-code gate ............. 15
CI hardening (OIDC, perms, pinning, gates) ..... 25
Signing + SBOM + provenance in the pipeline .... 20
Admission verification in Enforce (all containers) 25
Attack attempts documented and blocked ........ 10
Report ....................................... 5
Further reading
- Reference pipeline:
slsa-framework/slsa-github-generatorexamples; Chainguard's "secure-by-default" pipeline posts; the CNCFtag-security"Secure Software Factory" reference architecture. - Practice: the
sigstore/cosigntutorials; Kyverno "verify images" playground; Kubernetes Goat's supply-chain scenarios. - Standard: NIST SP 800-204D; SLSA v1.0; CIS Software Supply Chain Benchmark.
End of Part 3 — Milestone check
- I scan the IaC plan, protect state as a secrets store, pin modules, and detect drift
- I have written policy-as-code that blocks a real misconfiguration in review
- I can explain direct vs indirect Poisoned Pipeline Execution and defend against both
- I have set up OIDC from CI to the cloud with a tightly-pinned trust policy and proven a PR branch cannot use it
- I can produce and verify keyless signatures, SBOM attestations, and SLSA provenance
- I have a cluster that refuses images not built the approved way, in Enforce mode
- I completed the supply-chain-hardened pipeline lab and defeated every attack attempt against it