diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c102662ecf1..6daa93882a7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,6 +35,7 @@ jobs: outputs: k6_version: ${{ steps.get_k6_version.outputs.k6_version }} go_version: ${{ steps.get_go_version.outputs.go_version }} + sign_windows_artifacts: ${{ steps.determine_windows_signing.outputs.sign_windows_artifacts }} steps: - name: Checkout code uses: actions/checkout@v5 @@ -47,7 +48,7 @@ jobs: INPUT_K6_VERSION: ${{ github.event.inputs.k6_version }} run: | set -x # Show exactly what commands are executed - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] && [[ "${INPUT_K6_VERSION}" != "" ]]; then + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]] && [[ "${INPUT_K6_VERSION}" != "" ]]; then VERSION="${INPUT_K6_VERSION}" echo "Building custom dev build with version '${VERSION}' from manual workflow_dispatch..." elif [[ "${GITHUB_REF}" =~ ^refs/tags/v.+$ ]]; then @@ -65,7 +66,7 @@ jobs: INPUT_GO_VERSION: ${{ github.event.inputs.go_version }} run: | set -x # Show exactly what commands are executed - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] && [[ "${INPUT_GO_VERSION}" != "" ]]; then + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]] && [[ "${INPUT_GO_VERSION}" != "" ]]; then GO_VERSION="${INPUT_GO_VERSION}" echo "Using custom Go version '${GO_VERSION}' from manual workflow_dispatch..." else @@ -75,6 +76,26 @@ jobs: echo "GO_VERSION=${GO_VERSION}" echo "go_version=${GO_VERSION}" >> $GITHUB_OUTPUT + # Secrets are unavailable when building from project forks, so this + # will fail for external PRs, even if we wanted to do it. And we don't. + # We are only going to sign packages that are built from the default branch + # or a version tag, or manually triggered dev builds, so we have enough + # assurance that package signing works, but don't sign every PR build. + - name: Determine whether to sign the Windows artifacts + id: determine_windows_signing + env: + SIGN_FILES: ${{ github.ref_name == github.event.repository.default_branch || startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' }} + run: | + set -x # Show exactly what commands are executed + if [[ "${SIGN_FILES}" == "true" ]]; then + echo "Windows artifacts will be signed" + sign_windows_artifacts="true" + else + echo "Windows artifacts will not be signed" + sign_windows_artifacts="false" + fi + echo "sign_windows_artifacts=${sign_windows_artifacts}" >> ${GITHUB_OUTPUT} + build: runs-on: ubuntu-latest needs: [configure] @@ -228,17 +249,111 @@ jobs: -t $DOCKER_IMAGE_ID:latest-with-browser \ -t ghcr.io/$GHCR_IMAGE_ID:latest-with-browser . - package-windows: + # Forks, PRs etc. won't actually sign the binary, but the workflow will run most of the same steps as + # GitHub Actions workflows don't support conditional `needs` so we have to run the signing step unconditionally. + sign-binaries: permissions: contents: read actions: read id-token: write # Required for Vault + env: + VERSION: ${{ needs.configure.outputs.k6_version }} + + environment: + name: azure-trusted-signing + runs-on: windows-latest defaults: run: shell: pwsh needs: [configure, build] + outputs: + binary_artifact_name: ${{ steps.assign-artifact-names.outputs.binary-artifact-name }} + windows_binary_artifact_name: ${{ steps.assign-artifact-names.outputs.windows-binary-artifact-name }} + steps: + - name: Download binaries + uses: actions/download-artifact@v4 + with: + name: binaries + path: dist + + - name: Unzip Windows binary + run: | + Expand-Archive -Path ".\dist\k6-${env:VERSION}-windows-amd64.zip" -DestinationPath .\packaging\ + + - name: Upload artifact for Windows installer build + uses: actions/upload-artifact@v4 + with: + name: windows-binary + path: 'packaging/k6-${{ env.VERSION }}-windows-amd64/k6.exe' + retention-days: 7 + if-no-files-found: error + + - name: Get secrets for Azure Trusted Signing + uses: grafana/shared-workflows/actions/get-vault-secrets@get-vault-secrets/v1.3.0 + id: get-signing-secrets + if: needs.configure.outputs.sign_windows_artifacts == 'true' + with: + export_env: false + repo_secrets: | + client-id=azure-trusted-signing:client-id + subscription-id=azure-trusted-signing:subscription-id + tenant-id=azure-trusted-signing:tenant-id + + - name: Sign Windows binary + uses: grafana/shared-workflows/actions/azure-trusted-signing@azure-trusted-signing/v1.0.0 + id: sign-artifacts + if: needs.configure.outputs.sign_windows_artifacts == 'true' + with: + application-description: 'Grafana k6' + artifact-to-sign: 'windows-binary' + azure-client-id: ${{ fromJSON(steps.get-signing-secrets.outputs.secrets).client-id }} + azure-subscription-id: ${{ fromJSON(steps.get-signing-secrets.outputs.secrets).subscription-id }} + azure-tenant-id: ${{ fromJSON(steps.get-signing-secrets.outputs.secrets).tenant-id }} + signed-artifact-name: 'windows-binary-signed' + + - name: Download signed Windows binary + uses: actions/download-artifact@v4 + if: needs.configure.outputs.sign_windows_artifacts == 'true' + with: + name: ${{ steps.sign-artifacts.outputs.artifact-name }} + path: 'packaging/k6-${{ env.VERSION }}-windows-amd64' + + # Re-zip the signed Windows binary to replace the original unsigned version + - name: Zip signed Windows binary + if: needs.configure.outputs.sign_windows_artifacts == 'true' + run: | + Compress-Archive -Path ".\packaging\*" -DestinationPath ".\dist\k6-${env:VERSION}-windows-amd64.zip" -Force + + - name: Upload signed artifacts + uses: actions/upload-artifact@v4 + if: needs.configure.outputs.sign_windows_artifacts == 'true' + with: + name: binaries-signed + path: dist/ + retention-days: 7 + if-no-files-found: error + + - name: Assign artifact name for Windows binary for installer build + id: assign-artifact-names + env: + BINARY_ARTIFACT_NAME: ${{ needs.configure.outputs.sign_windows_artifacts == 'true' && 'binaries-signed' || 'binaries' }} + WINDOWS_BINARY_ARTIFACT_NAME: ${{ needs.configure.outputs.sign_windows_artifacts == 'true' && steps.sign-artifacts.outputs.artifact-name || 'windows-binary' }} + run: | + echo "binary-artifact-name=${env:BINARY_ARTIFACT_NAME}" >> ${env:GITHUB_OUTPUT} + echo "windows-binary-artifact-name=${env:WINDOWS_BINARY_ARTIFACT_NAME}" >> ${env:GITHUB_OUTPUT} + + package: + permissions: + contents: read + actions: read + + runs-on: windows-latest + defaults: + run: + shell: pwsh + needs: [configure, build, sign-binaries] env: VERSION: ${{ needs.configure.outputs.k6_version }} steps: @@ -255,71 +370,93 @@ jobs: curl -Lso wix311-binaries.zip https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip Expand-Archive -Path .\wix311-binaries.zip -DestinationPath .\wix311\ echo "$pwd\wix311" | Out-File -FilePath $env:GITHUB_PATH -Append - - name: Download binaries + - name: Download Windows binary uses: actions/download-artifact@v5 with: - name: binaries - path: dist - - name: Unzip Windows binary - run: | - Expand-Archive -Path ".\dist\k6-$env:VERSION-windows-amd64.zip" -DestinationPath .\packaging\ - move .\packaging\k6-$env:VERSION-windows-amd64\k6.exe .\packaging\ - rmdir .\packaging\k6-$env:VERSION-windows-amd64\ + name: ${{ needs.sign-binaries.outputs.windows_binary_artifact_name }} + path: packaging - name: Create the MSI package run: | $env:VERSION = $env:VERSION -replace 'v(\d+\.\d+\.\d+).*','$1' pandoc -s -f markdown -t rtf -o packaging\LICENSE.rtf LICENSE.md cd .\packaging - candle.exe -arch x64 "-dVERSION=$env:VERSION" k6.wxs + candle.exe -arch x64 "-dVERSION=${env:VERSION}" k6.wxs light.exe -ext WixUIExtension k6.wixobj - - # GH secrets are unavailable when building from project forks, so this - # will fail for external PRs, even if we wanted to do it. And we don't. - # We are only going to sign packages that are built from master or a - # version tag, or manually triggered dev builds, so we have enough - # assurance that package signing works, but don't sign every PR build. - - if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' }} - uses: grafana/shared-workflows/actions/get-vault-secrets@get-vault-secrets/v1.3.0 - with: - repo_secrets: | - WIN_SIGN_CERT=winsign:WIN_SIGN_CERT - WIN_SIGN_PASS=winsign:WIN_SIGN_PASS - - - name: Sign Windows binary and .msi package - if: ${{ env.WIN_SIGN_CERT != '' && env.WIN_SIGN_PASS != '' }} - run: | - # Convert base64 certificate to PFX - $bytes = [Convert]::FromBase64String("${{ env.WIN_SIGN_CERT }}") - [IO.File]::WriteAllBytes("k6.pfx", $bytes) - - # Get the latest signtool executable - $SignTool = Get-ChildItem -Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Recurse -Filter signtool.exe | Where-Object { $_.DirectoryName -like "*\x64" } | Sort-Object -Descending | Select-Object -First 1 - - # Sign the Windows binary - & $SignTool sign /f k6.pfx /p "${{ env.WIN_SIGN_PASS }}" /tr "http://timestamp.digicert.com" /td sha256 /fd sha256 "packaging\k6.exe" - - # Sign the MSI package - & $SignTool sign /f k6.pfx /p "${{ env.WIN_SIGN_PASS }}" /tr "http://timestamp.digicert.com" /td sha256 /fd sha256 "packaging\k6.msi" - - # Cleanup signing artifacts - del k6.pfx - name: Rename MSI package # To keep it consistent with the other artifacts - run: move "packaging\k6.msi" "packaging\k6-$env:VERSION-windows-amd64.msi" + run: move "packaging\k6.msi" "packaging\k6-${env:VERSION}-windows-amd64.msi" - - name: Upload artifacts + - name: Upload Windows installer uses: actions/upload-artifact@v4 with: name: binaries-windows path: | packaging/k6-*.msi retention-days: 7 + if-no-files-found: error + + # Forks, PRs etc. won't actually sign the installer, but the workflow will run most of the same steps as + # GitHub Actions workflows don't support conditional `needs` so we have to run the signing step unconditionally. + sign-packages: + permissions: + actions: read + contents: read + id-token: write # Required for Vault + + environment: + name: azure-trusted-signing + + outputs: + artifact_name: ${{ steps.assign-artifact-name.outputs.artifact-name }} + + runs-on: windows-latest + defaults: + run: + shell: pwsh + needs: [configure, package] + steps: + - name: Download Windows artifacts + uses: actions/download-artifact@v4 + if: needs.configure.outputs.sign_windows_artifacts == 'true' + with: + name: binaries-windows + path: packaging + + - name: Get secrets for Azure Trusted Signing + uses: grafana/shared-workflows/actions/get-vault-secrets@get-vault-secrets/v1.3.0 + id: get-signing-secrets + if: needs.configure.outputs.sign_windows_artifacts == 'true' + with: + export_env: false + repo_secrets: | + client-id=azure-trusted-signing:client-id + subscription-id=azure-trusted-signing:subscription-id + tenant-id=azure-trusted-signing:tenant-id + + - name: Sign Windows installer + uses: grafana/shared-workflows/actions/azure-trusted-signing@azure-trusted-signing/v1.0.0 + id: sign-artifacts + if: needs.configure.outputs.sign_windows_artifacts == 'true' + with: + application-description: 'Grafana k6' + artifact-to-sign: 'binaries-windows' + azure-client-id: ${{ fromJSON(steps.get-signing-secrets.outputs.secrets).client-id }} + azure-subscription-id: ${{ fromJSON(steps.get-signing-secrets.outputs.secrets).subscription-id }} + azure-tenant-id: ${{ fromJSON(steps.get-signing-secrets.outputs.secrets).tenant-id }} + signed-artifact-name: 'binaries-windows-signed' + + - name: Assign artifact name for Windows installer + id: assign-artifact-name + env: + ARTIFACT_NAME: ${{ needs.configure.outputs.sign_windows_artifacts == 'true' && steps.sign-artifacts.outputs.artifact-name || 'binaries-windows' }} + run: | + echo "artifact-name=${env:ARTIFACT_NAME}" >> ${env:GITHUB_OUTPUT} publish-github: runs-on: ubuntu-latest - needs: [configure, build, package-windows] + needs: [configure, sign-binaries, sign-packages] if: ${{ startsWith(github.ref, 'refs/tags/v') && github.event_name != 'workflow_dispatch' }} env: VERSION: ${{ needs.configure.outputs.k6_version }} @@ -334,12 +471,12 @@ jobs: - name: Download binaries uses: actions/download-artifact@v5 with: - name: binaries + name: ${{ needs.sign-binaries.outputs.binary_artifact_name }} path: dist - name: Download Windows binaries uses: actions/download-artifact@v5 with: - name: binaries-windows + name: ${{ needs.sign-packages.outputs.artifact_name }} path: dist - name: Generate checksum file run: cd dist && sha256sum * > "k6-${VERSION}-checksums.txt" @@ -363,7 +500,7 @@ jobs: publish-packages: runs-on: ubuntu-latest - needs: [configure, build, package-windows] + needs: [configure, sign-binaries, sign-packages] if: ${{ startsWith(github.ref, 'refs/tags/v') && github.event_name != 'workflow_dispatch' }} env: VERSION: ${{ needs.configure.outputs.k6_version }} @@ -380,12 +517,12 @@ jobs: - name: Download binaries uses: actions/download-artifact@v5 with: - name: binaries + name: ${{ needs.sign-binaries.outputs.binary_artifact_name }} path: dist - name: Download Windows binaries uses: actions/download-artifact@v5 with: - name: binaries-windows + name: ${{ needs.sign-packages.outputs.artifact_name }} path: dist - name: Rename binaries # To be consistent with the filenames used in dl.k6.io @@ -409,15 +546,15 @@ jobs: - name: Setup docker compose environment run: | cat > packaging/.env < packaging/sign-key.gpg + echo "${PGP_SIGN_KEY}" > packaging/sign-key.gpg - name: Publish packages env: GITHUB_ACTOR: ${{ github.actor }}