Cosign is used to sign and verify various kind of artifacts.
Cosign supports different way to sign images, including:
- keyless signing through Sigstore’s Fulcio Certificate Authority
- signing with our personal keys
- KMS signing
- hardware signing
This demo illustrates the creation and usage of a public/private key pair.
Installation
Cosign can be installed in several ways at details in the documentation. On macOS, it can be installed using Homebrew.
brew install cosign
Next we create a public/private key pair
cosign generate-key-pair
It generates 2 files:
- cosign.key: private key
- cosign.pub: public key
Signing an image
In this step, we’ll sign the lucj/btcprice DockerHub image. This image packages a demo application which gets the BTC USD price on a regular basis, and sends it to various external systems.
Several tags exist, including:
- 0.0.4 (sha256:7b08c75f45d82bedfcd1a06d970b663203b77092c84280f07f6eba87a3c843f5)
- 0.0.5 (sha256:e14812c2d6d827a9402b8109c65e024fcd04cb4f998d59329e816a9c908a4e23)
We only signed the image using the sha256 associated to the 0.0.5 tag.
cosign sign --key cosign.key lucj/btcprice@sha256:e14812c2d6d827a9402b8109c65e024fcd04cb4f998d59329e816a9c908a4e23
This creates a signature and pushes is alongside the image in the DockerHub.
Enforcing the usage of the signed image
To ensure only the signed image can be run on our cluster, we install Kyverno policy engine.
helm repo add kyverno https://kyverno.github.io/kyverno/
helm install kyverno kyverno/kyverno --version 3.3.4 -n kyverno --create-namespace
Next, we define a Kyverno ClusterPolicy (CRD installed by Kyverno) which prevents an image from lucj/btcprice from being pulled unless it has a valid signature. This resource contains to cosign public key so Kyverno can verify the signature.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-image
spec:
webhookConfiguration:
failurePolicy: Fail
timeoutSeconds: 30
background: false
rules:
- name: check-image
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "docker.io/lucj/btcprice*"
failureAction: Enforce
attestors:
- count: 1
entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+mMILfgAYx3OxsGNnBBIS71ByNhj
wjquvzRPDJkHfRiw9ibxxDMfy2wyXuBg/ALryPjL4YfSemr9WCrl8u4RKg==
-----END PUBLIC KEY-----
Then, we create this ClusterPolicy.
kubectl apply -f check-signature.yaml
Verification
First, we try to create a Deployment based on the image 0.0.4 using its sha256 lucj/btcprice@sha256:7b08c75f45d82bedfcd1a06d970b663203b77092c84280f07f6eba87a3c843f5.
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: btcprice
name: btcprice
spec:
selector:
matchLabels:
app: btcprice
template:
metadata:
labels:
app: btcprice
spec:
containers:
- image: lucj/btcprice@sha256:7b08c75f45d82bedfcd1a06d970b663203b77092c84280f07f6eba87a3c843f5
name: btcprice
env:
- name: WEBHOOK_ENABLED
value: "true"
We are prevented from creating this Deployment as the image is not signed.
$ kubectl apply -f btcprice.yaml
Error from server: error when creating "btcprice.yaml": admission webhook "mutate.kyverno.svc-fail" denied the request:
resource Deployment/default/btcprice was blocked due to the following policies
check-image:
autogen-check-image: 'failed to verify image docker.io/lucj/btcprice@sha256:7b08c75f45d82bedfcd1a06d970b663203b77092c84280f07f6eba87a3c843f5:
.attestors[0].entries[0].keys: no signatures found'
Let’s now change the image tag, using the one corresponding to the signed image.
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: btcprice
name: btcprice
spec:
selector:
matchLabels:
app: btcprice
template:
metadata:
creationTimestamp: null
labels:
app: btcprice
spec:
containers:
- image: lucj/btcprice@sha256:e14812c2d6d827a9402b8109c65e024fcd04cb4f998d59329e816a9c908a4e23
name: btcprice
env:
- name: WEBHOOK_ENABLED
value: "true"
Next, we create the Deployment.
$ kubectl apply -f btcprice.yaml
deployment.apps/btcprice created
Then, we verify the Pod is running fine.
$ kubectl get po
NAME READY STATUS RESTARTS AGE
btcprice-7476b66687-rtvzl 1/1 Running 0 3s
The application is configured to send BTC price to a default backend, which we can verify is working fine.
Everything is fine as Kyverno allowed this Pod to be deployed.
Key takeaways
In this demo, we generated a key pair and used it to sign and verify an image. Feel free to explore the other signature mechanisms detailed in the cosign documentation.