Signing container images: Comparing Sigstore, Notary, and Docker Content Trust
Tags: containersecurity, docker, kubernetes
Published: 2023-09-27 02:00:23

In the modern software ecosystem, containerization has become a popular method for packaging and deploying applications. Alongside this growing trend, ensuring the security of software supply chains has become a critical concern for businesses of all sizes. Implementing best practices, such as signing and verifying images to mitigate [man-in-the-middle (MITM) attacks](https://snyk.io/learn/man-in-the-middle-attack/) and validating their authenticity and freshness, play a pivotal role in safeguarding the integrity of the software supply chain.


Signing container images involves cryptographic techniques that bind the image to a specific entity or organization, creating a chain of trust. This helps ensure the authenticity and integrity of the code within the images.


Additionally, signing container images helps establish ownership and accountability. Signatures can be useful for auditing purposes, helping organizations track and verify the origins and changes made to images over time and enforcing specific compliance standards and regulations.


In this article, we will compare three popular container signing solutions: [Sigstore Cosign](https://github.com/sigstore/cosign), [Notary v2](https://github.com/notaryproject/notaryproject#the-notary-v2-journey), and [Docker Content Trust (DCT)](https://docs.docker.com/engine/security/trust/), (a.k.a. Notary v1). You'll learn about their features, capabilities, and suitability for securing container image supply chains. We will finish with a tutorial that will teach you how to integrate one of the tools into your workflow is also included.


Container Signing Tools
-----------------------


When it comes to securely signing your images, there are numerous tools available. Three of the most notable are Cosign, Notary, and DCT — and each of these tools has different specifications and requirements


### Sigstore Cosign


[Cosign](https://github.com/sigstore/cosign) is a tool that enables users to sign, verify, and protect software in a seamless and secure manner. It operates under the Apache License 2.0 and is built on the Sigstore standard, which ensures the integrity and authenticity of container images. Cosign is quickly growing in popularity, with around [3,400-plus stars on GitHub](https://github.com/sigstore/cosign).


Cosign is known for its support of keyless signing through the [Sigstore Fulcio certificate authority](https://docs.sigstore.dev/fulcio/certificate-issuing-overview/) and [Rekor immutable transparency log](https://docs.sigstore.dev/rekor/overview/) with authenticated OpenID Connect (OIDC) identity. This approach leverages an invisible infrastructure, which means that users don't have to worry about managing or maintaining keys on their own. Cosign simplifies the process by generating ephemeral keys, making it easy for users to verify and sign the authenticity of signed images and blobs.


### Docker Content Trust


Docker Content Trust (DCT) was developed by Docker in 2015 and was subsequently donated to the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/) as the [Notary project](https://www.cncf.io/projects/notary/); it is presently an incubating project.


DCT allows users to sign and verify container images while ensuring the integrity and authenticity of specific image tags through client-side or runtime verification.


The v1 of Notary functions by adding your public key to the registry and signing your image with the key's private counterpart before uploading it. Users can then verify the image by comparing the public key against the registry command group's pulled data. Additionally, you can automate software supply chains by signing content as part of the release process.


### Notary v2


The Notary v2 signing system is a new and improved version that aims to overcome the limitations of its predecessor. With [Notation](https://github.com/notaryproject/notation) and Notary v2, users can build and integrate their own implementation of the specifications into their signing and verification workflows in order to sign multiple artifacts (*i.e.,* container images, software bill of materials, and scan results). This is made possible with the help of a [group formed in December 2019](https://github.com/notaryproject/meeting-notes/blob/main/meeting-notes-2019.md#notary-v2-kickoff-meeting), whose goal is to improve the image-signing experience and solve problems such as supporting multiple registries and moving images within registries with the original v1 implementation.


Notary v2 is relatively new and was launched in [December 2022](https://github.com/notaryproject/notaryproject/releases/tag/v1.0.0-rc.1). It aims to act as a cross-industry and cross-registry specification for signing and verifying any Open Container Initiative (OCI) image or registry artifact. The project is still under development, and new capabilities will be added in the [future](https://github.com/notaryproject/notaryproject/blob/main/requirements/requirements.md).


Comparing Cosign, Notary v2, and Docker Container Trust
-------------------------------------------------------


In this article, there are four key characteristics that help you determine which tool is the best fit:


1. **Trust delegation model** is the mechanism used to establish and distribute trust within the artifacts and registry to ensure their integrity and authenticity.
2. **Transparency and auditability** are the ability to observe and verify actions and changes within the system, ensuring accountability and integrity.
3. **Ease of use** includes the simplicity and user-friendliness of the signing tools, enabling easy adoption and integration.
4. **Community support** includes the level of engagement, collaboration, and resources provided by the community surrounding the system, ensuring ongoing development and assistance.


Each of these factors can have a substantial impact on the effectiveness of each tool. In the upcoming sections, take a look at each category and evaluate the tools based on these factors.


### Trust Delegation Model


Cosign uses a [decentralized and federated trust delegation model](https://dlorenc.medium.com/the-sigstore-trust-model-4b146b2ecf2c) with a [root of trust](https://cpl.thalesgroup.com/faq/hardware-security-modules/what-root-trust) consisting of certificates and public keys provided by the Sigstore project. [The Update Framework (TUF)](https://www.cncf.io/projects/the-update-framework-tuf/) is used to distribute and obtain key materials. Sigstore's root of trust is also used to verify proof of inclusion in [Rekor](https://docs.sigstore.dev/rekor/overview/).


In comparison, Notary v2 and v1 use a [hierarchical trust delegation model](https://github.com/notaryproject/notary/blob/master/docs/service_architecture.md) that involves the distribution of images using TUF. In this model, a trust store is utilized to store trusted identities.


For producing artifacts in v2, users or administrators configure the trust policy to define which identities are considered trustworthy. The authenticity of signed artifacts is determined based on the trust derived from the identities in the trust store and trust policy.


For v1, DCT uses an offline key as the source of trust for image tags, with trust delegated to a repository or tagging keys and server-managed keys providing additional security. This model allows for different levels of trust within the system to enforce [different security policies and access controls](https://docs.docker.com/engine/security/trust/#docker-content-trust-keys).


In summary, because Cosign emphasizes decentralized and federated trust with a root of trust provided by Sigstore, it's a good choice when it comes to trust delegation. It implements a [tag-based discovery scheme](https://github.com/sigstore/cosign/blob/main/specs/SIGNATURE_SPEC.md#storage) and has support for most of the production registries. In contrast, you should choose Notary v1 when you don't require support for multiple registries, as there's no interoperability between registries. If you want interoperability, v2 offers that, and you can distribute trust between various registries.


### **Transparency and Auditability**


When it comes to transparency and auditability, Sigstore provides a secure and transparent system for software updates by storing all signatures in a public registry. This allows organizations to easily verify the authenticity of software updates. Additionally, using Rekor, all signing events can be publicly audited. This helps in ensuring accountability and trust in the software supply chain.


In comparison, DCT has some drawbacks. It only supports one signature per image, which means that if the vendor has already signed the image on Docker Hub, you can't add your own signature to indicate its suitability for your organization. This can limit your flexibility and control over the software supply chain.


Meanwhile, Notary v2 takes a more comprehensive approach to maintaining trust throughout the ecosystem. It challenges the assumption that images pulled directly from Docker Hub are inherently safe. By supporting multiple signatures, Notary v2 enables validation of image safety and allows organizations to record their own seal of approval, ensuring a higher level of trust in the software supply chain.


As of publishing this article, Notary v2 1.0.0 is in a release candidate state but has not had a [general release](https://github.com/notaryproject/notaryproject/releases/) yet, so in new and complicated environments, Sigstore may be a better choice, thanks to its maturity and integrations.


### **Ease of Use**


Cosign strives to offer a user-friendly experience by simplifying key management and the signing process. Moreover, developers can easily integrate it into their existing workflow while building images. Keyless signing eliminates the complexity of managing keys, and thanks to a cosign-generated private/public key pair, the steps to implement it are simple.


In comparison, Notary seamlessly integrates with the Docker container ecosystem and provides command line tools and APIs for signing and verifying containers. However, because it's relatively new, it lacks support for many clients except [Notation](https://github.com/notaryproject/notation). Additionally, for both Notary versions, you have to manage a lot of keys, which means that a proper working knowledge of TUF is necessary.


Meanwhile, DCT is tightly integrated with Docker, which means it's easy to enable image signing and verification with intuitive commands. However, it doesn't work between registries, and the signing data is lost when pulling a public image and pushing it to a private registry without an accompanying Notary server. Additionally, setting up a Notary service requires a Notary server, Notary signer, Notary client, and MySQL database, along with mTLS (mutual TLS) between the Notary server and signer. This often requires additional effort and is said to [disappear](https://github.com/notaryproject/notaryproject/tree/main/requirements), with v2 becoming more mainstream.


In terms of ease of use, Cosign appears to have an advantage with its user-friendly approach, simplified key management, and integration into existing workflows. In contrast, Notary v2 also provides integration and command line tools, but it requires more effort and knowledge to set up and manage than DCT. However, it does come bundled with [docker trust](https://docs.docker.com/engine/reference/commandline/trust/), which is very easy to use.


### **Community Support**


All the projects are under the CNCF umbrella, which means they have good community support; however, Cosign has gained additional support from industry partners and has an active community of [contributors](https://github.com/sigstore/cosign/graphs/contributors) that ensure ongoing development, support, and collaboration. Notary is also an open source project with strong community support and is widely used within the Docker ecosystem, benefiting from contributions and feedback from a large user base. Because the v2 project is relatively new, documentation and tutorial content are not as prolific as the other solutions and can act as a barrier for new users trying to learn and use v2 with CLIs like Notation.


DCT is part of the Docker platform and benefits from the extensive [Docker community](https://forums.docker.com/). It's gained significant traction and has a robust ecosystem of tools and integrations, making it easy to enable. Moreover, there are numerous [tutorials](https://docs.docker.com/engine/security/trust/#signing-images-with-docker-content-trust) available to help you get started.


### **And the Winner Is…**


The choice of a winner depends on the specific requirements, priorities, and constraints of the organization or system you're working with. Sigstore is well-suited for organizations prioritizing secure and transparent software updates for various artifacts, including [Helm](https://helm.sh/) charts, alongside images. It has great community support and features, such as interoperability between registries, making it a great choice for most organizations. Moreover, most container registries support the signing format.


In comparison, DCT stands out in scenarios where simplicity and seamless integration with Docker are essential. Its tight integration with Docker and intuitive commands make image signing and verification easy without the need for complex trust models or interoperability between different registries.


Notary v2 offers a more comprehensive solution for maintaining trust in the software supply chain and addresses the limitations of DCT. However, as a project still under development, it may require careful consideration and planning for implementation across an organization. The setup process and management of TLS certificates and TUF keys can be complex, requiring a steeper learning curve and becoming an operational burden if not configured properly. Moreover, not all registries support it, so you may have to make some compromises if you choose to use it.


**How to Sign with Cosign**
---------------------------


Now that you know a little more about Cosign, Notary, and DCT, we will take it one step further by using one of these tools: Cosign. For this example, we will use the simple Docker `registry:2` reference image to run a simple registry. In a real-world scenario, a managed registry such as [Harbor](https://goharbor.io/), [Amazon ECR](https://aws.amazon.com/ecr), [Docker Hub](https://hub.docker.com/), etc.


### **Prerequisite**


Before you begin, you need the following tools installed:


* [**Cosign**](https://docs.sigstore.dev/cosign/installation/)**:** The tool you'll use for signing and verifying container images.
* [**Docker**](https://docs.docker.com/engine/installation/)**:** A tool for building, running, and managing Docker containers


Once you have both of these tools installed, it's time to begin!


### **Set Up a Registry**


To store your images, you need to create an instance of the Docker `registry:2` reference image registry.



```
docker run -d -p 15000:5000 --name registry registry:2
```

Note: We are binding local port 15000 here to avoid conflicts — especially on Mac OS machines where recent releases used 5000 for AirDrop services — feel free to change this to any available port on your workstation if you like, but be sure to use the same port in later commands.


### **Push an Image to the Registry**


To sign a Docker image and push it to the registry, you need to follow these steps:


Pull any image from a public registry (in this example, [BusyBox](https://www.busybox.net/) is used):



```
docker pull busybox
```

This command retrieves the BusyBox image from the public Docker registry.


Next, you need to tag the image with the IP address of your local registry:



```
docker tag busybox localhost:15000/library/busybox:latest
```

Then, to upload the image, push the signed image to your Harbor registry:



```
docker push localhost:15000/library/busybox:latest
```

This command pushes the tagged image to your Harbor registry using the `docker push` command. Note in the response you should see a sha256 digest come back similar to the following:



```
The push refers to repository [localhost:15000/busybox]
3694737149b1: Pushed
latest: digest: sha256:1fa89c01cd0473cedbd1a470abb8c139eeb80920edf1bc55de87851bfb63ea11 size: 528
```

Copy the digest value, starting from the `sha256:` portion all of the way to the end of the string (don’t include the space or “size” following it). This is the cryptographic hash for the image, which we will use in the next step to ensure what we are signing is the exact image we just pushed.


Once you've followed these steps, you'll have successfully pushed a Docker image to your registry, making it available for deployment and distribution within your environment. However, you still need to sign it.


### **Sign the Pushed Image**


Before signing a pushed container image using Cosign, you need to generate private and public keys. Run the following command to generate the private and public key pair for signing:



```
cosign generate-key-pair
```

After running the previous command, you are prompted to enter a password for the private key. Enter a password and confirm it. The private key is saved in a file named `cosign.key`, and the public key is saved in `cosign.pub`. Be sure to store the private key and password you set securely.


Next, use the `cosign sign` command to sign the image like this:



```
cosign sign --key cosign.key localhost:15000/library/busybox@[sha256 digest]
```

If you’ve changed directories, replace `cosign.key` with the path to your private key file and `[sha256 digest]` with the full hash value copied from the above step. You are prompted to enter the password for the private key, so enter the password to proceed.


By signing the image using Cosign, you ensure the freshness, authenticity, and integrity of the image. This adds an additional layer of security to your container images and helps maintain trust in your software supply chain.


### **Demonstrate the Signed Image**


To demonstrate the signed image, you need to use the built-in `cosign verify` command like this:



```
cosign verify --key cosign.pub localhost:15000/library/busybox@[sha256 digest]
```

You will most likely be presented with a couple of Yes/No questions about using a private registry and accepting the Sigstore project’s terms of use. Read through them and answer accordingly.



```
WARNING: "localhost:15000/library/busybox" appears to be a private repository, please confirm uploading to the transparency log at "https://rekor.sigstore.dev"
Are you sure you would like to continue? [y/N] y

	The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/.
…
By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above.
Are you sure you would like to continue? [y/N] y
```

With successful validation of the signature, you'll see a confirmation message assuring you that the image is securely signed and verified. The confirmation message looks like this:



```
Verification for localhost:15000/library/busybox@sha256:1fa89c01cd0473cedbd1a470abb8c139eeb80920edf1bc55de87851bfb63ea11 --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - The signatures were verified against the specified public key

[{"critical":{"identity":{"docker-reference":"localhost:15000/library/busybox"},"image":{"docker-manifest-digest":"sha256:1fa89c01cd0473cedbd1a470abb8c139eeb80920edf1bc55de87851bfb63ea11"},"type":"cosign container image signature"},"optional":{"Bundle":{"SignedEntryTimestamp":"MEUCIQC9yxeo0hGggaWFwBKe4ERHz62emuyqIKNqa1kbPMvNqgIgVea7Fu1zFMqXGYWbacV+gJrVLDUTbchiDdKqcUpEgRk=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEi
…
```

This confirmation message is crucial for maintaining a strong security posture in your organization.


### **Signing Other Artifacts**


While container images are a crucial component of software supply chains, signing other software artifacts is equally important to ensure end-to-end security. In addition to container images, various types of software artifacts, such as executables, Java Archive (JAR) files, packages, and software bill of materials (SBOMs), can benefit from the signing process. For instance, here's a tutorial on how Cosign supports the signing of [SBOMs](https://edu.chainguard.dev/open-source/sigstore/cosign/how-to-sign-an-SBOM-with-cosign/).


Signing other artifacts provides the same benefits you've learned about already, including integrity, freshness, and provenance, helping you keep up with compliance, auditing, or reference. However, it's important to note that the process might not look as simple as your container signing, as in the example of SBOM, which is done via Cosign after attaching it to the container image hosted on a registry.


For different artifacts like JAR, tools like [Jarsigner](https://docs.oracle.com/javase/tutorial/deployment/jar/signing.html) can be used, which makes things a bit more complicated than traditional image signing, as you need to store and manage the complexity with a few more additional steps, such as generating, storing, moving, and securely utilizing keys and signatures.


**Conclusion**
--------------


Although the signing process for various artifacts may introduce additional complexities, it's crucial to understand that these complexities are implemented to enhance software artifacts' security, integrity, and compliance. By incorporating digital signatures and adhering to best practices for key management, organizations can establish trust, meet compliance requirements, and facilitate auditing and reference purposes to protect against major security risks.


To start mitigating the security of your images and gain protection from tampering, signing containers is a good place to start. The different tools discussed in this article cater to various user groups, and your choice should depend on the level of effectiveness and flexibility required by your security and compliance standards. Once image signing is implemented, you can use solutions to assist with scanning and auditing the security of image layers.


For instance, [Snyk](https://snyk.io/) is a developer security platform that prioritizes a seamless developer experience. It offers container image scanning capabilities, allowing users to identify known vulnerabilities in the packages included in their Docker image. This empowers users to proactively detect and address vulnerabilities before pushing the image to Docker Hub or any other registry, bolstering their overall security posture.