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
- 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.
- A valid Dockerhub account
- 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: