diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 96a442e..ccb17a1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,12 +7,14 @@ on: jobs: build: - name: Build and Release + name: Build binaries runs-on: ubuntu-latest - + permissions: + contents: read + steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all history for version info @@ -21,21 +23,115 @@ jobs: with: go-version: '1.21' - - name: Make build script executable - run: chmod +x scripts/build.sh + - name: Make scripts executable + run: chmod +x scripts/build.sh scripts/package.sh + + - name: Build binaries + run: SKIP_PACKAGE=1 ./scripts/build.sh ${GITHUB_REF#refs/tags/} + + - name: Upload unsigned build artifacts + uses: actions/upload-artifact@v4 + with: + name: unsigned-dist + path: dist/* + if-no-files-found: error + retention-days: 1 + + sign-windows: + name: Sign Windows binaries + runs-on: windows-2022 + needs: build + environment: release-signing + permissions: + contents: read + id-token: write + + steps: + - name: Download unsigned build artifacts + uses: actions/download-artifact@v4 + with: + name: unsigned-dist + path: dist + + - name: Azure login + uses: azure/login@v3 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Sign Windows binaries with Azure Artifact Signing + uses: azure/artifact-signing-action@v1 + with: + endpoint: ${{ vars.AZURE_CODESIGN_ENDPOINT }} + signing-account-name: ${{ vars.AZURE_CODESIGN_ACCOUNT_NAME }} + certificate-profile-name: ${{ vars.AZURE_CODESIGN_CERTIFICATE_PROFILE }} + files-folder: ${{ github.workspace }}\dist + files-folder-filter: git-flow-*-windows-*.exe + file-digest: SHA256 + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + + - name: Verify Windows signatures + shell: pwsh + run: | + $files = Get-ChildItem -Path "$env:GITHUB_WORKSPACE\dist" -Filter "git-flow-*-windows-*.exe" + if ($files.Count -eq 0) { + throw "No Windows executables found to verify." + } + + foreach ($file in $files) { + $signature = Get-AuthenticodeSignature -FilePath $file.FullName + if ($signature.Status -ne "Valid") { + Write-Error "Invalid signature for $($file.Name): $($signature.Status) $($signature.StatusMessage)" + exit 1 + } + Write-Host "Verified signature for $($file.Name)" + } + + - name: Upload signed build artifacts + uses: actions/upload-artifact@v4 + with: + name: signed-dist + path: dist/* + if-no-files-found: error + retention-days: 1 + + release: + name: Package and release + runs-on: ubuntu-latest + needs: sign-windows + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download signed build artifacts + uses: actions/download-artifact@v4 + with: + name: signed-dist + path: dist + + - name: Make packaging script executable + run: chmod +x scripts/package.sh + + - name: Restore Unix executable permissions + run: chmod +x dist/git-flow-*-darwin-* dist/git-flow-*-linux-* + + - name: Package signed artifacts + run: ./scripts/package.sh ${GITHUB_REF#refs/tags/} - name: Check if preview release id: check_preview run: | - if [[ ${{ github.ref }} =~ ^refs/tags/v.*-(alpha|beta|rc)\. ]]; then + if [[ "${GITHUB_REF_NAME}" =~ ^v.*-(alpha|beta|rc)\. ]]; then echo "is_preview=true" >> $GITHUB_OUTPUT else echo "is_preview=false" >> $GITHUB_OUTPUT fi - - name: Build binaries - run: ./scripts/build.sh ${GITHUB_REF#refs/tags/} - - name: Extract release notes from CHANGELOG id: changelog run: | @@ -79,4 +175,4 @@ jobs: ## Checksums SHA-256 checksums for the release artifacts are available in the checksums.txt file. env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/RELEASING.md b/RELEASING.md index ae89e0f..c192535 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -39,6 +39,28 @@ We follow [Keep a Changelog](https://keepachangelog.com/). Only **user-facing ch ## Release Process +### Release Signing Setup + +Windows release executables are signed with Azure Artifact Signing before archives and checksums are created. The release workflow uses GitHub Actions OIDC. + +Configure a GitHub environment named `release-signing` with these secrets: + +- `AZURE_CLIENT_ID` +- `AZURE_TENANT_ID` +- `AZURE_SUBSCRIPTION_ID` + +Configure the same environment with these variables: + +- `AZURE_CODESIGN_ENDPOINT` +- `AZURE_CODESIGN_ACCOUNT_NAME` +- `AZURE_CODESIGN_CERTIFICATE_PROFILE` + +The Azure identity must have the `Artifact Signing Certificate Profile Signer` role on the certificate profile. Configure the Entra federated credential for this GitHub environment subject: + +```text +repo:gittower/git-flow-next:environment:release-signing +``` + ### 1. Determine Version Bump Review commits since last release: @@ -93,10 +115,13 @@ git push origin vX.Y.Z The `.github/workflows/release.yml` workflow automatically: -1. Builds binaries for all platforms (darwin, linux, windows) -2. Creates GitHub release with artifacts -3. Generates checksums -4. Marks as prerelease if tag contains `-alpha`, `-beta`, or `-rc` +1. Builds raw binaries for all platforms (darwin, linux, windows) +2. Signs Windows `.exe` files with Azure Artifact Signing +3. Verifies Windows Authenticode signatures +4. Creates archives containing the signed Windows executables +5. Generates checksums after signing and packaging +6. Creates GitHub release with artifacts +7. Marks as prerelease if tag contains `-alpha`, `-beta`, or `-rc` ### 7. Update Homebrew Tap @@ -143,6 +168,7 @@ These are automatically marked as prereleases on GitHub. - [ ] Committed: `chore: Bump version to X.Y.Z` - [ ] Pushed to main - [ ] Created and pushed tag +- [ ] Verified Windows signing job succeeded - [ ] Verified GitHub release was created - [ ] Updated Homebrew tap (`./update_formula.rb`) - [ ] Updated website documentation (if commands changed) diff --git a/scripts/build.sh b/scripts/build.sh index 7775484..cc783be 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -euo pipefail + # Get version from command line or use "dev" as default VERSION=${1:-dev} BINARY_NAME="git-flow" @@ -64,31 +66,9 @@ if [[ ${#MISSING_BINARIES[@]} -gt 0 ]]; then exit 1 fi -# Create archives for each binary -echo "Creating archives..." - -# macOS -echo "Creating darwin archives..." -tar czf "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-darwin-amd64.tar.gz" -C "$BUILD_DIR" "${BINARY_NAME}-${VERSION}-darwin-amd64" -tar czf "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-darwin-arm64.tar.gz" -C "$BUILD_DIR" "${BINARY_NAME}-${VERSION}-darwin-arm64" - -# Linux -echo "Creating linux archives..." -tar czf "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-linux-amd64.tar.gz" -C "$BUILD_DIR" "${BINARY_NAME}-${VERSION}-linux-amd64" -tar czf "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-linux-arm64.tar.gz" -C "$BUILD_DIR" "${BINARY_NAME}-${VERSION}-linux-arm64" -tar czf "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-linux-386.tar.gz" -C "$BUILD_DIR" "${BINARY_NAME}-${VERSION}-linux-386" - -# Windows (using zip instead of tar.gz) -echo "Creating windows archives..." -if command -v zip >/dev/null 2>&1; then - (cd "$BUILD_DIR" && zip "${PACKAGE_NAME}-${VERSION}-windows-amd64.zip" "${BINARY_NAME}-${VERSION}-windows-amd64.exe") - (cd "$BUILD_DIR" && zip "${PACKAGE_NAME}-${VERSION}-windows-386.zip" "${BINARY_NAME}-${VERSION}-windows-386.exe") +if [[ "${SKIP_PACKAGE:-}" == "1" ]]; then + echo "Build complete! Binaries are in the $BUILD_DIR directory" else - echo "Warning: zip command not found, skipping Windows archives" + "$(dirname "$0")/package.sh" "$VERSION" + echo "Build complete! Artifacts are in the $BUILD_DIR directory" fi - -# Generate checksums -echo "Generating checksums..." -(cd "$BUILD_DIR" && shasum -a 256 * > "${PACKAGE_NAME}-${VERSION}-checksums.txt") - -echo "Build complete! Artifacts are in the $BUILD_DIR directory" \ No newline at end of file diff --git a/scripts/package.sh b/scripts/package.sh new file mode 100755 index 0000000..5a2e626 --- /dev/null +++ b/scripts/package.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +set -euo pipefail + +# Get version from command line or use "dev" as default +VERSION=${1:-dev} +BINARY_NAME="git-flow" +PACKAGE_NAME="git-flow-next" + +# Build directory +BUILD_DIR="dist" + +echo "Packaging $PACKAGE_NAME version $VERSION..." + +# Verify all binaries are present before packaging +echo "Verifying binaries..." +MISSING_BINARIES=() + +for binary in "${BINARY_NAME}-${VERSION}-darwin-amd64" "${BINARY_NAME}-${VERSION}-darwin-arm64" \ + "${BINARY_NAME}-${VERSION}-linux-amd64" "${BINARY_NAME}-${VERSION}-linux-arm64" "${BINARY_NAME}-${VERSION}-linux-386" \ + "${BINARY_NAME}-${VERSION}-windows-amd64.exe" "${BINARY_NAME}-${VERSION}-windows-386.exe"; do + if [[ ! -f "$BUILD_DIR/$binary" ]]; then + MISSING_BINARIES+=("$binary") + fi +done + +if [[ ${#MISSING_BINARIES[@]} -gt 0 ]]; then + echo "Error: The following binaries were not found:" + for binary in "${MISSING_BINARIES[@]}"; do + echo " - $binary" + done + exit 1 +fi + +# Remove generated artifacts for this version so checksums describe the final outputs. +rm -f "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-"*.tar.gz \ + "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-"*.zip \ + "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-checksums.txt" + +# Create archives for each binary +echo "Creating archives..." + +# macOS +echo "Creating darwin archives..." +tar czf "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-darwin-amd64.tar.gz" -C "$BUILD_DIR" "${BINARY_NAME}-${VERSION}-darwin-amd64" +tar czf "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-darwin-arm64.tar.gz" -C "$BUILD_DIR" "${BINARY_NAME}-${VERSION}-darwin-arm64" + +# Linux +echo "Creating linux archives..." +tar czf "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-linux-amd64.tar.gz" -C "$BUILD_DIR" "${BINARY_NAME}-${VERSION}-linux-amd64" +tar czf "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-linux-arm64.tar.gz" -C "$BUILD_DIR" "${BINARY_NAME}-${VERSION}-linux-arm64" +tar czf "$BUILD_DIR/${PACKAGE_NAME}-${VERSION}-linux-386.tar.gz" -C "$BUILD_DIR" "${BINARY_NAME}-${VERSION}-linux-386" + +# Windows (using zip instead of tar.gz) +echo "Creating windows archives..." +if command -v zip >/dev/null 2>&1; then + (cd "$BUILD_DIR" && zip "${PACKAGE_NAME}-${VERSION}-windows-amd64.zip" "${BINARY_NAME}-${VERSION}-windows-amd64.exe") + (cd "$BUILD_DIR" && zip "${PACKAGE_NAME}-${VERSION}-windows-386.zip" "${BINARY_NAME}-${VERSION}-windows-386.exe") +else + echo "Warning: zip command not found, skipping Windows archives" +fi + +# Generate checksums +echo "Generating checksums..." +(cd "$BUILD_DIR" && shasum -a 256 * > "${PACKAGE_NAME}-${VERSION}-checksums.txt") + +echo "Packaging complete! Artifacts are in the $BUILD_DIR directory"