25 April 2023

How to Sign Container Images Using Cosign

A demo of signing a container image using Cosign with a Cosign locally generated key and an AWS KMS key.

Mario Lunato
Mario Lunato Senior Security Engineer LinkedIn

Cosign is an open-source tool developed by the Sigstore project that provides a simple and secure way to sign software artifacts. (I explored the importance of signing software artifacts and how Cosign can help improve the security of the software supply chain in this recent blog post.) Signing software artifacts ensures its integrity and authenticity, and protects against tampering and other malicious activities. Cosign simplifies this process by providing an easy-to-use command-line tool for signing container images, by using open standards for key management and verification. In this post, we will dive deeper into Cosign and demonstrate how to use it to sign container images with different types of keys.

In particular, I will show you how to sign a container image using a local key pair generated by Cosign, and how to sign another image using a key pair stored in Amazon Web Services (AWS) Key Management Service (KMS), a managed service for creating and managing cryptographic keys and certificates. By using a KMS key, you can benefit from the scalability and durability of the AWS cloud, while still keeping your keys under your control and protected by strict security policies. We will also demonstrate how to verify the signatures using the corresponding public keys, and how to push the signed images to a Docker registry for distribution.

Whether you are a developer, a security practitioner, or an IT administrator, understanding the importance of software signing and supply chain security is critical for ensuring the trustworthiness of your software stack. By leveraging tools like Cosign and best practices like key management and verification, you can mitigate the risk of software vulnerabilities, supply chain attacks, and compliance issues. So, let’s get started and learn how to sign container images with Cosign and AWS KMS, and how to enhance the security of your software supply chain!

Signing a Container Image Using a Local Key

You’ll need to install Cosign first, and you will need access to a container registry for Cosign to work with. You can follow the install documentation provided by Sigstore to install Cosign for your respective OS. ttl.sh offers free, short-lived (ie: hours), anonymous container image hosting if you just want to try these commands out.

Once the installation is complete, you can verify that Cosign is installed correctly by running the following command:

$ cosign version
  ______   ______        _______. __    _______ .__   __.
 /      | /  __  \      /       ||  |  /  _____||  \ |  |
|  ,----'|  |  |  |    |   (----`|  | |  |  __  |   \|  |
|  |     |  |  |  |     \   \    |  | |  | |_ | |  . `  |
|  `----.|  `--'  | .----)   |   |  | |  |__| | |  |\   |
 \______| \______/  |_______/    |__|  \______| |__| \__|
cosign: A tool for Container Signing, Verification and Storage in an OCI registry.

GitVersion:    2.0.1
GitCommit:     8faaee4d2b5f65678eb0831a8a3d5990a0271d3a
GitTreeState:  “clean”
BuildDate:     2023-04-06T19:10:33Z
GoVersion:     go1.20.3
Compiler:      gc
Platform:      darwin/arm64

Next, generate a key pair to sign the container image with Cosign. Run the following command:

$ cosign generate-key-pair
Enter password for private key:
Enter password for private key again:
Private key written to cosign.key
Public key written to cosign.pub

To sign the container and store the signature in the registry along with the container image, run the following command in the CLI. Replace “mlunato47/cosign-demo:signed-local” with the name of your image and Docker repo:

$ docker push mlunato47/cosign-demo:signed-local
The push refers to repository [docker.io/mlunato47/cosign-demo]
26cbea5cba74: Mounted from library/alpine 
signed-local: digest: sha256:cafe8aee8c4ca63149ab4dc32af1c76192e0872583a800cce2dbbb3f35990bb0 size: 527

$ cosign sign --key cosign.key mlunato47/cosign-demo:signed-local
Enter password for private key:
Pushing signature to: index.docker.io/mlunato47/cosign-demo

Now that the image has been signed and pushed to the registry, the signature can be verified using the public key from the key pair generated earlier. To verify the signature, run the following command:

$ cosign verify --key cosign.pub mlunato47/cosign-demo:signed-local

Verification for index.docker.io/mlunato47/cosign-demo:signed-local --
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":"index.docker.io/mlunato47/cosign-demo"},"image":{"docker-manifest-digest":"sha256:cafe8aee8c4ca63149ab4dc32af1c76192e0872583a800cce2dbbb3f35990bb0"},"type":"cosign container image signature"},"optional":{"Bundle":{"SignedEntryTimestamp":"MEYCIQDJoYPqr0xQnib1V3TlD6WpKBNC7BXLxng05TLsJvOXjgIhALSkeeZRrSkz/hohF9U4TZAx7ba0VlAaccRdGNgMTcaP","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJkZDhlZWQyMTdkNTY4MTQ0ZTBiZTMxNGYxMDhjZTQ0ZTdlMDMwYzliODhlMWQwM2RhMjkyNDBhOWM2NmQ4NTM2In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUUNmdEFTYWVUQXV1WjNtQlBqK2oydG1lWkJjU1RiRjdVT0d4UmNjbUc2SnFnSWdlamNOTnNlczJNTkRhR05iODYzZzdLL1lPMGVrSEhXTmpHdnVYbHl2b1B3PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGVmpKcmREQlRhMXB3VVZsRFFuUmxXWEppY0ZsYWFIWjNXbTlaUkFwalNrWlRPRGxxTm1sU1JrbG5URXBUZW5WaVpsaE1iMEUwVTNaTWJUWlJRM2RQVFVOdE5uVnZNMEpEU0hsaFNuVmFUbUp2TWxsdldUVkJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=","integratedTime":1681847524,"logIndex":18308399,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}}]

Running Cosign locally and signing a container image is a straightforward process. After installing Cosign and running the appropriate commands, the image was signed and stored in the registry. With the added security of a verifiable signature, software supply chain attacks can be mitigated and trust can be established between teams, organizations, and end users.

This demo for local signing with the generated key pair is shown as an example of how to sign images if you do not have an AWS account for access to KMS. This SHOULD NOT be used for any type of production deployment and proper key management protections should be put in place.

Signing Container Images with AWS KMS and Cosign

If you’re using AWS, you can leverage AWS KMS to store and manage your keys, including those used to sign your container images with Cosign. You don’t have to worry about managing keys or storing them securely yourself with this method.

To sign a container image with an AWS KMS managed key, you first need to create a KMS key and set the necessary permissions. You’ll also need to ensure that you have the AWS command-line interface (CLI) tool installed and properly configured. You can follow this documentation provided by AWS on how to install the CLI tool for your respective OS. Keys can also be generated by using the AWS UI or using Infrastructure as Code (IAC) tools such as Terraform.

$ aws kms create-key --key-usage SIGN_VERIFY --customer-master-key-spec RSA_4096 --description "KMS key for signing images with Cosign"
{
    "KeyMetadata": {
        "AWSAccountId": "123456789012",
        "KeyId": "f5a95d9b-0a2f-4d5a-bc3e-01e16648a8a4",
        "Arn": "arn:aws:kms:us-east-1:123456789012:key/f5a95d9b-0a2f-4d5a-bc3e-01e16648a8a4",
        "CreationDate": "2023-04-18T18:13:00.684000-04:00",
        "Enabled": true,
        "Description": "KMS key for signing images with Cosign",
        "KeyUsage": "SIGN_VERIFY",
        "KeyState": "Enabled",
        "Origin": "AWS_KMS",
        "KeyManager": "CUSTOMER",
        "CustomerMasterKeySpec": "RSA_4096",
        "KeySpec": "RSA_4096",
        "SigningAlgorithms": [
            "RSASSA_PKCS1_V1_5_SHA_256",
            "RSASSA_PKCS1_V1_5_SHA_384",
            "RSASSA_PKCS1_V1_5_SHA_512",
            "RSASSA_PSS_SHA_256",
            "RSASSA_PSS_SHA_384",
            "RSASSA_PSS_SHA_512"
        ],
        "MultiRegion": false
    }
}

$ aws kms create-alias \
--alias-name alias/cosign-key \
--target-key-id f5a95d9b-0a2f-4d5a-bc3e-01e16648a8a4

{
    "AliasArn": "arn:aws:kms:us-west-2:123456789012:alias/cosign-key", 
    "AliasName": "alias/cosign-key", 
    "TargetKeyId": "f5a95d9b-0a2f-4d5a-bc3e-01e16648a8a4"
}

Once you have your KMS key set up, you can use Cosign to sign your container images. First, export the public key to verify the signature of the image. The public key can be exported via the AWS CLI with the following command:

$ aws kms get-public-key \
--key-id alias/cosign-key \
--output text \
--query 'PublicKey' | base64 –decode > cosignkms.pub

Signing an image using a KMS key is similar to using a local key but requires active credentials to AWS.

$ docker push mlunato47/cosign-demo:signed-kms
The push refers to repository [docker.io/mlunato47/cosign-demo]
943d74a92286: Pushed
signed-kms: digest: sha256:32b7371d10947b09944c6ce92634889118c406aec51c307b4a025aadfdecd1b18 size: 527

$ cosign sign --key aws-kms://alias/cosign-key mlunato47/cosign-demo:signed-kms 
                                                                                                
Pushing signature to: index.docker.io/mlunato47/cosign-demo

To use the public key that we exported earlier to verify the signature of the container image, run the following command:

$ cosign verify --key cosignkms.pub mlunato47/cosign-demo:signed-kms

Verification for index.docker.io/mlunato47/cosign-demo:signed-kms --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
[{"critical":{"identity":{"docker-reference":"index.docker.io/mlunato47/cosign-demo"},"image":{"docker-manifest-digest":"sha256:32b7371d10947b09944c6ce92634889118c406aec51c307b4a025aadfdecd1b1"},"type":"cosign container image signature"},"optional":null}]

Using AWS KMS to generate and store keys for signing container images with Cosign provides a secure way to manage your own keys while still enhancing the security and integrity of your software supply chain. Armed with Cosign, you can sign your container images and other software artifacts with confidence and rest easy knowing your software supply chain is secure.

Conclusion

Software supply chain attacks have become a growing concern in recent years, and it is crucial to secure the process of building and distributing software artifacts. One of the key ways to accomplish this is by digitally signing these artifacts. By doing so, developers can ensure their software has not been compromised and it has not been tampered with in transit.

In this blog, we used Cosign, an open-source tool from Sigstore, to demonstrate how to sign container images with local or AWS KMS-generated key pairs. Cosign not only provides a simple way to sign artifacts, but it also offers relief when it comes to a secure way to store and manage keys when using the keyless signing option. Additionally, by using Cosign, developers can achieve compliance with the Supply Chain Levels for Software Artifacts (SLSA) levels of security.

Overall, the process of signing software artifacts can be simple yet powerful, as it can prevent potential supply chain attacks and increase the trustworthiness of software. As more companies and organizations begin to recognize the importance of software security and supply chain integrity, we will likely see an increase in the adoption of digital signing tools like Cosign.

If you have any questions, or would like to discuss this topic in more detail, feel free to contact us and we would be happy to schedule some time to chat about how Aquia can help you and your organization.

Categories

Security Supply Chain Risk