Skip to content

Getting Started⚓︎

This guide offers a simple default configuration for setting up Connaisseur using public infrastructure and verifying your first self-signed images. You will learn how to:

  1. Create signing key pairs
  2. Configure Connaisseur
  3. Deploy Connaisseur
  4. Test Connaisseur (and sign images)
  5. Cleanup

In the tutorial, you can choose to use either Notary (V1) via Docker Content Trust (DCT) or Cosign from the sigstore project as a signing solution referred to as DCT and Cosign from here on. Furthermore we will work with public images on Docker Hub as a container registry and a Kubernetes test cluster which might for example be MicroK8s or minikube for local setups. However, feel free to bring your own solutions for registry or cluster and check out our notes on compatibility.

In general, Connaisseur can be fully configured via helm/values.yaml, so feel free to take a look and try for yourself. For more advanced usage in more complex cases (e.g. authentication, multiple registries, signers, validators, additional features), we strongly advise to review the following pages:

  • Basics: understanding, configuring and using Connaisseur (e.g. image policy and validators)
  • Validators: applying different signature solutions and specific configurations
  • Features: using additional features (e.g. alerting)

In case you need help, feel free to reach out via GitHub Discussions 👋

Note: As more than only public keys can be used to validate integrity and provenance of an image, we refer to these trust anchors in a generalized form as trust roots.

Requirements⚓︎

You should have a Kubernetes test cluster running. Furthermore, docker, git, helm and kubectl should be installed and usable, i.e. having run docker login and switched to the appropriate kubectl context.

Create signing key pairs⚓︎

Before getting started with Connaisseur, we need to create our signing key pair. This obviously depends on the signing solution. Here, we will walk you through it for DCT and Cosign. In case you have previously worked with Docker Content Trust or Cosign before and already possess key pairs, you can skip this step (how to retrieve a previously created DCT key is described here). Otherwise, pick your preferred signing solution below.

In case you are uncertain which solution to go with, you might be better off to start with DCT, as it comes packaged with docker. Cosign on the other hand is somewhat more straightforward to use.

General usage of DCT is described in the docker documentation. For now, we just need to generate a public-private root key pair via:

docker trust key generate root

You will be prompted for a password, the private key is automatically imported and a root.pub file is created in your current folder that contains your public key which should look similar to:

-----BEGIN PUBLIC KEY-----
role: root

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELDzXwqie/P66R3gVpFDWMhxOyol5
YWD/KWnAaEIcJVTYUR+21NJSZz0yL7KLGrv50H9kHai5WWVsVykOZNoZYQ==
-----END PUBLIC KEY-----

We will only need the actual base64 encoded part of the key later.

Usage of Cosign is very well described in the docs. You can download Cosign from its GitHub repository. A keypair is generated via:

cosign generate-key-pair

You will be prompted to set a password, after which a private (cosign.key) and public (cosign.pub) key are created. In the next step, we will need the public key that should look similar to:

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvtc/qpHtx7iUUj+rRHR99a8mnGni
qiGkmUb9YpWWTS4YwlvwdmMDiGzcsHiDOYz6f88u2hCRF5GUCvyiZAKrsA==
-----END PUBLIC KEY-----

Configure Connaisseur⚓︎

Now, we will need to configure Connaisseur. Let's first clone the repository:

git clone https://github.com/sse-secure-systems/connaisseur.git
cd connaisseur

Connaisseur is configured via helm/values.yaml, so we will start there. We need to set Connaisseur to use our previously created public key for validation. To do so, go to the .validators and find the default validator. We need to uncomment the trust root with name default and add our previously created public key. The result should look similar to this:

# the `default` validator is used if no validator is specified in image policy
- name: default
  type: notaryv1  # or other supported validator (e.g. "cosign")
  host: notary.docker.io # configure the notary server to be used
  trust_roots:
  # the `default` key is used if no key is specified in image policy
  - name: default
    key: |  # enter your key below
      -----BEGIN PUBLIC KEY-----
      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELDzXwqie/P66R3gVpFDWMhxOyol5
      YWD/KWnAaEIcJVTYUR+21NJSZz0yL7KLGrv50H9kHai5WWVsVykOZNoZYQ==
      -----END PUBLIC KEY-----
  #cert: |  # in case the trust data host is using a self-signed certificate
  #  -----BEGIN CERTIFICATE-----
  #  ...
  #  -----END CERTIFICATE-----
  #auth:  # credentials in case the trust data requires authentication
  #  # either (preferred solution)
  #  secret_name: mysecret  # reference a k8s secret in the form required by the validator type (check the docs)
  #  # or (only for notaryv1 validator)
  #  username: myuser
  #  password: mypass

In addition for Cosign, the type needs to be set to cosign and the host is not required.

# the `default` validator is used if no validator is specified in image policy
- name: default
  type: cosign  # or other supported validator (e.g. "cosign")
  trust_roots:
  # the `default` key is used if no key is specified in image policy
  - name: default
    key: |  # enter your key below
      -----BEGIN PUBLIC KEY-----
      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvtc/qpHtx7iUUj+rRHR99a8mnGni
      qiGkmUb9YpWWTS4YwlvwdmMDiGzcsHiDOYz6f88u2hCRF5GUCvyiZAKrsA==
      -----END PUBLIC KEY-----
  #cert: |  # in case the trust data host is using a self-signed certificate
  #  -----BEGIN CERTIFICATE-----
  #  ...
  #  -----END CERTIFICATE-----
  #auth:  # credentials in case the trust data requires authentication
  #  # either (preferred solution)
  #  secret_name: mysecret  # reference a k8s secret in the form required by the validator type (check the docs)
  #  # or (only for notaryv1 validator)
  #  username: myuser
  #  password: mypass

We have now configured the validator default with trust root default. This will automatically be used if no validator and trust root is specified in the image policy (.policy). Per default, Connaisseur's image policy under .policy in helm/values.yaml comes with a pattern "*:*" that does not specify a validator or trust root and thus all images that do not meet any of the more specific pre-configured patterns will be verified using this validator. Consequently, we leave the rest untouched in this tutorial, but strongly recommend to read the basics to leverage the full potential of Connaisseur.

Deploy Connaisseur⚓︎

So let's deploy Connaisseur to the cluster:

helm install connaisseur helm --atomic --create-namespace --namespace connaisseur

This can take a few minutes. You should be prompted something like:

NAME: connaisseur
LAST DEPLOYED: Fri Jul  9 20:43:10 2021
NAMESPACE: connaisseur
STATUS: deployed
REVISION: 1
TEST SUITE: None

Afterwards, we can check that Connaisseur is running via kubectl get all -n connaisseur which should look similar to:

NAME                                          READY   STATUS    RESTARTS   AGE
pod/connaisseur-deployment-6876c87c8c-txrkj   1/1     Running   0          2m9s
pod/connaisseur-deployment-6876c87c8c-wvr7q   1/1     Running   0          2m9s
pod/connaisseur-deployment-6876c87c8c-rnc7k   1/1     Running   0          2m9s

NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/connaisseur-svc   ClusterIP   10.152.183.166   <none>        443/TCP   2m10s

NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/connaisseur-deployment   3/3     3            3           2m9s

NAME                                                DESIRED   CURRENT   READY   AGE
replicaset.apps/connaisseur-deployment-6876c87c8c   3         3         3       2m9s

Test Connaisseur⚓︎

Now that we created our key pairs, configured and deployed Connaisseur, the next step is to test our setup. So let's create and push a test image. Feel free to use our simple test Dockerfile under tests/Dockerfile (make sure to set your own IMAGE name):

# Typically, IMAGE=<REGISTRY>/<REPOSITORY-NAME>/<IMAGE-NAME>:<TAG>, like
IMAGE=docker.io/securesystemsengineering/demo:test
docker build -f tests/Dockerfile -t ${IMAGE} .
docker push ${IMAGE}

In case you have DCT turned on per default via environment variable DOCKER_CONTENT_TRUST=1, you should disable for now during the docker push by adding the --disable-content-trust=true.

If we try to deploy this unsigned image:

kubectl run test --image=${IMAGE}

Connaisseur denies the request due to lack of trust data or signed digest, e.g.:

Error from server: admission webhook "connaisseur-svc.connaisseur.svc" denied the request: Unable to get root trust data from default.
# or
Error from server: admission webhook "connaisseur-svc.connaisseur.svc" denied the request: No trust data for image "docker.io/securesystemsengineering/demo:test".
# or
Error from server: admission webhook "connaisseur-svc.connaisseur.svc" denied the request: could not find signed digest for image "docker.io/securesystemsengineering/demo:test" in trust data.

So let's sign the image and try again.

In DCT signing works via docker push using the --disable-content-trust flag:

docker push ${IMAGE} --disable-content-trust=false

You will be prompted to provide your password and might be asked to set a new repository key. The trust data will then be pushed to the Docker Hub Notary server.

For Cosign, we use the private key file from the first step:

cosign sign -key cosign.key ${IMAGE}

You will be asked to enter your password after wich the signature data will be pushed to your repository.

After successful signing, we try again:

kubectl run test --image=${IMAGE}

Now, the request is admitted to the cluster and Kubernetes returns:

pod/test created

You did it 🥳 you just verified your first signed images in your Kuberenetes cluster 👯

Read on to learn how to fully configure Connaisseur 🛠

Cleanup⚓︎

To uninstall Connaisseur, use:

helm uninstall connaisseur --namespace connaisseur

Uninstallation can take a moment as Connaisseur needs to validate the deletion webhook.

Back to top