Verify published release evidence #3
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| workflow_dispatch: | |
| inputs: | |
| publish_to_pypi: | |
| description: "Publish dist artifacts to PyPI through Trusted Publishing. Requires the pypi environment and PyPI publisher to already exist." | |
| required: true | |
| default: false | |
| type: boolean | |
| trusted_publisher_configured: | |
| description: "Set to yes only after PyPI has a Trusted Publisher for this repo, workflow, and pypi environment." | |
| required: true | |
| default: "no" | |
| type: choice | |
| options: | |
| - "no" | |
| - "yes" | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| test: | |
| name: Test Python ${{ matrix.python-version }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.11", "3.13"] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| cache-dependency-path: | | |
| requirements.txt | |
| requirements-dev.txt | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -r requirements-dev.txt | |
| - name: Run tests | |
| run: python -m pytest test_secure_vault.py test_release_workflow.py -v | |
| build-release: | |
| name: Build, attest, and upload release assets | |
| runs-on: ubuntu-latest | |
| needs: test | |
| permissions: | |
| contents: write | |
| id-token: write | |
| attestations: write | |
| outputs: | |
| release_id: ${{ steps.release-id.outputs.release_id }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| cache: pip | |
| cache-dependency-path: | | |
| requirements.txt | |
| requirements-dev.txt | |
| - name: Install build dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -r requirements-dev.txt | |
| - name: Run release tests | |
| run: python -m pytest test_secure_vault.py test_release_workflow.py -v | |
| - name: Clean generated release outputs | |
| run: rm -rf dist release-artifacts | |
| - name: Build distributions | |
| run: python -m build | |
| - name: Generate release transparency artifacts | |
| run: | | |
| if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then | |
| python tools/generate_release_artifacts.py --require-tag --require-clean | |
| else | |
| python tools/generate_release_artifacts.py | |
| fi | |
| - name: Resolve release artifact directory | |
| id: release-id | |
| run: | | |
| tag="$(git describe --tags --exact-match HEAD 2>/dev/null || true)" | |
| if [[ -n "${tag}" ]]; then | |
| release_id="${tag}" | |
| else | |
| version="$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")" | |
| release_id="${version}-$(git rev-parse --short HEAD)" | |
| fi | |
| test -d "release-artifacts/${release_id}" | |
| echo "release_id=${release_id}" >> "${GITHUB_OUTPUT}" | |
| - name: Upload distribution artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: python-dist | |
| path: dist/* | |
| if-no-files-found: error | |
| - name: Upload transparency artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-transparency | |
| path: release-artifacts/${{ steps.release-id.outputs.release_id }}/* | |
| if-no-files-found: error | |
| - name: Attest release artifacts | |
| uses: actions/attest@v4 | |
| with: | |
| subject-path: | | |
| ${{ github.workspace }}/dist/* | |
| ${{ github.workspace }}/release-artifacts/${{ steps.release-id.outputs.release_id }}/* | |
| - name: Publish GitHub release assets | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_ID: ${{ steps.release-id.outputs.release_id }} | |
| run: | | |
| notes="$(mktemp)" | |
| cat > "${notes}" <<'EOF' | |
| This release publishes Python distribution files plus SBOM, checksum, and local provenance artifacts for reviewability. | |
| AES Secure Vault remains an educational authenticated-encryption tool. These release artifacts and GitHub attestations are supply-chain transparency evidence, not production cryptography certification. | |
| EOF | |
| assets=(dist/* release-artifacts/${RELEASE_ID}/*) | |
| if gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1; then | |
| gh release upload "${GITHUB_REF_NAME}" "${assets[@]}" --clobber | |
| else | |
| gh release create "${GITHUB_REF_NAME}" "${assets[@]}" --title "AES Secure Vault ${GITHUB_REF_NAME}" --notes-file "${notes}" | |
| fi | |
| - name: Verify assets as published | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| python tools/verify_published_release.py \ | |
| --repository "${GITHUB_REPOSITORY}" \ | |
| --tag "${GITHUB_REF_NAME}" \ | |
| --expected-commit "${GITHUB_SHA}" \ | |
| --report published-release-verification.json | |
| - name: Upload published verification result | |
| if: startsWith(github.ref, 'refs/tags/v') && always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: published-release-verification-${{ github.ref_name }} | |
| path: published-release-verification.json | |
| if-no-files-found: warn | |
| pypi-preflight: | |
| name: PyPI Trusted Publishing preflight | |
| runs-on: ubuntu-latest | |
| needs: build-release | |
| if: github.event_name == 'workflow_dispatch' && inputs.publish_to_pypi == true | |
| permissions: | |
| contents: read | |
| actions: read | |
| steps: | |
| - name: Stop unless this is a tagged release | |
| run: | | |
| if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then | |
| echo "::error::PyPI publishing is allowed only from a v* tag. Re-run this workflow from the release tag." | |
| exit 1 | |
| fi | |
| - name: Stop unless PyPI Trusted Publisher setup is confirmed | |
| run: | | |
| if [[ "${{ inputs.trusted_publisher_configured }}" != "yes" ]]; then | |
| echo "::error::Configure the PyPI Trusted Publisher first, then re-run with trusted_publisher_configured=yes." | |
| exit 1 | |
| fi | |
| - name: Verify GitHub pypi environment exists | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| curl --fail --silent --show-error \ | |
| -H "Authorization: Bearer ${GH_TOKEN}" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${GITHUB_REPOSITORY}/environments/pypi" >/dev/null | |
| pypi-publish: | |
| name: Publish to PyPI | |
| runs-on: ubuntu-latest | |
| needs: [build-release, pypi-preflight] | |
| if: github.event_name == 'workflow_dispatch' && inputs.publish_to_pypi == true | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/project/aes-secure-vault/ | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Download distribution artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: python-dist | |
| path: dist | |
| - name: Publish package distributions to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 |