Blog.

Signing Docker images using Docker Content Trust

Marco Franssen

Marco Franssen /

9 min read1620 words

Cover Image for Signing Docker images using Docker Content Trust

In this blog I want to introduce you to the concept of signing Docker images. Signing your docker images will add some layer of trust to your images. This can guarantee a consumer of your image that this image is for sure published by you and hasn't been tampered with by others.

You might already used PGP to sign your Git commits. In this blogpost I shown a nice way of setting PGP signing keys using Krypton that adds 2FA. In practice Docker image signing is the same concept.

If this all sounds a bit fuzzy to you, please continue reading, hopefully I am able to make things more clear. ;-)

Docker Content Trust (DCT) provides the ability to use digital signatures for data sent to and received from remote Docker registries. These signatures allow client-side or runtime verification of the integrity and publisher of specific image tags.

Through DCT, image publishers can sign their images and image consumers can ensure that the images they use are signed. Publishers could be individuals or organizations manually signing their content or automated software supply chains signing content as part of their release process.

In practice for a consumer nothing changes until DCT is enabled. From then you will only be able to work with Signed images. So for us developers it is important that we sign our images, so they can also be used by more restricted environments with DCT enabled.

DCT builds on top of Notary which is a more general purpose solution to sign artifacts. DCT implements this functionality for Docker images.

To be able to sign your images we will have to create x509 certificates which are used to sign repositories but also to define who is allowed to sign the repositories. You can also import existing certificates. E.g. managed by a PKI solution or created via openssl commandline.

Setting up your certificates

Before we start I want to mention it is very important to backup your certificates and store the credentials to use them in a password manager. Especially the root certificate we are about to generate.

WARNING: Loss of the root key is very difficult to recover from. Correcting this loss requires intervention from Docker Support to reset the repository state. This loss also requires manual intervention from every consumer that used a signed tag from this repository prior to the loss.

The keys will be stored at following location ~/.docker/trust/private (MacOSX/Linux) or %USERPROFILE%\.docker\trust\private (Windows). Please consult Docker Content Trust Documentation to properly manage the backups of your signing keys.

There are different types of keys, which have some hierachical structure as depicted in below diagram.

| Key | Description | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | root key | Root of content trust for an image tag. When content trust is enabled, you create the root key once. Also known as the offline key, because it should be kept offline. | | targets | This key allows you to sign image tags, to manage delegations including delegated keys or permitted delegation paths. Also known as the repository key, since this key determines what tags can be signed into an image repository. | | snapshot | This key signs the current collection of image tags, preventing mix and match attacks. | | timestamp | This key allows Docker image repositories to have freshness security guarantees without requiring periodic content refreshes on the client’s side. | | delegation | Delegation keys are optional tagging keys and allow you to delegate signing image tags to other publishers without having to share your targets key. |

Lets have a look on the docker trust cli.

terminal
$ docker trust --help
Usage:	docker trust COMMAND
 
Manage trust on Docker images
 
Management Commands:
  key         Manage keys for signing Docker images
  signer      Manage entities who can sign Docker images
 
Commands:
  inspect     Return low-level information about keys and signatures
  revoke      Remove trust for an image
  sign        Sign an image
 
Run 'docker trust COMMAND --help' for more information on a command.

As you can see the docker trust command allows us to manage keys, manage the entities allowed to sign our images, inspect the signatures, revoke and sign our images.

Lets first create our signing keys. The --dir command below defines where you would like to store the public key.

terminal
$ docker trust key generate marco --dir ~/.docker/trust
docker trust key generate marco
Generating key for marco...
Enter passphrase for new marco key with ID 8ede853:
Repeat passphrase for new marco key with ID 8ede853:
Successfully generated and loaded private key. Corresponding public key
available: /Users/marco/.docker/trust/marco.pub

Once we have our key created we can use notary to get an overview of our keys. Oh, don't forget to store the credentials and ensure you will arrange some backups.

terminal
$ notary -d ~/.docker/trust key list
ROLE     GUN    KEY ID                                                              LOCATION
----     ---    ------                                                              --------
marco           8ede853f5c5f6b119202e86478d0dff4fc7e37f803fefcf2da7138d8dcce1401    /Users/marco/.docker/trust/private

Manage entities for a repository

Now we have our personal signing key we can authorize our key to sign docker images for a given repository. This will also create new target keys in case the repository doesn't exist yet. The root key will be required to create new repository keys. The repository key will be required to add or remove new signing entities on the repository.

terminal
$ docker trust signer add --key ~/.docker/trust/marco.pub marco marcofranssen/whalesay
Adding signer "marco" to marcofranssen/whalesay...
Initializing signed repository for marcofranssen/whalesay...
Enter passphrase for root key with ID 2c799e5:
Enter passphrase for new repository key with ID b635efe:
Repeat passphrase for new repository key with ID b635efe:
Successfully initialized "marcofranssen/whalesay"
Successfully added signer: marco to marcofranssen/whalesay

Running the notary command now will show you a bunch more keys.

terminal
$ notary -d ~/.docker/trust key list
ROLE     GUN                           KEY ID                                                              LOCATION
----     ---                           ------                                                              --------
marco                                  8ede853f5c5f6b119202e86478d0dff4fc7e37f803fefcf2da7138d8dcce1401    /Users/marco/.docker/trust/private
targets  .../marcofranssen/whalesay    b635efe210fb79e2425e3b7fa102324e5bb759151bdb089bdd79e820b75b160b    /Users/marco/.docker/trust/private

Signing

Now we have all the keys in place to be able to sign a docker image called marcofranssen/whalesay. To be able to do so I will first have to create this image or simply download an existing image and tag it as such.

terminal
docker pull docker/whalesay
docker tag docker/whalesay marcofranssen/whalesay:latest
$ docker trust sign marcofranssen/whalesay:latest
Signing and pushing trust data for local image marcofranssen/whalesay:latest, may overwrite remote trust data
The push refers to repository [docker.io/marcofranssen/whalesay]
5f70bf18a086: Layer already exists
d061ee1340ec: Layer already exists
d511ed9e12e1: Layer already exists
091abc5148e4: Layer already exists
b26122d57afa: Layer already exists
37ee47034d9b: Layer already exists
528c8710fd95: Layer already exists
1154ba695078: Layer already exists
latest: digest: sha256:4a79736c5f63638261bc21228b48e9991340ca6d977b73de3598be20606e5d87 size: 2402
Signing and pushing trust metadata
Enter passphrase for marco key with ID eb9dd99:
Successfully signed docker.io/marcofranssen/whalesay:latest

Now we can inspect the signing details of our image.

terminal
$ docker trust inspect --pretty marcofranssen/whalesay
Signatures for marcofranssen/whalesay
 
SIGNED TAG          DIGEST                                                             SIGNERS
latest              4a79736c5f63638261bc21228b48e9991340ca6d977b73de3598be20606e5d87   marco
 
List of signers and their keys for marcofranssen/whalesay
 
SIGNER              KEYS
marco               eb9dd99255f9
 
Administrative keys for marcofranssen/whalesay
 
  Repository Key:       b635efeddff59751e8b6b59abb45383555103d702e7d3f46fbaaa9a8ac144ab8
  Root Key:     0428c356406a6ea3543012855c117d13d784774e49aa6db461cfbad5726d187b

If you would like to revoke a signed image tag you can do that using the following command.

terminal
$ docker trust revoke marcofranssen/whalesay:latest
Enter passphrase for marco key with ID eb9dd99:
Successfully deleted signature for marcofranssen/whalesay:latest
$ docker trust inspect --pretty marcofranssen/whalesay
No signatures for marcofranssen/whalesay
 
List of signers and their keys for marcofranssen/whalesay
 
SIGNER              KEYS
marco               eb9dd99255f9
 
Administrative keys for marcofranssen/whalesay
 
  Repository Key:       b635efeddff59751e8b6b59abb45383555103d702e7d3f46fbaaa9a8ac144ab8
  Root Key:     0428c356406a6ea3543012855c117d13d784774e49aa6db461cfbad5726d187b

Last but not least you could add your collegues as a signing entity to your repositories using their public key. This will require them to generate a key first and provide their public key in order to be authorized.

For your own reference you might want to create a bookmark now to the TL;DR below so you can easily lookup the commands and how to use as a future reference.

TL;DR

Generate / Load Keys

Create a new signing key or load an existing signing key.

NOTE: the name you provide will also be the name used in the signer data. Consider using a descriptive name.

docker trust key generate marco --dir ~/.docker/trust
docker trust key load key.pem --name marco

Adding delegation key

Add another team member to allow them to sign images for a given repository.

docker trust signer add --key buddy.pub buddy <image>:<tag>

Removing a signer

Remove a team member from being able to sign images for a given repository.

docker trust signer remove buddy <image>:<tag>

Signing an image

Sign a specific tag of the image.

docker trust sign <image>:<tag>

Revoke trust

Remove the signature from a specific image tag.

docker trust revoke <image>:<tag>

Inspect image trust

Inspect the signing data of an image.

docker trust inspect --pretty <image>:<tag>

Enabling content trust

Content trust is disabled by default. To work only with signed images Docker Content Trust should be enabled.

NOTE: this will disallow you from using non signed images. Docker pull will fail if the image is not signed. You will not be able to build your own image using a non-signed image in the FROM definition

Linux/MacOSX
export DOCKER_CONTENT_TRUST=1
Windows
set DOCKER_CONTENT_TRUST=1

References

Thanks for reading my blog.

You have disabled cookies. To leave me a comment please allow cookies at functionality level.

More Stories

Cover Image for Build a Go Webserver on HTTP/2 using Letsencrypt

Build a Go Webserver on HTTP/2 using Letsencrypt

Marco Franssen

Marco Franssen /

Pretty often I see developers struggle with setting up a webserver running on https. Now some might argue, why to run a webserver on https during development? The reason for that is simple. If you would like to benefit from HTTP/2 features like server push, utilizing the http.Pusher interface, you will need to run your webserver on HTTP/2. That is the only way how you can very early on in the development process test this. In this blog I'm showing you how to do that in Go using Letsencrypt and a…

Cover Image for React Router and Nginx over HTTP/2

React Router and Nginx over HTTP/2

Marco Franssen

Marco Franssen /

In this blogpost I want to show you how you can easily get your React SPA app with clientside router work properly with your Nginx setup. I will also show you how to serve your React App over HTTP/2 and how you can leverage from http2 server pushes. To do so I will show you how to do that with the Nginx Docker image. When running your webapp using the development server you will in general not face any issues, however when running the static build on a production server you will most likely fac…

Cover Image for Secure 2FA SSH and PGP using Krypton

Secure 2FA SSH and PGP using Krypton

Marco Franssen

Marco Franssen /

In this blogpost I want to show you how easy it is to setup SSH and PGP key securily without even having them on your laptop. Instead we will have those keys on our mobile device. Yes, I hear you thinking…. Wutt, but, but, but, whaat! No worries bear with me, I walk you through it an will even explain you some magic behind the Krypton commands which we are about to use, so you will have a fully transparant understanding on Krypton. First of all you shouldn't worry about the safety of your keys…

Cover Image for Howto Secure Shell easily from the terminal

Howto Secure Shell easily from the terminal

Marco Franssen

Marco Franssen /

I see many struggle when it comes to using Secure Shell in a comfortable way. Many are installing unneeded applications like Putty on Windows for example. Just like I did 4 years ago. Over the years I have been working a lot on servers where there was no GUI available and learned a lot doing that. I would like to share my tips and tricks so you can also be empowered by just sticking to the terminal on your OS or simply using Git Bash on Windows. What is SSH The SSH protocol (also referred to a…