diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a9b3e9528..a8dfb39d2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -118,7 +118,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh attestation verify source.tar.gz \ + gh attestation verify dfetch-source.tar.gz \ --repo "${{ github.repository }}" \ --predicate-type https://slsa.dev/provenance/v1 \ --cert-identity-regex "^https://github\.com/${{ github.repository }}/\.github/workflows/source-provenance\.yml@refs/(heads/main|tags/[0-9]+\.[0-9]+\.[0-9]+)$" \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da042dc0b..3b3bc1ab3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,9 +49,13 @@ jobs: security-events: write test: + needs: source-provenance + if: always() && (needs.source-provenance.result == 'success' || needs.source-provenance.result == 'skipped') uses: ./.github/workflows/test.yml permissions: contents: read + attestations: write + id-token: write secrets: CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} diff --git a/.github/workflows/source-provenance.yml b/.github/workflows/source-provenance.yml index a1ebef8a9..5825effb1 100644 --- a/.github/workflows/source-provenance.yml +++ b/.github/workflows/source-provenance.yml @@ -38,18 +38,18 @@ jobs: persist-credentials: false - name: Generate source archive - run: git archive HEAD --format=tar.gz -o source.tar.gz + run: git archive HEAD --format=tar.gz -o dfetch-source.tar.gz - name: Attest source provenance uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: - subject-path: source.tar.gz + subject-path: dfetch-source.tar.gz - name: Verify source provenance env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh attestation verify source.tar.gz \ + gh attestation verify dfetch-source.tar.gz \ --repo "${{ github.repository }}" \ --predicate-type https://slsa.dev/provenance/v1 \ --cert-identity-regex "^https://github\.com/${{ github.repository }}/\.github/workflows/source-provenance\.yml@refs/(heads/main|tags/[0-9]+\.[0-9]+\.[0-9]+)$" \ @@ -59,4 +59,4 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: source-archive - path: source.tar.gz + path: dfetch-source.tar.gz diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ca779660..69478e015 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,10 @@ permissions: jobs: test: runs-on: ubuntu-latest + permissions: + contents: read + attestations: write + id-token: write steps: - name: "Harden the runner (Block egress traffic: Only allow calls to allowed endpoints)" uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 @@ -48,6 +52,12 @@ jobs: security.ubuntu.com:443 svn.code.sf.net:3690 svn.code.sf.net:443 + api.github.com:443 + uploads.github.com:443 + fulcio.sigstore.dev:443 + rekor.sigstore.dev:443 + tuf-repo-cdn.sigstore.dev:443 + *.blob.core.windows.net:443 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -81,8 +91,10 @@ jobs: - run: pydocstyle dfetch # Checks doc strings - run: bandit -r dfetch # Checks security issues - run: xenon -b B -m A -a A dfetch # Check code quality - - run: pytest --cov=dfetch tests # Run tests - - run: coverage run --source=dfetch --append -m behave features # Run features tests + - id: pytest + run: pytest --cov=dfetch tests # Run tests + - id: behave + run: coverage run --source=dfetch --append -m behave features # Run features tests - run: coverage xml -o coverage.xml # Create XML report - run: pyroma --directory --min=10 . # Check pyproject - run: find dfetch -name "*.py" | xargs pyupgrade --py310-plus # Check syntax @@ -96,3 +108,45 @@ jobs: env: CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} if: "${{ (!!env.CODACY_PROJECT_TOKEN) }}" + + - name: Download canonical source archive + id: download-source + if: github.event_name != 'pull_request' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v5 + with: + name: source-archive + + - name: Generate test result predicate + if: steps.download-source.outcome == 'success' + run: | + if [[ "${{ steps.pytest.outcome }}" == "success" && "${{ steps.behave.outcome }}" == "success" ]]; then + tests_result="PASSED" + else + tests_result="FAILED" + fi + cat > test-result-predicate.json <&2 + exit 1 + fi + + - name: Attest test results + if: steps.download-source.outcome == 'success' + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-path: dfetch-source.tar.gz + predicate-type: https://in-toto.io/attestation/test-result/v0.1 + predicate-path: test-result-predicate.json diff --git a/doc/howto/verify-integrity.rst b/doc/howto/verify-integrity.rst new file mode 100644 index 000000000..074089950 --- /dev/null +++ b/doc/howto/verify-integrity.rst @@ -0,0 +1,222 @@ +Verify release integrity +======================== + +Every dfetch release, and every commit merged to ``main``, has cryptographic +attestations signed by GitHub Actions and anchored in +`Sigstore `_, all published in the +`attestation registry `_. +There are four complementary kinds: + +- **SLSA build provenance** — answers *"where did this come from?"*: proves the + artifact was produced from the official source commit by the official CI + workflow, and records the exact inputs used at build time. +- **SBOM attestation** (CycloneDX) — answers *"what is inside it?"*: lists every + dependency bundled in the package so you can audit its composition. +- **Verification Summary Attestation (VSA)** — answers *"was the source + independently verified?"*: records that the source archive for this commit was + attested and verified before the binary was produced, linking source-level + trust to the binary package. +- **Test result attestation** (in-toto) — answers *"did the source pass its tests?"*: + records that the full CI test suite ran against this exact source archive and every + check passed, before any binary was produced. + +Binary installers have **build provenance, SBOM, and VSA** attestations when source +provenance verification passes (signed by ``build.yml``). +Python packages installed from PyPI have an **SBOM attestation only** (signed by +``python-publish.yml``). +The source archive has a **SLSA build provenance** attestation (signed by +``source-provenance.yml``) and a **test result attestation** (signed by ``test.yml``). + +To verify, use the `GitHub CLI `_. Pass +``--predicate-type`` to target one kind specifically; omit it to accept any. + +In the commands below, replace ``@refs/tags/v`` with +``@refs/heads/main`` when verifying a development build installed directly +from the ``main`` branch. + +.. tabs:: + + .. tab:: Linux + + **Binary installer — verify build provenance:** + + .. code-block:: bash + + $ gh attestation verify dfetch--nix.deb \ + --repo dfetch-org/dfetch \ + --predicate-type https://slsa.dev/provenance/v1 \ + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ + --cert-oidc-issuer https://token.actions.githubusercontent.com + + **Binary installer — verify SBOM attestation:** + + .. code-block:: bash + + $ gh attestation verify dfetch--nix.deb \ + --repo dfetch-org/dfetch \ + --predicate-type https://cyclonedx.org/bom \ + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ + --cert-oidc-issuer https://token.actions.githubusercontent.com + + **Binary installer — verify source provenance summary (VSA):** + + .. code-block:: bash + + $ gh attestation verify dfetch--nix.deb \ + --repo dfetch-org/dfetch \ + --predicate-type https://slsa.dev/verification_summary/v1 \ + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ + --cert-oidc-issuer https://token.actions.githubusercontent.com + + **pip / PyPI wheel — verify SBOM attestation:** + + Download the wheel before installing so you have the original file to verify: + + .. code-block:: bash + + $ pip download dfetch== --no-deps -d . + $ gh attestation verify dfetch--py3-none-any.whl \ + --repo dfetch-org/dfetch \ + --predicate-type https://cyclonedx.org/bom \ + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/python-publish.yml@refs/tags/v \ + --cert-oidc-issuer https://token.actions.githubusercontent.com + + .. tab:: macOS + + **Binary installer — verify build provenance:** + + .. code-block:: bash + + $ gh attestation verify dfetch--osx.pkg \ + --repo dfetch-org/dfetch \ + --predicate-type https://slsa.dev/provenance/v1 \ + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ + --cert-oidc-issuer https://token.actions.githubusercontent.com + + **Binary installer — verify SBOM attestation:** + + .. code-block:: bash + + $ gh attestation verify dfetch--osx.pkg \ + --repo dfetch-org/dfetch \ + --predicate-type https://cyclonedx.org/bom \ + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ + --cert-oidc-issuer https://token.actions.githubusercontent.com + + **Binary installer — verify source provenance summary (VSA):** + + .. code-block:: bash + + $ gh attestation verify dfetch--osx.pkg \ + --repo dfetch-org/dfetch \ + --predicate-type https://slsa.dev/verification_summary/v1 \ + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ + --cert-oidc-issuer https://token.actions.githubusercontent.com + + **pip / PyPI wheel — verify SBOM attestation:** + + Download the wheel before installing so you have the original file to verify: + + .. code-block:: bash + + $ pip download dfetch== --no-deps -d . + $ gh attestation verify dfetch--py3-none-any.whl \ + --repo dfetch-org/dfetch \ + --predicate-type https://cyclonedx.org/bom \ + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/python-publish.yml@refs/tags/v \ + --cert-oidc-issuer https://token.actions.githubusercontent.com + + .. tab:: Windows + + **Binary installer — verify build provenance:** + + .. code-block:: powershell + + > gh attestation verify dfetch--win.msi ` + --repo dfetch-org/dfetch ` + --predicate-type https://slsa.dev/provenance/v1 ` + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v ` + --cert-oidc-issuer https://token.actions.githubusercontent.com + + **Binary installer — verify SBOM attestation:** + + .. code-block:: powershell + + > gh attestation verify dfetch--win.msi ` + --repo dfetch-org/dfetch ` + --predicate-type https://cyclonedx.org/bom ` + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v ` + --cert-oidc-issuer https://token.actions.githubusercontent.com + + **Binary installer — verify source provenance summary (VSA):** + + .. code-block:: powershell + + > gh attestation verify dfetch--win.msi ` + --repo dfetch-org/dfetch ` + --predicate-type https://slsa.dev/verification_summary/v1 ` + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v ` + --cert-oidc-issuer https://token.actions.githubusercontent.com + + **pip / PyPI wheel — verify SBOM attestation:** + + Download the wheel before installing so you have the original file to verify: + + .. code-block:: powershell + + > pip download dfetch== --no-deps -d . + > gh attestation verify dfetch--py3-none-any.whl ` + --repo dfetch-org/dfetch ` + --predicate-type https://cyclonedx.org/bom ` + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/python-publish.yml@refs/tags/v ` + --cert-oidc-issuer https://token.actions.githubusercontent.com + +**Source archive — verify build provenance and test results:** + +The source archive has two attestations and is produced for every release and +every ``main``-branch commit. Download ``dfetch-source.tar.gz`` from the *Artifacts* +section of the relevant CI run, then verify each in turn. Use +``@refs/tags/v`` for a release or ``@refs/heads/main`` for a +development build. + +Verify SLSA build provenance (proves the archive was produced by the official +workflow from the tagged commit): + +.. code-block:: bash + + $ gh attestation verify dfetch-source.tar.gz \ + --repo dfetch-org/dfetch \ + --predicate-type https://slsa.dev/provenance/v1 \ + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/source-provenance.yml@refs/tags/v \ + --cert-oidc-issuer https://token.actions.githubusercontent.com + +Verify test results (proves the full CI suite passed on that exact source before +any binary was produced): + +.. code-block:: bash + + $ gh attestation verify dfetch-source.tar.gz \ + --repo dfetch-org/dfetch \ + --predicate-type https://in-toto.io/attestation/test-result/v0.1 \ + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/test.yml@refs/tags/v \ + --cert-oidc-issuer https://token.actions.githubusercontent.com + +See `GitHub artifact attestations`_ for details. + +.. note:: + + The VSA is present on releases built from a commit whose source provenance + was verified. If the ``verification_summary`` attestation is absent for a + release, fall back to checking build provenance and SBOM independently. + +.. note:: + + ``--cert-oidc-issuer https://token.actions.githubusercontent.com`` pins + verification to GitHub Actions as the identity provider. Without it, an + attacker could generate a valid attestation using a different OIDC issuer + (for example a self-hosted Sigstore instance) that also produces a + certificate matching the ``--cert-identity`` workflow path. Supplying + both flags together ensures the certificate was issued specifically by + GitHub Actions for the declared workflow. + +.. _`GitHub artifact attestations`: https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations diff --git a/doc/index.rst b/doc/index.rst index 6877fb8b4..c9c5b0775 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -67,6 +67,7 @@ upstream. See :ref:`vendoring` for background on the problem this solves. howto/patching howto/check-ci howto/sbom + howto/verify-integrity howto/troubleshooting howto/contributing diff --git a/doc/tutorials/installation.rst b/doc/tutorials/installation.rst index 22b9d5529..03db12c15 100644 --- a/doc/tutorials/installation.rst +++ b/doc/tutorials/installation.rst @@ -91,172 +91,6 @@ Run the following command to verify the installation Verifying release integrity --------------------------- -Every dfetch release carries cryptographic attestations signed by GitHub Actions -and anchored in `Sigstore `_. There are two -complementary kinds: - -- **SLSA build provenance** — answers *"where did this come from?"*: proves the - artifact was produced from the official source commit by the official CI - workflow, and records the exact inputs used at build time. -- **SBOM attestation** (CycloneDX) — answers *"what is inside it?"*: lists every - dependency bundled in the package so you can audit its composition. -- **Verification Summary Attestation (VSA)** — answers *"was the source - independently verified?"*: records that the source archive for this commit was - attested and verified before the binary was produced, linking source-level - trust to the binary package. - -Binary installers carry **all three** kinds of attestation when source -provenance verification passes (signed by ``build.yml``). -Python packages installed from PyPI carry an **SBOM attestation only** (signed by -``python-publish.yml``). - -To verify, use the `GitHub CLI `_. Pass -``--predicate-type`` to target one kind specifically; omit it to accept either. - -.. tabs:: - - .. tab:: Linux - - **Binary installer — verify build provenance:** - - .. code-block:: bash - - $ gh attestation verify dfetch--nix.deb \ - --repo dfetch-org/dfetch \ - --predicate-type https://slsa.dev/provenance/v1 \ - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ - --cert-oidc-issuer https://token.actions.githubusercontent.com - - **Binary installer — verify SBOM attestation:** - - .. code-block:: bash - - $ gh attestation verify dfetch--nix.deb \ - --repo dfetch-org/dfetch \ - --predicate-type https://cyclonedx.org/bom \ - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ - --cert-oidc-issuer https://token.actions.githubusercontent.com - - **Binary installer — verify source provenance summary (VSA):** - - .. code-block:: bash - - $ gh attestation verify dfetch--nix.deb \ - --repo dfetch-org/dfetch \ - --predicate-type https://slsa.dev/verification_summary/v1 \ - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ - --cert-oidc-issuer https://token.actions.githubusercontent.com - - **pip / PyPI wheel — verify SBOM attestation:** - - .. code-block:: bash - - $ gh attestation verify ~/.local/lib/python3.x/site-packages/dfetch-.dist-info/ \ - --repo dfetch-org/dfetch \ - --predicate-type https://cyclonedx.org/bom \ - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/python-publish.yml@refs/tags/v \ - --cert-oidc-issuer https://token.actions.githubusercontent.com - - .. tab:: macOS - - **Binary installer — verify build provenance:** - - .. code-block:: bash - - $ gh attestation verify dfetch--osx.pkg \ - --repo dfetch-org/dfetch \ - --predicate-type https://slsa.dev/provenance/v1 \ - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ - --cert-oidc-issuer https://token.actions.githubusercontent.com - - **Binary installer — verify SBOM attestation:** - - .. code-block:: bash - - $ gh attestation verify dfetch--osx.pkg \ - --repo dfetch-org/dfetch \ - --predicate-type https://cyclonedx.org/bom \ - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ - --cert-oidc-issuer https://token.actions.githubusercontent.com - - **Binary installer — verify source provenance summary (VSA):** - - .. code-block:: bash - - $ gh attestation verify dfetch--osx.pkg \ - --repo dfetch-org/dfetch \ - --predicate-type https://slsa.dev/verification_summary/v1 \ - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v \ - --cert-oidc-issuer https://token.actions.githubusercontent.com - - **pip / PyPI wheel — verify SBOM attestation:** - - .. code-block:: bash - - $ gh attestation verify /usr/local/lib/python3.x/site-packages/dfetch-.dist-info/ \ - --repo dfetch-org/dfetch \ - --predicate-type https://cyclonedx.org/bom \ - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/python-publish.yml@refs/tags/v \ - --cert-oidc-issuer https://token.actions.githubusercontent.com - - .. tab:: Windows - - **Binary installer — verify build provenance:** - - .. code-block:: powershell - - > gh attestation verify dfetch--win.msi ` - --repo dfetch-org/dfetch ` - --predicate-type https://slsa.dev/provenance/v1 ` - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v ` - --cert-oidc-issuer https://token.actions.githubusercontent.com - - **Binary installer — verify SBOM attestation:** - - .. code-block:: powershell - - > gh attestation verify dfetch--win.msi ` - --repo dfetch-org/dfetch ` - --predicate-type https://cyclonedx.org/bom ` - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v ` - --cert-oidc-issuer https://token.actions.githubusercontent.com - - **Binary installer — verify source provenance summary (VSA):** - - .. code-block:: powershell - - > gh attestation verify dfetch--win.msi ` - --repo dfetch-org/dfetch ` - --predicate-type https://slsa.dev/verification_summary/v1 ` - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v ` - --cert-oidc-issuer https://token.actions.githubusercontent.com - - **pip / PyPI wheel — verify SBOM attestation:** - - .. code-block:: powershell - - > gh attestation verify $env:APPDATA\Python\Python3x\site-packages\dfetch-.dist-info\ ` - --repo dfetch-org/dfetch ` - --predicate-type https://cyclonedx.org/bom ` - --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/python-publish.yml@refs/tags/v ` - --cert-oidc-issuer https://token.actions.githubusercontent.com - -See `GitHub artifact attestations`_ for details. - -.. note:: - - The VSA is present on releases built from a commit whose source provenance - was verified. If the ``verification_summary`` attestation is absent for a - release, fall back to checking build provenance and SBOM independently. - -.. note:: - - ``--cert-oidc-issuer https://token.actions.githubusercontent.com`` pins - verification to GitHub Actions as the identity provider. Without it, an - attacker could generate a valid attestation using a different OIDC issuer - (for example a self-hosted Sigstore instance) that also produces a - certificate matching the ``--cert-identity`` workflow path. Supplying - both flags together ensures the certificate was issued specifically by - GitHub Actions for the declared workflow. - -.. _`GitHub artifact attestations`: https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations +Every dfetch release ships with cryptographic attestations so you can confirm +that what you downloaded was built from the official source and passed its test +suite. See :doc:`/howto/verify-integrity` for the full verification steps.