Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/workflows/sign-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Sign release binaries

# Keyless-signs the release's Linux binaries with cosign. Keyless signing
# records the signer's OIDC identity (repo, workflow path, commit SHA, run id)
# in the public Rekor transparency log, so signing runs here, in the same
# repository that owns the release — the recorded identity is this repository.
#
# Triggered when a release is published. Because the release is published with
# a GitHub App installation token (not the default GITHUB_TOKEN, which does not
# trigger further workflows), this `release` event fires as expected.

on:
release:
types: [published]

permissions:
contents: write # upload signature bundles back to the release
id-token: write # OIDC token for keyless cosign / Fulcio

jobs:
sign:
name: Sign Linux binaries
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Install cosign
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
with:
cosign-release: 'v2.5.3'

- name: Sign raw Linux binaries (keyless cosign)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.event.release.tag_name }}
COSIGN_YES: "true"
run: |
set -euo pipefail
workdir="$(mktemp -d)"
cd "$workdir"

# Download the Linux binary tarballs attached by the upstream release
# job. The tarball is the distributed asset, but we sign the raw
# binary *inside* it so the signature verifies the extracted (and
# installed) binary directly, not just the archive.
gh release download "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--pattern 'secrets-engine-linux_*.tar.gz' \
--dir .

shopt -s nullglob
signed=0
for tarball in secrets-engine-linux_*.tar.gz; do
name="${tarball%.tar.gz}" # e.g. secrets-engine-linux_amd64
dir="extract/${name}"
mkdir -p "$dir"
tar -xzf "$tarball" -C "$dir"
bin="${dir}/secrets-engine"
[ -f "$bin" ] || { echo "::error::$tarball is missing secrets-engine"; exit 1; }
echo "Signing raw binary from $tarball"
# Sigstore bundle (protobuf spec) — verifiable by any Sigstore-conformant
# tool, not just the cosign CLI. Written as <name>.sigstore.json.
cosign sign-blob --yes --new-bundle-format \
--bundle "${name}.sigstore.json" "$bin"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] No per-file existence check that .sigstore.json was written before bulk upload

After cosign sign-blob runs, the script increments signed but does not verify that ${name}.sigstore.json actually exists on disk before proceeding to the next iteration. The bulk gh release upload *.sigstore.json *.sha256 at the end relies on the glob expanding correctly.

Under the active set -euo pipefail, a non-zero cosign exit aborts the script — so the common failure path is covered. The gap is the theoretical edge case where cosign sign-blob exits 0 but fails to write the bundle (e.g., a disk-full or permission error on the temp dir). In that case nullglob would cause *.sigstore.json to expand to nothing at upload time, and gh release upload would receive no file arguments and either fail or upload nothing silently.

A defensive guard after cosign sign-blob would close this gap:

[ -f "${name}.sigstore.json" ] || { echo "::error::cosign did not write ${name}.sigstore.json"; exit 1; }
[ -f "${name}.sha256" ]       || { echo "::error::sha256 file for ${name} not written"; exit 1; }

echo "$(sha256sum "$bin" | cut -d' ' -f1) secrets-engine" > "${name}.sha256"
signed=$((signed + 1))
done

if [ "$signed" -eq 0 ]; then
echo "::error::no Linux binary tarballs (secrets-engine-linux_*.tar.gz) found on release $TAG"
exit 1
fi

# Upload signature bundles and checksums back onto the release.
gh release upload "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--clobber \
*.sigstore.json \
*.sha256
Loading