Signing Docker images and archives for Go Projects
Signing software artifacts, such as Docker images and software archives, is becoming increasingly important nowadays, especially in the enterprise landscape, where companies are trying to comply to various certifications and compliance frameworks like FedRAMP. This process is essential for ensuring the integrity and authenticity of the software components used and developed within the organization. This blog post will introduce a way to automatize the signing process for your Go projects using Cosign and GoReleaser.
Cosign is a security tool for signing OCI images and artifacts to improve protection against supply chain attacks. It supports software artifact signing, verification, and storage in various registries. Cosign supports both long-lived key-pairs and keyless methods for signing. In this post, I will focus on using long-lived key-pairs for signing software artifacts of Go projects.
GoReleaser is a powerful tool for the automation of Go project releases. It can take care of building binaries, archives, and images for various platforms, publishing Homebrew recipes, creating GitHub Releases, and signing artifacts using Cosign. In the following paragraphs, I will show you how to configure GoReleaser to build archives and Docker images, sign them using Cosign, and publish them as part of a GitHub Release and all of that will be done as part of the GitHub workflow inside your GitHub repository.
First, we will bootstrap a GitHub repository with the Cosign key-pair. This can be done using the Cosign tool by running this command:
cosign generate-key-pair github://[owner]/[project]
This will create new repository secrets in your GitHub repository, containing the public and private keys, as well as the password
for the private key. We will use these secrets as part of the GitHub workflow, which will run GoReleaser to handle the release
orchestration. Additionally, a cosign.pub
file containing the public key will be generated. It is good practice to commit this file
to your GitHub repository.
The next step is to configure signing as part of the .goreleaser.yaml
configuration file. We will add this section to the
file:
signs:
- cmd: cosign
stdin: "{{ .Env.COSIGN_PASSWORD }}"
args:
- "sign-blob"
- "--key=env://COSIGN_PRIVATE_KEY"
- "--output-signature=${signature}"
- "${artifact}"
- "--yes" # needed on cosign 2.0.0+
artifacts: all
docker_signs:
- cmd: cosign
stdin: "{{ .Env.COSIGN_PASSWORD }}"
args:
- "sign"
- "--key=env://COSIGN_PRIVATE_KEY"
- "${artifact}"
- "--yes" # needed on cosign 2.0.0+
artifacts: all
Finally, we will configure a GitHub workflow, that will take care of running GoReleaser, when a new tag is pushed to the GitHub repository:
name: goreleaser
# Release is triggered upon pushing a new tag
on:
push:
tags:
- '*'
permissions: write-all
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.RELEASE_TOKEN }}
- uses: sigstore/cosign-installer@v3.7.0
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
COSIGN_PRIVATE_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
Now, whenever a new tag is created, a release will be created in your GitHub repository, and as part of the release assets, archive signatures will be stored. Also, the Docker image will be signed and it will contain a signature.
Verification of the signature can be done using cosign
tool and generated public key.
To verify a Docker image signature run:
cosign verify your-docker-image --key cosign.pub
To verify an archive signature:
cosign verify-blob --signature your-archive-signature.sig --key cosign.pub your-archive