This demo will show you how to use the sigstore policy (admission) controller to verify keyless sigstore signatures.
Keyless is the slight of hand we use to perform crypto signing with no need for long term key management.
The flow is in general terms:
An ecdsa key pair is generated (encoded to memory, never to touch any disk).
A user connects to sigstores fulcio CA to request a signing certificate.
Fulcio directs them to an OpenID connect provider of their choosing (such as Github, Google, Microsoft etc).
If the user auths correctly, an ID Token is provided.
The user then presents their OAuth2 ID Token to the sigstore fulcio CA and requests a X509 certificate.
The certificate contains:
- The Public Key of the ephemeral key pair.
- The email address (stuck in a SAN)
- It has a chain to the sigstore root CA
The user then signs an artifact (container in this instance), and the digest / x509 cert are stored in sigstore rekor (transparency log)
We can then drop the private key, as that signing event is effectively frozen in time.
We can now look at the transparency log and know that:
At X time, a user with control of X OpenID account, was in possession of X key pair, and X key pair signed X container digest.
Using the policy controller, we can then allow / deny an image, based on which OAuth2 account signed the image.
As a follow up, I will add details of how we can use this for a sort of code provenance using GitHubs OpenID ambient credentials.
## Prerequisites
Five tools are required to run this demo:
- cosign
- cosign-policy-controller (installed as part of the demo later on)
- kind
- kubectl
- helm
Grab the nginx image:
docker pull nginxdemos/nginx-hello
Tag to images:
docker tag nginxdemos/nginx-hello ghcr.io/{$GITHUB_USERNAME}/nginx:signed
docker tag nginxdemos/nginx-hello ghcr.io/{$GITHUB_USERNAME}/nginx2:unsigned
docker push ghcr.io/{$GITHUB_USERNAME}/nginx:signed
docker push ghcr.io/{$GITHUB_USERNAME}/nginx2:unsigned
Create a kind cluster:
kind create cluster --name sigstore-demo --config manifests/kind-config.yaml
kubectl config use-context kind-sigstore-demo
Apply manifests:
kubectl apply -f manifests/base
kubectl apply -f manifests/ingress-controller
Setup the policy controller helm chart and install:
helm repo add sigstore https://sigstore.github.io/helm-charts
helm repo update
helm install policy-controller -n sigstore-system sigstore/policy-controller
Wait for ready STATUS
$ kubectl get pod -n sigstore-system
NAME READY STATUS RESTARTS AGE
policy-controller-policy-webhook-767d96744d-n2tf4 1/1 Running 0 59s
policy-controller-webhook-55587bc76d-v5j9w 1/1 Running 0
Apply the image policy, but first set the email account you plan to sign with:
- keyless:
identities:
- issuer: https://accounts.google.com
subject: jdoe@gmail.com
$ kubectl apply -f manifests/imagePolicy.yaml
Ensure webhooks are set correctly:
$ kubectl get validatingwebhookconfiguration
NAME WEBHOOKS AGE
ingress-nginx-admission 1 52m
policy.sigstore.dev 1 79s
validating.clusterimagepolicy.sigstore.dev 1 79s
$ kubectl get mutatingwebhookconfiguration
NAME WEBHOOKS AGE
defaulting.clusterimagepolicy.sigstore.dev 1 83s
policy.sigstore.dev
Ensure the policy is applied to the correct namespace
$ kubectl get ns nginx-ns1 -o jsonpath='{.metadata.labels}'
{"kubernetes.io/metadata.name":"nginx-ns1","policy.sigstore.dev/include":"true"}
Sign the image with cosign
cosign sign ghcr.io/{$GITHUB_USERNAME}/nginx:signed
Attempt to deploy the signed version (should succeed)
$ helm install nginx-signed --atomic -n nginx-ns1 ./manifests/nginx-demo -f manifests/nginx-signed.yaml
NAME: nginx-signed
LAST DEPLOYED: Fri Sep 2 18:55:28 2022
NAMESPACE: nginx-ns1
STATUS: deployed
REVISION: 1
TEST SUITE: None
Attempt to deploy the unsigned version (should fail)
$ helm install nginx-unsigned --atomic -n nginx-ns1 ./manifests/nginx-demo -f manifests/nginx-unsigned.yaml
Error: INSTALLATION FAILED: release nginx-unsigned failed, and has been uninstalled due to atomic being set: admission webhook "policy.sigstore.dev" denied the request: validation failed: failed policy: sigstore-demo: spec.template.spec.containers[0].image
ghcr.io/lukehinds/nginx2@sha256:32d4567494509b13a40899885dfbee46cec32ce918e39125161ac4de8337339c signature keyless validation failed for authority authority-0 for ghcr.io/lukehinds/nginx2@sha256:32d4567494509b13a40899885dfbee46cec32ce918e39125161ac4de8337339c: no matching signatures:
Altenatively, you can use a different email address to sign the unsigned image, which should also fail
& helm install nginx-unsigned --atomic -n nginx-ns1 ./manifests/nginx-demo -f manifests/nginx-unsigned.yaml
Error: INSTALLATION FAILED: release nginx-unsigned failed, and has been uninstalled due to atomic being set: admission webhook "policy.sigstore.dev" denied the request: validation failed: failed policy: sigstore-demo: spec.template.spec.containers[0].image
ghcr.io/lukehinds/nginx2@sha256:32d4567494509b13a40899885dfbee46cec32ce918e39125161ac4de8337339c signature keyless validation failed for authority authority-0 for ghcr.io/lukehinds/nginx2@sha256:32d4567494509b13a40899885dfbee46cec32ce918e39125161ac4de8337339c: no matching signatures:
none of the expected identities matched what was in the certificate
A lot of the k8s configs was lifted from here, so thanks due to @slimm609 / Brian Davis.