Skip to content

Commit

Permalink
Create HSM testing workflow
Browse files Browse the repository at this point in the history
This commit is an initial prototype based on the deployment workflow, using the
Azure Code Signing service to sign Windows .exe and .msi files.

These changes have been isolated as much as possible to not affect existing
deployment workflows while also working around design issues with how GitHub
CLI workflow works with GoReleaser and now with ACS support.  The biggest smell
was over whether to break from using GoReleaser or have GoReleaser control as
much about the release process as it has been versus opening / signing /
archiving the resulting GoReleaser artifacts; needless to say, the latter was
chosen for expedience as well as leaning into officially supported solutions.
  • Loading branch information
andyfeller committed Dec 5, 2023
1 parent 3bb62d4 commit dea2cd5
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 0 deletions.
127 changes: 127 additions & 0 deletions .github/workflows/hsm-testing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: HSM Testing
run-name: ${{ inputs.tag_name }} / go ${{ inputs.go_version }}

concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: true

permissions:
contents: write

on:
workflow_dispatch:
inputs:
tag_name:
required: true
type: string
go_version:
default: "1.21"
type: string

jobs:
windows:
runs-on: windows-latest
environment: production
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ inputs.go_version }}
- name: Install GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
version: "~1.17.1"
install-only: true
- name: Build release binaries
shell: bash
env:
TAG_NAME: ${{ inputs.tag_name }}
run: script/release-hsm --local "$TAG_NAME" --platform windows --config .goreleaser-hsm.yml

# As official Azure HSM support for signing Windows .exe binaries is in the form of an action,
# we must unzip the archives created by GoReleaser, sign the binaries, and then re-zip them.
# This choice was due to the fact that GoReleaser produces
- name: Expand goreleaser archives for signing
shell: bash
run: |
for ZIP_FILE in dist/gh_*_windows_*.zip; do
unzip -d "${ZIP_FILE%.zip}" "$ZIP_FILE"
done
- name: Sign .exe release binaries
uses: azure/azure-code-signing-action@6c86237186b7eed50c9e8a3a6e42131bcc5e4601
with:
azure-tenant-id: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO_TENANT_ID }}
azure-client-id: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO_CLIENT_ID }}
azure-client-secret: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO }}
endpoint: https://wus.codesigning.azure.net/
code-signing-account-name: GitHubInc
certificate-profile-name: GitHubInc
files-folder: ${{ github.workspace }}/dist
files-folder-filter: exe
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: Zip goreleaser directories
shell: bash
run: |
for DIR in dist/gh_*_windows_*; do
zip -r "$DIR.zip" "$DIR"
done
- name: Set up MSBuild
id: setupmsbuild
uses: microsoft/[email protected]
- name: Build MSI
shell: bash
env:
MSBUILD_PATH: ${{ steps.setupmsbuild.outputs.msbuildPath }}
run: |
for ZIP_FILE in dist/gh_*_windows_*.zip; do
MSI_NAME="$(basename "$ZIP_FILE" ".zip")"
MSI_VERSION="$(cut -d_ -f2 <<<"$MSI_NAME" | cut -d- -f1)"
case "$MSI_NAME" in
*_386 )
source_dir="$PWD/dist/windows_windows_386"
platform="x86"
;;
*_amd64 )
source_dir="$PWD/dist/windows_windows_amd64_v1"
platform="x64"
;;
*_arm64 )
echo "skipping building MSI for arm64 because WiX 3.11 doesn't support it: https://github.com/wixtoolset/issues/issues/6141" >&2
continue
#source_dir="$PWD/dist/windows_windows_arm64"
#platform="arm64"
;;
* )
printf "unsupported architecture: %s\n" "$MSI_NAME" >&2
exit 1
;;
esac
"${MSBUILD_PATH}\MSBuild.exe" ./build/windows/gh.wixproj -p:SourceDir="$source_dir" -p:OutputPath="$PWD/dist" -p:OutputName="$MSI_NAME" -p:ProductVersion="${MSI_VERSION#v}" -p:Platform="$platform"
done
- name: Sign .msi release binaries
uses: azure/azure-code-signing-action@6c86237186b7eed50c9e8a3a6e42131bcc5e4601
with:
azure-tenant-id: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO_TENANT_ID }}
azure-client-id: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO_CLIENT_ID }}
azure-client-secret: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO }}
endpoint: https://wus.codesigning.azure.net/
code-signing-account-name: GitHubInc
certificate-profile-name: GitHubInc
files-folder: ${{ github.workspace }}/dist
files-folder-filter: msi
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- uses: actions/upload-artifact@v3
with:
name: windows
if-no-files-found: error
retention-days: 7
path: |
dist/*.zip
dist/*.msi
93 changes: 93 additions & 0 deletions .goreleaser-hsm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
project_name: gh

release:
prerelease: auto
draft: true # we only publish after the Windows MSI gets uploaded
name_template: "GitHub CLI {{.Version}}"

before:
hooks:
- >-
{{ if eq .Runtime.Goos "windows" }}echo{{ end }} make manpages GH_VERSION={{.Version}}
- >-
{{ if ne .Runtime.Goos "linux" }}echo{{ end }} make completions
builds:
- id: macos #build:macos
goos: [darwin]
goarch: [amd64, arm64]
hooks:
post:
- cmd: ./script/sign '{{ .Path }}'
output: true
binary: bin/gh
main: ./cmd/gh
ldflags:
- -s -w -X github.com/cli/cli/v2/internal/build.Version={{.Version}} -X github.com/cli/cli/v2/internal/build.Date={{time "2006-01-02"}}

- id: linux #build:linux
goos: [linux]
goarch: [386, arm, amd64, arm64]
env:
- CGO_ENABLED=0
binary: bin/gh
main: ./cmd/gh
ldflags:
- -s -w -X github.com/cli/cli/v2/internal/build.Version={{.Version}} -X github.com/cli/cli/v2/internal/build.Date={{time "2006-01-02"}}

- id: windows #build:windows
goos: [windows]
goarch: [386, amd64, arm64]
binary: bin/gh
main: ./cmd/gh
ldflags:
- -s -w -X github.com/cli/cli/v2/internal/build.Version={{.Version}} -X github.com/cli/cli/v2/internal/build.Date={{time "2006-01-02"}}

archives:
- id: linux-archive
builds: [linux]
name_template: "gh_{{ .Version }}_linux_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
wrap_in_directory: true
format: tar.gz
rlcp: true
files:
- LICENSE
- ./share/man/man1/gh*.1
- id: macos-archive
builds: [macos]
name_template: "gh_{{ .Version }}_macOS_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
wrap_in_directory: true
format: zip
rlcp: true
files:
- LICENSE
- ./share/man/man1/gh*.1
- id: windows-archive
builds: [windows]
name_template: "gh_{{ .Version }}_windows_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
wrap_in_directory: false
format: zip
rlcp: true
files:
- LICENSE

nfpms: #build:linux
- license: MIT
maintainer: GitHub
homepage: https://github.com/cli/cli
bindir: /usr
dependencies:
- git
description: GitHub’s official command line tool.
formats:
- deb
- rpm
contents:
- src: "./share/man/man1/gh*.1"
dst: "/usr/share/man/man1"
- src: "./share/bash-completion/completions/gh"
dst: "/usr/share/bash-completion/completions/gh"
- src: "./share/fish/vendor_completions.d/gh.fish"
dst: "/usr/share/fish/vendor_completions.d/gh.fish"
- src: "./share/zsh/site-functions/_gh"
dst: "/usr/share/zsh/site-functions/_gh"
120 changes: 120 additions & 0 deletions script/release-hsm
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/bin/bash
set -e

print_help() {
cat <<EOF
To tag a new release:
script/release [--staging] <tag-name> [--platform {linux|macos|windows}] [--branch <branch>]
To build staging binaries from the current branch:
script/release --current [--platform {linux|macos|windows}]
To build binaries locally with goreleaser:
script/release --local --platform {linux|macos|windows}
EOF
}

if [ $# -eq 0 ]; then
print_help >&2
exit 1
fi

tag_name=""
is_local=""
do_push=""
platform=""
branch="trunk"
deploy_env="production"
goreleaser_config=".goreleaser.yml"

while [ $# -gt 0 ]; do
case "$1" in
-h | --help )
print_help
exit 0
;;
-b | --branch )
branch="$2"
shift 2
;;
-c | --config )
goreleaser_config="$2"
shift 2
;;
-p | --platform )
platform="$2"
shift 2
;;
--local )
is_local=1
shift 1
;;
--staging )
deploy_env="staging"
shift 1
;;
--current )
deploy_env="staging"
tag_name="$(git describe --tags --abbrev=0)"
branch="$(git rev-parse --symbolic-full-name '@{upstream}' 2>/dev/null || git branch --show-current)"
branch="${branch#refs/remotes/*/}"
do_push=1
shift 1
;;
-* )
printf "unrecognized flag: %s\n" "$1" >&2
exit 1
;;
* )
tag_name="$1"
shift 1
;;
esac
done

announce() {
local tmpdir="${TMPDIR:-/tmp}"
echo "$*" | sed "s:${tmpdir%/}:\$TMPDIR:"
"$@"
}

trigger_deployment() {
announce gh workflow -R cli/cli run deployment.yml --ref "$branch" -f tag_name="$tag_name" -f environment="$deploy_env"
}

build_local() {
local config="$goreleaser_config"
case "$platform" in
linux )
sed '/#build:windows/,/^$/d; /#build:macos/,/^$/d' .goreleaser.yml >.goreleaser.generated.yml
config=".goreleaser.generated.yml"
;;
macos )
sed '/#build:windows/,/^$/d; /#build:linux/,/^$/d' .goreleaser.yml >.goreleaser.generated.yml
config=".goreleaser.generated.yml"
;;
windows )
sed '/#build:linux/,/^$/d; /#build:macos/,/^$/d' .goreleaser.yml >.goreleaser.generated.yml
config=".goreleaser.generated.yml"
;;
esac
[ -z "$tag_name" ] || export GORELEASER_CURRENT_TAG="$tag_name"
announce goreleaser release -f "$config" --clean --skip-validate --skip-publish --release-notes="$(mktemp)"
}

if [ -n "$is_local" ]; then
build_local
else
if [ -n "$do_push" ]; then
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "refusing to continue due to uncomitted local changes" >&2
exit 1
fi
announce git push
fi
trigger_deployment
if [ "$deploy_env" = "production" ]; then
echo
echo "Go to Slack to manually approve this production deployment."
fi
fi

0 comments on commit dea2cd5

Please sign in to comment.