A Kubernetes operator that manages a full software supply chain pipeline from two CRs. Built with Kubebuilder, powered by Tekton, secured by Sigstore.
"Treat your pipeline as infrastructure, not a script."
secure-software-supply-chain is part of the BlanketOps platform engineering project. It implements a supply chain security pipeline as a Kubernetes controller — declarative, auditable, and self-managing.
A SupplyChain CR defines the pipeline for a repository. An ImageBuild CR is auto-created on every GitHub push via a Tekton EventListener — no manual triggering required. The controller drives a Tekton PipelineRun through build, scan, sign, attest, and publish — all reconciled automatically.
GitHub Push
└── EventListener (Tekton Triggers)
└── creates ──► ImageBuild CR
└── owns ──► Tekton PipelineRun
├── git-clone (source fetch)
├── authentication-fulcio (OIDC warm-up)
├── sonarqube-scanner (quality gate)
├── build-image-buildah (OCI image build)
├── push-image-docker (registry push)
├── vulnerability-scan-trivy (CVE scan)
├── sign-image-cosign (keyless signing)
├── attest-image-rekor-fulcio (provenance)
└── publish-metadata-grafeas (metadata)
The pipeline is driven end-to-end by two CRs and a set of Tekton Tasks. The controller reconciles the full lifecycle — from webhook registration to signed image attestation.
SupplyChain — the pipeline definition for a repository. One per repo. The controller enforces this at reconcile time. It owns and reconciles:
- Custom Tekton Tasks
- TriggerBinding, TriggerTemplate, EventListener (GitHub webhook automation)
- Ingress for the EventListener (host sourced from
spec.webhookHost) - The
supply-chain-runnerServiceAccount
GitHubWebhook — manages GitHub webhook registration. Automatically registers the webhook URL with GitHub using a GitHub App installation token. Idempotent — safe to apply on every reconcile.
ImageBuild — a single pipeline execution. Auto-created by the EventListener on every GitHub push. Owns the Tekton PipelineRun and tracks per-step status.
ImageSignature — the cryptographic audit record. Created before the PipelineRun with Phase=Pending, updated to Phase=Signed on success. Carries the Fulcio cert reference, principal identity, and Rekor log index.
ImageBuildResult — the durable execution record. Survives PipelineRun pruning. Captures full pipeline step results: git provenance, image digest, Trivy scan summary, SonarQube gate status, Grafeas occurrence.
The SupplyChain controller automatically provisions the full Tekton Triggers stack per chain:
- TriggerBinding — extracts
git-repo-url,git-revision,git-commit-sha,short-sha,repo-full-namefrom the GitHub push payload - TriggerTemplate — creates an
ImageBuildCR with extracted params. Name is deterministic:<supplychain>-<branch>-<full-sha>— idempotent on replay - EventListener — shared across all SupplyChains in the namespace, exposed via nginx Ingress
- Ingress — routes
spec.webhookHost→ EventListener. Host updated automatically whenwebhookHostchanges
The ImageBuildReconciler uses a mediator pattern to sequence prerequisites before pipeline construction.
Gate 1 — Prerequisites
- Git SSH ExternalSecret reconciliation
- Registry ExternalSecret reconciliation (Buildah + Tekton Chains split credentials)
- SonarQube ExternalSecret reconciliation
- Convergence wait — all secrets must materialise before proceeding
Gate 2 — Signing Context (three-proof authorization)
Before Fulcio is called, three SubjectAccessReviews are performed against the supply-chain-runner ServiceAccount. All three must pass:
| Proof | Resource | Verb | Meaning |
|---|---|---|---|
| ScopeProof | supplychains |
get |
SA can see the chain it claims to execute against |
| IntentProof | imagebuilds |
create |
SA is authorized to initiate a build |
| OutputProof | imagesignatures |
create |
SA is authorized to produce signing records |
All three proofs are embedded in the attestation predicate that Fulcio signs over. This makes the ephemeral cert meaningful — it signs over a complete, API-server-verified authorization story, not just an identity claim.
After all three SARs pass, a short-lived OIDC token is minted from the ServiceAccount and exchanged with Fulcio for an ephemeral signing certificate. The principal and cert PEM are stored on ImageBuild.Status for the terminal block to read back after the PipelineRun completes.
Gate 3 — PipelineRun
Builds and creates the Tekton PipelineRun with the signing context injected. The ImageSignature CR is created at Phase=Pending before the PipelineRun starts.
When a PipelineRun reaches a terminal state (Succeeded or Failed), the reconciler runs three best-effort actions:
- Recorder — creates or updates an
ImageBuildResultCR with the full pipeline step results - Signature — marks the
ImageSignatureasSigned(with digest) orFailed - Pruner — deletes old PipelineRuns beyond the retention window (keeps last 3 succeeded, 1 failed)
supplychain installThis applies all platform dependencies in order:
- MetalLB (LoadBalancer support for kind)
- Tekton Pipelines, Triggers, Interceptors, Chains, Dashboard, Tasks, Results
- Sigstore (Fulcio, Rekor)
- Grafeas
- NGINX Ingress Controller
- SonarQube
After supplychain install completes and SonarQube is ready:
supplychain init-sonarqube --new-password <your-password>This automatically:
- Waits for SonarQube to be ready
- Changes the default admin password
- Generates a
supply-chainuser token - Patches the
ClusterSecretStorewith the token at/supplychain/sonarqube/token
# Build and load into kind
docker build -t blanketops/supply-chain-controller:latest .
kind load docker-image blanketops/supply-chain-controller:latest --name blanketops
# Install CRDs
make install
# Apply RBAC
kubectl apply -f config/rbac/signing_role.yaml
kubectl apply -f config/rbac/eventlistener_role.yaml
# Deploy
make deploy IMG=blanketops/supply-chain-controller:latestkubectl apply -k config/samplesThis applies:
ClusterSecretStore(ESO fake provider with credentials)SupplyChainCRGitHubWebhookCR
The GitHubWebhook controller auto-registers the webhook with GitHub. The hookURL must be publicly reachable. We use Tailscale Funnel to expose the in-cluster EventListener without a cloud load balancer.
# Expose the nginx ingress via Tailscale Funnel
tailscale serve --bg --https=443 http://<metallb-ingress-ip>
tailscale funnel --bg 443Set spec.webhookHost in the SupplyChain CR to your Tailscale hostname:
spec:
webhookHost: your-machine.tailf8145.ts.netThe controller automatically updates the EventListener Ingress host and the GitHubWebhook CR uses the same URL for webhook registration.
To survive reboots, create a systemd service that bridges the MetalLB IP to localhost:
sudo tee /etc/systemd/system/kind-ingress-bridge.service <<EOF
[Unit]
Description=Bridge localhost to kind ingress-nginx
After=network.target
[Service]
ExecStart=/usr/bin/socat TCP-LISTEN:8888,fork,reuseaddr TCP:<metallb-ip>:80
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now kind-ingress-bridge
tailscale funnel --bg 8888apiVersion: supplychain.blanketops.dev/v1alpha1
kind: SupplyChain
metadata:
name: for-kaniko-app
namespace: default
spec:
repository: ntlaletsi70/for-kaniko-app
serviceAccountName: supply-chain-runner
webhookHost: your-machine.tailf8145.ts.net
image:
registry: docker.io
name: nkanyezisolutions/for-kaniko-app
tagStrategy: git-sha
cloneSecretRef: github-ssh-credentials
registrySecretRef: registry-credentials
steps:
trivy: true
sign: true
attest: true
sonarQube:
serverURL: http://sonarqube-sonarqube.default.svc.cluster.local:9000
tokenSecretRef: sonarqube-token
projectKey: ntlaletsi70_for-kaniko-app
grafeas:
serverURL: http://grafeas.grafeas.svc.cluster.local:8080
signing:
fulcioURL: http://fulcio-server.fulcio-system.svc.cluster.local
rekorURL: http://rekor-server.rekor-system.svc.cluster.localapiVersion: supplychain.blanketops.dev/v1alpha1
kind: GitHubWebhook
metadata:
name: for-kaniko-app-webhook
namespace: default
spec:
repository: ntlaletsi70/for-kaniko-app
supplyChainRef: for-kaniko-app
hookURL: https://your-machine.tailf8145.ts.net
secretRef: github-app-credentialskubectl get imagebuilds -n default -wPhase transitions: Pending → Running → Succeeded
apiVersion: supplychain.blanketops.dev/v1alpha1
kind: ImageBuild
metadata:
name: for-kaniko-app-manual-001
namespace: default
spec:
supplyChainRef:
name: for-kaniko-app
gitRef:
url: git@github.com:ntlaletsi70/for-kaniko-app.git
revision: main
imageTag: manual-001# Build status
kubectl get imagebuilds -n default
kubectl describe imagebuild <name>
# Signing audit record
kubectl get imagesignatures -n default
# Durable build result (survives PipelineRun pruning)
kubectl get imagebuildresults -n default
# PipelineRun logs
kubectl get pipelineruns -n default
tkn pipelinerun logs <name> -fcosign verify \
--certificate-identity-regexp=".*" \
--certificate-oidc-issuer="https://kubernetes.default.svc.cluster.local" \
docker.io/nkanyezisolutions/for-kaniko-app:<sha># Install all dependencies
supplychain install
# Bootstrap SonarQube (run once after install)
supplychain init-sonarqube --new-password <password>
# Check dependency status
supplychain status
# Remove all dependencies
supplychain uninstall
# Open supply chain dashboard
supplychain observe
# Open RBAC audit dashboard
supplychain observe rbacmake undeploy
make uninstall
supplychain uninstallsupplychain.blanketops.dev/v1alpha1
Resources: SupplyChain, GitHubWebhook, ImageBuild, ImageSignature, ImageBuildResult
This operator is one component of the BlanketOps platform:
- blanketops-environments-controller — environment orchestration
- blanketops-environments-supply-chain — supply chain pipeline (this repo)
- blanketops-zenith-runners-pool — GitHub Actions self-hosted runners
- Tekton
- Tekton Triggers
- Tekton Chains
- Cosign / Sigstore
- Fulcio
- Rekor
- Buildah
- Skopeo
- Trivy
- SonarQube
- Grafeas
- Kubebuilder
- External Secrets Operator
- Tailscale Funnel
| Demo | Description |
|---|---|
| Full Pipeline | GitHub push → ImageBuild → PipelineRun → Signed image |
| Setup | Fresh cluster setup: install deps, apply SupplyChain CR |
| Webhook Automation | GitHubWebhook CR: auto-register → push → pipeline fires |
| Signing Verification | Verify signed image with cosign + Rekor transparency log |
See demo/ for scripts and tape files.# secure-software-supplychain
