diff --git a/.github/workflows/pull-request-bot.yml b/.github/workflows/pull-request-bot.yml new file mode 100644 index 0000000..8b53b31 --- /dev/null +++ b/.github/workflows/pull-request-bot.yml @@ -0,0 +1,58 @@ +name: open-pr-dev-to-main + +on: + push: + branches: [dev] + +permissions: + contents: read + +concurrency: + group: open-pr-dev-to-main + cancel-in-progress: true + +jobs: + pr: + runs-on: ubuntu-latest + environment: submit-pull-request + steps: + - name: App token + id: app + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2 + with: + app-id: ${{ secrets.PR_BOT_APP_ID }} + private-key: ${{ secrets.PR_BOT_PRIVATE_KEY }} + repositories: ${{ github.event.repository.name }} + permission-pull-requests: write + permission-contents: read + + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + + - name: Create or update PR + env: + GH_TOKEN: ${{ steps.app.outputs.token }} + run: | + set -euo pipefail + git fetch origin main dev + N=$(git rev-list --count origin/main..origin/dev) + [ "$N" -gt 0 ] || { echo "dev is level with main; nothing to open"; exit 0; } + FIRST=$(git log -1 --format=%s origin/dev) + if [ "$N" -eq 1 ]; then + TITLE="$FIRST" + else + TITLE="dev to main: $FIRST (+$((N-1)) more)" + fi + { + echo "## $N commit(s) ahead of main" + echo + git log --reverse --format='### %s%n%n%b%n(%h)%n' origin/main..origin/dev + } | head -c 60000 > body.md + NUM=$(gh pr list --base main --head dev --state open --json number --jq '.[0].number // empty') + if [ -n "$NUM" ]; then + gh pr edit "$NUM" --title "$TITLE" --body-file body.md + else + gh pr create --base main --head dev --title "$TITLE" --body-file body.md + fi \ No newline at end of file diff --git a/.github/workflows/release-policy.yml b/.github/workflows/release-policy.yml index 9bc7d7c..47fe2a1 100644 --- a/.github/workflows/release-policy.yml +++ b/.github/workflows/release-policy.yml @@ -5,7 +5,7 @@ name: release-policy # # Trust model (who vouches for what): # commit GPG-signed by the author -> who wrote the policy -# tag gitsign (Sigstore OIDC) annotated -> who approved this exact commit as a release +# tag GPG-signed annotated, Verified by GitHub -> who approved this exact commit as a release # build this workflow + GitHub attestation -> these bytes were produced by this workflow # release SHA256SUMS.sigstore.json, keyless cosign signed ON THE MAINTAINER'S # WORKSTATION (sign-release.sh) -> a human approved these exact bytes @@ -15,9 +15,8 @@ name: release-policy # publishes by hand (immutable releases: nothing can be swapped afterwards). # # Manual only, from main, behind the `policy-release` environment. -# Enforced (vN) policies need a pre-existing gitsign-signed annotated tag -# `policy-builder-image-vN` pointing at the build SHA, verified here against the identity -# pinned in repository variables POLICY_SIGNER_IDENTITY / POLICY_SIGNER_ISSUER. +# Enforced (vN) policies need a pre-existing GPG-signed annotated tag +# `policy-builder-image-vN` pointing at the build SHA, and GitHub must report it as Verified. # Descriptive (legacy-*) policies need no tag and carry no provenance # attestation, so they cannot be mistaken for an enforcement contract. @@ -45,9 +44,6 @@ jobs: env: GH_TOKEN: ${{ github.token }} WANT: ${{ inputs.policy_version }} - SIGNER_IDENTITY: ${{ vars.POLICY_SIGNER_IDENTITY }} - SIGNER_ISSUER: ${{ vars.POLICY_SIGNER_ISSUER }} - GITSIGN_REF: dd51a25b4f9fa103ca6f5cdca4183fa15cb3e627 # v0.14.0 steps: - name: Checkout uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 @@ -100,7 +96,7 @@ jobs: tag="$TAG" git fetch --force --tags origin if ! git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then - echo "::error::tag ${tag} not found - create it locally with your gitsign tag alias and push it first" + echo "::error::tag ${tag} not found - create a GPG-signed annotated tag locally (git tag -s) and push it first" exit 1 fi if [ "$(git cat-file -t "refs/tags/${tag}")" != "tag" ]; then diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 249f674..053fa5d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,7 @@ name: release-image # Dry run: `gh workflow run release.yml --ref ` (workflow_dispatch on a tag ref). It does # everything above, using the policy.json in the tagged commit instead of the published release, # and creates no release. The sha256 check still applies. +# The attest steps still run, so a dry run pushes real attestations to the image in the registry. on: push: @@ -280,7 +281,7 @@ jobs: set -euo pipefail gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --draft --verify-tag \ --title "Builder image $GITHUB_REF_NAME" \ - --notes "Draft. Verified against policy ${POLICY_TAG} (policy.json sha256 ${POLICY_SHA256}) by release.yml. Verify the image: gh attestation verify oci://${IMAGE}@> --repo ${GITHUB_REPOSITORY} --signer-workflow ${GITHUB_REPOSITORY}/.github/workflows/build-environment-image.yml, and with --predicate-type for the SBOM and VEX attestations signed by release.yml." + --notes "Verified against policy ${POLICY_TAG} (policy.json sha256 ${POLICY_SHA256}) by release.yml. Verify the image: gh attestation verify oci://${IMAGE}@DIGEST --repo ${GITHUB_REPOSITORY} --signer-workflow ${GITHUB_REPOSITORY}/.github/workflows/build-environment-image.yml (DIGEST is the value in the image.digest.ARCH asset; ARCH is x86_64 or aarch64), and with --predicate-type for the SBOM and VEX attestations signed by release.yml." gh release upload "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" release-assets/* got="$(gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --json assets --jq '[.assets[].name] | sort | .[]')" [ "$got" = "$(cat assets.txt)" ] || { echo "::error::draft asset set differs from the verified set"; exit 1; }