From 56ac203570fd9412f6bd3447f7d0ad4f0434d4a8 Mon Sep 17 00:00:00 2001 From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com> Date: Mon, 17 Jun 2024 23:37:53 +0100 Subject: [PATCH] Attest contents of NuGet packages (#87) --- .github/workflows/assert-contents.sh | 14 ++++ .github/workflows/dotnet.yaml | 113 ++++++++++++++++++++++++--- .github/workflows/nuget-push.sh | 24 ++++++ 3 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/assert-contents.sh create mode 100644 .github/workflows/nuget-push.sh diff --git a/.github/workflows/assert-contents.sh b/.github/workflows/assert-contents.sh new file mode 100644 index 0000000..ebefdfb --- /dev/null +++ b/.github/workflows/assert-contents.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +echo "Unzipping version from NuGet" +ls from-nuget.nupkg +mkdir from-nuget && cp from-nuget.nupkg from-nuget/zip.zip && cd from-nuget && unzip zip.zip && rm zip.zip && cd - || exit 1 + +echo "Unzipping version from local build" +ls packed/ +mkdir from-local && cp packed/*.nupkg from-local/zip.zip && cd from-local && unzip zip.zip && rm zip.zip && cd - || exit 1 + +cd from-local && find . -type f -exec sha256sum {} \; | sort > ../from-local.txt && cd .. || exit 1 +cd from-nuget && find . -type f -and -not -name '.signature.p7s' -exec sha256sum {} \; | sort > ../from-nuget.txt && cd .. || exit 1 + +diff from-local.txt from-nuget.txt diff --git a/.github/workflows/dotnet.yaml b/.github/workflows/dotnet.yaml index f55857b..c6b0316 100644 --- a/.github/workflows/dotnet.yaml +++ b/.github/workflows/dotnet.yaml @@ -240,11 +240,53 @@ jobs: steps: - run: echo "All required checks complete." - nuget-publish: + attestation-lib: + runs-on: ubuntu-latest + needs: [all-required-checks-complete] + if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }} + permissions: + id-token: write + attestations: write + contents: read + steps: + - name: Download NuGet artifact + uses: actions/download-artifact@v4 + with: + name: nuget-package-lib + path: packed + - name: Attest Build Provenance + uses: actions/attest-build-provenance@897ed5eab6ed058a474202017ada7f40bfa52940 # v1.0.0 + with: + subject-path: "packed/*.nupkg" + + attestation-tool: + runs-on: ubuntu-latest + needs: [all-required-checks-complete] + if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }} + permissions: + id-token: write + attestations: write + contents: read + steps: + - name: Download NuGet artifact + uses: actions/download-artifact@v4 + with: + name: nuget-package-tool + path: packed + - name: Attest Build Provenance + uses: actions/attest-build-provenance@897ed5eab6ed058a474202017ada7f40bfa52940 # v1.0.0 + with: + subject-path: "packed/*.nupkg" + + nuget-publish-lib: runs-on: ubuntu-latest if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }} needs: [all-required-checks-complete] environment: main-deploy + permissions: + id-token: write + attestations: write + contents: read steps: - uses: actions/checkout@v4 - name: Install Nix @@ -252,20 +294,73 @@ jobs: with: extra_nix_config: | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} - - name: Download NuGet artifact (lib) + - name: Download NuGet artifact uses: actions/download-artifact@v4 with: name: nuget-package-lib - path: packed-lib - - name: Publish to NuGet (lib) - run: nix develop --command dotnet nuget push "packed-lib/WoofWare.NUnitTestRunner.Lib.*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate - - name: Download NuGet artifact (tool) + path: packed + - name: Publish to NuGet + id: publish-success + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: 'nix develop --command bash ./.github/workflows/nuget-push.sh "packed/WoofWare.NUnitTestRunner.Lib.*.nupkg"' + - name: Wait for availability + if: steps.publish-success.outputs.result == 'published' + env: + PACKAGE_VERSION: ${{ steps.publish-success.outputs.version }} + run: 'echo "$PACKAGE_VERSION" && while ! curl -L --fail -o from-nuget.nupkg "https://www.nuget.org/api/v2/package/WoofWare.NUnitTestRunner.Lib/$PACKAGE_VERSION" ; do sleep 10; done' + # Astonishingly, NuGet.org considers it to be "more secure" to tamper with my package after upload (https://devblogs.microsoft.com/nuget/introducing-repository-signatures/). + # So we have to *re-attest* it after it's uploaded. Mind-blowing. + - name: Assert package contents + if: steps.publish-success.outputs.result == 'published' + run: 'bash ./.github/workflows/assert-contents.sh' + - name: Attest Build Provenance + if: steps.publish-success.outputs.result == 'published' + uses: actions/attest-build-provenance@897ed5eab6ed058a474202017ada7f40bfa52940 # v1.0.0 + with: + subject-path: "from-nuget.nupkg" + + nuget-publish-tool: + runs-on: ubuntu-latest + if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }} + needs: [all-required-checks-complete] + environment: main-deploy + permissions: + id-token: write + attestations: write + contents: read + steps: + - uses: actions/checkout@v4 + - name: Install Nix + uses: cachix/install-nix-action@V27 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Download NuGet artifact uses: actions/download-artifact@v4 with: name: nuget-package-tool - path: packed-tool - - name: Publish to NuGet (tool) - run: nix develop --command dotnet nuget push "packed-tool/WoofWare.NUnitTestRunner.*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate + path: packed + - name: Publish to NuGet + id: publish-success + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: 'nix develop --command bash ./.github/workflows/nuget-push.sh "packed/WoofWare.NUnitTestRunner.*.nupkg"' + - name: Wait for availability + if: steps.publish-success.outputs.result == 'published' + env: + PACKAGE_VERSION: ${{ steps.publish-success.outputs.version }} + run: 'echo "$PACKAGE_VERSION" && while ! curl -L --fail -o from-nuget.nupkg "https://www.nuget.org/api/v2/package/WoofWare.NUnitTestRunner/$PACKAGE_VERSION" ; do sleep 10; done' + # Astonishingly, NuGet.org considers it to be "more secure" to tamper with my package after upload (https://devblogs.microsoft.com/nuget/introducing-repository-signatures/). + # So we have to *re-attest* it after it's uploaded. Mind-blowing. + - name: Assert package contents + if: steps.publish-success.outputs.result == 'published' + run: 'bash ./.github/workflows/assert-contents.sh' + - name: Attest Build Provenance + if: steps.publish-success.outputs.result == 'published' + uses: actions/attest-build-provenance@897ed5eab6ed058a474202017ada7f40bfa52940 # v1.0.0 + with: + subject-path: "from-nuget.nupkg" github-release-tool: runs-on: ubuntu-latest diff --git a/.github/workflows/nuget-push.sh b/.github/workflows/nuget-push.sh new file mode 100644 index 0000000..edc170d --- /dev/null +++ b/.github/workflows/nuget-push.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +SOURCE_NUPKG=$(find . -type f -name '*.nupkg') + +PACKAGE_VERSION=$(basename "$SOURCE_NUPKG" | rev | cut -d '.' -f 2-4 | rev) + +echo "version=$PACKAGE_VERSION" >> "$GITHUB_OUTPUT" + +tmp=$(mktemp) + +if ! dotnet nuget push "$SOURCE_NUPKG" --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json > "$tmp" ; then + cat "$tmp" + if grep 'already exists and cannot be modified' "$tmp" ; then + echo "result=skipped" >> "$GITHUB_OUTPUT" + exit 0 + else + echo "Unexpected failure to upload" + exit 1 + fi +fi + +cat "$tmp" + +echo "result=published" >> "$GITHUB_OUTPUT"