From 786ee1f9ad8669b46ca813503c93f278425a54d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 13:17:10 +0000 Subject: [PATCH 1/2] CI: build Dependabot PRs unsigned on Ubuntu instead of failing Every Dependabot PR touching build-ubuntu.yaml has failed build-package at "Import GPG keys" (all 29 such runs since 2025-09; most recently run 40638632117 for #303): echo '' | gpg --import gpg: no valid OpenPGP data found. GitHub withholds Actions secrets from Dependabot-triggered runs, as it does for fork PRs. The step was guarded only against forks (head.repo.fork == false), and Dependabot's branches are in this repository, so the step ran with an empty key. Decide on signing from whether a PR actually has the key rather than from where its branch lives, and use that one decision both for importing the keys and for the -uc -us of the binary build. The unsigned path is the one fork PRs already take: no ~/.gnupg is created, so dpkg-buildpackage does not try to sign the source package either. Non-PR events (schedule, workflow_dispatch) still always sign, so a missing or empty key keeps failing them loudly. Edited the template and regenerated with `make -C .github/workflows/template`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PvaGRY7auLbAfwNGj21YXd --- .github/workflows/build-ubuntu.yaml | 14 ++++++++++++-- .../workflows/template/build-{{ostype}}.yaml.j2 | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-ubuntu.yaml b/.github/workflows/build-ubuntu.yaml index 020abcfa32..63daeea803 100644 --- a/.github/workflows/build-ubuntu.yaml +++ b/.github/workflows/build-ubuntu.yaml @@ -103,8 +103,18 @@ jobs: - name: Adjust changelog generation script to use original branch run: sed -i -e "s,HEAD,$BUILD_COMMIT,g" debian/create-standalone-changelog + # A PR is built unsigned when the signing key is not available to it: + # Actions secrets are withheld not only from fork PRs but also from + # Dependabot PRs, whose branches live in this repository. Without the + # key, ~/.gnupg must not be created either, or dpkg-buildpackage would + # try to sign the source package. Other events always sign, so that a + # missing key fails them. + - name: Check for the signing key + id: signing + run: echo "sign=${{ github.event_name != 'pull_request' || secrets.datalad_builder_gpgkey != '' }}" >> "$GITHUB_OUTPUT" + - name: Import GPG keys - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + if: steps.signing.outputs.sign == 'true' run: | # private key for signing - comes from secrets echo '${{ secrets.datalad_builder_gpgkey }}' | gpg --import @@ -122,7 +132,7 @@ jobs: - name: Build binary package from the source package run: | - sign="${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}" + sign="${{ steps.signing.outputs.sign }}" if [ "x$sign" = xtrue ] then opts= else opts='-uc -us' diff --git a/.github/workflows/template/build-{{ostype}}.yaml.j2 b/.github/workflows/template/build-{{ostype}}.yaml.j2 index 643c74ef6d..254e17d2e1 100644 --- a/.github/workflows/template/build-{{ostype}}.yaml.j2 +++ b/.github/workflows/template/build-{{ostype}}.yaml.j2 @@ -147,8 +147,18 @@ jobs: - name: Adjust changelog generation script to use original branch run: sed -i -e "s,HEAD,$BUILD_COMMIT,g" debian/create-standalone-changelog + # A PR is built unsigned when the signing key is not available to it: + # Actions secrets are withheld not only from fork PRs but also from + # Dependabot PRs, whose branches live in this repository. Without the + # key, ~/.gnupg must not be created either, or dpkg-buildpackage would + # try to sign the source package. Other events always sign, so that a + # missing key fails them. + - name: Check for the signing key + id: signing + run: echo "sign=${{ github.event_name != 'pull_request' || secrets.datalad_builder_gpgkey != '' }}" >> "$GITHUB_OUTPUT" + - name: Import GPG keys - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + if: steps.signing.outputs.sign == 'true' run: | # private key for signing - comes from secrets echo '${{ secrets.datalad_builder_gpgkey }}' | gpg --import @@ -166,7 +176,7 @@ jobs: - name: Build binary package from the source package run: | - sign="${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}" + sign="${{ steps.signing.outputs.sign }}" if [ "x$sign" = xtrue ] then opts= else opts='-uc -us' From 59e1413f38550f58f36cfbbe1d927c5ef2335270 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 13:47:40 +0000 Subject: [PATCH 2/2] CI: require and verify signing of Ubuntu packages outside PRs The previous commit had non-PR events always import the key, so a missing key failed them only through `gpg --import` of an empty string, and nothing checked that the packages actually came out signed. "Decide whether to sign" now makes the decision explicitly: - with the key, always sign; - a PR without it (fork, Dependabot) builds unsigned, as before; - any other build without it fails with an error naming the secret, unless the repository is a fork (asked of the API, as schedule payloads need not carry it; an unclear answer counts as not a fork). When signing, "Import GPG keys" checks that the secret key for DEB_SIGN_KEYID is there, and a new "Check that the packages are signed" step requires a good signature by DEB_SIGN_KEYID on the .dsc and on the source and binary .buildinfo and .changes, since dpkg-buildpackage can skip signing without failing (e.g. when ~/.gnupg is missing). Tested locally with dpkg-buildpackage 1.22.6 and a throwaway key: an unsigned, wrong-key, partially signed, tampered, or missing file fails the check, and a fully signed build passes. The comment on the decision is cut down to two lines. Edited the template and regenerated with `make -C .github/workflows/template`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PvaGRY7auLbAfwNGj21YXd --- .github/workflows/build-ubuntu.yaml | 40 +++++++++++++++---- .../template/build-{{ostype}}.yaml.j2 | 40 +++++++++++++++---- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-ubuntu.yaml b/.github/workflows/build-ubuntu.yaml index 63daeea803..df9c706e5a 100644 --- a/.github/workflows/build-ubuntu.yaml +++ b/.github/workflows/build-ubuntu.yaml @@ -103,21 +103,34 @@ jobs: - name: Adjust changelog generation script to use original branch run: sed -i -e "s,HEAD,$BUILD_COMMIT,g" debian/create-standalone-changelog - # A PR is built unsigned when the signing key is not available to it: - # Actions secrets are withheld not only from fork PRs but also from - # Dependabot PRs, whose branches live in this repository. Without the - # key, ~/.gnupg must not be created either, or dpkg-buildpackage would - # try to sign the source package. Other events always sign, so that a - # missing key fails them. - - name: Check for the signing key + # Fork and Dependabot PRs get no secrets: build them unsigned, without ~/.gnupg + # (dpkg-buildpackage signs if it exists). Builds outside PRs in a non-fork must sign. + - name: Decide whether to sign id: signing - run: echo "sign=${{ github.event_name != 'pull_request' || secrets.datalad_builder_gpgkey != '' }}" >> "$GITHUB_OUTPUT" + run: | + if [ "$HAVE_KEY" = true ]; then + sign=true + elif [ "$GITHUB_EVENT_NAME" = pull_request ]; then + sign=false + else + fork="$(gh api "repos/$GITHUB_REPOSITORY" --jq .fork)" + if [ "$fork" != true ]; then + echo "::error::No signing key (secrets.datalad_builder_gpgkey), but $GITHUB_EVENT_NAME builds of $GITHUB_REPOSITORY must be signed" + exit 1 + fi + sign=false + fi + echo "sign=$sign" >> "$GITHUB_OUTPUT" + env: + HAVE_KEY: ${{ secrets.datalad_builder_gpgkey != '' }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Import GPG keys if: steps.signing.outputs.sign == 'true' run: | # private key for signing - comes from secrets echo '${{ secrets.datalad_builder_gpgkey }}' | gpg --import + gpg --list-secret-keys "$DEB_SIGN_KEYID" # public key for verification gpg --import .github/workflows/tools/datalad-builder-key.asc @@ -151,6 +164,17 @@ jobs: run: grep -E '^All [[:digit:]]{3} tests passed' "${bbuild_log}" if: "!contains(env.DEB_BUILD_OPTIONS, 'nocheck')" + - name: Check that the packages are signed + if: steps.signing.outputs.sign == 'true' + run: | + arch="$(dpkg --print-architecture)" + for f in git-annex_*.dsc git-annex_*_{source,"$arch"}.{buildinfo,changes}; do + if ! gpg --status-fd 1 --verify "$f" | grep "^\[GNUPG:\] VALIDSIG .* $DEB_SIGN_KEYID\$"; then + echo "::error::$f is not signed by $DEB_SIGN_KEYID" + exit 1 + fi + done + - name: Upload packages uses: actions/upload-artifact@v7 with: diff --git a/.github/workflows/template/build-{{ostype}}.yaml.j2 b/.github/workflows/template/build-{{ostype}}.yaml.j2 index 254e17d2e1..181c62eeff 100644 --- a/.github/workflows/template/build-{{ostype}}.yaml.j2 +++ b/.github/workflows/template/build-{{ostype}}.yaml.j2 @@ -147,21 +147,34 @@ jobs: - name: Adjust changelog generation script to use original branch run: sed -i -e "s,HEAD,$BUILD_COMMIT,g" debian/create-standalone-changelog - # A PR is built unsigned when the signing key is not available to it: - # Actions secrets are withheld not only from fork PRs but also from - # Dependabot PRs, whose branches live in this repository. Without the - # key, ~/.gnupg must not be created either, or dpkg-buildpackage would - # try to sign the source package. Other events always sign, so that a - # missing key fails them. - - name: Check for the signing key + # Fork and Dependabot PRs get no secrets: build them unsigned, without ~/.gnupg + # (dpkg-buildpackage signs if it exists). Builds outside PRs in a non-fork must sign. + - name: Decide whether to sign id: signing - run: echo "sign=${{ github.event_name != 'pull_request' || secrets.datalad_builder_gpgkey != '' }}" >> "$GITHUB_OUTPUT" + run: | + if [ "$HAVE_KEY" = true ]; then + sign=true + elif [ "$GITHUB_EVENT_NAME" = pull_request ]; then + sign=false + else + fork="$(gh api "repos/$GITHUB_REPOSITORY" --jq .fork)" + if [ "$fork" != true ]; then + echo "::error::No signing key (secrets.datalad_builder_gpgkey), but $GITHUB_EVENT_NAME builds of $GITHUB_REPOSITORY must be signed" + exit 1 + fi + sign=false + fi + echo "sign=$sign" >> "$GITHUB_OUTPUT" + env: + HAVE_KEY: ${{ secrets.datalad_builder_gpgkey != '' }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Import GPG keys if: steps.signing.outputs.sign == 'true' run: | # private key for signing - comes from secrets echo '${{ secrets.datalad_builder_gpgkey }}' | gpg --import + gpg --list-secret-keys "$DEB_SIGN_KEYID" # public key for verification gpg --import .github/workflows/tools/datalad-builder-key.asc @@ -195,6 +208,17 @@ jobs: run: grep -E '^All [[:digit:]]{3} tests passed' "${bbuild_log}" if: "!contains(env.DEB_BUILD_OPTIONS, 'nocheck')" + - name: Check that the packages are signed + if: steps.signing.outputs.sign == 'true' + run: | + arch="$(dpkg --print-architecture)" + for f in git-annex_*.dsc git-annex_*_{source,"$arch"}.{buildinfo,changes}; do + if ! gpg --status-fd 1 --verify "$f" | grep "^\[GNUPG:\] VALIDSIG .* $DEB_SIGN_KEYID\$"; then + echo "::error::$f is not signed by $DEB_SIGN_KEYID" + exit 1 + fi + done + {% elif ostype.startswith("macos") %} - name: Configure build run: |