From 315ca7684a9c910258aa6241a4cb0f1bd16fecd0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:17:27 +0000 Subject: [PATCH 1/9] Initial plan From 09104f002da5489b9f68388deb35e3a6fe798c68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:26:14 +0000 Subject: [PATCH 2/9] Add release provenance artifact generation --- .github/workflows/release.yaml | 8 +++++++- .goreleaser.yml | 21 +++++++++++++++++++++ README.md | 6 +----- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 89ea8fdf..8392e02f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -21,7 +21,7 @@ jobs: steps: - if: ${{ !startsWith(github.ref, 'refs/tags/v') }} - run: echo "flags=--snapshot" >> $GITHUB_ENV + run: echo "flags=--snapshot --skip=sign" >> $GITHUB_ENV - name: Checkout uses: actions/checkout@v6 @@ -32,6 +32,10 @@ jobs: uses: actions/setup-go@v6 with: go-version-file: 'go.mod' + - + name: Import GPG key + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + run: echo "${{ secrets.GPG_PRIVATE_KEY }}" | base64 --decode | gpg --batch --import - name: Run GoReleaser uses: goreleaser/goreleaser-action@v7 @@ -41,3 +45,5 @@ jobs: args: release --clean ${{ env.flags }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GPG_FINGERPRINT: ${{ secrets.GPG_FINGERPRINT }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 1c2d5f01..03fdf2f7 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -52,6 +52,27 @@ archives: - README.md - plugin.yaml - LICENSE + +signs: + - id: plugin-provenance + artifacts: archive + signature: "${artifact}.prov" + cmd: sh + args: + - -ec + - | + artifact="$1" + signature="$2" + filename="$(basename "$artifact")" + digest="$(sha256sum "$artifact" | cut -d' ' -f1)" + { + cat plugin.yaml + printf '\n...\n' + printf 'files:\n %s: "sha256:%s"\n' "$filename" "$digest" + } | gpg --batch --yes --armor --pinentry-mode loopback --passphrase "${GPG_PASSPHRASE:-}" --local-user "$GPG_FINGERPRINT" --clearsign --output "$signature" + - -- + - ${artifact} + - ${signature} changelog: use: github-native diff --git a/README.md b/README.md index 6e6f257d..fffc12d6 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,7 @@ The install script will skip the GitHub download and instead install from the `. **For Helm 4 users:** -Helm 4 requires plugin verification by default. Since this plugin does not yet provide provenance artifacts, you need to use the `--verify=false` flag: - -```shell -helm plugin install https://github.com/databus23/helm-diff --verify=false -``` +Helm 4 verifies plugin provenance by default. This project publishes release provenance artifacts (`.prov`) alongside release tarballs to support verification. For more information about Helm 4's plugin verification, see: - [Helm 4 Overview](https://helm.sh/docs/overview) From b7db1ed1faf60d276c066fd8f1cc1a49eb87d4bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Jun 2026 03:02:27 +0000 Subject: [PATCH 3/9] Fix GPG secret handling: use passphrase-file and restrict secrets to tag runs --- .github/workflows/release.yaml | 8 ++++++-- .goreleaser.yml | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8392e02f..56b5d44d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -36,6 +36,12 @@ jobs: name: Import GPG key if: ${{ startsWith(github.ref, 'refs/tags/v') }} run: echo "${{ secrets.GPG_PRIVATE_KEY }}" | base64 --decode | gpg --batch --import + - + name: Set GPG environment for signing + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + run: | + echo "GPG_FINGERPRINT=${{ secrets.GPG_FINGERPRINT }}" >> "$GITHUB_ENV" + echo "GPG_PASSPHRASE=${{ secrets.GPG_PASSPHRASE }}" >> "$GITHUB_ENV" - name: Run GoReleaser uses: goreleaser/goreleaser-action@v7 @@ -45,5 +51,3 @@ jobs: args: release --clean ${{ env.flags }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GPG_FINGERPRINT: ${{ secrets.GPG_FINGERPRINT }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 03fdf2f7..9d2fe276 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -65,11 +65,15 @@ signs: signature="$2" filename="$(basename "$artifact")" digest="$(sha256sum "$artifact" | cut -d' ' -f1)" + passphrase_file="$(mktemp)" + trap 'rm -f "$passphrase_file"' EXIT + printf '%s' "${GPG_PASSPHRASE:-}" > "$passphrase_file" + chmod 600 "$passphrase_file" { cat plugin.yaml printf '\n...\n' printf 'files:\n %s: "sha256:%s"\n' "$filename" "$digest" - } | gpg --batch --yes --armor --pinentry-mode loopback --passphrase "${GPG_PASSPHRASE:-}" --local-user "$GPG_FINGERPRINT" --clearsign --output "$signature" + } | gpg --batch --yes --armor --pinentry-mode loopback --passphrase-file "$passphrase_file" --local-user "$GPG_FINGERPRINT" --clearsign --output "$signature" - -- - ${artifact} - ${signature} From 44ad328a1fc478e5992d005bd7766ef86f625e83 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sat, 6 Jun 2026 11:37:52 +0800 Subject: [PATCH 4/9] Improve provenance signing: add GPG agent setup, smoke test, and docs - Add gpgconf --launch gpg-agent before GPG key import in CI - Fix provenance separator format to match Helm parser (\n...\n -> ...\n) - Add provenance-smoke-test job that validates signing with disposable key - Add workflow_dispatch trigger for manual testing - Document required GPG secrets and key rotation in workflow header - Update README with public key import guidance for Helm 4 users Signed-off-by: yxxhero --- .github/workflows/release.yaml | 77 +++++++++++++++++++++++++++++++++- .goreleaser.yml | 2 +- README.md | 2 +- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 56b5d44d..8f080d4b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,3 +1,15 @@ +# Release workflow +# +# Prerequisites (configure in Settings > Secrets and variables > Actions): +# - GPG_PRIVATE_KEY: base64-encoded GPG private key for signing release artifacts +# - GPG_FINGERPRINT: Fingerprint of the GPG key +# - GPG_PASSPHRASE: Passphrase for the GPG private key +# +# Key management notes: +# - Use a key with no expiration or set a calendar reminder before expiry +# - To rotate: generate a new keypair, update all three secrets, and verify +# with a test release (see the test-provenance-sign-dry job) + name: Release on: @@ -11,6 +23,7 @@ on: branches: - 'main' - 'master' + workflow_dispatch: permissions: contents: write @@ -35,7 +48,9 @@ jobs: - name: Import GPG key if: ${{ startsWith(github.ref, 'refs/tags/v') }} - run: echo "${{ secrets.GPG_PRIVATE_KEY }}" | base64 --decode | gpg --batch --import + run: | + gpgconf --launch gpg-agent + echo "${{ secrets.GPG_PRIVATE_KEY }}" | base64 --decode | gpg --batch --import - name: Set GPG environment for signing if: ${{ startsWith(github.ref, 'refs/tags/v') }} @@ -51,3 +66,63 @@ jobs: args: release --clean ${{ env.flags }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + provenance-smoke-test: + runs-on: ubuntu-latest + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + steps: + - + name: Checkout + uses: actions/checkout@v6 + - + name: Test provenance signing with disposable key + run: | + export GNUPGHOME="$(mktemp -d)" + chmod 700 "$GNUPGHOME" + trap 'rm -rf "$GNUPGHOME"' EXIT + + GPG_FINGERPRINT=$(gpg --batch --passphrase '' --quick-generate-key \ + "helm-diff-test" ed25519 sign 0 2>&1 \ + | grep -o '[A-F0-9]\{40\}' | head -1) + export GPG_FINGERPRINT + export GPG_PASSPHRASE="" + + tmpdir="$(mktemp -d)" + echo "dummy binary" > "$tmpdir/bin" + tar czf "$tmpdir/helm-diff-linux-amd64.tgz" -C "$tmpdir" bin + + artifact="$tmpdir/helm-diff-linux-amd64.tgz" + signature="${artifact}.prov" + filename="$(basename "$artifact")" + digest="$(sha256sum "$artifact" | cut -d' ' -f1)" + passphrase_file="$(mktemp)" + trap 'rm -f "$passphrase_file"' EXIT + printf '%s' "${GPG_PASSPHRASE:-}" > "$passphrase_file" + chmod 600 "$passphrase_file" + { + cat plugin.yaml + printf '...\n' + printf 'files:\n %s: "sha256:%s"\n' "$filename" "$digest" + } | gpg --batch --yes --armor --pinentry-mode loopback \ + --passphrase-file "$passphrase_file" \ + --local-user "$GPG_FINGERPRINT" \ + --clearsign --output "$signature" + + if [ ! -f "$signature" ]; then + echo "ERROR: provenance file was not created" + exit 1 + fi + + echo "=== gpg --verify ===" + gpg --verify "$signature" + + echo "" + echo "=== Signed .prov content ===" + cat "$signature" + + echo "" + echo "=== Parsed provenance block ===" + gpg --batch --output - "$signature" 2>/dev/null + + echo "" + echo "Provenance signing dry-run test passed" diff --git a/.goreleaser.yml b/.goreleaser.yml index 9d2fe276..d6ac1316 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -71,7 +71,7 @@ signs: chmod 600 "$passphrase_file" { cat plugin.yaml - printf '\n...\n' + printf '...\n' printf 'files:\n %s: "sha256:%s"\n' "$filename" "$digest" } | gpg --batch --yes --armor --pinentry-mode loopback --passphrase-file "$passphrase_file" --local-user "$GPG_FINGERPRINT" --clearsign --output "$signature" - -- diff --git a/README.md b/README.md index fffc12d6..31a3c490 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ The install script will skip the GitHub download and instead install from the `. **For Helm 4 users:** -Helm 4 verifies plugin provenance by default. This project publishes release provenance artifacts (`.prov`) alongside release tarballs to support verification. +Helm 4 verifies plugin provenance by default. This project publishes GPG-signed provenance artifacts (`.prov`) alongside release tarballs. To verify, import the project's public key into your keyring before running `helm plugin install`. For more information about Helm 4's plugin verification, see: - [Helm 4 Overview](https://helm.sh/docs/overview) From a8db4107ae3a50cd316c8300dee790a514658dff Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sat, 6 Jun 2026 11:48:20 +0800 Subject: [PATCH 5/9] Address PR review comments - Add GPG_FINGERPRINT guard with clear error message - Add public key download instructions and fingerprint note to README - Fix header comment referencing provenance-smoke-test job name - Fix double trap overwriting GNUPGHOME cleanup in smoke test - Consolidate cleanup into single trap statement Signed-off-by: yxxhero --- .github/workflows/release.yaml | 9 ++++----- .goreleaser.yml | 4 ++++ README.md | 9 ++++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8f080d4b..916639a3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,7 +8,7 @@ # Key management notes: # - Use a key with no expiration or set a calendar reminder before expiry # - To rotate: generate a new keypair, update all three secrets, and verify -# with a test release (see the test-provenance-sign-dry job) +# with a test release (see the provenance-smoke-test job) name: Release @@ -78,8 +78,10 @@ jobs: name: Test provenance signing with disposable key run: | export GNUPGHOME="$(mktemp -d)" + tmpdir="$(mktemp -d)" + passphrase_file="$(mktemp)" + trap 'rm -rf "$GNUPGHOME" "$tmpdir"; rm -f "$passphrase_file"' EXIT chmod 700 "$GNUPGHOME" - trap 'rm -rf "$GNUPGHOME"' EXIT GPG_FINGERPRINT=$(gpg --batch --passphrase '' --quick-generate-key \ "helm-diff-test" ed25519 sign 0 2>&1 \ @@ -87,7 +89,6 @@ jobs: export GPG_FINGERPRINT export GPG_PASSPHRASE="" - tmpdir="$(mktemp -d)" echo "dummy binary" > "$tmpdir/bin" tar czf "$tmpdir/helm-diff-linux-amd64.tgz" -C "$tmpdir" bin @@ -95,8 +96,6 @@ jobs: signature="${artifact}.prov" filename="$(basename "$artifact")" digest="$(sha256sum "$artifact" | cut -d' ' -f1)" - passphrase_file="$(mktemp)" - trap 'rm -f "$passphrase_file"' EXIT printf '%s' "${GPG_PASSPHRASE:-}" > "$passphrase_file" chmod 600 "$passphrase_file" { diff --git a/.goreleaser.yml b/.goreleaser.yml index d6ac1316..cc28837c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -65,6 +65,10 @@ signs: signature="$2" filename="$(basename "$artifact")" digest="$(sha256sum "$artifact" | cut -d' ' -f1)" + if [ -z "${GPG_FINGERPRINT:-}" ]; then + echo "ERROR: GPG_FINGERPRINT is not set. Cannot sign provenance artifact." + exit 1 + fi passphrase_file="$(mktemp)" trap 'rm -f "$passphrase_file"' EXIT printf '%s' "${GPG_PASSPHRASE:-}" > "$passphrase_file" diff --git a/README.md b/README.md index 31a3c490..a6b42594 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,14 @@ The install script will skip the GitHub download and instead install from the `. **For Helm 4 users:** -Helm 4 verifies plugin provenance by default. This project publishes GPG-signed provenance artifacts (`.prov`) alongside release tarballs. To verify, import the project's public key into your keyring before running `helm plugin install`. +Helm 4 verifies plugin provenance by default. This project publishes GPG-signed provenance artifacts (`.prov`) alongside release tarballs. To verify, import the project's public key into your keyring before running `helm plugin install`: + +```shell +gpg --keyserver keys.openpgp.org --recv-keys +helm plugin install https://github.com/databus23/helm-diff +``` + +The public key fingerprint is published in each GitHub release notes. For more information about Helm 4's plugin verification, see: - [Helm 4 Overview](https://helm.sh/docs/overview) From ca53393287b0b7139de21a970680cebb98ff6179 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sat, 6 Jun 2026 11:58:20 +0800 Subject: [PATCH 6/9] Address remaining PR review comments - Use printf instead of echo for GPG key import to avoid corruption - Use --with-colons --list-secret-keys for reliable fingerprint extraction - Use HKPS keyserver URL in README for TLS-protected key fetch Signed-off-by: yxxhero --- .github/workflows/release.yaml | 9 +++++---- README.md | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 916639a3..561385e3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -50,7 +50,7 @@ jobs: if: ${{ startsWith(github.ref, 'refs/tags/v') }} run: | gpgconf --launch gpg-agent - echo "${{ secrets.GPG_PRIVATE_KEY }}" | base64 --decode | gpg --batch --import + printf '%s' "${{ secrets.GPG_PRIVATE_KEY }}" | base64 --decode | gpg --batch --import - name: Set GPG environment for signing if: ${{ startsWith(github.ref, 'refs/tags/v') }} @@ -83,9 +83,10 @@ jobs: trap 'rm -rf "$GNUPGHOME" "$tmpdir"; rm -f "$passphrase_file"' EXIT chmod 700 "$GNUPGHOME" - GPG_FINGERPRINT=$(gpg --batch --passphrase '' --quick-generate-key \ - "helm-diff-test" ed25519 sign 0 2>&1 \ - | grep -o '[A-F0-9]\{40\}' | head -1) + gpg --batch --pinentry-mode loopback --passphrase '' \ + --quick-generate-key "helm-diff-test" ed25519 sign 0 + GPG_FINGERPRINT=$(gpg --batch --with-colons --list-secret-keys "helm-diff-test" \ + | grep '^fpr:' | head -1 | cut -d: -f10) export GPG_FINGERPRINT export GPG_PASSPHRASE="" diff --git a/README.md b/README.md index a6b42594..c3346df3 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ The install script will skip the GitHub download and instead install from the `. Helm 4 verifies plugin provenance by default. This project publishes GPG-signed provenance artifacts (`.prov`) alongside release tarballs. To verify, import the project's public key into your keyring before running `helm plugin install`: ```shell -gpg --keyserver keys.openpgp.org --recv-keys +gpg --keyserver hkps://keys.openpgp.org --recv-keys helm plugin install https://github.com/databus23/helm-diff ``` From a61b52341a9672a08caf829ab009458ecf5554ce Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sat, 6 Jun 2026 13:46:17 +0800 Subject: [PATCH 7/9] Extract signing logic to shared script and address review comments - Extract provenance signing to scripts/sign-provenance.sh (used by both goreleaser and smoke test) to prevent logic drift - Add sha256sum fallback to shasum for macOS compatibility - Fix grammar in README key fingerprint sentence Signed-off-by: yxxhero --- .github/workflows/release.yaml | 28 +++++++--------------------- .goreleaser.yml | 22 +--------------------- README.md | 2 +- scripts/sign-provenance.sh | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 43 deletions(-) create mode 100755 scripts/sign-provenance.sh diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 561385e3..4959ed8c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -79,8 +79,7 @@ jobs: run: | export GNUPGHOME="$(mktemp -d)" tmpdir="$(mktemp -d)" - passphrase_file="$(mktemp)" - trap 'rm -rf "$GNUPGHOME" "$tmpdir"; rm -f "$passphrase_file"' EXIT + trap 'rm -rf "$GNUPGHOME" "$tmpdir"' EXIT chmod 700 "$GNUPGHOME" gpg --batch --pinentry-mode loopback --passphrase '' \ @@ -93,36 +92,23 @@ jobs: echo "dummy binary" > "$tmpdir/bin" tar czf "$tmpdir/helm-diff-linux-amd64.tgz" -C "$tmpdir" bin - artifact="$tmpdir/helm-diff-linux-amd64.tgz" - signature="${artifact}.prov" - filename="$(basename "$artifact")" - digest="$(sha256sum "$artifact" | cut -d' ' -f1)" - printf '%s' "${GPG_PASSPHRASE:-}" > "$passphrase_file" - chmod 600 "$passphrase_file" - { - cat plugin.yaml - printf '...\n' - printf 'files:\n %s: "sha256:%s"\n' "$filename" "$digest" - } | gpg --batch --yes --armor --pinentry-mode loopback \ - --passphrase-file "$passphrase_file" \ - --local-user "$GPG_FINGERPRINT" \ - --clearsign --output "$signature" + ./scripts/sign-provenance.sh "$tmpdir/helm-diff-linux-amd64.tgz" "$tmpdir/helm-diff-linux-amd64.tgz.prov" - if [ ! -f "$signature" ]; then + if [ ! -f "$tmpdir/helm-diff-linux-amd64.tgz.prov" ]; then echo "ERROR: provenance file was not created" exit 1 fi echo "=== gpg --verify ===" - gpg --verify "$signature" + gpg --verify "$tmpdir/helm-diff-linux-amd64.tgz.prov" echo "" echo "=== Signed .prov content ===" - cat "$signature" + cat "$tmpdir/helm-diff-linux-amd64.tgz.prov" echo "" echo "=== Parsed provenance block ===" - gpg --batch --output - "$signature" 2>/dev/null + gpg --batch --output - "$tmpdir/helm-diff-linux-amd64.tgz.prov" 2>/dev/null echo "" - echo "Provenance signing dry-run test passed" + echo "Provenance smoke test passed" diff --git a/.goreleaser.yml b/.goreleaser.yml index cc28837c..c0f53c1e 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -57,28 +57,8 @@ signs: - id: plugin-provenance artifacts: archive signature: "${artifact}.prov" - cmd: sh + cmd: ./scripts/sign-provenance.sh args: - - -ec - - | - artifact="$1" - signature="$2" - filename="$(basename "$artifact")" - digest="$(sha256sum "$artifact" | cut -d' ' -f1)" - if [ -z "${GPG_FINGERPRINT:-}" ]; then - echo "ERROR: GPG_FINGERPRINT is not set. Cannot sign provenance artifact." - exit 1 - fi - passphrase_file="$(mktemp)" - trap 'rm -f "$passphrase_file"' EXIT - printf '%s' "${GPG_PASSPHRASE:-}" > "$passphrase_file" - chmod 600 "$passphrase_file" - { - cat plugin.yaml - printf '...\n' - printf 'files:\n %s: "sha256:%s"\n' "$filename" "$digest" - } | gpg --batch --yes --armor --pinentry-mode loopback --passphrase-file "$passphrase_file" --local-user "$GPG_FINGERPRINT" --clearsign --output "$signature" - - -- - ${artifact} - ${signature} changelog: diff --git a/README.md b/README.md index c3346df3..3c0b4c59 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ gpg --keyserver hkps://keys.openpgp.org --recv-keys helm plugin install https://github.com/databus23/helm-diff ``` -The public key fingerprint is published in each GitHub release notes. +The public key fingerprint is published in the notes for each GitHub release. For more information about Helm 4's plugin verification, see: - [Helm 4 Overview](https://helm.sh/docs/overview) diff --git a/scripts/sign-provenance.sh b/scripts/sign-provenance.sh new file mode 100755 index 00000000..5b2cb4c7 --- /dev/null +++ b/scripts/sign-provenance.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ $# -lt 2 ]; then + echo "Usage: $0 [plugin.yaml path]" + exit 1 +fi + +artifact="$1" +signature="$2" +plugin_yaml="${3:-plugin.yaml}" + +if [ -z "${GPG_FINGERPRINT:-}" ]; then + echo "ERROR: GPG_FINGERPRINT is not set. Cannot sign provenance artifact." + exit 1 +fi + +filename="$(basename "$artifact")" +digest="$(sha256sum "$artifact" 2>/dev/null | cut -d' ' -f1 || shasum -a 256 "$artifact" | cut -d' ' -f1)" + +passphrase_file="$(mktemp)" +trap 'rm -f "$passphrase_file"' EXIT +printf '%s' "${GPG_PASSPHRASE:-}" > "$passphrase_file" +chmod 600 "$passphrase_file" + +{ + cat "$plugin_yaml" + printf '...\n' + printf 'files:\n %s: "sha256:%s"\n' "$filename" "$digest" +} | gpg --batch --yes --armor --pinentry-mode loopback \ + --passphrase-file "$passphrase_file" \ + --local-user "$GPG_FINGERPRINT" \ + --clearsign --output "$signature" From c88c0ffbeb89f2abbcc96c41bad8e1e667cde262 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sat, 6 Jun 2026 14:14:09 +0800 Subject: [PATCH 8/9] Add offline GPG key import instructions for airgapped environments Signed-off-by: yxxhero --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 3c0b4c59..99fe1660 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,12 @@ gpg --keyserver hkps://keys.openpgp.org --recv-keys helm plugin install https://github.com/databus23/helm-diff ``` +For offline/airgapped environments, download the public key from the GitHub release assets on a connected machine, transfer it, and import it locally: + +```shell +gpg --import +``` + The public key fingerprint is published in the notes for each GitHub release. For more information about Helm 4's plugin verification, see: From 373db6fe424ea6682b8a791f2433ba333933682d Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sat, 6 Jun 2026 15:19:28 +0800 Subject: [PATCH 9/9] Add comment explaining Helm required ...\n provenance separator Signed-off-by: yxxhero --- scripts/sign-provenance.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/sign-provenance.sh b/scripts/sign-provenance.sh index 5b2cb4c7..056e75c2 100755 --- a/scripts/sign-provenance.sh +++ b/scripts/sign-provenance.sh @@ -27,6 +27,9 @@ chmod 600 "$passphrase_file" cat "$plugin_yaml" printf '...\n' printf 'files:\n %s: "sha256:%s"\n' "$filename" "$digest" + # NOTE: The ...\n separator is required by Helm's provenance parser. + # See helm/helm pkg/provenance/sign.go: parseMessageBlock splits on "\n...\n" + # and messageBlock writes the same separator between metadata and checksums. } | gpg --batch --yes --armor --pinentry-mode loopback \ --passphrase-file "$passphrase_file" \ --local-user "$GPG_FINGERPRINT" \