Signing Docker images using Docker Content Trust
Marco Franssen /
9 min read • 1613 words
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
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.
|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.
$ 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.
$ 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.
$ 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.
$ 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
notary command now will show you a bunch more keys.
$ 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
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.
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.
$ 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.
$ 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.
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>
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
Thanks for reading my blog.