Sign Docker Images with Docker Content Trust

Sign Docker Images with Docker Content Trust

Docker Content Trust provides additional security when using and publishing Docker Images. In this article, we’ll make use of DCT to sign an image tag within a CircleCI job.

Requirements

  1. This guide assumes the use of a Machine Executor. A Docker Executor with Remote Docker (setup_remote\docker) could potentially be used as well with a few additional steps.
  2. A valid Dockerhub account
  3. The following instructions assume the use of the default Dockerhub notary server at https://notary.docker.io

Following the official documentation, create and add delegation key pair

This step will contain an abridged version from the official documention. It is recommended that all customers review and understand that documentation for creating and managing their keys for content trust.

The following process assumes a root key has already been created. If you do not yet have a root key set up, you will be prompted to generate a new root key and set a passphrase. It is highly recommended that you review the official documentation about securing and maintain the root key.

We’ll create the delegation key pair and make note of the location.

$ mkdir dct
$ cd dct
$ docker trust key generate circlecikey
Generating key for circlecikey...
Enter passphrase for new circlecikey key with ID eef7c96:
Repeat passphrase for new circlecikey key with ID eef7c96:
Successfully generated and loaded private key. Corresponding public key available: /Users/example/dct/circlecikey.pub

Then add the delegation public key to the defaul Dockerhub notary server. Be sure to have the correct path to the public key file (*.pub). Replace examplenamespace and exampleimage with your namespace and image name. The process will ask for the password for the root key created earlier and then ask to create a new key password.

$ docker trust signer add --key circlecikey.pub circlecikey examplenamespace/examplenamespace
Adding signer "examplesignername" to examplenamespace/exampleimage...
Initializing signed repository for examplenamespace/exampleimage...
Enter passphrase for root key with ID 63d2c1a:
Enter passphrase for new repository key with ID 10a128a:
Repeat passphrase for new repository key with ID 10a128a:
Successfully initialized "examplenamespace/exampleimage"
Successfully added signer: circlecikey to examplenamespace/exampleimage

We’ll then use the repository key passphrase created in the previous step to sign our image. Make sure the image tag has already been created. We’ll use the passphrase from the key generate step when we generated the delegation key pair. This should sign and push the image and tag.

$ docker build . -t examplenamespace/exampleimage:0.1.0
[+] Building 0.1s (6/6) FINISHED
...
$ docker trust sign examplenamespace/exampleimage:0.1.0
Signing and pushing trust data for local image examplenamespace/exampleimage:0.1.0, may overwrite remote trust data
...
Signing and pushing trust metadata
Enter passphrase for circlecikey key with ID eef7c96:
Successfully signed docker.io/examplenamespace/exampleimage:0.1.0

Store the delegation private key for use on CircleCI

Make use of the base64 command to encode the private key to be stored as a Project Environment Variable or in Contexts.

The delegation private key will be stored by default in the ~/docker/trust/private directory. The filename will be the key hash with .key as the filename extention. The 6 characters from the docker trust key generate circleci step will be the the first six characters of the file name. In our case this was eef7c9. The file contents will also contain role: circlecikey.

base64 ~/.docker/trust/private/eef7c96f206094f178XXXXXXXXXXXXe77def77571b5889XXXXXXXXXXXXXX.key

We’ll copy that output and store that in either a Project Environment Variable or in Contexts with the name DCT_KEY

We’ll also create and store a second environment variable with the full hash as the private key name will need the same filename as we have locally. If the file is ~/.docker/trust/private/eef7c96f206094f178XXXXXXXXXXXXe77def77571b5889XXXXXXXXXXXXXX.key the hash would be eef7c96f206094f178XXXXXXXXXXXXe77def77571b5889XXXXXXXXXXXXXX. For this example, the environment variable name should be DCT_HASH.

Store the delegation private key passphrase

Store the passphrase for the delegation private key in either a Project Environment Variable or in Contexts as DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE. This special environment variable is described in more detail in the official documentation.

Store the Dockerhub username and password

Create and store environment variables in either a Project Environment Variable or in Contexts to log into Dockerhub. We’ll name those DOCKER_USERNAME and DOCKER_PASSWORD and they’ll hold the Dockerhub username and password.

Example Job to build, push, and sign a tagged image

jobs:
  build-push-sign:
    machine:
      image: ubuntu-2004:202104-01
    steps:
      - checkout
      - run:
          name: Build, push, and sign tagged image
          command: |
            # Make sure directory exists
            mkdir -p $HOME/.docker/trust/private

            # Decode and store signer key to file named as the key hash
            echo $DCT_KEY | base64 --decode > $HOME/.docker/trust/private/$DCT_HASH.key

            # Set required permissions level on key file
            chmod 600 $HOME/.docker/trust/private/$KEY_HASH.key

            # Build image
            docker build . -t examplenamespace/exampleimage:0.1.0

            # Tag image
            docker tag hello-world examplenamespace/exampleimage:0.1.0

            # Log into docker
            echo "$DOCKER_PASSWORD" | docker login --username $DOCKER_USERNAME --password-stdin

            # Load key for signing
            docker trust key load $HOME/.docker/trust/private/$KEY_HASH.key --name circlecikey

            # Sign
            docker trust sign examplenamespace/exampleimage:0.1.0

            # Push
            docker push examplenamespace/exampleimage:0.1.0

            # Verify signature
            docker trust inspect --pretty examplenamespace/exampleimage:0.1.0

Resources

You are highly encouraged to read the official documention:

1 Like