Skip to content

Commit

Permalink
Merge pull request chinmina#60 from jamestelfer/signed-binaries
Browse files Browse the repository at this point in the history
feat: sign released binaries and images with `cosign`
  • Loading branch information
jamestelfer authored Sep 21, 2024
2 parents 734450e + a57c0c7 commit 58e68fe
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 10 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ on:
- "v*"

permissions:
# required for OIDC token used as the signing identity
id-token: write

# required to publish the release
contents: write

jobs:
Expand All @@ -27,6 +31,11 @@ jobs:
with:
go-version-file: go.mod

- name: Install Cosign
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.4.0'

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
Expand All @@ -41,3 +50,4 @@ jobs:
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
KO_DOCKER_REPO: "chinmina" # the DockerHub chinmina repository
63 changes: 53 additions & 10 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
version: 2

env:
- 'CI={{ (envOrDefault "CI" "false") }}'
- 'CHANGELOG_DISABLE={{ eq (envOrDefault "CI" "false") "false" }}'
- 'RELEASE_DISABLE={{ eq (envOrDefault "CI" "false") "false" }}'
- 'KO_DOCKER_REPO={{ envOrDefault "KO_DOCKER_REPO" (printf "ttl.sh/chinmina-prerelease/%d" .Now.Unix) }}'

builds:
- id: release
binary: chinmina-bridge
Expand All @@ -13,18 +19,39 @@ builds:
- amd64
- arm64

# Sign with cosign -- this picks up the OIDC token from the environment in GHA.
# If you do this locally, sign with an OAuth identity you don't mind being permanently
# published to a transparency log.
binary_signs:
- cmd: './ci-only.sh'
args:
- "cosign"
- "sign-blob"
- "${artifact}"
- "--bundle=${artifact}.cosign.bundle"
- "--yes" # needed on cosign 2.0.0+
output: false # the necessary output is the .cosign.bundle file

checksum:
name_template: "checksums.txt"

archives:
- format: tar.gz
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
files:
# cosign produces a bundle file to allow for verification of the artifacts
# this is included in the archive to allow for easier verification after download
- src: '{{ .ArtifactPath }}.cosign.bundle'
strip_parent: true

changelog:
disable: "{{ .Env.CHANGELOG_DISABLE }}"
use: github-native
sort: asc

release:
disable: "{{ .Env.RELEASE_DISABLE }}"

prerelease: auto
header: |
Distributions for this release are published as binaries and a Docker image.
Expand All @@ -40,26 +67,19 @@ release:
If needed, binaries of this build (including Mac) can be found below.
kos:
-
id: chinmina-bridge
- id: chinmina-bridge
build: release
working_dir: .
base_image: cgr.dev/chainguard/static

repository: chinmina
# repository is set using environment variables in the top-level env section
# (see above).

# Platforms to build and publish.
#
# Default: 'linux/amd64'
platforms:
- linux/amd64
- linux/arm64

# Tag to build and push.
# Empty tags are ignored.
#
# Default: 'latest'
# Templates: allowed
tags:
- "{{if not .Prerelease}}latest{{end}}"
- "{{.Tag}}"
Expand All @@ -74,3 +94,26 @@ kos:

# Whether to use the base path without the MD5 hash after the repository name.
base_import_paths: true

# Sign with cosign -- this picks up the OIDC token from the environment in GHA.
# If you do this locally, sign with an OAuth identity you don't mind being permanently
# published to a transparency log.
docker_signs:
- id: ko-signing

cmd: './ci-only.sh'
args:
- "cosign"
- "sign"
- "${artifact}"
- "--yes"

artifacts: all

ids:
# id of ko image above
- chinmina-bridge

# output is not necessary, as the signing is done in place, but it helps to
# provide the index in the transparency log.
output: true
11 changes: 11 additions & 0 deletions ci-only.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh
set -eu

if [ "${CI:-false}" != "true" ]; then
echo "CI environment not detected, skipping script execution:"
echo " --> $*"
exit 0
fi

# execute the parameters as the script
exec "$@"
72 changes: 72 additions & 0 deletions docs/releases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Release process

In short:

1. Releases are triggered by creating a release tag from `main`. This is currently manual.
2. Release tags conform to semantic versioning
3. Commits use conventional commit messages to aid in the changelog creation process
4. A GoReleaser pipeline is used to create the artifacts
5. All artifacts (binaries and images) are signed by the build process using `cosign`

## When is a release ready?

Releases are created on an as-needed basis. We prefer multiple, smaller releases over releases that have a greater number of changes.

A release is ready when:

- there are committed changes on `main`, and
- there is confidence in its stability.

Stability is a pre-requisite for merging, so there should not be significant questions about the appropriateness of a `main` release.

## Triggering a release

Releases are triggered via the creation of a semantic-versioned tag, in the format `vX.Y.Z`. Creation of a tag in this format triggers the automated release process.

Only repository administrators may create a tag in this format.

## Release signing

The [Sigstore][sigstore] ecosystem is leveraged for signing executable release outputs. ([Docs][sigstore-docs].)

- [`cosign`][cosign] is used as the signing CLI tool
- The [`fulcio`][fulcio] public-good instance is used for ephemeral signing certificates
- The [`rekor`][rekor] [public-good instance][rekor-search] is used for Certificate Transparency record publishing.

The signing process allows some useful attributes of the binaries to be verified:

- the provider of the identity for the build process (i.e. GitHub Actions)
- the build process that was used to generate them (both scripts and compute)
- the Git reference of the code that was used to build the binary

Releases are signed with `cosign`, with transparency records published to the [public-good Rekor instance].

[sigstore]: https://www.sigstore.dev/
[sigstore-docs]: https://docs.sigstore.dev/
[cosign]: https://github.com/sigstore/cosign?tab=readme-ov-file
[fulcio]: https://github.com/sigstore/fulcio?tab=readme-ov-file
[rekor]: https://github.com/sigstore/rekor?tab=readme-ov-file
[rekor-search]: https://search.sigstore.dev/

## Testing the release process

It is possible to run GoReleaser locally to test some of the release proceses.
(`goreleaser` must be available.)

```shell
# from the root of the local working copy
goreleaser release --clean --verbose --skip "announce,validate"
```

This will run the binary and image builds, and publish a temporary image to
[`ttl.sh`](https://ttl.sh/). Temporary images can be used in local testing with
`docker compose`.

Some processes are skipped when doing this:

- binary signing
- image signing
- changelog generation
- GitHub release creation

Thus release testing verifies a proportion of the GoReleaser configuration, and allows the image/binary builds to be integration tested.

0 comments on commit 58e68fe

Please sign in to comment.