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 install
This 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:latest
kubectl apply -k config/samples
This 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 443
Set spec.webhookHost in the SupplyChain CR to your Tailscale hostname:
spec: webhookHost: your-machine.tailf8145.ts.net
The 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 8888
apiVersion: 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.local
apiVersion: 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-credentials
kubectl get imagebuilds -n default -w
Phase 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> -f
cosign 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 rbac
make undeploy make uninstall supplychain uninstall
supplychain.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