Securing a Harbor Container Registry with TUF and Cosign

Hrittik Roy
Minute Read

Container adoption has been fueled by benefits like flexibility, scalability, and portability in cloud-native systems. When using containers, it's vital to make sure that updates are secure and reliable to protect the safety and security of your customers and systems.

The Update Framework (TUF) specification provides a framework for secure software updates that is designed to maintain security even if a software update server or a client machine is compromised. TUF achieves this through the use of a multilayered security system that verifies the authenticity and integrity of software updates. With TUF, you can secure your software update processes by ensuring that the updates are from legitimate sources and have not been tampered with. This helps protect against attacks that may attempt to insert malicious code into the update or mislead the update process into downloading and installing a compromised version of the software. Using TUF, you can have confidence that your software updates are secure and trustworthy.

In this article, you'll learn how to use TUF with Cosign to secure the container update processes in artifact registries like Harbor.

Why Sign Images with Cosign?

Cosign is a tool from Sigstore that helps you secure your container images by signing, verifying, and storing them in an Open Container Initiative (OCI) registry, using TUF as an implementation detail throughout the process for distributing keys. The goal is to have a "signature invisible" infrastructure that helps you protect against compromised container images and not have to worry about managing the signatures or the provenance metadata.

With attacks like image tampering, man-in-the-middle attacks, and image poisoning becoming more common, securing the overall distribution pipeline is essential, and unsigned container images can be a threat to the system as they can be tampered with or compromised during distribution. Systems that counter these attacks by automatically verifying the authenticity during release or distribution can help minimize security risks.

Using signatures also ensures that your images are safe to use, even if your image or artifact registries don't have security features or are compromised. The additional layer of security helps you in the following ways:

  • Authenticity: Verifying that images are from a trusted source and have not been tampered with.
  • Integrity: Ensuring container images are not modified in transit or at rest, maintaining trust across systems and preventing attacks like image poisoning.
  • Freshness: Ensuring container images are the newest version with security updates so that attackers can't trick users into blindly using an older, insecure version or updating to the same version, which maintains the security flaws for attackers to exploit even post-update.
  • How Cosign Works

    When a container image is pushed to a registry, it's given a tag that uniquely identifies its version. For example, latest is the tag in the image myimage:latest. To ensure the authenticity and integrity of the image, the registry stores its checksum, which looks something like sha256-a3ce1d751d313177aec574f83131e652466b2f6. This checksum acts as a unique identifier and helps to detect if the image has been tampered with.

    Cosign takes the extra step of signing the image and attaching a digest of the digital signature, a cryptographic hash generated with the help of a private key, to the image tag. This is done without the need for additional storage or management of signatures. When the image is pulled or used, the checksum is verified and the authenticity of the image is ensured.

    When verification is required, team members can share their public keys with each other to verify the authenticity of images, as long as they have access to the registry with the help of the cosign verify command.

    Alternatively, the OCI registry can perform the verification process to help you protect your images and verify their authenticity, integrity, and freshness.

    Why Secure Container Registries with TUF and Cosign?

    As supply chain security becomes increasingly important in modern environments, it's essential for organizations to adopt tools such as TUF and Cosign to ensure their security. Focusing on your artifact registries and ensuring that the container images currently in use are trustworthy is a good place to start improving your supply chain protection.

    Secure and reliable image signing processes are vital for the security of containerized applications to ensure that any compromised images or tampering can be detected before the images are deployed in production.

    However, many image registries do not have security features built in. This creates a security gap, as developers may not have the necessary tools to manage and distribute the metadata required for image signing. Cosign is a practical option for filling that gap, as it can be used with any image registry and integrates easily with existing workflows, allowing developers to ensure the integrity and freshness of their container images and reducing the risk of security breaches in their infrastructure.

    How to Secure a Harbor Container Registry with TUF and Cosign

    Harbor is an open source and OCI-compliant registry that comes equipped with built-in security features and artifact store sign validation.

    The following sections demonstrate how to secure a Harbor container registry with TUF and Cosign, using an example scenario of an organization using Cosign for image signing.

    Scenario: Organization Using Cosign for Image Signing

    In this example scenario, your organization relies on workloads that incorporate container images pushed by developers. If these images were compromised, an attacker could push a malicious image that causes damage to the system. This can be prevented with the help of end-to-end signing and verification that uses signatures to verify which images are safe to use and which are compromised. So, you opt to utilize Cosign for image signing and verification and Harbor as your container registry.

    Image signing

    To ensure that the images pushed by developers are authentic and not tampered with, you use Cosign's secure signing and verification with TUF to enforce a policy that only allows container images to be pushed after being signed with trusted keys. These verification and signing precautions will ensure that the images you use are protected and aren't compromised when used by your cluster.

    Considering this scenario, the remainder of the tutorial will cover how to set up and validate the security measures to make your supply chain more secure with the help of image signing.

    Prerequisites

    You'll need the following tools for the tutorial:

  • Docker to manage the containers and authenticate to Harbor
  • Docker Compose to run your Harbor Instance
  • Installing and Setting Up Harbor

    The first step is to create an instance of Harbor in your image registry to store your images. Harbor is flexible and can be installed locally in a variety of ways, such as using a Kubernetes cluster or bootstrapping with the help of manual installation. To install Harbor with Kubernetes, you need to install Helm and kubectl to operate and install the charts, which you can learn more about in this tutorial.

    For simplicity, this tutorial installs Harbor on a system running Ubuntu, but you can use any Linux system.

    First, get your hostname address for your Harbor instance using the echo $(hostname -I|cut -d" " -f 1) command, and the output should be similar to the following:

    hrittik@hrittik:~$ echo $(hostname -I|cut -d" " -f 1)
    10.0.0.4
    

    This IP is important for the next steps when configuring your harbor.yml file in your installer which you can download and extract using the following command:

    wget https://github.com/goharbor/harbor/releases/download/v2.7.1/harbor-online-installer-v2.7.1.tgz && tar -xvf harbor-online-installer-v2.7.1.tgz && cd harbor && cp harbor.yml.tmpl harbor.yml
    

    In the next step, you update the docker configuration to include insecure registries. In production, the installation process contains configuring TLS and you don’t need to add insecure registries but that will be overkill for this article so you’ll moving with the basic config:

    IP=$(hostname -I|cut -d" " -f 1)
    sudo tee /etc/docker/daemon.json >/dev/null <<EOF
    {
        "exec-opts": ["native.cgroupdriver=systemd"],
        "insecure-registries" : ["$IP:443","$IP:80","0.0.0.0/0"],
        "log-driver": "json-file",
        "log-opts": {
            "max-size": "100m"
        },
        "storage-driver": "overlay2"
    }
    EOF
    

    With the insecure registry setup, you can focus on configuring your harbor.yml to start the installation. There are two steps to it: First one is to set hostname to the IP you got in the hostname query step of installation like below in your YAML file:

    # The IP address or hostname to access admin UI and registry service.
    # DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
    
    hostname: 10.0.0.4
    

    Next step includes commenting out HTTPS configuration because that’s out of the scope of this article by adding # in front of the lines:

    # https related config
    #https:
      # https port for harbor, default is 443
    #  port: 443
      # The path of cert and key files for nginx
    #  certificate: /your/certificate/path
      # private_key: /your/private/key/path
    

    The final step for installation is to create your volume directory and then start the installation script which you can do with the below command:

    sudo mkdir -p /var/log/harbor
    sudo ./install.sh
    

    Once completed, you can access your Harbor instance at the IP address, and you can use the docker login command with the IP and your credentials like below:

    docker login -u admin -p Harbor12345 10.0.0.4
    

    Finally, you can access your Harbor Dashboard in your browser on the same IP and the same credentials as above:

    Harbor login page

    Setting Up Cosign

    With your OCI registry setup completed and authenticated, the next step is to install Cosign to sign your images. For a Linux distribution, you can follow the steps below, and installation instructions for other distributions are listed in the official documentation:

    wget "https://github.com/sigstore/cosign/releases/download/v2.0.0/cosign-linux-amd64" &&
    mv cosign-linux-amd64 /usr/local/bin/cosign &&
    chmod +x /usr/local/bin/cosign
    

    You can use cosign version to validate your installation, as shown below:

    hrittik@hrittik:~$ cosign version
      ______   ______        _______. __    _______ .__   __.
     /      | /  __  \      /       ||  |  /  _____||  \ |  |
    |  ,----'|  |  |  |    |   (----`|  | |  |  __  |   \|  |
    |  |     |  |  |  |     \   \    |  | |  | |_ | |  . `  |
    |  `----.|  `--'  | .----)   |   |  | |  |__| | |  |\   |
     \______| \______/  |_______/    |__|  \______| |__| \__|
    cosign: A tool for Container Signing, Verification and Storage in an OCI registry.
    
    GitVersion:    v2.0.0
    GitCommit:     d6b9001f8e6ed745fb845849d623274c897d55f2
    GitTreeState:  clean
    BuildDate:     2023-02-23T19:26:35Z
    GoVersion:     go1.20.1
    Compiler:      gc
    Platform:      linux/amd64
    

    Cosign uses asymmetric encryption, and you need to create private keys for signing to work, which can be done using the cosign generate-key-pair command:

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

    Note: The password you set in the key creation phase will be required when signing images in the next steps, so store it in a safe place.

    Pushing an Unsigned Image

    Harbor is a registry similar to Docker Hub, but with additional benefits and the ability to self-host. The images that you push to Docker Hub are not signed, so you can pull a simple Nginx image and push it without signing to validate an invalid signature.

    The first step is to pull an Nginx image from Docker Hub using the following command:

    docker pull nginx
    

    From the Harbor installation step, you should have obtained an IP address that you can use to tag the image with the IP address of the registry:

    docker tag nginx 10.0.0.4/library/nginx:latest
    

    Pushing the image is as easy as using the docker push command with the tagged image, and you can find the image on your Harbor registry:

    docker push 10.0.0.4/library/nginx:latest
    

    Not signed by Cosign

    The "Signed by Cosign" section has a red mark indicating that the image has not been signed and that there may be security issues if the image remains unsigned.

    Signing Images

    In environments where security should be maintained, you can use the Cosign CLI to verify the images for freshness, authenticity, and integrity. You can use the cosign sign command to sign an image:

    cosign sign -key <private key> <image>
    

    In the following snippet, cosign.key refers to the path of the private key file that you generated in the previous stages, and 10.0.0.4/library/nginx:latest is the fully qualified name of the Nginx image that you tagged with the IP address of your Harbor registry:

    hrittik@hrittik:~$ cosign sign --key cosign.key 10.0.0.4/library/nginx:latest
    Enter password for private key: 
    Pushing signature to: 10.0.0.4/library/nginx
    

    Refresh Harbor to see a green check mark signifying that the image has been validated and is secure:

    Signed by Cosign

    To verify the signature of the image against the public key, use the cosign verify command with the image and the public key:

    cosign verify --key cosign.pub 10.0.0.4/library/nginx:latest
    

    If the signature is valid, it will return a message confirming that the image is signed and verified, as you can see in the example below:

    Verification for 10.0.0.4/library/nginx:latest --
    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":"10.0.0.4/library/nginx"},"image":{"docker-manifest-digest":"sha256:7f797701ded5055676d656f11071f84e2888548a2e7ed12a4977c28ef6114b17"},"type":"cosign container image signature"},"optional":null}]
    

    With validations, you can be confident that your images have not been tampered with, and your organization has a robust security posture.

    Conclusion

    In this article, you learned how Cosign can help you validate your images and enhance the security of container image verification by providing a more robust method of ensuring the authenticity and integrity of container images, which is particularly important for organizations that prioritize security in their workflows.

    Cosign can use TUF to access public key materials and distribute them for authentication, and an OCI registry like Harbor can position you to be safe from attacks in the case of registry attacks or configure container trust to only allow signed images to be deployed for additional security.

    For more information about Cosign and how you can use it with other key management systems for better key management, you can go through the documentation.

    Sign up for our newsletter

    Be the first to know about new features, announcements and industry insights.