Skip to content
Merged
Show file tree
Hide file tree
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
62 changes: 62 additions & 0 deletions .github/scripts/signexe.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash

set -e -o pipefail

# Authenticode-sign a Windows PE via golift/codesign (YubiKey-backed signerd).
# GoReleaser calls this from builds.hooks.post on windows binaries, after the
# CLI is installed into a temp GOBIN (never /usr/bin/codesign).
#
# Skip when CODESIGN_URL is unset so local snapshots still work.
# Prefer CODESIGN_BIN, then GOBIN/codesign, then GOPATH/bin, then PATH
# entries that are not Apple's /usr/bin/codesign.

function pick_codesign() {
if [ -n "${CODESIGN_BIN:-}" ]; then
echo "${CODESIGN_BIN}"
return
fi
if [ -n "${GOBIN:-}" ] && [ -x "${GOBIN}/codesign" ]; then
echo "${GOBIN}/codesign"
return
fi
gopath="$(go env GOPATH 2>/dev/null || true)"
if [ -n "${gopath}" ] && [ -x "${gopath}/bin/codesign" ]; then
echo "${gopath}/bin/codesign"
return
fi
while IFS= read -r p; do
case "$p" in
/usr/bin/codesign|/bin/codesign) continue ;;
esac
echo "$p"
return
done < <(type -a -p codesign 2>/dev/null || true)
return 1
}

function sign() {
case "${FILE}" in
*.exe) ;;
*)
# GoReleaser runs this hook for every GOOS. Only Authenticode-sign PE.
exit 0
;;
esac

if [ -z "${CODESIGN_URL:-}" ]; then
echo "Skipped signing ${FILE} (CODESIGN_URL unset) .." >&2
exit 0
fi

bin="$(pick_codesign)" || {
echo "CODESIGN_URL is set but golift codesign CLI not found (set CODESIGN_BIN)" >&2
exit 1
}

CODESIGN_NAME="${CODESIGN_NAME:-Go Lift Code Sign}" \
CODESIGN_WEBSITE="${CODESIGN_WEBSITE:-https://github.com/golift/codesign}" \
"${bin}" -- "${FILE}"
echo "Signed ${FILE} .." >&2
}

[ -z "$1" ] || FILE="$1" sign
53 changes: 50 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Release pipeline. Push a full semver tag (v1.2.3) and this workflow:
# 1. Builds and publishes binaries with GoReleaser.
# 1. Builds and publishes binaries with GoReleaser Pro (Authenticode on
# Windows PE when CODESIGN_URL is set; Apple sign+notarize on darwin
# when MACOS_SIGN_P12 is set).
# 2. Publishes the signerd Docker image to GHCR with the same tag.
# 3. Force-moves the floating tags (v1, v1.2) that GitHub Actions
# consumers track with `uses: golift/codesign@v1`, but only when this
Expand Down Expand Up @@ -44,13 +46,58 @@ jobs:
with:
go-version-file: go.mod
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: install-codesign
env:
GOBIN: ${{ runner.temp }}/codesign-bin
run: |
set -euo pipefail
mkdir -p "${GOBIN}"
go install ./cmd/codesign
echo "${GOBIN}" >> "${GITHUB_PATH}"
echo "CODESIGN_BIN=${GOBIN}/codesign" >> "${GITHUB_ENV}"
# GoReleaser Pro enables notarize when MACOS_SIGN_P12 is set, then
# skips the notary submit if issuer/key/id are empty and still exits 0.
# Fail here so a half-configured org cannot publish signed-only darwin.
- name: require-apple-sign-secrets
env:
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
run: |
set -euo pipefail
if [ -z "${MACOS_SIGN_P12}" ]; then
echo "MACOS_SIGN_P12 unset; Darwin archives will not be signed or notarized"
exit 0
fi
missing=
[ -n "${MACOS_SIGN_PASSWORD}" ] || missing="${missing} MACOS_SIGN_PASSWORD"
[ -n "${MACOS_NOTARY_KEY}" ] || missing="${missing} MACOS_NOTARY_KEY"
[ -n "${MACOS_NOTARY_KEY_ID}" ] || missing="${missing} MACOS_NOTARY_KEY_ID"
[ -n "${MACOS_NOTARY_ISSUER_ID}" ] || missing="${missing} MACOS_NOTARY_ISSUER_ID"
if [ -n "${missing}" ]; then
echo "MACOS_SIGN_P12 is set but Apple sign+notarize secrets are incomplete:${missing}" >&2
exit 1
fi
- uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
distribution: goreleaser-pro
version: '~> v2'
args: release --clean
args: release --clean --timeout 60m
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_PRO_KEY }}
CODESIGN_URL: ${{ secrets.CODESIGN_URL }}
CODESIGN_CLIENT_CERT: ${{ secrets.CODESIGN_CLIENT_CERT }}
CODESIGN_CLIENT_KEY: ${{ secrets.CODESIGN_CLIENT_KEY }}
CODESIGN_NAME: Go Lift Code Sign
CODESIGN_WEBSITE: https://github.com/golift/codesign
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
Comment thread
davidnewhall marked this conversation as resolved.
docker:
runs-on: ubuntu-latest
permissions:
Expand Down
58 changes: 51 additions & 7 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
version: 2

before:
hooks:
- go mod tidy

# Two small CGO-free binaries. signerd runs on the box with the YubiKey
# (Linux/unRAID/Docker or a Mac); codesign runs on CI runners and laptops.
# No Windows signerd on purpose, and these artifacts are NOT
# Authenticode-signed in v1 (chicken and egg). checksums.txt is signed
# keyless with cosign (Sigstore). The Docker image is a second build of
# the same commit (needs debian+pcscd); stamps use the git commit time so
# tarball and image version strings match.
# Two CGO-free binaries. signerd sits next to the YubiKey (Linux, macOS, or
# Windows); codesign is the CLI the Action and operators run. Windows PE is
# Authenticode-signed in a build hook when CODESIGN_URL is set (house
# signerd). Darwin universal binaries are Apple-signed and notarized when
# MACOS_SIGN_P12 is set (GoReleaser Pro / quill, works on Linux CI). Local
# snapshots skip both. checksums.txt is still signed keyless with cosign.
# The Docker image is a second build of the same commit (needs debian+pcscd);
# stamps use the git commit time so tarball and image version strings match.
builds:
- id: signerd
main: ./cmd/signerd
Expand All @@ -20,6 +22,7 @@ builds:
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
Expand All @@ -30,6 +33,9 @@ builds:
- -X "golift.io/version.BuildDate={{.CommitDate}}"
- -X "golift.io/version.Revision={{.FullCommit}}"
- -X "golift.io/version.Branch={{.ShortCommit}} [{{.Branch}}]"
hooks:
post:
- cmd: bash .github/scripts/signexe.sh "{{ .Path }}"
- id: codesign
main: ./cmd/codesign
binary: codesign
Expand All @@ -38,6 +44,7 @@ builds:
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
Expand All @@ -48,19 +55,56 @@ builds:
- -X "golift.io/version.BuildDate={{.CommitDate}}"
- -X "golift.io/version.Revision={{.FullCommit}}"
- -X "golift.io/version.Branch={{.ShortCommit}} [{{.Branch}}]"
hooks:
post:
- cmd: bash .github/scripts/signexe.sh "{{ .Path }}"

universal_binaries:
- id: signerd
ids: [signerd]
replace: true
name_template: signerd
- id: codesign
ids: [codesign]
replace: true
name_template: codesign

# Sign + notarize the macOS universal binaries (quill; works on Linux CI).
# Enabled only when MACOS_SIGN_P12 is set so local snapshots still work.
notarize:
macos:
- enabled: '{{ isEnvSet "MACOS_SIGN_P12" }}'
ids:
- signerd
- codesign
sign:
certificate: "{{ .Env.MACOS_SIGN_P12 }}"
password: "{{ .Env.MACOS_SIGN_PASSWORD }}"
notarize:
issuer_id: "{{ .Env.MACOS_NOTARY_ISSUER_ID }}"
key_id: "{{ .Env.MACOS_NOTARY_KEY_ID }}"
key: "{{ .Env.MACOS_NOTARY_KEY }}"
wait: true
timeout: 20m

archives:
- id: signerd
ids: [signerd]
formats: ['tar.gz']
name_template: 'signerd-{{ .Version }}.{{ .Os }}.{{ .Arch }}'
format_overrides:
- goos: windows
formats: ['zip']
files:
- src: LICENSE
dst: LICENSE.txt
- id: codesign
ids: [codesign]
formats: ['tar.gz']
name_template: 'codesign-{{ .Version }}.{{ .Os }}.{{ .Arch }}'
format_overrides:
- goos: windows
formats: ['zip']
files:
- src: LICENSE
dst: LICENSE.txt
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This module provides:

- A composite GitHub Action (`uses: golift/codesign@v1`).
- `signerd` — HTTP daemon next to the YubiKey (`osslsigncode` or
[jsign](https://github.com/ebourg/jsign)), on Linux (Docker/systemd) or macOS (launchd).
[jsign](https://github.com/ebourg/jsign)), on Linux (Docker/systemd), macOS (launchd), or Windows (Task Scheduler).
- `codesign` — CLI and Go client library used by the Action.

Remote requests require **both** gates:
Expand Down Expand Up @@ -47,8 +47,9 @@ jobs:

`@v1` tracks a floating tag and silently picks up new Action code. Pin by
commit SHA (`uses: golift/codesign@<sha>`) when you need a frozen install;
that is also why release checksums are cosign-signed and the image ships
SLSA provenance.
that is also why release checksums are cosign-signed, Windows PE in the
GitHub Release is Authenticode-signed, darwin universals are notarized, and
the image ships SLSA provenance.

Files are replaced in place. The operator must allowlist your `Owner/repo`
and issue a client certificate that chains to the proxy CA. Server-side docs:
Expand All @@ -57,6 +58,7 @@ and issue a client certificate that chains to the proxy CA. Server-side docs:
- mTLS: [docs/mtls.md](docs/mtls.md)
- nginx: [docs/nginx.md](docs/nginx.md)
- YubiKey facts: [docs/yubikey.md](docs/yubikey.md)
- Windows host: [docs/windows.md](docs/windows.md)

## Deploying the daemon

Expand All @@ -68,6 +70,8 @@ Start with [examples/signerd.toml.example](examples/signerd.toml.example):
- **unRAID**: [examples/unraid/signerd.xml](examples/unraid/signerd.xml) (Community Applications / user template).
- **systemd**: [examples/systemd/signerd.service.example](examples/systemd/signerd.service.example).
- **launchd** (macOS): [examples/launchd/signerd.plist.example](examples/launchd/signerd.plist.example).
- **Windows**: [docs/windows.md](docs/windows.md) +
[examples/windows/](examples/windows/).
- **nginx**: [examples/nginx/sign.conf](examples/nginx/sign.conf).

A `v1.0.0` release plants the floating `v1` tag the Action tracks.
Expand Down
Loading