Skip to content

Commit b2d9652

Browse files
authored
Merge branch 'main' into empty-manifest
2 parents ab05eb1 + 2011364 commit b2d9652

7 files changed

Lines changed: 291 additions & 176 deletions

File tree

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ jobs:
118118
env:
119119
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
120120
run: |
121-
gh attestation verify source.tar.gz \
121+
gh attestation verify dfetch-source.tar.gz \
122122
--repo "${{ github.repository }}" \
123123
--predicate-type https://slsa.dev/provenance/v1 \
124124
--cert-identity-regex "^https://github\.com/${{ github.repository }}/\.github/workflows/source-provenance\.yml@refs/(heads/main|tags/[0-9]+\.[0-9]+\.[0-9]+)$" \

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,13 @@ jobs:
4949
security-events: write
5050

5151
test:
52+
needs: source-provenance
53+
if: always() && (needs.source-provenance.result == 'success' || needs.source-provenance.result == 'skipped')
5254
uses: ./.github/workflows/test.yml
5355
permissions:
5456
contents: read
57+
attestations: write
58+
id-token: write
5559
secrets:
5660
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
5761

.github/workflows/source-provenance.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,18 @@ jobs:
3838
persist-credentials: false
3939

4040
- name: Generate source archive
41-
run: git archive HEAD --format=tar.gz -o source.tar.gz
41+
run: git archive HEAD --format=tar.gz -o dfetch-source.tar.gz
4242

4343
- name: Attest source provenance
4444
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
4545
with:
46-
subject-path: source.tar.gz
46+
subject-path: dfetch-source.tar.gz
4747

4848
- name: Verify source provenance
4949
env:
5050
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5151
run: |
52-
gh attestation verify source.tar.gz \
52+
gh attestation verify dfetch-source.tar.gz \
5353
--repo "${{ github.repository }}" \
5454
--predicate-type https://slsa.dev/provenance/v1 \
5555
--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:
5959
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
6060
with:
6161
name: source-archive
62-
path: source.tar.gz
62+
path: dfetch-source.tar.gz

.github/workflows/test.yml

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ permissions:
1212
jobs:
1313
test:
1414
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
17+
attestations: write
18+
id-token: write
1519
steps:
1620
- name: "Harden the runner (Block egress traffic: Only allow calls to allowed endpoints)"
1721
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
@@ -48,6 +52,12 @@ jobs:
4852
security.ubuntu.com:443
4953
svn.code.sf.net:3690
5054
svn.code.sf.net:443
55+
api.github.com:443
56+
uploads.github.com:443
57+
fulcio.sigstore.dev:443
58+
rekor.sigstore.dev:443
59+
tuf-repo-cdn.sigstore.dev:443
60+
*.blob.core.windows.net:443
5161
5262
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
5363
with:
@@ -81,8 +91,10 @@ jobs:
8191
- run: pydocstyle dfetch # Checks doc strings
8292
- run: bandit -r dfetch # Checks security issues
8393
- run: xenon -b B -m A -a A dfetch # Check code quality
84-
- run: pytest --cov=dfetch tests # Run tests
85-
- run: coverage run --source=dfetch --append -m behave features # Run features tests
94+
- id: pytest
95+
run: pytest --cov=dfetch tests # Run tests
96+
- id: behave
97+
run: coverage run --source=dfetch --append -m behave features # Run features tests
8698
- run: coverage xml -o coverage.xml # Create XML report
8799
- run: pyroma --directory --min=10 . # Check pyproject
88100
- run: find dfetch -name "*.py" | xargs pyupgrade --py310-plus # Check syntax
@@ -96,3 +108,45 @@ jobs:
96108
env:
97109
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
98110
if: "${{ (!!env.CODACY_PROJECT_TOKEN) }}"
111+
112+
- name: Download canonical source archive
113+
id: download-source
114+
if: github.event_name != 'pull_request'
115+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v5
116+
with:
117+
name: source-archive
118+
119+
- name: Generate test result predicate
120+
if: steps.download-source.outcome == 'success'
121+
run: |
122+
if [[ "${{ steps.pytest.outcome }}" == "success" && "${{ steps.behave.outcome }}" == "success" ]]; then
123+
tests_result="PASSED"
124+
else
125+
tests_result="FAILED"
126+
fi
127+
cat > test-result-predicate.json <<JSONEOF
128+
{
129+
"result": "${tests_result}",
130+
"configuration": [{
131+
"uri": "https://github.com/${{ github.repository }}/blob/${{ github.sha }}/.github/workflows/test.yml",
132+
"digest": {"gitCommit": "${{ github.sha }}"}
133+
}],
134+
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
135+
}
136+
JSONEOF
137+
138+
- name: Verify subject artifact exists
139+
if: steps.download-source.outcome == 'success'
140+
run: |
141+
if [ ! -f dfetch-source.tar.gz ]; then
142+
echo "Error: dfetch-source.tar.gz not found after artifact download" >&2
143+
exit 1
144+
fi
145+
146+
- name: Attest test results
147+
if: steps.download-source.outcome == 'success'
148+
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
149+
with:
150+
subject-path: dfetch-source.tar.gz
151+
predicate-type: https://in-toto.io/attestation/test-result/v0.1
152+
predicate-path: test-result-predicate.json

doc/howto/verify-integrity.rst

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
Verify release integrity
2+
========================
3+
4+
Every dfetch release, and every commit merged to ``main``, has cryptographic
5+
attestations signed by GitHub Actions and anchored in
6+
`Sigstore <https://www.sigstore.dev/>`_, all published in the
7+
`attestation registry <https://github.com/dfetch-org/dfetch/attestations>`_.
8+
There are four complementary kinds:
9+
10+
- **SLSA build provenance** — answers *"where did this come from?"*: proves the
11+
artifact was produced from the official source commit by the official CI
12+
workflow, and records the exact inputs used at build time.
13+
- **SBOM attestation** (CycloneDX) — answers *"what is inside it?"*: lists every
14+
dependency bundled in the package so you can audit its composition.
15+
- **Verification Summary Attestation (VSA)** — answers *"was the source
16+
independently verified?"*: records that the source archive for this commit was
17+
attested and verified before the binary was produced, linking source-level
18+
trust to the binary package.
19+
- **Test result attestation** (in-toto) — answers *"did the source pass its tests?"*:
20+
records that the full CI test suite ran against this exact source archive and every
21+
check passed, before any binary was produced.
22+
23+
Binary installers have **build provenance, SBOM, and VSA** attestations when source
24+
provenance verification passes (signed by ``build.yml``).
25+
Python packages installed from PyPI have an **SBOM attestation only** (signed by
26+
``python-publish.yml``).
27+
The source archive has a **SLSA build provenance** attestation (signed by
28+
``source-provenance.yml``) and a **test result attestation** (signed by ``test.yml``).
29+
30+
To verify, use the `GitHub CLI <https://cli.github.com/>`_. Pass
31+
``--predicate-type`` to target one kind specifically; omit it to accept any.
32+
33+
In the commands below, replace ``@refs/tags/v<version>`` with
34+
``@refs/heads/main`` when verifying a development build installed directly
35+
from the ``main`` branch.
36+
37+
.. tabs::
38+
39+
.. tab:: Linux
40+
41+
**Binary installer — verify build provenance:**
42+
43+
.. code-block:: bash
44+
45+
$ gh attestation verify dfetch-<version>-nix.deb \
46+
--repo dfetch-org/dfetch \
47+
--predicate-type https://slsa.dev/provenance/v1 \
48+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v<version> \
49+
--cert-oidc-issuer https://token.actions.githubusercontent.com
50+
51+
**Binary installer — verify SBOM attestation:**
52+
53+
.. code-block:: bash
54+
55+
$ gh attestation verify dfetch-<version>-nix.deb \
56+
--repo dfetch-org/dfetch \
57+
--predicate-type https://cyclonedx.org/bom \
58+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v<version> \
59+
--cert-oidc-issuer https://token.actions.githubusercontent.com
60+
61+
**Binary installer — verify source provenance summary (VSA):**
62+
63+
.. code-block:: bash
64+
65+
$ gh attestation verify dfetch-<version>-nix.deb \
66+
--repo dfetch-org/dfetch \
67+
--predicate-type https://slsa.dev/verification_summary/v1 \
68+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v<version> \
69+
--cert-oidc-issuer https://token.actions.githubusercontent.com
70+
71+
**pip / PyPI wheel — verify SBOM attestation:**
72+
73+
Download the wheel before installing so you have the original file to verify:
74+
75+
.. code-block:: bash
76+
77+
$ pip download dfetch==<version> --no-deps -d .
78+
$ gh attestation verify dfetch-<version>-py3-none-any.whl \
79+
--repo dfetch-org/dfetch \
80+
--predicate-type https://cyclonedx.org/bom \
81+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/python-publish.yml@refs/tags/v<version> \
82+
--cert-oidc-issuer https://token.actions.githubusercontent.com
83+
84+
.. tab:: macOS
85+
86+
**Binary installer — verify build provenance:**
87+
88+
.. code-block:: bash
89+
90+
$ gh attestation verify dfetch-<version>-osx.pkg \
91+
--repo dfetch-org/dfetch \
92+
--predicate-type https://slsa.dev/provenance/v1 \
93+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v<version> \
94+
--cert-oidc-issuer https://token.actions.githubusercontent.com
95+
96+
**Binary installer — verify SBOM attestation:**
97+
98+
.. code-block:: bash
99+
100+
$ gh attestation verify dfetch-<version>-osx.pkg \
101+
--repo dfetch-org/dfetch \
102+
--predicate-type https://cyclonedx.org/bom \
103+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v<version> \
104+
--cert-oidc-issuer https://token.actions.githubusercontent.com
105+
106+
**Binary installer — verify source provenance summary (VSA):**
107+
108+
.. code-block:: bash
109+
110+
$ gh attestation verify dfetch-<version>-osx.pkg \
111+
--repo dfetch-org/dfetch \
112+
--predicate-type https://slsa.dev/verification_summary/v1 \
113+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v<version> \
114+
--cert-oidc-issuer https://token.actions.githubusercontent.com
115+
116+
**pip / PyPI wheel — verify SBOM attestation:**
117+
118+
Download the wheel before installing so you have the original file to verify:
119+
120+
.. code-block:: bash
121+
122+
$ pip download dfetch==<version> --no-deps -d .
123+
$ gh attestation verify dfetch-<version>-py3-none-any.whl \
124+
--repo dfetch-org/dfetch \
125+
--predicate-type https://cyclonedx.org/bom \
126+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/python-publish.yml@refs/tags/v<version> \
127+
--cert-oidc-issuer https://token.actions.githubusercontent.com
128+
129+
.. tab:: Windows
130+
131+
**Binary installer — verify build provenance:**
132+
133+
.. code-block:: powershell
134+
135+
> gh attestation verify dfetch-<version>-win.msi `
136+
--repo dfetch-org/dfetch `
137+
--predicate-type https://slsa.dev/provenance/v1 `
138+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v<version> `
139+
--cert-oidc-issuer https://token.actions.githubusercontent.com
140+
141+
**Binary installer — verify SBOM attestation:**
142+
143+
.. code-block:: powershell
144+
145+
> gh attestation verify dfetch-<version>-win.msi `
146+
--repo dfetch-org/dfetch `
147+
--predicate-type https://cyclonedx.org/bom `
148+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v<version> `
149+
--cert-oidc-issuer https://token.actions.githubusercontent.com
150+
151+
**Binary installer — verify source provenance summary (VSA):**
152+
153+
.. code-block:: powershell
154+
155+
> gh attestation verify dfetch-<version>-win.msi `
156+
--repo dfetch-org/dfetch `
157+
--predicate-type https://slsa.dev/verification_summary/v1 `
158+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/build.yml@refs/tags/v<version> `
159+
--cert-oidc-issuer https://token.actions.githubusercontent.com
160+
161+
**pip / PyPI wheel — verify SBOM attestation:**
162+
163+
Download the wheel before installing so you have the original file to verify:
164+
165+
.. code-block:: powershell
166+
167+
> pip download dfetch==<version> --no-deps -d .
168+
> gh attestation verify dfetch-<version>-py3-none-any.whl `
169+
--repo dfetch-org/dfetch `
170+
--predicate-type https://cyclonedx.org/bom `
171+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/python-publish.yml@refs/tags/v<version> `
172+
--cert-oidc-issuer https://token.actions.githubusercontent.com
173+
174+
**Source archive — verify build provenance and test results:**
175+
176+
The source archive has two attestations and is produced for every release and
177+
every ``main``-branch commit. Download ``dfetch-source.tar.gz`` from the *Artifacts*
178+
section of the relevant CI run, then verify each in turn. Use
179+
``@refs/tags/v<version>`` for a release or ``@refs/heads/main`` for a
180+
development build.
181+
182+
Verify SLSA build provenance (proves the archive was produced by the official
183+
workflow from the tagged commit):
184+
185+
.. code-block:: bash
186+
187+
$ gh attestation verify dfetch-source.tar.gz \
188+
--repo dfetch-org/dfetch \
189+
--predicate-type https://slsa.dev/provenance/v1 \
190+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/source-provenance.yml@refs/tags/v<version> \
191+
--cert-oidc-issuer https://token.actions.githubusercontent.com
192+
193+
Verify test results (proves the full CI suite passed on that exact source before
194+
any binary was produced):
195+
196+
.. code-block:: bash
197+
198+
$ gh attestation verify dfetch-source.tar.gz \
199+
--repo dfetch-org/dfetch \
200+
--predicate-type https://in-toto.io/attestation/test-result/v0.1 \
201+
--cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/test.yml@refs/tags/v<version> \
202+
--cert-oidc-issuer https://token.actions.githubusercontent.com
203+
204+
See `GitHub artifact attestations`_ for details.
205+
206+
.. note::
207+
208+
The VSA is present on releases built from a commit whose source provenance
209+
was verified. If the ``verification_summary`` attestation is absent for a
210+
release, fall back to checking build provenance and SBOM independently.
211+
212+
.. note::
213+
214+
``--cert-oidc-issuer https://token.actions.githubusercontent.com`` pins
215+
verification to GitHub Actions as the identity provider. Without it, an
216+
attacker could generate a valid attestation using a different OIDC issuer
217+
(for example a self-hosted Sigstore instance) that also produces a
218+
certificate matching the ``--cert-identity`` workflow path. Supplying
219+
both flags together ensures the certificate was issued specifically by
220+
GitHub Actions for the declared workflow.
221+
222+
.. _`GitHub artifact attestations`: https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ upstream. See :ref:`vendoring` for background on the problem this solves.
6767
howto/patching
6868
howto/check-ci
6969
howto/sbom
70+
howto/verify-integrity
7071
howto/troubleshooting
7172
howto/contributing
7273

0 commit comments

Comments
 (0)