diff --git a/.github/set_up_esrp.ps1 b/.github/set_up_esrp.ps1 index ca56266e3..abe9183e0 100644 --- a/.github/set_up_esrp.ps1 +++ b/.github/set_up_esrp.ps1 @@ -1,5 +1,5 @@ # Install ESRP client -az storage blob download --file esrp.zip --auth-mode login --account-name esrpsigningstorage --container signing-resources --name microsoft.esrpclient.1.2.76.nupkg +az storage blob download --file esrp.zip --auth-mode login --account-name $env:AZURE_STORAGE_ACCOUNT --container $env:AZURE_STORAGE_CONTAINER --name $env:ESRP_TOOL Expand-Archive -Path esrp.zip -DestinationPath .\esrp # Install certificates diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d0cd7bb5..82ec3b1b6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,27 +3,41 @@ name: release on: workflow_dispatch: +permissions: + id-token: write + contents: write + jobs: + prereqs: + name: Prerequisites + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - uses: actions/checkout@v4 + + - name: Set version + run: echo "version=$(cat VERSION | sed -E 's/.[0-9]+$//')" >> $GITHUB_OUTPUT + id: version + # ================================ -# macOS +# macOS # ================================ - osx-build: - name: Build macOS + create-macos-artifacts: + name: Create macOS artifacts runs-on: macos-latest environment: release + needs: prereqs strategy: matrix: runtime: [ osx-x64, osx-arm64 ] steps: - uses: actions/checkout@v4 - - name: Set up dotnet + - name: Set up .NET uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.201 - - - name: Install dependencies - run: dotnet restore + dotnet-version: 7.0.x - name: Build run: | @@ -41,221 +55,104 @@ jobs: --configuration=MacRelease --output=payload \ --symbol-output=symbols --runtime=${{ matrix.runtime }} - - name: Create keychain + - name: Set up signing/notarization infrastructure env: - CERT_BASE64: ${{ secrets.DEVELOPER_CERTIFICATE_BASE64 }} - CERT_PASSPHRASE: ${{ secrets.DEVELOPER_CERTIFICATE_PASSWORD }} - run: | + A1: ${{ secrets.APPLICATION_CERTIFICATE_BASE64 }} + A2: ${{ secrets.APPLICATION_CERTIFICATE_PASSWORD }} + I1: ${{ secrets.INSTALLER_CERTIFICATE_BASE64 }} + I2: ${{ secrets.INSTALLER_CERTIFICATE_PASSWORD }} + N1: ${{ secrets.APPLE_TEAM_ID }} + N2: ${{ secrets.APPLE_DEVELOPER_ID }} + N3: ${{ secrets.APPLE_DEVELOPER_PASSWORD }} + N4: ${{ secrets.APPLE_KEYCHAIN_PROFILE }} + run: | + echo "Setting up signing certificates" security create-keychain -p pwd $RUNNER_TEMP/buildagent.keychain security default-keychain -s $RUNNER_TEMP/buildagent.keychain security unlock-keychain -p pwd $RUNNER_TEMP/buildagent.keychain - echo $CERT_BASE64 | base64 -D > $RUNNER_TEMP/cert.p12 - security import $RUNNER_TEMP/cert.p12 -k $RUNNER_TEMP/buildagent.keychain -P $CERT_PASSPHRASE -T /usr/bin/codesign - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $RUNNER_TEMP/buildagent.keychain - - - name: Developer sign - env: - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - run: | - .github/run_developer_signing.sh payload $APPLE_TEAM_ID $GITHUB_WORKSPACE/src/osx/Installer.Mac/entitlements.xml - - - name: Upload macOS artifacts - uses: actions/upload-artifact@v3 - with: - name: tmp.${{ matrix.runtime }}-build - path: | - payload - symbols - - osx-payload-sign: - name: Sign macOS payload - # ESRP service requires signing to run on Windows - runs-on: windows-latest - environment: release - strategy: - matrix: - runtime: [ osx-x64, osx-arm64 ] - needs: osx-build - steps: - - uses: actions/checkout@v4 - - name: Download payload - uses: actions/download-artifact@v3 - with: - name: tmp.${{ matrix.runtime }}-build - - - name: Zip unsigned payload - shell: pwsh - run: | - Compress-Archive -Path payload payload/payload.zip - cd payload - Get-ChildItem -Exclude payload.zip | Remove-Item -Recurse -Force - - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - - name: Set up ESRP client - shell: pwsh - env: - AZURE_VAULT: ${{ secrets.AZURE_VAULT }} - AUTH_CERT: ${{ secrets.AZURE_VAULT_AUTH_CERT_NAME }} - REQUEST_SIGNING_CERT: ${{ secrets.AZURE_VAULT_REQUEST_SIGNING_CERT_NAME }} - run: | - .github\set_up_esrp.ps1 - - - name: Run ESRP client - shell: pwsh + echo $A1 | base64 -D > $RUNNER_TEMP/cert.p12 + security import $RUNNER_TEMP/cert.p12 \ + -k $RUNNER_TEMP/buildagent.keychain \ + -P $A2 \ + -T /usr/bin/codesign + security set-key-partition-list \ + -S apple-tool:,apple:,codesign: \ + -s -k pwd \ + $RUNNER_TEMP/buildagent.keychain + + echo $I1 | base64 -D > $RUNNER_TEMP/cert.p12 + security import $RUNNER_TEMP/cert.p12 \ + -k $RUNNER_TEMP/buildagent.keychain \ + -P $I2 \ + -T /usr/bin/productbuild + security set-key-partition-list \ + -S apple-tool:,apple:,productbuild: \ + -s -k pwd \ + $RUNNER_TEMP/buildagent.keychain + + echo "Setting up notarytool" + xcrun notarytool store-credentials \ + --team-id $N1 \ + --apple-id $N2 \ + --password $N3 \ + "$N4" + + - name: Run codesign against payload env: - AZURE_AAD_ID: ${{ secrets.AZURE_AAD_ID }} - APPLE_KEY_CODE: ${{ secrets.APPLE_KEY_CODE }} - APPLE_SIGNING_OP_CODE: ${{ secrets.APPLE_SIGNING_OPERATION_CODE }} + A3: ${{ secrets.APPLE_APPLICATION_SIGNING_IDENTITY }} run: | - python .github\run_esrp_signing.py payload ` - $env:APPLE_KEY_CODE $env:APPLE_SIGNING_OP_CODE ` - --params 'Hardening' '--options=runtime' - - - name: Unzip signed payload - shell: pwsh - run: | - Expand-Archive signed/payload.zip -DestinationPath signed - Remove-Item signed/payload.zip - - - name: Upload signed payload - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.runtime }}-payload-sign - path: | - signed - - osx-pack: - name: Package macOS payload - runs-on: macos-latest - strategy: - matrix: - runtime: [ osx-x64, osx-arm64 ] - needs: osx-payload-sign - steps: - - uses: actions/checkout@v4 - - - name: Set version environment variable - run: echo "VERSION=$(cat VERSION | sed -E 's/.[0-9]+$//')" >> $GITHUB_ENV - - - name: Set up dotnet - uses: actions/setup-dotnet@v3.2.0 - with: - dotnet-version: 6.0.201 - - - name: Download signed payload - uses: actions/download-artifact@v3 - with: - name: ${{ matrix.runtime }}-payload-sign + ./src/osx/Installer.Mac/codesign.sh "payload" "$A3" \ + "$GITHUB_WORKSPACE/src/osx/Installer.Mac/entitlements.xml" - name: Create component package run: | - src/osx/Installer.Mac/pack.sh --payload=payload \ - --version=$VERSION \ - --output=components/com.microsoft.gitcredentialmanager.component.pkg + src/osx/Installer.Mac/pack.sh --payload="payload" \ + --version="${{ needs.prereqs.outputs.version }}" \ + --output="components/com.microsoft.gitcredentialmanager.component.pkg" - - name: Create product archive - run: | - src/osx/Installer.Mac/dist.sh --package-path=components \ - --version=$VERSION --runtime=${{ matrix.runtime }} \ - --output=pkg/gcm-${{ matrix.runtime }}-$VERSION.pkg || exit 1 - - - name: Upload package - uses: actions/upload-artifact@v3 - with: - name: tmp.${{ matrix.runtime }}-pack - path: | - pkg - - osx-sign: - name: Sign and notarize macOS package - # ESRP service requires signing to run on Windows - runs-on: windows-latest - environment: release - strategy: - matrix: - runtime: [ osx-x64, osx-arm64 ] - needs: osx-pack - steps: - - uses: actions/checkout@v4 - - - name: Download unsigned package - uses: actions/download-artifact@v3 - with: - name: tmp.${{ matrix.runtime }}-pack - path: pkg - - - name: Zip unsigned package - shell: pwsh - run: | - Compress-Archive -Path pkg/*.pkg pkg/gcm-pkg.zip - cd pkg - Get-ChildItem -Exclude gcm-pkg.zip | Remove-Item -Recurse -Force - - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - - name: Set up ESRP client - shell: pwsh + - name: Create and sign product archive env: - AZURE_VAULT: ${{ secrets.AZURE_VAULT }} - AUTH_CERT: ${{ secrets.AZURE_VAULT_AUTH_CERT_NAME }} - REQUEST_SIGNING_CERT: ${{ secrets.AZURE_VAULT_REQUEST_SIGNING_CERT_NAME }} - run: | - .github\set_up_esrp.ps1 - - - name: Sign package - shell: pwsh - env: - AZURE_AAD_ID: ${{ secrets.AZURE_AAD_ID }} - APPLE_KEY_CODE: ${{ secrets.APPLE_KEY_CODE }} - APPLE_SIGNING_OP_CODE: ${{ secrets.APPLE_SIGNING_OPERATION_CODE }} + I3: ${{ secrets.APPLE_INSTALLER_SIGNING_IDENTITY }} run: | - python .github\run_esrp_signing.py pkg $env:APPLE_KEY_CODE $env:APPLE_SIGNING_OP_CODE - - - name: Unzip signed package - shell: pwsh - run: | - mkdir unsigned - Expand-Archive -LiteralPath signed\gcm-pkg.zip -DestinationPath .\unsigned -Force - Remove-Item signed\gcm-pkg.zip -Force + src/osx/Installer.Mac/dist.sh --package-path=components \ + --version="${{ needs.prereqs.outputs.version }}" \ + --runtime="${{ matrix.runtime }}" \ + --output="pkg/gcm-${{ matrix.runtime }}-${{ needs.prereqs.outputs.version }}.pkg" \ + --identity="$I3" || exit 1 - - name: Notarize signed package - shell: pwsh + - name: Notarize product archive env: - AZURE_AAD_ID: ${{ secrets.AZURE_AAD_ID }} - APPLE_KEY_CODE: ${{ secrets.APPLE_KEY_CODE }} - APPLE_NOTARIZATION_OP_CODE: ${{ secrets.APPLE_NOTARIZATION_OPERATION_CODE }} + N4: ${{ secrets.APPLE_KEYCHAIN_PROFILE }} run: | - python .github\run_esrp_signing.py unsigned $env:APPLE_KEY_CODE $env:APPLE_NOTARIZATION_OP_CODE --params 'BundleId' 'com.microsoft.gitcredentialmanager' + src/osx/Installer.Mac/notarize.sh \ + --package="pkg/gcm-${{ matrix.runtime }}-${{ needs.prereqs.outputs.version }}.pkg" \ + --keychain-profile="$N4" - - name: Publish signed package + - name: Upload artifacts uses: actions/upload-artifact@v3 with: - name: ${{ matrix.runtime }}-sign - path: signed/*.pkg + name: macos-${{ matrix.runtime }}-artifacts + path: | + ./pkg/* + ./symbols/* + ./payload/* # ================================ -# Windows +# Windows # ================================ - win-sign: - name: Build and Sign Windows + create-windows-artifacts: + name: Create Windows Artifacts runs-on: windows-latest environment: release + needs: prereqs steps: - uses: actions/checkout@v4 - - name: Set up dotnet + - name: Set up .NET uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.201 - - - name: Install dependencies - run: dotnet restore + dotnet-version: 7.0.x - name: Build run: | @@ -266,167 +163,145 @@ jobs: dotnet test --configuration=WindowsRelease - name: Lay out Windows payload and symbols - shell: pwsh run: | - cd src/windows/Installer.Windows/ - ./layout.ps1 -Configuration WindowsRelease -Output payload -SymbolOutput symbols - mkdir unsigned-payload - Get-ChildItem -Path payload/* -Include *.exe, *.dll | Move-Item -Destination unsigned-payload + cd $env:GITHUB_WORKSPACE\src\windows\Installer.Windows\ + ./layout.ps1 -Configuration WindowsRelease ` + -Output $env:GITHUB_WORKSPACE\payload ` + -SymbolOutput $env:GITHUB_WORKSPACE\symbols - - uses: azure/login@v1 + - name: Log into Azure + uses: azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - name: Set up ESRP client - shell: pwsh - env: - AZURE_VAULT: ${{ secrets.AZURE_VAULT }} - AUTH_CERT: ${{ secrets.AZURE_VAULT_AUTH_CERT_NAME }} - REQUEST_SIGNING_CERT: ${{ secrets.AZURE_VAULT_REQUEST_SIGNING_CERT_NAME }} - run: | - .github\set_up_esrp.ps1 - - - name: Run ESRP client for unsigned payload - shell: pwsh - env: - AZURE_AAD_ID: ${{ secrets.AZURE_AAD_ID }} - WINDOWS_KEY_CODE: ${{ secrets.WINDOWS_KEY_CODE }} - WINDOWS_OP_CODE: ${{ secrets.WINDOWS_OPERATION_CODE }} - run: | - python .github\run_esrp_signing.py ` - src/windows/Installer.Windows/unsigned-payload ` - $env:WINDOWS_KEY_CODE $env:WINDOWS_OP_CODE ` - --params 'OpusName' 'Microsoft' ` - 'OpusInfo' 'http://www.microsoft.com' ` - 'FileDigest' '/fd "SHA256"' 'PageHash' '/NPH' ` - 'TimeStamp' '/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256' - - - name: Lay out signed payload - shell: pwsh - run: | - mkdir signed-payload - Move-Item -Path signed/* -Destination signed-payload - # ESRP will not sign the *.exe.config or NOTICE files, but they are needed to build the installers. - # Due to this, we copy them after signing. - Get-ChildItem -Path src/windows/Installer.Windows/payload/* -Include *.exe.config, NOTICE | Move-Item -Destination signed-payload - Remove-Item signed -Recurse -Force + - name: Sign payload files with Azure Code Signing + uses: azure/azure-code-signing-action@v0.2.21 + with: + endpoint: https://wus2.codesigning.azure.net/ + code-signing-account-name: git-fundamentals-signing + certificate-profile-name: git-fundamentals-windows-signing + files-folder: ${{ github.workspace }}\payload + files-folder-filter: exe,dll + file-digest: SHA256 + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + + # The Azure Code Signing action overrides the .NET version, so we reset it. + - name: Set up .NET + uses: actions/setup-dotnet@v3.2.0 + with: + dotnet-version: 7.0.x - name: Build with signed payload - shell: pwsh run: | - dotnet build src/windows/Installer.Windows /p:PayloadPath=$env:GITHUB_WORKSPACE/signed-payload /p:NoLayout=true --configuration=WindowsRelease + dotnet build $env:GITHUB_WORKSPACE\src\windows\Installer.Windows ` + /p:PayloadPath=$env:GITHUB_WORKSPACE\payload /p:NoLayout=true ` + --configuration=WindowsRelease + mkdir installers + Move-Item -Path .\out\windows\Installer.Windows\bin\Release\net472\*.exe ` + -Destination $env:GITHUB_WORKSPACE\installers - - name: Run ESRP client for installers - shell: pwsh - env: - AZURE_AAD_ID: ${{ secrets.AZURE_AAD_ID }} - WINDOWS_KEY_CODE: ${{ secrets.WINDOWS_KEY_CODE }} - WINDOWS_OP_CODE: ${{ secrets.WINDOWS_OPERATION_CODE }} - run: | - python .github\run_esrp_signing.py ` - .\out\windows\Installer.Windows\bin\WindowsRelease\net472 ` - $env:WINDOWS_KEY_CODE ` - $env:WINDOWS_OP_CODE ` - --params 'OpusName' 'Microsoft' ` - 'OpusInfo' 'http://www.microsoft.com' ` - 'FileDigest' '/fd "SHA256"' 'PageHash' '/NPH' ` - 'TimeStamp' '/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256' - - - name: Publish final artifacts + - name: Sign installers with Azure Code Signing + uses: azure/azure-code-signing-action@v0.2.21 + with: + endpoint: https://wus2.codesigning.azure.net/ + code-signing-account-name: git-fundamentals-signing + certificate-profile-name: git-fundamentals-windows-signing + files-folder: ${{ github.workspace }}\installers + files-folder-filter: exe + file-digest: SHA256 + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + + - name: Upload artifacts uses: actions/upload-artifact@v3 with: - name: win-sign + name: windows-artifacts path: | - signed - signed-payload - src/windows/Installer.Windows/symbols + payload + installers + symbols # ================================ # Linux # ================================ - linux-build: - name: Build Linux + create-linux-artifacts: + name: Create Linux Artifacts runs-on: ubuntu-latest + environment: release + needs: prereqs steps: - uses: actions/checkout@v4 - - name: Setup .NET + - name: Set up .NET uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.201 - - - name: Install dependencies - run: dotnet restore + dotnet-version: 7.0.x - name: Build run: dotnet build --configuration=LinuxRelease - - name: Lay out + - name: Run Linux unit tests run: | - mkdir -p linux-build/deb linux-build/tar - mv out/linux/Packaging.Linux/Release/deb/*.deb linux-build/deb - mv out/linux/Packaging.Linux/Release/tar/*.tar.gz linux-build/tar + dotnet test --configuration=LinuxRelease - - name: Upload artifacts - uses: actions/upload-artifact@v3 + - name: Log into Azure + uses: azure/login@v1 with: - name: linux-build - path: | - linux-build + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - linux-sign: - name: Sign Linux tarball and Debian package - needs: linux-build - # ESRP service requires signing to run on Windows - runs-on: windows-latest - environment: release - steps: - - uses: actions/checkout@v4 + - name: Prepare for GPG signing + env: + AZURE_VAULT: ${{ secrets.AZURE_VAULT }} + GPG_KEY_SECRET_NAME: ${{ secrets.GPG_KEY_SECRET_NAME }} + GPG_PASSPHRASE_SECRET_NAME: ${{ secrets.GPG_PASSPHRASE_SECRET_NAME }} + GPG_KEYGRIP_SECRET_NAME: ${{ secrets.GPG_KEYGRIP_SECRET_NAME }} + run: | + # Install debsigs + sudo apt install debsigs - - name: Download artifacts - uses: actions/download-artifact@v3 - with: - name: linux-build + # Download GPG key, passphrase, and keygrip from Azure Key Vault + key=$(az keyvault secret show --name $GPG_KEY_SECRET_NAME --vault-name $AZURE_VAULT --query "value") + passphrase=$(az keyvault secret show --name $GPG_PASSPHRASE_SECRET_NAME --vault-name $AZURE_VAULT --query "value") + keygrip=$(az keyvault secret show --name $GPG_KEYGRIP_SECRET_NAME --vault-name $AZURE_VAULT --query "value") - - name: Remove symbols - run: | - rm tar/*symbols* + # Remove quotes from downloaded values + key=$(sed -e 's/^"//' -e 's/"$//' <<<"$key") + passphrase=$(sed -e 's/^"//' -e 's/"$//' <<<"$passphrase") + keygrip=$(sed -e 's/^"//' -e 's/"$//' <<<"$keygrip") - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + # Import GPG key + echo "$key" | base64 -d | gpg --import --no-tty --batch --yes - - name: Set up ESRP client - shell: pwsh - env: - AZURE_VAULT: ${{ secrets.AZURE_VAULT }} - AUTH_CERT: ${{ secrets.AZURE_VAULT_AUTH_CERT_NAME }} - REQUEST_SIGNING_CERT: ${{ secrets.AZURE_VAULT_REQUEST_SIGNING_CERT_NAME }} - run: | - .github\set_up_esrp.ps1 + # Configure GPG + echo "allow-preset-passphrase" > ~/.gnupg/gpg-agent.conf + gpg-connect-agent RELOADAGENT /bye + /usr/lib/gnupg2/gpg-preset-passphrase --preset "$keygrip" <<<"$passphrase" - - name: Run ESRP client - shell: pwsh - env: - AZURE_AAD_ID: ${{ secrets.AZURE_AAD_ID }} - LINUX_KEY_CODE: ${{ secrets.LINUX_KEY_CODE }} - LINUX_OP_CODE: ${{ secrets.LINUX_OPERATION_CODE }} + - name: Sign Debian package and tarball run: | - python .github/run_esrp_signing.py deb $env:LINUX_KEY_CODE $env:LINUX_OP_CODE - python .github/run_esrp_signing.py tar $env:LINUX_KEY_CODE $env:LINUX_OP_CODE + # Sign Debian package + version=${{ needs.prereqs.outputs.version }} + mv out/linux/Packaging.Linux/Release/deb/gcm-linux_amd64.$version.deb . + debsigs --sign=origin --verify --check gcm-linux_amd64.$version.deb - - name: Re-name tarball signature file - shell: bash - run: | - signaturepath=$(find signed/*.tar.gz) - mv "$signaturepath" "${signaturepath%.tar.gz}.asc" + # Generate tarball signature file + mv -v out/linux/Packaging.Linux/Release/tar/* . + gpg --batch --yes --armor --output gcm-linux_amd64.$version.tar.gz.asc \ + --detach-sig gcm-linux_amd64.$version.tar.gz - - name: Upload signed tarball and Debian package + - name: Upload artifacts uses: actions/upload-artifact@v3 with: - name: linux-sign + name: linux-artifacts path: | - signed + ./*.deb + ./*.asc + ./*.tar.gz # ================================ # .NET Tool @@ -434,13 +309,14 @@ jobs: dotnet-tool-build: name: Build .NET tool runs-on: ubuntu-latest + needs: prereqs steps: - uses: actions/checkout@v4 - - name: Setup .NET + - name: Set up .NET uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.201 + dotnet-version: 7.0.x - name: Build .NET tool run: | @@ -474,14 +350,20 @@ jobs: cd payload Get-ChildItem -Exclude payload.zip | Remove-Item -Recurse -Force - - uses: azure/login@v1 + - name: Log into Azure + uses: azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Set up ESRP client shell: pwsh env: AZURE_VAULT: ${{ secrets.AZURE_VAULT }} + AZURE_STORAGE_ACCOUNT: ${{ secrets.AZURE_STORAGE_ACCOUNT }} + AZURE_STORAGE_CONTAINER: ${{ secrets.AZURE_STORAGE_CONTAINER }} + ESRP_TOOL: ${{ secrets.ESRP_TOOL }} AUTH_CERT: ${{ secrets.AZURE_VAULT_AUTH_CERT_NAME }} REQUEST_SIGNING_CERT: ${{ secrets.AZURE_VAULT_REQUEST_SIGNING_CERT_NAME }} run: | @@ -515,30 +397,26 @@ jobs: dotnet-tool-pack: name: Package .NET tool runs-on: ubuntu-latest - needs: dotnet-tool-payload-sign + needs: [ prereqs, dotnet-tool-payload-sign ] steps: - uses: actions/checkout@v4 - - name: Set version environment variable - run: echo "VERSION=$(cat VERSION | sed -E 's/.[0-9]+$//')" >> $GITHUB_ENV - - - uses: actions/checkout@v4 - - name: Download signed payload uses: actions/download-artifact@v3 with: name: dotnet-tool-payload-sign path: signed - - name: Setup .NET + - name: Set up .NET uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.201 + dotnet-version: 7.0.x - name: Package tool run: | src/shared/DotnetTool/pack.sh --configuration=Release \ - --version=$VERSION --publish-dir=$(pwd)/signed + --version="${{ needs.prereqs.outputs.version }}" \ + --publish-dir=$(pwd)/signed - name: Upload unsigned package uses: actions/upload-artifact@v3 @@ -569,14 +447,20 @@ jobs: cd nupkg Get-ChildItem -Exclude gcm-nupkg.zip | Remove-Item -Recurse -Force - - uses: azure/login@v1 + - name: Log into Azure + uses: azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Set up ESRP client shell: pwsh env: AZURE_VAULT: ${{ secrets.AZURE_VAULT }} + AZURE_STORAGE_ACCOUNT: ${{ secrets.AZURE_STORAGE_ACCOUNT }} + AZURE_STORAGE_CONTAINER: ${{ secrets.AZURE_STORAGE_CONTAINER }} + ESRP_TOOL: ${{ secrets.ESRP_TOOL }} AUTH_CERT: ${{ secrets.AZURE_VAULT_AUTH_CERT_NAME }} REQUEST_SIGNING_CERT: ${{ secrets.AZURE_VAULT_REQUEST_SIGNING_CERT_NAME }} run: | @@ -612,19 +496,15 @@ jobs: matrix: component: - os: ubuntu-latest - artifact: linux-sign - command: git-credential-manager - description: debian - - os: ubuntu-latest - artifact: linux-build + artifact: linux-artifacts command: git-credential-manager - description: tarball + description: linux - os: macos-latest - artifact: osx-x64-sign + artifact: macos-osx-x64-artifacts command: git-credential-manager description: osx-x64 - os: windows-latest - artifact: win-sign + artifact: windows-artifacts # Even when a standalone GCM version is installed, GitHub actions # runners still only recognize the version bundled with Git for # Windows due to its placement on the PATH. For this reason, we use @@ -636,10 +516,15 @@ jobs: command: git-credential-manager description: dotnet-tool runs-on: ${{ matrix.component.os }} - needs: [ osx-sign, win-sign, linux-sign, dotnet-tool-sign ] + needs: [ create-macos-artifacts, create-windows-artifacts, create-linux-artifacts, dotnet-tool-sign ] steps: - uses: actions/checkout@v4 + - name: Set up .NET + uses: actions/setup-dotnet@v3.2.0 + with: + dotnet-version: 7.0.x + - name: Download artifacts uses: actions/download-artifact@v3 with: @@ -649,24 +534,24 @@ jobs: if: contains(matrix.component.description, 'windows') shell: pwsh run: | - $exePaths = Get-ChildItem -Path ./signed/*.exe | %{$_.FullName} + $exePaths = Get-ChildItem -Path ./installers/*.exe | %{$_.FullName} foreach ($exePath in $exePaths) { Start-Process -Wait -FilePath "$exePath" -ArgumentList "/SILENT /VERYSILENT /NORESTART" } - name: Install Linux (Debian package) - if: contains(matrix.component.description, 'debian') + if: contains(matrix.component.description, 'linux') run: | debpath=$(find ./*.deb) sudo apt install $debpath "${{ matrix.component.command }}" configure - name: Install Linux (tarball) - if: contains(matrix.component.description, 'tarball') + if: contains(matrix.component.description, 'linux') run: | # Ensure we find only the source tarball, not the symbols - tarpath=$(find ./tar -name '*[[:digit:]].tar.gz') + tarpath=$(find . -name '*[[:digit:]].tar.gz') tar -xvf $tarpath -C /usr/local/bin "${{ matrix.component.command }}" configure @@ -674,7 +559,7 @@ jobs: if: contains(matrix.component.description, 'osx-x64') run: | # Only validate x64, given arm64 agents are not available - pkgpath=$(find ./*.pkg) + pkgpath=$(find ./pkg/*.pkg) sudo installer -pkg $pkgpath -target / - name: Install .NET tool @@ -697,46 +582,60 @@ jobs: create-github-release: name: Publish GitHub draft release runs-on: ubuntu-latest + env: + AZURE_VAULT: ${{ secrets.AZURE_VAULT }} + GPG_PUBLIC_KEY_SECRET_NAME: ${{ secrets.GPG_PUBLIC_KEY_SECRET_NAME }} environment: release - needs: [ validate ] + needs: [ prereqs, validate ] steps: - uses: actions/checkout@v4 - - name: Set version environment variable - run: | - # Remove the "revision" portion of the version - echo "VERSION=$(cat VERSION | sed -E 's/.[0-9]+$//')" >> $GITHUB_ENV - - - name: Set up dotnet + - name: Set up .NET uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.201 + dotnet-version: 7.0.x - name: Download artifacts uses: actions/download-artifact@v3 - name: Archive macOS payload and symbols run: | + version="${{ needs.prereqs.outputs.version }}" mkdir osx-payload-and-symbols - tar -C osx-x64-payload-sign -czf osx-payload-and-symbols/gcm-osx-x64-$VERSION.tar.gz . - tar -C tmp.osx-x64-build/symbols -czf osx-payload-and-symbols/gcm-osx-x64-$VERSION-symbols.tar.gz . + tar -C macos-osx-x64-artifacts/payload -czf osx-payload-and-symbols/gcm-osx-x64-$version.tar.gz . + tar -C macos-osx-x64-artifacts/symbols -czf osx-payload-and-symbols/gcm-osx-x64-$version-symbols.tar.gz . - tar -C osx-arm64-payload-sign -czf osx-payload-and-symbols/gcm-osx-arm64-$VERSION.tar.gz . - tar -C tmp.osx-arm64-build/symbols -czf osx-payload-and-symbols/gcm-osx-arm64-$VERSION-symbols.tar.gz . + tar -C macos-osx-arm64-artifacts -czf osx-payload-and-symbols/gcm-osx-arm64-$version.tar.gz . + tar -C macos-osx-arm64-artifacts/symbols -czf osx-payload-and-symbols/gcm-osx-arm64-$version-symbols.tar.gz . - name: Archive Windows payload and symbols run: | + version="${{ needs.prereqs.outputs.version }}" mkdir win-x86-payload-and-symbols - zip -jr win-x86-payload-and-symbols/gcm-win-x86-$VERSION.zip win-sign/signed-payload - zip -jr win-x86-payload-and-symbols/gcm-win-x86-$VERSION-symbols.zip win-sign/src/windows/Installer.Windows/symbols + zip -jr win-x86-payload-and-symbols/gcm-win-x86-$version.zip windows-artifacts/payload + zip -jr win-x86-payload-and-symbols/gcm-win-x86-$version-symbols.zip windows-artifacts/symbols + + - name: Log into Azure + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Download GPG public key signature file + run: | + az keyvault secret show --name "$GPG_PUBLIC_KEY_SECRET_NAME" \ + --vault-name "$AZURE_VAULT" --query "value" \ + | sed -e 's/^"//' -e 's/"$//' | base64 -d >gcm-public.asc + mv gcm-public.asc linux-artifacts - uses: actions/github-script@v6 with: script: | const fs = require('fs'); const path = require('path'); - const version = process.env.VERSION + const version = "${{ needs.prereqs.outputs.version }}" var releaseMetadata = { owner: context.repo.owner, @@ -777,17 +676,16 @@ jobs: await Promise.all([ // Upload Windows artifacts - uploadDirectoryToRelease('win-sign/signed'), + uploadDirectoryToRelease('windows-artifacts/installers'), uploadDirectoryToRelease('win-x86-payload-and-symbols'), // Upload macOS artifacts - uploadDirectoryToRelease('osx-x64-sign'), - uploadDirectoryToRelease('osx-arm64-sign'), + uploadDirectoryToRelease('macos-osx-x64-artifacts/pkg'), + uploadDirectoryToRelease('macos-osx-arm64-artifacts/pkg'), uploadDirectoryToRelease('osx-payload-and-symbols'), // Upload Linux artifacts - uploadDirectoryToRelease('linux-build/tar'), - uploadDirectoryToRelease('linux-sign'), + uploadDirectoryToRelease('linux-artifacts'), // Upload .NET tool package uploadDirectoryToRelease('dotnet-tool-sign'), diff --git a/docs/install.md b/docs/install.md index b4989ba13..4268858bb 100644 --- a/docs/install.md +++ b/docs/install.md @@ -72,7 +72,7 @@ installation method. #### Install -Download the latest [.deb package][latest-release], and run the following: +Download the latest [.deb package][latest-release]*, and run the following: ```shell sudo dpkg -i @@ -86,13 +86,16 @@ git-credential-manager unconfigure sudo dpkg -r gcm ``` +*If you'd like to validate the package's signature after downloading, check out +the instructions [here][linux-validate-gpg-debian]. + --- ### Tarball #### Install -Download the latest [tarball][latest-release], and run the following: +Download the latest [tarball][latest-release]*, and run the following: ```shell tar -xvf -C /usr/local/bin @@ -106,6 +109,9 @@ git-credential-manager unconfigure rm $(command -v git-credential-manager) ``` +*If you would like to validate the tarball's signature after downloading, check +out the instructions [here][linux-validate-gpg-tarball]. + --- ### Install from source helper script @@ -238,4 +244,6 @@ dotnet tool uninstall -g git-credential-manager [git-for-windows-screenshot]: https://user-images.githubusercontent.com/5658207/140082529-1ac133c1-0922-4a24-af03-067e27b3988b.png [latest-release]: https://github.com/git-ecosystem/git-credential-manager/releases/latest [linux-uninstall]: linux-fromsrc-uninstall.md +[linux-validate-gpg-debian]: ./linux-validate-gpg.md#debian-package +[linux-validate-gpg-tarball]: ./linux-validate-gpg.md#tarball [ms-wsl]: https://aka.ms/wsl# diff --git a/docs/linux-validate-gpg.md b/docs/linux-validate-gpg.md new file mode 100644 index 000000000..49150c1e5 --- /dev/null +++ b/docs/linux-validate-gpg.md @@ -0,0 +1,85 @@ +# Validating GCM's GPG signature + +Follow the below instructions to import GCM's public key and use it to validate +the latest Debian package and/or tarball signature. + +## Debian package + +```shell +# Install needed packages +apt-get install -y curl debsig-verify + +# Download public key signature file +curl -s https://api.github.com/repos/git-ecosystem/git-credential-manager/releases/latest \ +| grep -E 'browser_download_url.*gcm-public.asc' \ +| cut -d : -f 2,3 \ +| tr -d \" \ +| xargs -I 'url' curl -L -o gcm-public.asc 'url' + +# De-armor public key signature file +gpg --output gcm-public.gpg --dearmor gcm-public.asc + +# Note that the fingerprint of this key is "3C853823978B07FA", which you can +# determine by running: +gpg --show-keys gcm-public.asc | head -n 2 | tail -n 1 | tail -c 17 + +# Copy de-armored public key to debsig keyring folder +mkdir /usr/share/debsig/keyrings/3C853823978B07FA +mv gcm-public.gpg /usr/share/debsig/keyrings/3C853823978B07FA/ + +# Create an appropriate policy file +mkdir /etc/debsig/policies/3C853823978B07FA +cat > /etc/debsig/policies/3C853823978B07FA/generic.pol << EOL + + + + + + + + + + + + + + + +EOL + +# Download Debian package +curl -s https://api.github.com/repos/git-ecosystem/git-credential-manager/releases/latest \ +| grep "browser_download_url.*deb" \ +| cut -d : -f 2,3 \ +| tr -d \" \ +| xargs -I 'url' curl -L -o gcm.deb 'url' + +# Verify +debsig-verify gcm.deb +``` + +## Tarball +```shell +# Download the public key signature file +curl -s https://api.github.com/repos/git-ecosystem/git-credential-manager/releases/latest \ +| grep -E 'browser_download_url.*gcm-public.asc' \ +| cut -d : -f 2,3 \ +| tr -d \" \ +| xargs -I 'url' curl -L -o gcm-public.asc 'url' + +# Import the public key +gpg --import gcm-public.asc + +# Download the tarball and its signature file +curl -s https://api.github.com/repos/ldennington/git-credential-manager/releases/latest \ +| grep -E 'browser_download_url.*gcm-linux.*[0-9].[0-9].[0-9].tar.gz' \ +| cut -d : -f 2,3 \ +| tr -d \" \ +| xargs -I 'url' curl -LO 'url' + +# Trust the public key +echo -e "5\ny\n" | gpg --command-fd 0 --expert --edit-key 3C853823978B07FA trust + +# Verify the signature +gpg --verify gcm-linux_amd64*.tar.gz.asc gcm-linux*.tar.gz +``` diff --git a/.github/run_developer_signing.sh b/src/osx/Installer.Mac/codesign.sh similarity index 90% rename from .github/run_developer_signing.sh rename to src/osx/Installer.Mac/codesign.sh index 8b3de88a3..d66c8acd9 100755 --- a/.github/run_developer_signing.sh +++ b/src/osx/Installer.Mac/codesign.sh @@ -26,9 +26,9 @@ for f in * do macho=$(file --mime $f | grep mach) # Runtime sign dylibs and Mach-O binaries - if [[ $f == *.dylib ]] || [ ! -z "$macho" ]; - then - echo "Runtime Signing $f" + if [[ $f == *.dylib ]] || [ ! -z "$macho" ]; + then + echo "Runtime Signing $f" codesign -s "$DEVELOPER_ID" $f --timestamp --force --options=runtime --entitlements $ENTITLEMENTS_FILE elif [ -d "$f" ]; then @@ -39,8 +39,8 @@ do codesign -s "$DEVELOPER_ID" $i --timestamp --force done cd .. - else + else echo "Signing $f" codesign -s "$DEVELOPER_ID" $f --timestamp --force fi -done \ No newline at end of file +done diff --git a/src/osx/Installer.Mac/dist.sh b/src/osx/Installer.Mac/dist.sh index c60d0767b..f26761e26 100755 --- a/src/osx/Installer.Mac/dist.sh +++ b/src/osx/Installer.Mac/dist.sh @@ -35,6 +35,10 @@ case "$i" in RUNTIME="${i#*=}" shift ;; + --identity=*) + IDENTITY="${i#*=}" + shift + ;; *) # unknown option ;; @@ -93,6 +97,7 @@ echo "Building product package..." --distribution "$DISTPATH" \ --identifier "$IDENTIFIER" \ --version "$VERSION" \ + ${IDENTITY:+"--sign"} ${IDENTITY:+"$IDENTITY"} \ "$DISTOUT" || exit 1 echo "Product build complete." diff --git a/src/osx/Installer.Mac/notarize.sh b/src/osx/Installer.Mac/notarize.sh new file mode 100755 index 000000000..9315d688a --- /dev/null +++ b/src/osx/Installer.Mac/notarize.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +for i in "$@" +do +case "$i" in + --package=*) + PACKAGE="${i#*=}" + shift # past argument=value + ;; + --keychain-profile=*) + KEYCHAIN_PROFILE="${i#*=}" + shift # past argument=value + ;; + *) + die "unknown option '$i'" + ;; +esac +done + +if [ -z "$PACKAGE" ]; then + echo "error: missing package argument" + exit 1 +elif [ -z "$KEYCHAIN_PROFILE" ]; then + echo "error: missing keychain profile argument" + exit 1 +fi + +# Exit as soon as any line fails +set -e + +# Send the notarization request +xcrun notarytool submit -v "$PACKAGE" -p "$KEYCHAIN_PROFILE" --wait + +# Staple the notarization ticket (to allow offline installation) +xcrun stapler staple -v "$PACKAGE"