The Modern Trojan Horse
In today's world, we don't just write code; we assemble it from a vast ecosystem of open-source dependencies. A sophisticated attacker doesn't need to breach your firewall if they can sneak a malicious library into your application. This is a software supply chain attack, and it's one of the biggest threats facing modern development.
How can we be sure that the container image running in production is exactly what we intended to build, free from tampering and with a clear record of its contents? We needed a system to provide provenance and integrity.
Our Three-Step Solution
We built a robust security pipeline using three key open-source tools:
- Syft to generate a Software Bill of Materials (SBOM).
- Cosign to sign our container images and attach the SBOM as a verifiable attestation.
- Kyverno to enforce a policy in our Kubernetes cluster that only allows signed images from trusted builders.
Here’s how it all fits together:
sequenceDiagram
participant CI as CI/CD Pipeline
participant Reg as Container Registry
participant K8s as Kubernetes API
participant Kyverno as Kyverno Webhook
CI->>CI: 1. Build Image
CI->>CI: 2. Generate SBOM with Syft
CI->>CI: 3. Sign image & attest SBOM with Cosign
CI->>Reg: 4. Push Image, Signature, & Attestation
participant Dev as Developer
Dev->>K8s: 5. `kubectl apply -f app.yaml`
K8s->>Kyverno: 6. Validate Request
Kyverno->>Reg: 7. Fetch Signature & Attestation
Kyverno->>Kyverno: 8. Verify against Public Key
alt Valid Signature & Attestation
Kyverno-->>K8s: Allow
else Invalid or Missing
Kyverno-->>K8s: Deny
end
Step 1: Generating the SBOM
An SBOM is a detailed inventory of every component in your application. It's like a list of ingredients for your software. We use Anchore's Syft to scan our image and generate an SBOM in the standard SPDX format.
# In our CI pipeline, after building the image
IMAGE="my-registry/my-app:v1.2.3"
syft $IMAGE -o spdx-json > sbom.spdx.json
Step 2: Signing and Attesting with Cosign
Next, we use Cosign to perform two critical actions:
- Sign the image: This creates a cryptographic signature that proves the image was built by our CI pipeline and hasn't been altered.
- Attest the SBOM: We attach the SBOM we just generated to the image as a formal, verifiable attestation. This links the image's contents to its signature.
# Assumes COSIGN_PRIVATE_KEY is a CI/CD secret
# The public key is distributed to our clusters
# Sign the image digest
cosign sign --key env://COSIGN_PRIVATE_KEY $IMAGE
# Attach the SBOM as an attestation
cosign attest --key env://COSIGN_PRIVATE_KEY --predicate sbom.spdx.json --type spdx $IMAGE
Now, the image, its signature, and its SBOM attestation are all stored together in our container registry.
Step 3: Enforcing Verification with Kyverno
This is the final and most important step. A signature is useless if you don't check it. We use a Kyverno ClusterPolicy to act as an admission controller in Kubernetes. This policy rejects any Pod that tries to run a container image that isn't signed with our trusted key.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-image-signature
spec:
validationFailureAction: enforce
rules:
- name: verify-image-signature
match:
resources:
kinds:
- Pod
verifyImages:
- image: "my-registry/my-app:*"
key: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
attestations:
- type: "spdx"
conditions:
- all:
- key: "{{ data.predicate.name }}"
operator: "Equals"
value: "my-app"
This policy tells Kubernetes: "For any image matching my-registry/my-app:*, verify it has a valid signature using this public key AND that it has an SPDX attestation where the package name is my-app."
The Impact: A Chain of Trust
- Tamper-Proof Deployments: We have cryptographic proof that the code running in production is the exact code that passed our CI checks.
- Automated Compliance: Generating an SBOM for every build gives us an instant, auditable asset inventory for compliance and vulnerability management.
- Developer Transparency: Developers don't need to change their workflow. The signing and verification are completely automated within the platform.
By implementing this chain of trust, we've hardened our defenses against one of the most insidious threats in modern software development.