Skip to main content
Version: 1.1

Ratify with Venafi CodeSign Protect

This guide will explain how to get started with Ratify and the Venafi CodeSign Protect notation plugin. This will involve setting up the necessary components, and configuring them properly. Once everything is set up we will walk through a simple scenario of verifying the signature on a container image at deployment time.

By the end of this guide you will have a Kubernetes cluster with Gatekeeper and Ratify installed, and have validated that only images signed by an authorized Venafi CodeSign Protect signing identity can be deployed.

This guide assumes you have a working Kubernetes cluster and Venafi CodeSign Protect platform. Portions of this guide can be skipped if you have an existing cluster and/or repository.

Table of Contents

Prerequisites

There are a number of tools that you will need locally to complete this guide:

  • kubectl: This is used to interact with the cluster
  • helm: This is used to install ratify components into the cluster
  • docker: This is used to build the container image we will deploy in this guide
  • ratify: This is used to check images from ECR locally
  • jq: This is used to capture variables from json returned by commands
  • notation: This is used to sign the container image we will deploy in this guide
  • Venafi CodeSign Protect notation plugin: this is required to use notation with Venafi CodeSign Protect signing identities

Prepare Container Image

For this guide we will create a basic container image we can use to simulate deployments of a service. We will start by building the container image:

docker build -t $REPO_URI:v1 https://github.com/wabbit-networks/net-monitor.git#main

After the container is built we need to push it to a repository such as ECR:

aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin $REPO_URI

docker push $REPO_URI:v1

You can also push to other OCI-compatible registries such as GitHub:

echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin

docker push ghcr.io/myorg/net-monitor:v1

For more information on provisioning ECR repositories check the documentation.

Sign Container Image

For this guide, we will sign the image using notation with the Venafi CodeSign Protect platform and plugin resources.

To use signing identity in notation, we will add the certificate label as the signing key:

notation key add \
--plugin venafi-csp \
--id "venafi-csp-cert-label" \
--default "venafi-csp-cert-label" \
--plugin-config "config"="/path/to/vsign/config.ini"

After the signing identity has been added, we will use notation to sign the image:

notation sign $REPO_URI:v1

Both the container image and the signature should now be in the public ECR repository. We can also inspect the signature information using notation:

notation inspect $REPO_URI:v1

More information on signing with Venafi CodeSign Protect can be found in the Venafi Notation Plugin and notation documentation.

Deploy Gatekeeper

The Ratify container will perform the actual validation of images and their artifacts, but Gatekeeper is used as the policy controller for Kubernetes.

We first need to install Gatekeeper into the cluster. We will use the Gatekeeper helm chart with some customizations:

helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts

helm install gatekeeper/gatekeeper \
--name-template=gatekeeper \
--namespace gatekeeper-system --create-namespace \
--set enableExternalData=true \
--set validatingWebhookTimeoutSeconds=5 \
--set mutatingWebhookTimeoutSeconds=2 \
--set externaldataProviderResponseCacheTTL=10s

Next, we need to deploy a Gatekeeper policy and constraint. For this guide, we will use a sample policy and constraint that requires images to have at least one trusted signature.

kubectl apply -f https://raw.githubusercontent.com/deislabs/ratify/main/library/notation-validation/template.yml
kubectl apply -f https://raw.githubusercontent.com/deislabs/ratify/main/library/notation-validation/samples/constraint.yaml

More complex combinations of regos and Ratify verifiers can be used to accomplish many types of checks. See the Gatekeeper docs for more information on rego authoring.

Deploy Ratify

Now we can deploy Ratify to our cluster:

helm install ratify \
ratify/ratify --atomic \
--namespace gatekeeper-system \
--set featureFlags.RATIFY_EXPERIMENTAL_DYNAMIC_PLUGINS=true \
--set featureFlags.RATIFY_CERT_ROTATION=true

Refer to the Ratify documentation if you need to customize the helm chart installation.

After deploying Ratify, we will download the Venafi CodeSign Protect notation plugin to the Ratify pod using the Dynamic Plugins feature:

cat > venafi-notation-plugin.yaml << EOF
apiVersion: config.ratify.deislabs.io/v1beta1
kind: Verifier
metadata:
name: notation-venafi-csp-plugin
spec:
name: notation-venafi-csp
artifactTypes: application/vnd.oci.image.manifest.v1+json
source:
artifact: ghcr.io/venafi/notation-venafi-csp:linux-amd64-latest
EOF

kubectl apply -f venafi-notation-plugin.yaml

Next we will need to deploy the trusted Root certificate used to issue the signing identity referenced above:

cat > venafi_root.yaml << EOF
apiVersion: config.ratify.deislabs.io/v1beta1
kind: CertificateStore
metadata:
name: ratify-notation-inline-cert
namespace: gatekeeper-system
spec:
provider: inline
parameters:
value: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
EOF

kubectl apply -f venafi_root.yaml

Finally, we will create a verifier that specifies the trust policy to use when verifying signatures. In this guide, we will use a trust policy that only trusts images signed by the Venafi CodeSign Protect signing identity we created earlier:

cat > notation-verifier.yaml << EOF
apiVersion: config.ratify.deislabs.io/v1beta1
kind: Verifier
metadata:
name: verifier-notation
spec:
name: notation
artifactTypes: application/vnd.cncf.notary.signature
parameters:
verificationCertStores:
certs:
- ratify-notation-inline-cert
trustPolicyDoc:
version: "1.0"
trustPolicies:
- name: default
registryScopes:
- "*"
signatureVerification:
level: strict
trustStores:
- signingAuthority:certs
trustedIdentities:
- "x509.subject: CN=signer.example.com,O=Acme,L=Cupertino,ST=CA,C=US"
EOF

kubectl apply -f notation-verifier.yaml

More complex trust policies can be used to customize verification. See notation documentation for more information on writing trust policies.

Deploy Container Image

Now that the signed container image is in the registry and Ratify is installed into the Kubernetes cluster we can deploy our container image:

kubectl run demosigned -n default --image $REPO_URI:v1

We should be able to see from the Ratify and Gatekeeper logs that the container signature was validated. The pod for the container should also be running.

kubectl logs -n gatekeeper-system deployment/ratify
time=2023-11-15T18:24:22.104435502Z level=info msg=verify result for subject ghcr.io/myorg/net-monitor@sha256:fcc8a5d24fcc9619b80e2e86695d2a792108add778439ac0a0647c9cae745176: {
"isSuccess": true,
"verifierReports": [
{
"subject": "ghcr.io/myorg/net-monitor@sha256:fcc8a5d24fcc9619b80e2e86695d2a792108add778439ac0a0647c9cae745176",
"isSuccess": true,
"name": "notation",
"message": "signature verification success",
"extensions": {
"Issuer": "CN=Issuer,O=Example,C=US",
"SN": "CN=signer.example.com,O=Example,L=San Jose,ST=CA,C=US"
},
"artifactType": "application/vnd.cncf.notary.signature"
}
]
}

We can also test that an image without a valid signature is not able to run:

kubectl run demounsigned -n default --image busybox
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: docker.io/library/busybox@sha256:3fbc632167424a6d997e74f52b878d7cc478225cffac6bc977eedfe51c7f4e79

The command should fail with an error and we should be able to see from the Ratify and Gatekeeper logs that the signature validation failed.

time=2023-11-15T18:24:08.110678426Z level=info msg=verify result for subject docker.io/library/busybox@sha256:3fbc632167424a6d997e74f52b878d7cc478225cffac6bc977eedfe51c7f4e79: {
"verifierReports": [
{
"subject": "docker.io/library/busybox@sha256:3fbc632167424a6d997e74f52b878d7cc478225cffac6bc977eedfe51c7f4e79",
"isSuccess": false,
"message": "verification failed: Error: referrers not found, Code: REFERRERS_NOT_FOUND, Component Type: executor"
}
]
}