Skip to content

Commit 4e25eac

Browse files
committed
security: gate publication on verified attestations
1 parent 66d871d commit 4e25eac

2 files changed

Lines changed: 68 additions & 12 deletions

File tree

‎.github/workflows/package.yml‎

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ jobs:
317317
318318
publish:
319319
name: Publish reviewed distribution
320-
needs: [build, smoke, provenance]
320+
needs: [build, smoke, provenance, verify_attestations]
321321
if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || github.event_name == 'workflow_dispatch' }}
322322
runs-on: ubuntu-latest
323323
timeout-minutes: 10
@@ -381,9 +381,46 @@ jobs:
381381
subject-checksums: dist/SHA256SUMS
382382
sbom-path: dist/SBOM.spdx.json
383383

384+
verify_attestations:
385+
name: Verify reviewed release attestations
386+
needs: [build, smoke, provenance, attest]
387+
if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || github.event_name == 'workflow_dispatch' }}
388+
runs-on: ubuntu-latest
389+
timeout-minutes: 10
390+
permissions:
391+
contents: read
392+
attestations: read
393+
steps:
394+
- name: Download reviewed distributions
395+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
396+
with:
397+
name: base-cli-dist-${{ github.run_id }}
398+
path: dist
399+
400+
- name: Verify artifact provenance and SBOM attestations
401+
env:
402+
SOURCE_COMMIT: ${{ needs.build.outputs.source_commit }}
403+
RELEASE_TAG: ${{ needs.build.outputs.release_tag }}
404+
GH_TOKEN: ${{ github.token }}
405+
run: |
406+
set -euo pipefail
407+
for asset in dist/*.whl dist/*.tar.gz; do
408+
gh attestation verify "$asset" \
409+
--repo "$GITHUB_REPOSITORY" \
410+
--signer-workflow "github.com/$GITHUB_REPOSITORY/.github/workflows/package.yml" \
411+
--source-digest "$SOURCE_COMMIT" \
412+
--source-ref "refs/tags/$RELEASE_TAG"
413+
gh attestation verify "$asset" \
414+
--repo "$GITHUB_REPOSITORY" \
415+
--signer-workflow "github.com/$GITHUB_REPOSITORY/.github/workflows/package.yml" \
416+
--source-digest "$SOURCE_COMMIT" \
417+
--source-ref "refs/tags/$RELEASE_TAG" \
418+
--predicate-type "https://spdx.dev/Document"
419+
done
420+
384421
verify_pypi:
385422
name: Verify published PyPI metadata
386-
needs: [build, publish, attest]
423+
needs: [build, publish, verify_attestations]
387424
if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi' && inputs.release_tag != '') }}
388425
runs-on: ubuntu-latest
389426
timeout-minutes: 5
@@ -405,7 +442,7 @@ jobs:
405442

406443
release:
407444
name: Create GitHub Release
408-
needs: [build, smoke, provenance, publish, attest, verify_pypi]
445+
needs: [build, smoke, provenance, attest, verify_attestations, publish, verify_pypi]
409446
if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi' && inputs.release_tag != '') }}
410447
runs-on: ubuntu-latest
411448
timeout-minutes: 10
@@ -442,6 +479,7 @@ jobs:
442479
SOURCE_REVISION: ${{ needs.build.outputs.source_commit }}
443480
RELEASE_TAG: ${{ needs.build.outputs.release_tag }}
444481
SOURCE_COMMIT: ${{ needs.build.outputs.source_commit }}
482+
SOURCE_REF: refs/tags/${{ needs.build.outputs.release_tag }}
445483
run: |
446484
set -euo pipefail
447485
tag="$RELEASE_TAG"
@@ -458,13 +496,13 @@ jobs:
458496
gh attestation verify "$asset" \
459497
--repo "$GITHUB_REPOSITORY" \
460498
--signer-workflow "github.com/$GITHUB_REPOSITORY/.github/workflows/package.yml" \
461-
--source-digest "$GITHUB_SHA" \
462-
--source-ref "$GITHUB_REF"
499+
--source-digest "$SOURCE_COMMIT" \
500+
--source-ref "$SOURCE_REF"
463501
gh attestation verify "$asset" \
464502
--repo "$GITHUB_REPOSITORY" \
465503
--signer-workflow "github.com/$GITHUB_REPOSITORY/.github/workflows/package.yml" \
466-
--source-digest "$GITHUB_SHA" \
467-
--source-ref "$GITHUB_REF" \
504+
--source-digest "$SOURCE_COMMIT" \
505+
--source-ref "$SOURCE_REF" \
468506
--predicate-type "https://spdx.dev/Document"
469507
done
470508

‎tests/test_package_workflow.py‎

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ def test_package_workflow_does_not_replace_published_release_assets() -> None:
4040
assert "refusing to replace immutable release assets" in verifier
4141
assert "gh release download" in workflow
4242
assert "gh attestation verify" in workflow
43-
assert '--source-digest "$GITHUB_SHA"' in workflow
44-
assert '--source-ref "$GITHUB_REF"' in workflow
43+
assert '--source-digest "$SOURCE_COMMIT"' in workflow
44+
assert '--source-ref "$SOURCE_REF"' in workflow
4545
assert '--source-commit "$source_commit"' in workflow
4646
assert "--clobber" not in workflow
4747

@@ -63,7 +63,7 @@ def test_package_workflow_gates_tag_publication_on_release_documentation() -> No
6363
assert "python scripts/validate_release_docs.py" in workflow
6464
assert "scripts/verify_pypi_metadata.py" in workflow
6565
assert "name: Verify published PyPI metadata" in workflow
66-
assert "needs: [build, smoke, provenance, publish, attest, verify_pypi]" in workflow
66+
assert "needs: [build, smoke, provenance, attest, verify_attestations, publish, verify_pypi]" in workflow
6767

6868

6969
def test_package_workflow_gates_writes_on_release_provenance() -> None:
@@ -73,6 +73,24 @@ def test_package_workflow_gates_writes_on_release_provenance() -> None:
7373
assert "name: Verify reviewed release provenance" in workflow
7474
assert 'git fetch --no-tags --prune origin "refs/heads/main:refs/remotes/origin/main"' in workflow
7575
assert "python scripts/validate_release_provenance.py" in workflow
76-
assert jobs["publish"]["needs"] == ["build", "smoke", "provenance"]
76+
assert jobs["publish"]["needs"] == ["build", "smoke", "provenance", "verify_attestations"]
7777
assert jobs["attest"]["needs"] == ["build", "smoke", "provenance"]
78-
assert jobs["release"]["needs"] == ["build", "smoke", "provenance", "publish", "attest", "verify_pypi"]
78+
assert jobs["release"]["needs"] == [
79+
"build",
80+
"smoke",
81+
"provenance",
82+
"attest",
83+
"verify_attestations",
84+
"publish",
85+
"verify_pypi",
86+
]
87+
88+
89+
def test_package_workflow_verifies_attestations_before_publication() -> None:
90+
workflow = (Path(__file__).resolve().parents[1] / ".github/workflows/package.yml").read_text(encoding="utf-8")
91+
jobs = yaml.safe_load(workflow)["jobs"]
92+
93+
assert jobs["publish"]["needs"] == ["build", "smoke", "provenance", "verify_attestations"]
94+
assert jobs["verify_attestations"]["needs"] == ["build", "smoke", "provenance", "attest"]
95+
assert '--source-digest "$SOURCE_COMMIT"' in workflow
96+
assert '--source-ref "refs/tags/$RELEASE_TAG"' in workflow

0 commit comments

Comments
 (0)