diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 00000000..e4d83efd --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "authenticodelint": { + "version": "0.13.0", + "commands": [ + "authlint" + ], + "rollForward": false + }, + "dotnet-validate": { + "version": "0.0.1-preview.537", + "commands": [ + "dotnet-validate" + ], + "rollForward": false + }, + "sign": { + "version": "0.9.1-beta.25379.1", + "commands": [ + "sign" + ], + "rollForward": false + } + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e087e75a..2659d38f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,10 @@ jobs: runs-on: ${{ matrix.runner }} outputs: + authenticodelint-version: ${{ steps.get-dotnet-tools-versions.outputs.authenticodelint-version }} dotnet-sdk-version: ${{ steps.setup-dotnet.outputs.dotnet-version }} + dotnet-sign-version: ${{ steps.get-dotnet-tools-versions.outputs.dotnet-sign-version }} + dotnet-validate-version: ${{ steps.get-dotnet-tools-versions.outputs.dotnet-validate-version }} permissions: attestations: write @@ -89,6 +92,26 @@ jobs: path: ./artifacts/package/release if-no-files-found: error + - name: Upload signing file list + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: runner.os == 'Windows' + with: + name: signing-config + path: internal/signing + if-no-files-found: error + + - name: Get .NET tools versions + id: get-dotnet-tools-versions + shell: pwsh + run: | + $manifest = (Get-Content "./.config/dotnet-tools.json" | Out-String | ConvertFrom-Json) + $authenticodelintVersion = $manifest.tools.authenticodelint.version + $dotnetSignVersion = $manifest.tools.sign.version + $dotnetValidateVersion = $manifest.tools.'dotnet-validate'.version + "authenticodelint-version=${authenticodelintVersion}" >> ${env:GITHUB_OUTPUT} + "dotnet-sign-version=${dotnetSignVersion}" >> ${env:GITHUB_OUTPUT} + "dotnet-validate-version=${dotnetValidateVersion}" >> ${env:GITHUB_OUTPUT} + validate-packages: needs: build-test runs-on: ubuntu-latest @@ -106,8 +129,10 @@ jobs: - name: Validate NuGet packages shell: pwsh + env: + DOTNET_VALIDATE_VERSION: ${{ needs.build-test.outputs.dotnet-validate-version }} run: | - dotnet tool install --global dotnet-validate --version 0.0.1-preview.304 --allow-roll-forward + dotnet tool install --global dotnet-validate --version ${env:DOTNET_VALIDATE_VERSION} --allow-roll-forward $packages = Get-ChildItem -Filter "*.nupkg" | ForEach-Object { $_.FullName } $invalidPackages = 0 foreach ($package in $packages) { @@ -121,15 +146,13 @@ jobs: exit 1 } - publish-feedz-io: + + sign: needs: [ build-test, validate-packages ] - runs-on: ubuntu-latest - if: | - github.event.repository.fork == false && - (github.ref_name == github.event.repository.default_branch || startsWith(github.ref, 'refs/tags/')) + runs-on: windows-latest environment: - name: feedz.io + name: azure-trusted-signing permissions: contents: read @@ -137,40 +160,81 @@ jobs: steps: - - name: Download packages + - name: Download unsigned packages uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: packages-windows + path: packages + + - name: Download signing configuration + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: signing-config + path: signing-config - name: Setup .NET SDK uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 with: dotnet-version: ${{ needs.build-test.outputs.dotnet-sdk-version }} + - name: Install Sign CLI tool + env: + DOTNET_SIGN_VERSION: ${{ needs.build-test.outputs.dotnet-sign-version }} + run: dotnet tool install --tool-path . sign --version ${env:DOTNET_SIGN_VERSION} + - uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0 - id: get-token + id: get-signing-secrets with: export_env: false repo_secrets: | - token=feedz-io:token + client-id=azure-trusted-signing:client-id + subscription-id=azure-trusted-signing:subscription-id + tenant-id=azure-trusted-signing:tenant-id + + - name: Azure log in + uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 + with: + client-id: ${{ fromJSON(steps.get-signing-secrets.outputs.secrets).client-id }} + subscription-id: ${{ fromJSON(steps.get-signing-secrets.outputs.secrets).subscription-id }} + tenant-id: ${{ fromJSON(steps.get-signing-secrets.outputs.secrets).tenant-id }} - - name: Push NuGet packages to feedz.io - shell: bash + - name: Sign artifacts + shell: pwsh env: - API_KEY: ${{ fromJSON(steps.get-token.outputs.secrets).token }} - SOURCE: 'https://f.feedz.io/${{ github.repository }}/nuget/index.json' - run: dotnet nuget push "*.nupkg" --api-key "${API_KEY}" --skip-duplicate --source "${SOURCE}" + APPLICATION_DESCRIPTION: 'Grafana OpenTelemetry distribution for .NET' + PUBLISHER_NAME: 'Grafana Labs' + TRUSTED_SIGNING_ACCOUNT: 'grafana-premium-eastus' + TRUSTED_SIGNING_ENDPOINT: 'https://eus.codesigning.azure.net/' + TRUSTED_SIGNING_PROFILE: 'grafana-production' + VERBOSITY: ${{ runner.debug == '1' && 'Debug' || 'Warning' }} + run: | + ./sign code trusted-signing ` + **/*.nupkg ` + --base-directory "${env:GITHUB_WORKSPACE}/packages" ` + --file-list "${env:GITHUB_WORKSPACE}/signing-config/filelist.txt" ` + --application-name ${env:APPLICATION_DESCRIPTION} ` + --publisher-name ${env:PUBLISHER_NAME} ` + --description ${env:APPLICATION_DESCRIPTION} ` + --description-url "${env:GITHUB_SERVER_URL}/${env:GITHUB_REPOSITORY}" ` + --trusted-signing-account ${env:TRUSTED_SIGNING_ACCOUNT} ` + --trusted-signing-certificate-profile ${env:TRUSTED_SIGNING_PROFILE} ` + --trusted-signing-endpoint ${env:TRUSTED_SIGNING_ENDPOINT} ` + --verbosity "${env:VERBOSITY}" + if ($LASTEXITCODE -ne 0) { + Write-Output "::error::Failed to sign NuGet packages" + exit 1 + } - publish-nuget: - needs: [ build-test, validate-packages ] - runs-on: ubuntu-latest - if: | - github.event.repository.fork == false && - startsWith(github.ref, 'refs/tags/') + - name: Upload signed packages + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: signed-packages + path: packages + if-no-files-found: error - environment: - name: NuGet.org - url: https://www.nuget.org/profiles/Grafana + validate-signed-packages: + needs: [ build-test, sign ] + runs-on: windows-latest permissions: contents: read @@ -181,23 +245,84 @@ jobs: - name: Download packages uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: - name: packages-windows + name: signed-packages - name: Setup .NET SDK uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 with: dotnet-version: ${{ needs.build-test.outputs.dotnet-sdk-version }} - - uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0 - id: get-token - with: - export_env: false - repo_secrets: | - token=nuget:token + - name: Validate NuGet packages + shell: pwsh + env: + DOTNET_VALIDATE_VERSION: ${{ needs.build-test.outputs.dotnet-validate-version }} + run: | + dotnet tool install --global dotnet-validate --version ${env:DOTNET_VALIDATE_VERSION} --allow-roll-forward + if ($LASTEXITCODE -ne 0) { + Write-Output "::error::Failed to install dotnet-validate tool." + exit 1 + } + $packages = Get-ChildItem -Filter "*.nupkg" | ForEach-Object { $_.FullName } + $invalidPackages = 0 + foreach ($package in $packages) { + dotnet validate package local $package + if ($LASTEXITCODE -ne 0) { + $invalidPackages++ + } + } + if ($invalidPackages -gt 0) { + Write-Output "::error::$invalidPackages NuGet package(s) failed validation." + exit 1 + } - - name: Push NuGet packages to NuGet.org - shell: bash + - name: Validate signatures + shell: pwsh env: - API_KEY: ${{ fromJSON(steps.get-token.outputs.secrets).token }} - SOURCE: 'https://api.nuget.org/v3/index.json' - run: dotnet nuget push "*.nupkg" --api-key "${API_KEY}" --skip-duplicate --source "${SOURCE}" + AUTHENTICODELINT_VERSION: ${{ needs.build-test.outputs.authenticodelint-version }} + run: | + dotnet tool install --global AuthenticodeLint --version ${env:AUTHENTICODELINT_VERSION} --allow-roll-forward + if ($LASTEXITCODE -ne 0) { + Write-Output "::error::Failed to install AuthenticodeLint tool." + exit 1 + } + $packages = Get-ChildItem -Filter "*.nupkg" | ForEach-Object { $_.FullName } + $invalidPackages = 0 + foreach ($package in $packages) { + $packageName = Split-Path $package -Leaf + $extractedNupkg = Join-Path "." "extracted" $packageName + Expand-Archive -Path $package -DestinationPath $extractedNupkg -Force + + $dlls = Get-ChildItem -Path $extractedNupkg -Filter "*.dll" -Recurse | ForEach-Object { $_.FullName } + + $invalidDlls = 0 + foreach ($dll in $dlls) { + authlint -in $dll -verbose + if ($LASTEXITCODE -ne 0) { + Write-Output "::warning::$dll in NuGet package $package failed signature validation." + $invalidDlls++ + } else { + Write-Output "$dll in NuGet package $package has a valid signature." + } + } + + if ($invalidDlls -gt 0) { + $invalidPackages++ + } else { + Write-Output "All $($dlls.Length) DLLs in NuGet package $package have valid signatures." + } + + dotnet nuget verify $package + + if ($LASTEXITCODE -ne 0) { + Write-Output "::warning::$package failed signature validation." + $invalidPackages++ + } else { + Write-Output "$package has a valid signature." + } + } + if ($invalidPackages -gt 0) { + Write-Output "::error::$invalidPackages NuGet package(s) failed signature validation." + exit 1 + } else { + Write-Output "All $($packages.Length) NuGet packages have valid signatures." + } diff --git a/internal/signing/filelist.txt b/internal/signing/filelist.txt new file mode 100644 index 00000000..351a8aca --- /dev/null +++ b/internal/signing/filelist.txt @@ -0,0 +1 @@ +**/Grafana*