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.

cosign & GoReleaser logos

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.

cosign github secrets
cosign key-pair stored in GitHub repository secrets

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.

cosign signatures
example of archive signatures stored as part of the project's GitHub release

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