Securing Docker Images with Cosign: Thwarting Malware in the Software Supply Chain

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

What Happens If Your Docker Image Is Tampered With?

As DevOps or Backend engineers, you’re likely familiar with the docker pull command from Docker Hub or private registries. But have you ever wondered: Is the image you just pulled actually the genuine build from your colleague? Or has it been intercepted and replaced by a hacker with crypto-mining malware or a backdoor?

This is the challenging puzzle of Supply Chain Security. Previously, Docker Content Trust (Notary v1) was the go-to solution, but it was notoriously difficult to implement due to its complex configuration. I once spent an entire morning trying to set it up, only to be met with constant errors. Cosign (part of the Sigstore project) arrived as a lifesaver, making image signing and verification as simple as using Git every day.

Recently, while upgrading my entire system from docker-compose v1 to v2, I realized that security right from the build step is vital. Just one small vulnerability can cause a server’s CPU to spike to 100% due to a malicious image. If you want to avoid “locking the stable door after the horse has bolted,” this article is for you.

Install Cosign in 30 Seconds

Cosign stands out because it’s a single executable binary. You don’t need to install complex servers or databases; just drop it into your local machine or CI/CD pipeline and it’s ready to go.

For Linux users, use this command to grab the latest version:

LATEST_VERSION=$(curl -s "https://api.github.com/repos/sigstore/cosign/releases/latest" | grep 'tag_name' | cut -d\" -f4)
curl -LO https://github.com/sigstore/cosign/releases/download/${LATEST_VERSION}/cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
sudo chmod +x /usr/local/bin/cosign

If you’re on macOS or Windows, it’s even easier:

# macOS
brew install cosign

# Windows
scoop install cosign

Once installed, type cosign version. If the version information appears, you’re locked and loaded.

Generating Key Pairs and Signing Your First Image

Cosign’s mechanism is very intuitive: You create a key pair consisting of a Private Key (for signing) and a Public Key (for verification).

1. Initialize the Key Set

Run the following command to generate the keys:

cosign generate-key-pair

The system will prompt you for a passphrase. Once finished, two files will appear in your directory: cosign.key (a priceless asset—never lose it) and cosign.pub (which can be shared with anyone).

2. Perform Digital Signing

Assume you have an image itfromzero/my-app:v1 pushed to Docker Hub. To sign it, run:

cosign sign --key cosign.key itfromzero/my-app:v1

After entering your password, you’ll see a new tag ending in .sig appear on the Registry. Cosign stores signatures as standard artifacts right alongside the original image. This approach is brilliant because it doesn’t require maintaining any additional storage infrastructure.

Verifying the Image: Stopping Risks Before Deployment

Signing is pointless if you don’t verify. On the server side or in your deployment scripts, you only need the cosign.pub file to check the image’s integrity.

Manual Verification

Use the following command to check if the image was indeed signed by you:

cosign verify --key cosign.pub itfromzero/my-app:v1

If the result returns JSON code with the message Verification for itfromzero/my-app:v1 -- Check the signatures..., you can rest easy. Conversely, if someone stealthily modifies the image content while keeping the v1 tag, the verify command will fail and halt the deployment process.

Automation with CI/CD

Don’t force developers to manually sign every release. Offload this task to GitHub Actions or GitLab CI.

Here is a real-world GitHub Actions configuration I use:

- name: Sign Docker image
  env:
    COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
  run: |
    echo "${{ secrets.COSIGN_KEY }}" > cosign.key
    cosign sign --key cosign.key user/repo:tag

Keyless Signing: The Pinnacle of Convenience

If managing cosign.key files is too much hassle, try Keyless Signing. This feature allows you to sign images using OIDC identities (like Google or GitHub accounts). Signatures are written to the Rekor public log, making verification transparent without the worry of losing a Private Key.

Real-World Lessons Learned

After using Cosign for projects at itfromzero, I’ve identified three critical takeaways:

  • Secure Key Management: Never store raw .key files in your source code. Prioritize using AWS KMS, Google KMS, or HashiCorp Vault to protect your signatures.
  • Kubernetes Enforcement: Manual verification is just the first step. Install Kyverno or Admission Controllers so the cluster automatically rejects any image without a valid signature.
  • Standard Workflow: Always scan for vulnerabilities with Trivy before signing. An image must be both “clean” (no vulnerabilities) and “authentic” (verified owner) before it’s allowed into production.

Implementing Cosign doesn’t slow down your workflow, but it provides absolute confidence every time you hit the deploy button. I hope these insights help you build a more robust software supply chain!

Share: