Using cilock-k8s with your own application¶
Step-by-step guide to protecting your application with the webhook: attest your image, create and sign your policy, deploy the webhook with your trust material, and enforce it on your namespaces.
Throughout, replace:
REG— your registry (e.g.ghcr.io/you,localhost:5001for the kind dev registry)myapp/1.0.0— your image name / tag
0. What you need¶
cilockbuilt from your rookery checkout:cd <rookery>/cilock && go build -o ~/bin/cilock ./cmd/cilock- A registry that both your cluster nodes and the webhook pod can reach (the webhook resolves image digests from it at admission time).
- A cluster with the webhook deployable (see step 5 — the PoC scripts target kind, but the manifests work anywhere).
1. Create your signing keys¶
Two logical roles — for a first run one key can play both (that's what the demo does), but separating them is the real-world shape:
# functionary key: signs attestations (your CI would hold this)
openssl ecparam -genkey -name prime256v1 -noout -out functionary-key.pem
openssl ec -in functionary-key.pem -pubout -out functionary-pub.pem
# policy key: signs the policy itself (an org/security-team key)
openssl ecparam -genkey -name prime256v1 -noout -out policy-key.pem
openssl ec -in policy-key.pem -pubout -out policy-pub.pem
The webhook is configured with only policy-pub.pem — it's the single
trust anchor. Trust in the functionary key comes from inside the signed
policy (its public key is embedded there in step 3).
2. Attest your image build¶
Build and push as usual, then attest a docker save of the exact image you
pushed, wrapping it with cilock run:
docker build -t REG/myapp:1.0.0 .
docker push REG/myapp:1.0.0
cilock run \
--step build \
--workload manual \
--attestations oci \
--signer-file-key-path functionary-key.pem \
--outfile myapp-build.att.json \
--platform-url "" \
-- docker save -o myapp.tar REG/myapp:1.0.0
What this records: material (inputs), product (myapp.tar), and oci —
which parses the tar and emits the image ID (imageid) as a subject.
That subject is what the webhook matches after resolving REG/myapp:1.0.0
from the registry, and it's identical between the tar and the registry copy.
Worth knowing:
- Add more evidence with more attestors, e.g.
--attestations oci,git,environment—gitbinds the commit, and the generated policy will then require those attestations too. - In CI, run this as the build job itself (e.g. wrap the whole
docker build … && docker push … && docker save …in onecilock run -- sh -c '…') so the attestation covers the real pipeline step.cilock plan -- <cmd>previews which attestors would fire. cilock runwrites amyapp-build.att.json.detection.jsonsidecar — it is not a DSSE envelope; don't give it to the webhook.
3. Create and sign your policy¶
Generate a starter policy from the attestation you just made — it encodes
"a build step must exist with these attestation types, signed by this
functionary":
cilock policy from-bundles -k functionary-pub.pem myapp-build.att.json > policy.json
Now review and edit policy.json — it's plain JSON and it is your
rule set:
expires— from-bundles sets a default; set it to a date you actually intend to rotate the policy by.steps.build.attestations[]— the evidence required per step. Add or remove attestation types to match what you attest in step 2.steps.build.functionaries[]/publickeys— who may sign build attestations. Add each CI signer's public key here.- Optionally attach Rego
modules to an attestation entry to constrain its contents (e.g. "the git
attestation's branch must be
main") — see the witness policy schema (cilock policy validate --help, and the witness docs) for theregofield format.
Validate and sign it with the policy key:
cilock policy validate -p policy.json
cilock sign -k policy-key.pem -f policy.json -o policy.signed.json --platform-url ""
Multiple images/pipelines? Either one policy whose steps cover them all, or one policy per webhook deployment — the PoC webhook loads a single policy. Attest each image separately and give the webhook all the envelopes.
4. Give the webhook your trust material¶
The webhook reads three mounted paths (see dev/manifests/webhook.tmpl.yaml):
kubectl create ns cilock-system
# TLS for the API server → webhook connection (dev/gen-certs.sh does this
# for kind; in a real cluster use cert-manager or your PKI — the cert's SAN
# must be cilock-webhook.cilock-system.svc)
kubectl -n cilock-system create secret tls cilock-webhook-tls \
--cert=tls.crt --key=tls.key
# your policy + trust anchor
kubectl -n cilock-system create configmap cilock-config \
--from-file=policy.signed.json --from-file=policy-pub.pem=policy-pub.pem
# your attestation envelopes (repeat --from-file per envelope)
kubectl -n cilock-system create configmap cilock-attestations \
--from-file=myapp-build.att.json
5. Deploy the webhook¶
On kind, ./dev/deploy.sh does all of this. Elsewhere: build/push the
webhook image (Dockerfile expects the binary at bin/cilock-k8s,
cross-compile with CGO_ENABLED=0 GOOS=linux go build -o bin/cilock-k8s .),
then apply dev/manifests/webhook.tmpl.yaml after filling in:
- the image name,
- the
CA_BUNDLEplaceholder — base64 of the CA that signed the serving cert, - the
--registry-alias/--insecure-registryargs — delete them if your registry is a normal HTTPS registry reachable under its real name; they exist only for dev registries (private-registry auth uses the standard keychain, so mounting a docker config for pull credentials is the extension point if you need it).
6. Enforce and roll out¶
kubectl label namespace myapp-prod cilock-policy=enforce
kubectl -n myapp-prod create deployment myapp --image=REG/myapp:1.0.0
Pods from that deployment are now admitted only if REG/myapp:1.0.0 resolves
to digests covered by a valid, policy-satisfying attestation. Check
kubectl -n cilock-system logs deploy/cilock-webhook for the PASS/DENY lines.
Before enforcing a busy namespace, dry-run the exact check locally:
cilock-k8s verify \
--policy policy.signed.json --policy-key policy-pub.pem \
--attestation-dir ./attestations \
REG/myapp:1.0.0
7. Shipping a new version¶
Every new image needs new evidence — that's the security model, not a limitation:
- build + push
REG/myapp:1.0.1 cilock run … -- docker save …(step 2) → new envelope- add the envelope to the
cilock-attestationsConfigMap and restart the webhook (kubectl -n cilock-system rollout restart deploy/cilock-webhook) - deploy the new image
The policy usually does not change per release — only when you rotate
keys, change required evidence, or hit expires. (The redeploy-ConfigMap
loop is the PoC's static-store trade-off; an Archivista-backed
source.Sourcer would make step 3 automatic.)
Quick reference: what trusts what¶
policy-pub.pem ──trusts──► policy.signed.json ──trusts──► functionary keys
(webhook flag) (signed rule set) (inside policy)
│ │
▼ ▼
required steps & attestation envelopes must be
attestation types signed by these + contain the
pod image's digest as subject