From ee7fcb3210a85a675c6e9606c8356fbaecc44dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Saparelli?= Date: Sat, 23 Sep 2023 20:07:19 +1200 Subject: [PATCH] Sign our releases (#1347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Sign our releases * Use secrets instead of artifacts * And the universal * Apparently we can’t use secrets like that? * Minor fixes to doc * Private key requires untrusted comment * Dogfood one deeper --- .github/scripts/ephemeral-gen.sh | 16 ++++++++++++ .github/scripts/ephemeral-sign.sh | 19 ++++++++++++++ .github/workflows/release-build.yml | 20 +++++++++++++++ .github/workflows/release.yml | 39 +++++++++++++++++++++-------- SIGNING.md | 8 +++--- 5 files changed, 88 insertions(+), 14 deletions(-) create mode 100755 .github/scripts/ephemeral-gen.sh create mode 100755 .github/scripts/ephemeral-sign.sh diff --git a/.github/scripts/ephemeral-gen.sh b/.github/scripts/ephemeral-gen.sh new file mode 100755 index 00000000..065b4673 --- /dev/null +++ b/.github/scripts/ephemeral-gen.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +cargo binstall -y rsign2 +rsign generate -f -W -p minisign.pub -s minisign.key + +cat >> crates/bin/Cargo.toml <> "$GITHUB_OUTPUT" diff --git a/.github/scripts/ephemeral-sign.sh b/.github/scripts/ephemeral-sign.sh new file mode 100755 index 00000000..a657c915 --- /dev/null +++ b/.github/scripts/ephemeral-sign.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -euo pipefail + +echo "untrusted comment: rsign encrypted secret key" > minisign.key +cat >> minisign.key <<< "$SIGNING_KEY" + +set -x + +cargo binstall -y rsign2 + +ts=$(date --utc --iso-8601=seconds) +git=$(git rev-parse HEAD) +comment="gh=$GITHUB_REPOSITORY git=$git ts=$ts run=$GITHUB_RUN_ID" + +for file in "$@"; do + rsign sign -W -s minisign.key -x "$file.sig" -t "$comment" "$file" +done + diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 64c6796d..0b2c20a1 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -16,6 +16,10 @@ on: description: "Set to override default release profile codegen-units settings" required: false type: string + secrets: + signingkey: + description: "Minisign private key. Required when publishing" + required: false env: CARGO_TERM_COLOR: always @@ -84,6 +88,14 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: inputs.publish + uses: cargo-bins/cargo-binstall@main + + - if: inputs.publish + env: + SIGNING_KEY: ${{ secrets.signingkey }} + run: .github/scripts/ephemeral-sign.sh packages/cargo-binstall-* + - if: inputs.publish name: Upload to release uses: svenstaro/upload-release-action@v2 @@ -140,6 +152,14 @@ jobs: - run: just repackage-lipo - run: ls -shal packages/ + - if: inputs.publish + uses: cargo-bins/cargo-binstall@main + + - if: inputs.publish + env: + SIGNING_KEY: ${{ secrets.signingkey }} + run: .github/scripts/ephemeral-sign.sh packages/cargo-binstall-universal-* + - if: inputs.publish name: Upload to release uses: svenstaro/upload-release-action@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e82ab9a4..4e694e7d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,8 +22,8 @@ jobs: event-data: ${{ toJSON(github.event) }} extract-notes-under: '### Release notes' - tag: - if: needs.info.outputs.is-release == 'true' + libtag: + if: needs.info.outputs.is-release == 'true' && needs.info.outputs.crate != 'cargo-binstall' needs: info runs-on: ubuntu-latest steps: @@ -35,24 +35,43 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} custom_tag: ${{ needs.info.outputs.version }} tag_prefix: ${{ needs.info.outputs.crate }}-v - - name: Push cli release tag - if: needs.info.outputs.crate == 'cargo-binstall' - uses: mathieudutour/github-tag-action@v6.1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - custom_tag: ${{ needs.info.outputs.version }} - tag_prefix: v - name: Publish to crates.io run: | cargo publish -p '${{ needs.info.outputs.crate }}' env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + clitag: + if: needs.info.outputs.is-release == 'true' && needs.info.outputs.crate == 'cargo-binstall' + needs: info + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Push cli release tag + uses: mathieudutour/github-tag-action@v6.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + custom_tag: ${{ needs.info.outputs.version }} + tag_prefix: v + - uses: cargo-bins/cargo-binstall@main + - name: Create ephemeral keypair + id: keypair + run: .github/scripts/ephemeral-gen.sh + - name: Publish to crates.io + env: + crate: ${{ needs.info.outputs.crate }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish -p "$crate" + outputs: + signingkey: ${{ steps.keypair.outputs.private }} + package: if: needs.info.outputs.is-release == 'true' && needs.info.outputs.crate == 'cargo-binstall' needs: - info - - tag + - clitag uses: ./.github/workflows/release-build.yml with: publish: ${{ toJSON(needs.info.outputs) }} + secrets: + signingkey: ${{ needs.clitag.signingkey }} diff --git a/SIGNING.md b/SIGNING.md index 02927aa7..d18eff0f 100644 --- a/SIGNING.md +++ b/SIGNING.md @@ -59,7 +59,7 @@ It may or may not include the untrusted comment; it's ignored by Binstall so we ## Just-in-time signing -To reduce the risk of a key being stolen, this scheme supports just-in-time signing. +To reduce the risk of a key being stolen, this scheme supports just-in-time or "keyless" signing. The idea is to generate a keypair when releasing, use it for signing the packages, save the key in the Cargo.toml before publishing to a registry, and then discard the private key when it's done. That way, there's no key to steal nor to store securely, and every release is signed by a different key. And because crates.io is immutable, it's impossible to overwrite the key. @@ -73,7 +73,7 @@ There is one caveat to keep in mind: with the scheme as described above, Binstal The solution here is either: - Commit the Cargo.toml with the ephemeral public key to the repo when publishing. -- Omit the `[...signing]` section in the source, and write the entire section on publish instead of just filling in the `pubkey`; signatures won't be checked for `--git` installs. +- Omit the `[...signing]` section in the source, and write the entire section on publish instead of just filling in the `pubkey`; signatures won't be checked for `--git` installs. Binstall uses this approach. - Instruct your users to use `--skip-signatures` if they want to install with `--git`. ## Why not X? (Sigstore, GPG, signify, with SSH keys, ...) @@ -84,10 +84,10 @@ We chose minisign as the first supported algorithm as it's lightweight, fairly p ## There's a competing project that does package signature verification differently! -[Tell use about it](https://github.com/cargo-bins/cargo-binstall/issues/1)! +[Tell us about it](https://github.com/cargo-bins/cargo-binstall/issues/1)! We're not looking to fracture the ecosystem here, and will gladly implement support if something exists already. -We'll also work with others in the space to eventually formalise this beyond Binstall, for example around the `cargo-dist.json` metadata format. +We'll also work with others in the space to eventually formalise this beyond Binstall, for example around the [`dist-manifest.json`](https://crates.io/crates/cargo-dist-schema) metadata format. ## What's the relationship to crate/registry signing?