Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]+)$" \
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/source-provenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]+)$" \
Expand All @@ -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
58 changes: 56 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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 <<JSONEOF
{
"result": "${tests_result}",
"configuration": [{
"uri": "https://github.com/${{ github.repository }}/blob/${{ github.sha }}/.github/workflows/test.yml",
"digest": {"gitCommit": "${{ github.sha }}"}
}],
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
JSONEOF
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Verify subject artifact exists
if: steps.download-source.outcome == 'success'
run: |
if [ ! -f dfetch-source.tar.gz ]; then
echo "Error: dfetch-source.tar.gz not found after artifact download" >&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
Comment thread
coderabbitai[bot] marked this conversation as resolved.
222 changes: 222 additions & 0 deletions doc/howto/verify-integrity.rst
Original file line number Diff line number Diff line change
@@ -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 <https://www.sigstore.dev/>`_, all published in the
`attestation registry <https://github.com/dfetch-org/dfetch/attestations>`_.
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 <https://cli.github.com/>`_. Pass
``--predicate-type`` to target one kind specifically; omit it to accept any.

In the commands below, replace ``@refs/tags/v<version>`` 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-<version>-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<version> \
--cert-oidc-issuer https://token.actions.githubusercontent.com

**Binary installer — verify SBOM attestation:**

.. code-block:: bash

$ gh attestation verify dfetch-<version>-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<version> \
--cert-oidc-issuer https://token.actions.githubusercontent.com

**Binary installer — verify source provenance summary (VSA):**

.. code-block:: bash

$ gh attestation verify dfetch-<version>-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<version> \
--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==<version> --no-deps -d .
$ gh attestation verify dfetch-<version>-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<version> \
--cert-oidc-issuer https://token.actions.githubusercontent.com

.. tab:: macOS

**Binary installer — verify build provenance:**

.. code-block:: bash

$ gh attestation verify dfetch-<version>-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<version> \
--cert-oidc-issuer https://token.actions.githubusercontent.com

**Binary installer — verify SBOM attestation:**

.. code-block:: bash

$ gh attestation verify dfetch-<version>-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<version> \
--cert-oidc-issuer https://token.actions.githubusercontent.com

**Binary installer — verify source provenance summary (VSA):**

.. code-block:: bash

$ gh attestation verify dfetch-<version>-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<version> \
--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==<version> --no-deps -d .
$ gh attestation verify dfetch-<version>-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<version> \
--cert-oidc-issuer https://token.actions.githubusercontent.com

.. tab:: Windows

**Binary installer — verify build provenance:**

.. code-block:: powershell

> gh attestation verify dfetch-<version>-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<version> `
--cert-oidc-issuer https://token.actions.githubusercontent.com

**Binary installer — verify SBOM attestation:**

.. code-block:: powershell

> gh attestation verify dfetch-<version>-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<version> `
--cert-oidc-issuer https://token.actions.githubusercontent.com

**Binary installer — verify source provenance summary (VSA):**

.. code-block:: powershell

> gh attestation verify dfetch-<version>-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<version> `
--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==<version> --no-deps -d .
> gh attestation verify dfetch-<version>-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<version> `
--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<version>`` 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<version> \
--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<version> \
--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
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading
Loading