├── .github
├── dependabot.yml
└── workflows
│ ├── ci.yml
│ ├── release.yml
│ ├── schedule-selftest.yml
│ ├── selftest.yml
│ ├── semgrep.yml
│ └── zizmor.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── action.py
├── action.yml
├── requirements
├── dev.in
├── dev.txt
├── main.in
└── main.txt
├── setup
└── setup.bash
├── templates
├── sigstore-python-sign.md
└── sigstore-python-verify.md
└── test
├── another1.txt
├── another2.txt
├── artifact.txt
├── artifact1.txt
├── artifact2.txt
├── more white space.txt
├── subdir
├── hello1.txt
├── hello2.txt
└── hello3.txt
└── white space.txt
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | updates:
4 | - package-ecosystem: github-actions
5 | directory: /
6 | schedule:
7 | interval: daily
8 | open-pull-requests-limit: 99
9 | rebase-strategy: "disabled"
10 | groups:
11 | actions:
12 | patterns:
13 | - "*"
14 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | jobs:
10 | lint:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
14 | with:
15 | persist-credentials: false
16 |
17 | - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v5
18 |
19 | - name: lint
20 | run: make lint
21 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | on:
2 | release:
3 | types:
4 | - published
5 |
6 | name: release
7 |
8 | permissions:
9 | # Used to sign the release's artifacts with sigstore-python.
10 | id-token: write
11 |
12 | # Used to attach signing artifacts to the published release.
13 | contents: write
14 |
15 | jobs:
16 | release:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
20 | with:
21 | persist-credentials: false
22 |
23 | - name: sign
24 | uses: ./
25 | id: sigstore-python
26 | with:
27 | inputs: action.yml action.py
28 |
--------------------------------------------------------------------------------
/.github/workflows/schedule-selftest.yml:
--------------------------------------------------------------------------------
1 | name: Scheduled self-test
2 |
3 | on:
4 | schedule:
5 | - cron: '0 12 * * *' # Every day at 1200 UTC
6 |
7 | jobs:
8 | run-selftests:
9 | permissions:
10 | id-token: write
11 |
12 | uses: ./.github/workflows/selftest.yml
13 | open-issue:
14 | permissions:
15 | issues: write
16 |
17 | runs-on: ubuntu-latest
18 | if: ${{ failure() }}
19 | needs: run-selftests
20 |
21 | steps:
22 | - name: Generate issue text
23 | run: |
24 | cat <<- EOF >/tmp/issue.md
25 | ## Self-test failure
26 |
27 | A scheduled test of the workflow has failed.
28 |
29 | This suggests one of three conditions:
30 | * A backwards-incompatible change in a Sigstore component;
31 | * A regression in \`gh-action-sigstore-python\`;
32 | * A transient error.
33 |
34 | The full CI failure can be found here:
35 |
36 | ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/$GITHUB_RUN_ID
37 | EOF
38 |
39 | - name: Open issue
40 | uses: peter-evans/create-issue-from-file@e8ef132d6df98ed982188e460ebb3b5d4ef3a9cd # v5.0.1
41 | with:
42 | title: "[CI] Self-test failure"
43 | # created in the previous step
44 | content-filepath: /tmp/issue.md
45 | labels: bug
46 | assignees: woodruffw
47 |
--------------------------------------------------------------------------------
/.github/workflows/selftest.yml:
--------------------------------------------------------------------------------
1 | name: Self-test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | workflow_dispatch:
9 | workflow_call:
10 |
11 | permissions: {}
12 |
13 | jobs:
14 | selftest:
15 | permissions:
16 | id-token: write
17 | strategy:
18 | matrix:
19 | os:
20 | - ubuntu-latest
21 | - macos-latest
22 | - windows-latest
23 | # TODO: Can be removed when 24.04 becomes ubuntu-latest.
24 | - ubuntu-24.04
25 | runs-on: ${{ matrix.os }}
26 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
27 | steps:
28 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
29 | with:
30 | persist-credentials: false
31 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
32 | if: ${{ matrix.os != 'ubuntu-latest' }}
33 | with:
34 | python-version: "3.x"
35 | - name: Sign artifact and publish signature
36 | uses: ./
37 | id: sigstore-python
38 | with:
39 | inputs: ./test/artifact.txt
40 | internal-be-careful-debug: true
41 | - name: Check outputs
42 | shell: bash
43 | run: |
44 | [[ -f ./test/artifact.txt.sigstore.json ]] || exit 1
45 |
46 | selftest-runner-python:
47 | permissions:
48 | id-token: write
49 | strategy:
50 | matrix:
51 | os:
52 | - ubuntu-latest
53 | # TODO: Can be removed when 24.04 becomes ubuntu-latest.
54 | - ubuntu-24.04
55 | runs-on: ${{ matrix.os }}
56 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
57 | steps:
58 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
59 | with:
60 | persist-credentials: false
61 | - name: Sign artifact and publish signature
62 | uses: ./
63 | id: sigstore-python
64 | with:
65 | inputs: ./test/artifact.txt
66 | internal-be-careful-debug: true
67 | - name: Check outputs
68 | shell: bash
69 | run: |
70 | [[ -f ./test/artifact.txt.sigstore.json ]] || exit 1
71 |
72 | selftest-whitespace:
73 | permissions:
74 | id-token: write
75 | strategy:
76 | matrix:
77 | os:
78 | - ubuntu-latest
79 | - macos-latest
80 | - windows-latest
81 | runs-on: ${{ matrix.os }}
82 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
83 | steps:
84 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
85 | with:
86 | persist-credentials: false
87 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
88 | if: ${{ matrix.os != 'ubuntu-latest' }}
89 | with:
90 | python-version: "3.x"
91 | - name: Sign artifact and publish signature
92 | uses: ./
93 | id: sigstore-python
94 | with:
95 | inputs: |
96 | ./test/artifact.txt
97 | ./test/white\ space.txt
98 | ./test/"more white space.txt"
99 | internal-be-careful-debug: true
100 | - name: Check outputs
101 | shell: bash
102 | run: |
103 | [[ -f ./test/artifact.txt.sigstore.json ]] || exit 1
104 | [[ -f ./test/white\ space.txt ]] || exit 1
105 | [[ -f ./test/more\ white\ space.txt ]] || exit 1
106 |
107 | selftest-xfail-invalid-inputs:
108 | permissions:
109 | id-token: write
110 | runs-on: ubuntu-latest
111 | strategy:
112 | matrix:
113 | input:
114 | # We forbid inputs that look like flags
115 | - "--this-should-not-work"
116 | # We fail if the input doesn't exist
117 | - "/tmp/extremely-nonexistent-file"
118 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
119 | steps:
120 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
121 | with:
122 | persist-credentials: false
123 | - name: Sign artifact and publish signature
124 | continue-on-error: true
125 | uses: ./
126 | id: sigstore-python
127 | with:
128 | inputs: ${{ matrix.input }}
129 | internal-be-careful-debug: true
130 | - name: Check failure
131 | env:
132 | XFAIL: ${{ steps.sigstore-python.outcome == 'failure' }}
133 | JOB_NAME: ${{ github.job }}
134 | run: |
135 | echo "xfail ${JOB_NAME}: ${XFAIL}"
136 |
137 | [[ "${XFAIL}" == "true" ]] || { >&2 echo "expected step to fail"; exit 1; }
138 |
139 | selftest-staging:
140 | permissions:
141 | id-token: write
142 | runs-on: ubuntu-latest
143 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
144 | steps:
145 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
146 | with:
147 | persist-credentials: false
148 | - name: Sign artifact and publish signature
149 | uses: ./
150 | id: sigstore-python
151 | with:
152 | inputs: ./test/artifact.txt
153 | staging: true
154 | internal-be-careful-debug: true
155 | - name: Check outputs
156 | run: |
157 | [[ -f ./test/artifact.txt.sigstore.json ]] || exit 1
158 |
159 | selftest-glob:
160 | permissions:
161 | id-token: write
162 | runs-on: ubuntu-latest
163 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
164 | steps:
165 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
166 | with:
167 | persist-credentials: false
168 | - name: Sign artifacts and publish signatures
169 | uses: ./
170 | id: sigstore-python
171 | with:
172 | inputs: ./test/*.txt
173 | staging: true
174 | internal-be-careful-debug: true
175 | - name: Check outputs
176 | run: |
177 | [[ -f ./test/artifact.txt.sigstore.json ]] || exit 1
178 | [[ -f ./test/artifact1.txt.sigstore.json ]] || exit 1
179 | [[ -f ./test/artifact2.txt.sigstore.json ]] || exit 1
180 |
181 | selftest-xfail-glob-input-expansion:
182 | permissions:
183 | id-token: write
184 | runs-on: ubuntu-latest
185 | env:
186 | TEST_DIR: test
187 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
188 | steps:
189 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
190 | with:
191 | persist-credentials: false
192 | - name: Sign artifacts and publish signatures
193 | continue-on-error: true
194 | uses: ./
195 | id: sigstore-python
196 | with:
197 | # This should fail since we should never directly expand ${TEST_DIR};
198 | # the user should have to pre-expand it for us.
199 | inputs: ./${TEST_DIR}/*.txt
200 | staging: true
201 | internal-be-careful-debug: true
202 | - name: Check failure
203 | env:
204 | XFAIL: ${{ steps.sigstore-python.outcome == 'failure' }}
205 | JOB_NAME: ${{ github.job }}
206 | run: |
207 | echo "xfail ${JOB_NAME}: ${XFAIL}"
208 |
209 | [[ "${XFAIL}" == "true" ]] || { >&2 echo "expected step to fail"; exit 1; }
210 |
211 | selftest-glob-multiple:
212 | permissions:
213 | id-token: write
214 | runs-on: ubuntu-latest
215 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
216 | steps:
217 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
218 | with:
219 | persist-credentials: false
220 | - name: Sign artifacts and publish signatures
221 | uses: ./
222 | id: sigstore-python
223 | with:
224 | inputs: ./test/artifact*.txt ./test/another*.txt ./test/subdir/*.txt
225 | staging: true
226 | internal-be-careful-debug: true
227 | - name: Check outputs
228 | run: |
229 | [[ -f ./test/artifact.txt.sigstore.json ]] || exit 1
230 | [[ -f ./test/artifact1.txt.sigstore.json ]] || exit 1
231 | [[ -f ./test/artifact2.txt.sigstore.json ]] || exit 1
232 | [[ -f ./test/another1.txt.sigstore.json ]] || exit 1
233 | [[ -f ./test/another2.txt.sigstore.json ]] || exit 1
234 | [[ -f ./test/subdir/hello1.txt.sigstore.json ]] || exit 1
235 | [[ -f ./test/subdir/hello2.txt.sigstore.json ]] || exit 1
236 | [[ -f ./test/subdir/hello3.txt.sigstore.json ]] || exit 1
237 |
238 | selftest-upload-artifacts:
239 | permissions:
240 | id-token: write
241 | runs-on: ubuntu-latest
242 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
243 | steps:
244 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
245 | with:
246 | persist-credentials: false
247 | - name: Sign artifact and publish signature
248 | uses: ./
249 | id: sigstore-python
250 | with:
251 | inputs: ./test/artifact.txt
252 | staging: true
253 | upload-signing-artifacts: true
254 | internal-be-careful-debug: true
255 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
256 | with:
257 | name: "signing-artifacts-${{ github.job }}"
258 | path: ./test/uploaded
259 | - name: Verify presence of uploaded files
260 | run: |
261 | [[ -f ./artifact.txt ]] || exit 1
262 | [[ -f ./artifact.txt.sigstore.json ]] || exit 1
263 | working-directory: ./test/uploaded
264 |
265 | selftest-verify:
266 | permissions:
267 | id-token: write
268 | runs-on: ubuntu-latest
269 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
270 | steps:
271 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
272 | with:
273 | persist-credentials: false
274 | - name: Sign artifact and publish signature
275 | uses: ./
276 | id: sigstore-python
277 | with:
278 | inputs: ./test/artifact.txt
279 | verify: true
280 | verify-cert-identity: https://github.com/sigstore/gh-action-sigstore-python/.github/workflows/selftest.yml@${{ github.ref }}
281 | verify-oidc-issuer: https://token.actions.githubusercontent.com
282 | staging: true
283 | internal-be-careful-debug: true
284 |
285 | selftest-xfail-verify-missing-options:
286 | permissions:
287 | id-token: write
288 | runs-on: ubuntu-latest
289 | strategy:
290 | matrix:
291 | config:
292 | # fails if both verify-cert-identity and verify-oidc-issuer are missing
293 | - verify: true
294 |
295 | # fails if either is missing
296 | - verify: true
297 | verify-oidc-issuer: https://token.actions.githubusercontent.com
298 |
299 | - verify: true
300 | verify-cert-identity: https://github.com/sigstore/gh-action-sigstore-python/.github/workflows/selftest.yml@${{ github.ref }}
301 |
302 | # fails if either option is passed while verification is disabled
303 | - verify: false
304 | verify-oidc-issuer: https://token.actions.githubusercontent.com
305 |
306 | - verify: false
307 | verify-cert-identity: https://github.com/sigstore/gh-action-sigstore-python/.github/workflows/selftest.yml@${{ github.ref }}
308 |
309 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
310 | steps:
311 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
312 | with:
313 | persist-credentials: false
314 | - name: Sign artifact and publish signature
315 | continue-on-error: true
316 | uses: ./
317 | id: sigstore-python
318 | with:
319 | inputs: ./test/artifact.txt
320 | verify: ${{ matrix.config.verify }}
321 | verify-oidc-issuer: ${{ matrix.config.verify-oidc-issuer }}
322 | verify-cert-identity: ${{ matrix.config.verify-cert-identity }}
323 | staging: true
324 | internal-be-careful-debug: true
325 |
326 | - name: Check failure
327 | env:
328 | XFAIL: ${{ steps.sigstore-python.outcome == 'failure' }}
329 | JOB_NAME: ${{ github.job }}
330 | run: |
331 | echo "xfail ${JOB_NAME}: ${XFAIL}"
332 |
333 | [[ "${XFAIL}" == "true" ]] || { >&2 echo "expected step to fail"; exit 1; }
334 |
335 | selftest-identity-token:
336 | permissions:
337 | id-token: write
338 | runs-on: ubuntu-latest
339 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
340 | steps:
341 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
342 | with:
343 | persist-credentials: false
344 | - name: Get OIDC token
345 | id: get-oidc-token
346 | run: |
347 | identity_token=$( \
348 | curl -H \
349 | "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
350 | "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sigstore" \
351 | | jq -r .value \
352 | )
353 | echo "identity-token=$identity_token" >> $GITHUB_OUTPUT
354 | shell: bash
355 | - name: Sign artifact and publish signature
356 | uses: ./
357 | id: sigstore-python
358 | with:
359 | inputs: ./test/artifact.txt
360 | identity-token: ${{ steps.get-oidc-token.outputs.identity-token }}
361 | staging: true
362 | internal-be-careful-debug: true
363 |
364 | all-selftests-pass:
365 | if: always()
366 |
367 | needs:
368 | - selftest
369 | - selftest-whitespace
370 | - selftest-xfail-invalid-inputs
371 | - selftest-staging
372 | - selftest-glob
373 | - selftest-glob-multiple
374 | - selftest-upload-artifacts
375 | - selftest-verify
376 | - selftest-xfail-verify-missing-options
377 | - selftest-identity-token
378 |
379 | runs-on: ubuntu-latest
380 |
381 | steps:
382 | - name: check test jobs
383 | if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
384 | uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
385 | with:
386 | jobs: ${{ toJSON(needs) }}
387 |
--------------------------------------------------------------------------------
/.github/workflows/semgrep.yml:
--------------------------------------------------------------------------------
1 | on:
2 | pull_request: {}
3 | push:
4 | branches:
5 | - main
6 | - master
7 | paths:
8 | - .github/workflows/semgrep.yml
9 | schedule:
10 | - cron: '0 0 * * 0'
11 | name: Semgrep
12 | jobs:
13 | semgrep:
14 | name: Scan
15 | runs-on: ubuntu-latest
16 | env:
17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
18 | container:
19 | image: semgrep/semgrep
20 |
21 | steps:
22 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
23 | with:
24 | persist-credentials: false
25 | - run: semgrep ci
26 |
--------------------------------------------------------------------------------
/.github/workflows/zizmor.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Actions Security Analysis with zizmor 🌈
2 |
3 | on:
4 | push:
5 | branches: ["main"]
6 | pull_request:
7 | branches: ["**"]
8 |
9 | jobs:
10 | zizmor:
11 | name: zizmor latest via PyPI
12 | runs-on: ubuntu-latest
13 | permissions:
14 | security-events: write
15 | # required for workflows in private repositories
16 | contents: read
17 | actions: read
18 | steps:
19 | - name: Checkout repository
20 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
21 | with:
22 | persist-credentials: false
23 |
24 | - name: Install the latest version of uv
25 | uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v5
26 |
27 | - name: Run zizmor 🌈
28 | run: uvx zizmor --format sarif . > results.sarif
29 | env:
30 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 |
32 | - name: Upload SARIF file
33 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3
34 | with:
35 | sarif_file: results.sarif
36 | category: zizmor
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | env/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `gh-action-sigstore-python` will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6 |
7 | All versions prior to 3.0.0 are untracked.
8 |
9 | ## [Unreleased]
10 |
11 | ### Changed
12 |
13 | * The minimum Python version supported by this action is now 3.9
14 |
15 | ## [3.0.0]
16 |
17 | ### Added
18 |
19 | * `inputs` now allows recursive globbing with `**`
20 | ([#106](https://github.com/sigstore/gh-action-sigstore-python/pull/106))
21 |
22 | ### Removed
23 |
24 | * The following settings have been removed: `fulcio-url`, `rekor-url`,
25 | `ctfe`, `rekor-root-pubkey`
26 | ([#140](https://github.com/sigstore/gh-action-sigstore-python/pull/140))
27 | * The following output settings have been removed: `signature`,
28 | `certificate`, `bundle`
29 | ([#146](https://github.com/sigstore/gh-action-sigstore-python/pull/146))
30 |
31 |
32 | ### Changed
33 |
34 | * `inputs` is now parsed according to POSIX shell lexing rules, improving
35 | the action's consistency when used with filenames containing whitespace
36 | or other significant characters
37 | ([#104](https://github.com/sigstore/gh-action-sigstore-python/pull/104))
38 |
39 | * `inputs` is now optional *if* `release-signing-artifacts` is true
40 | *and* the action's event is a `release` event. In this case, the action
41 | takes no explicit inputs, but signs the source archives already attached
42 | to the associated release
43 | ([#110](https://github.com/sigstore/gh-action-sigstore-python/pull/110))
44 |
45 | * The default suffix has changed from `.sigstore` to `.sigstore.json`,
46 | per Sigstore's client specification
47 | ([#140](https://github.com/sigstore/gh-action-sigstore-python/pull/140))
48 |
49 | * `release-signing-artifacts` now defaults to `true`
50 | ([#142](https://github.com/sigstore/gh-action-sigstore-python/pull/142))
51 |
52 | ### Fixed
53 |
54 | * The `release-signing-artifacts` setting no longer causes a hard error
55 | when used under the incorrect event
56 | ([#103](https://github.com/sigstore/gh-action-sigstore-python/pull/103))
57 |
58 | * Various deprecations present in `sigstore-python`'s 2.x series have been
59 | resolved
60 | ([#140](https://github.com/sigstore/gh-action-sigstore-python/pull/140))
61 |
62 | * This workflow now supports CI runners that use PEP 668 to constrain global
63 | package prefixes
64 | ([#145](https://github.com/sigstore/gh-action-sigstore-python/pull/145))
65 |
66 |
67 | [Unreleased]: https://github.com/sigstore/gh-action-sigstore-python/compare/v3.0.0...HEAD
68 | [3.0.0]: https://github.com/sigstore/gh-action-sigstore-python/compare/v2.1.1...v3.0.0
69 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | .PHONY: all
3 | all:
4 | @echo "Run my targets individually!"
5 |
6 | .PHONY: requirements
7 | requirements: requirements/main.txt requirements/dev.txt
8 |
9 | requirements/%.txt: requirements/%.in
10 | uv pip compile --generate-hashes --prerelease=allow --output-file=$@ $<
11 |
12 | env/pyvenv.cfg: requirements/dev.txt requirements/main.txt
13 | uv venv
14 | uv pip install -r requirements/main.txt -r requirements/dev.txt
15 |
16 | .PHONY: dev
17 | dev: env/pyvenv.cfg
18 |
19 | .PHONY: lint
20 | lint: env/pyvenv.cfg action.py
21 | . ./.venv/bin/activate && \
22 | black action.py && \
23 | isort action.py && \
24 | mypy action.py && \
25 | flake8 --max-line-length 100 action.py
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | gh-action-sigstore-python
2 | =========================
3 |
4 | [](https://github.com/sigstore/gh-action-sigstore-python/actions/workflows/ci.yml)
5 | [](https://github.com/sigstore/gh-action-sigstore-python/actions/workflows/selftest.yml)
6 |
7 | This GitHub Action uses [`sigstore-python`](https://github.com/sigstore/sigstore-python)
8 | to generate Sigstore signatures. `gh-action-sigstore-python` is the easiest way to [integrate Sigstore into your CI system](https://docs.sigstore.dev/quickstart/quickstart-ci/) and can be used for not only Python projects, but projects in other languages as well.
9 |
10 | > [!IMPORTANT]
11 | > Are you publishing a package to PyPI? If so, you **do not need this action**:
12 | > [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish)
13 | > will handle signing for you!
14 |
15 | ## Index
16 |
17 | * [Usage](#usage)
18 | * [Configuration](#configuration)
19 | * [⚠️ Internal options ⚠️](#internal-options)
20 | * [Licensing](#licensing)
21 | * [Code of Conduct](#code-of-conduct)
22 |
23 | ## Usage
24 |
25 | Simply add `sigstore/gh-action-sigstore-python` to one of your workflows:
26 |
27 | ```yaml
28 | jobs:
29 | selftest:
30 | runs-on: ubuntu-latest
31 | permissions:
32 | id-token: write
33 | steps:
34 | - uses: actions/checkout@v4
35 | with:
36 | persist-credentials: false
37 | - name: install
38 | run: python -m pip install .
39 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
40 | with:
41 | inputs: file.txt
42 | ```
43 |
44 | Note: Your workflow **must** have permission to request the OIDC token to authenticate with.
45 | This can be done by setting `id-token: write` on your job (as above) or workflow.
46 |
47 | More information about permission settings can be found
48 | [here](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings).
49 |
50 | ## Configuration
51 |
52 | `gh-action-sigstore-python` takes a variety of configuration inputs, most of which are
53 | optional.
54 |
55 | ### `inputs`
56 |
57 | The `inputs` setting controls what files `sigstore-python` signs. At least one input must be
58 | provided unless [release-signing-artifacts](#release-signing-artifacts) is set to `true` on release events.
59 |
60 | To sign one or more files:
61 |
62 | ```yaml
63 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
64 | with:
65 | inputs: file0.txt file1.txt file2.txt
66 | ```
67 |
68 | The `inputs` argument also supports file globbing:
69 |
70 | ```yaml
71 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
72 | with:
73 | inputs: ./path/to/inputs/*.txt
74 | ```
75 |
76 | Multiple lines are fine, and whitespace in filenames can also be escaped using
77 | POSIX shell lexing rules:
78 |
79 | ```yaml
80 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
81 | with:
82 | inputs: |
83 | ./path/to/inputs/*.txt
84 | ./another/path/foo\ bar.txt
85 | ./a/third/path/"easier to quote than to escape".txt
86 | ```
87 |
88 | > [!NOTE]\
89 | > In versions of this action before 2.0.0, the `inputs` setting allowed for shell expansion.
90 | > This was unintentional, and was removed with 2.0.0.
91 |
92 | ### `identity-token`
93 |
94 | **Default**: Empty (the GitHub Actions credential will be used)
95 |
96 | The `identity-token` setting controls the OpenID Connect token provided to Fulcio. By default, the
97 | workflow will use the credentials found in the GitHub Actions environment.
98 |
99 | ```yaml
100 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
101 | with:
102 | inputs: file.txt
103 | identity-token: ${{ IDENTITY_TOKEN }} # assigned elsewhere
104 | ```
105 |
106 | ### `oidc-client-id`
107 |
108 | **Default**: `sigstore`
109 |
110 | The `oidc-client-id` setting controls the OpenID Connect client ID to provide to the OpenID Connect
111 | Server during OAuth2.
112 |
113 | Example:
114 |
115 | ```yaml
116 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
117 | with:
118 | inputs: file.txt
119 | oidc-client-id: alternative-sigstore-id
120 | ```
121 |
122 | ### `oidc-client-secret`
123 |
124 | **Default**: Empty (no OpenID Connect client secret provided by default)
125 |
126 | The `oidc-client-secret` setting controls the OpenID Connect client secret to provide to the OpenID
127 | Connect Server during OAuth2.
128 |
129 | Example:
130 |
131 | ```yaml
132 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
133 | with:
134 | inputs: file.txt
135 | oidc-client-secret: alternative-sigstore-secret
136 | ```
137 |
138 | ### `staging`
139 |
140 | **Default**: `false`
141 |
142 | The `staging` setting controls whether or not `sigstore-python` uses sigstore's staging instances,
143 | instead of the default production instances.
144 |
145 | Example:
146 |
147 | ```yaml
148 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
149 | with:
150 | inputs: file.txt
151 | staging: true
152 | ```
153 |
154 | ### `verify`
155 |
156 | **Default**: `false`
157 |
158 | The `verify` setting controls whether or not the generated signatures and certificates are
159 | verified with the `sigstore verify` subcommand after all files have been signed.
160 |
161 | This is **not strictly necessary** but can act as a smoke test to ensure that all
162 | signing artifacts were generated properly and the signature was properly
163 | submitted to Rekor.
164 |
165 | If `verify` is enabled, then you **must** also pass the `verify-cert-identity`
166 | and `verify-oidc-issuer` settings. Failing to pass these will produce an error.
167 |
168 | Example:
169 |
170 | ```yaml
171 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
172 | with:
173 | inputs: file.txt
174 | verify: true
175 | verify-oidc-issuer: https://some-oidc-issuer.example.com
176 | verify-cert-identity: some-identity
177 | ```
178 |
179 | ### `verify-cert-identity`
180 |
181 | **Default**: Empty
182 |
183 | The `verify-cert-identity` setting controls whether to verify the Subject Alternative Name (SAN) of the
184 | signing certificate after signing has taken place. If it is set, `sigstore-python` will compare the
185 | certificate's SAN against the provided value.
186 |
187 | This setting only applies if `verify` is set to `true`. Supplying it without `verify: true`
188 | will produce an error.
189 |
190 | This setting may only be used in conjunction with `verify-oidc-issuer`.
191 | Supplying it without `verify-oidc-issuer` will produce an error.
192 |
193 | ```yaml
194 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
195 | with:
196 | inputs: file.txt
197 | verify: true
198 | verify-cert-identity: john.hancock@example.com
199 | verify-oidc-issuer: https://oauth2.sigstage.dev/auth
200 | ```
201 |
202 | ### `verify-oidc-issuer`
203 |
204 | **Default**: `https://oauth2.sigstore.dev/auth`
205 |
206 | The `verify-oidc-issuer` setting controls whether to verify the issuer extension of the signing
207 | certificate after signing has taken place. If it is set, `sigstore-python` will compare the
208 | certificate's issuer extension against the provided value.
209 |
210 | This setting only applies if `verify` is set to `true`. Supplying it without `verify: true`
211 | will produce an error.
212 |
213 | This setting may only be used in conjunction with `verify-cert-identity`.
214 | Supplying it without `verify-cert-identity` will produce an error.
215 |
216 | Example:
217 |
218 | ```yaml
219 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
220 | with:
221 | inputs: file.txt
222 | verify: true
223 | verify-cert-identity: john.hancock@example.com
224 | verify-oidc-issuer: https://oauth2.sigstage.dev/auth
225 | ```
226 |
227 | ### `upload-signing-artifacts`
228 |
229 | **Default**: `false`
230 |
231 | The `upload-signing-artifacts` setting controls whether or not `sigstore-python` creates
232 | [workflow artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts)
233 | for the outputs produced by signing operations.
234 |
235 | By default, no workflow artifacts are uploaded. When enabled, the default
236 | workflow artifact retention period is used.
237 |
238 | Example:
239 |
240 | ```yaml
241 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
242 | with:
243 | inputs: file.txt
244 | upload-signing-artifacts: true
245 | ```
246 |
247 | ### `release-signing-artifacts`
248 |
249 | **Default**: `true`
250 |
251 | The `release-signing-artifacts` setting controls whether or not `sigstore-python`
252 | uploads signing artifacts to the release publishing event that triggered this run.
253 | This setting has no effect on non-`release` events.
254 |
255 | If enabled, this setting also re-uploads and signs GitHub's default source code artifacts,
256 | as they are not guaranteed to be stable.
257 |
258 | Requires the [`contents: write` permission](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token).
259 |
260 | Example:
261 |
262 | ```yaml
263 | permissions:
264 | contents: write
265 |
266 | # ...
267 |
268 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
269 | with:
270 | inputs: file.txt
271 | release-signing-artifacts: true
272 | ```
273 |
274 | On release events, it is also valid to have no explicit inputs. When used on release
275 | events, this action will sign any pre-existing release artifacts:
276 |
277 | ```yaml
278 | permissions:
279 | contents: write
280 |
281 | # ...
282 |
283 | # no explicit settings needed, signs all pre-existing release artifacts
284 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
285 | ```
286 |
287 | ### Internal options
288 |
289 | ⚠️ Internal options ⚠️
290 |
291 | Everything below is considered "internal," which means that it
292 | isn't part of the stable public settings and may be removed or changed at
293 | any points. **You probably do not need these settings.**
294 |
295 | All internal options are prefixed with `internal-be-careful-`.
296 |
297 | #### `internal-be-careful-debug`
298 |
299 | **Default**: `false`
300 |
301 | The `internal-be-careful-debug` setting enables additional debug logs,
302 | both within `sigstore-python` itself and the action's harness code. You can
303 | use it to debug troublesome configurations.
304 |
305 | Example:
306 |
307 | ```yaml
308 | - uses: sigstore/gh-action-sigstore-python@v3.0.0
309 | with:
310 | inputs: file.txt
311 | internal-be-careful-debug: true
312 | ```
313 |
314 |
315 |
316 | ## Licensing
317 |
318 | `gh-action-sigstore-python` is licensed under the Apache 2.0 License.
319 |
320 | ## Code of Conduct
321 |
322 | Everyone interacting with this project is expected to follow the
323 | [sigstore Code of Conduct](https://github.com/sigstore/.github/blob/main/CODE_OF_CONDUCT.md)
324 |
325 | ## Security
326 |
327 | Should you discover any security issues, please refer to sigstore's [security
328 | process](https://github.com/sigstore/.github/blob/main/SECURITY.md).
329 |
330 | ## Info
331 |
332 | `gh-action-sigstore-python` is developed as part of the [`sigstore`](https://sigstore.dev) project.
333 |
334 | We also use a [slack channel](https://sigstore.slack.com)!
335 | Click [here](https://join.slack.com/t/sigstore/shared_invite/zt-mhs55zh0-XmY3bcfWn4XEyMqUUutbUQ) for the invite link.
336 |
--------------------------------------------------------------------------------
/action.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2022 The Sigstore Authors
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # action.py: run sigstore-python
18 | #
19 | # most state is passed in as environment variables; the only argument
20 | # is a whitespace-separated list of inputs
21 |
22 | import os
23 | import shlex
24 | import string
25 | import subprocess
26 | import sys
27 | from glob import glob
28 | from pathlib import Path
29 |
30 | import requests
31 |
32 | _HERE = Path(__file__).parent.resolve()
33 | _TEMPLATES = _HERE / "templates"
34 |
35 | _summary_path = os.getenv("GITHUB_STEP_SUMMARY")
36 | assert _summary_path is not None
37 | _SUMMARY = Path(_summary_path).open("a")
38 |
39 | _RENDER_SUMMARY = os.getenv("GHA_SIGSTORE_PYTHON_SUMMARY", "true") == "true"
40 | _DEBUG = (
41 | os.getenv("GHA_SIGSTORE_PYTHON_INTERNAL_BE_CAREFUL_DEBUG", "false") != "false"
42 | or os.getenv("ACTIONS_STEP_DEBUG", "false") == "true"
43 | )
44 |
45 | _RELEASE_SIGNING_ARTIFACTS = (
46 | os.getenv("GHA_SIGSTORE_PYTHON_RELEASE_SIGNING_ARTIFACTS", "true") == "true"
47 | and os.getenv("GITHUB_EVENT_NAME") == "release"
48 | )
49 |
50 |
51 | def _template(name):
52 | path = _TEMPLATES / f"{name}.md"
53 | return string.Template(path.read_text())
54 |
55 |
56 | def _summary(msg):
57 | if _RENDER_SUMMARY:
58 | print(msg, file=_SUMMARY)
59 |
60 |
61 | def _debug(msg):
62 | if _DEBUG:
63 | print(f"\033[93mDEBUG: {msg}\033[0m", file=sys.stderr)
64 |
65 |
66 | def _log(msg):
67 | print(msg, file=sys.stderr)
68 |
69 |
70 | def _download_ref_asset(ext):
71 | repo = os.getenv("GITHUB_REPOSITORY")
72 | ref = os.getenv("GITHUB_REF")
73 |
74 | # NOTE: Branch names often have `/` in them (e.g. `feat/some-name`),
75 | # which would break the artifact path we construct below.
76 | # We "fix" these by lossily replacing all `/` with `-`.
77 | ref_name_normalized = os.getenv("GITHUB_REF_NAME").replace("/", "-")
78 |
79 | artifact = Path(f"/tmp/{ref_name_normalized}.{ext}")
80 |
81 | # GitHub supports /:org/:repo/archive/:ref<.tar.gz|.zip>.
82 | r = requests.get(f"https://github.com/{repo}/archive/{ref}.{ext}", stream=True)
83 | r.raise_for_status()
84 | with artifact.open("wb") as io:
85 | for chunk in r.iter_content(chunk_size=None):
86 | io.write(chunk)
87 |
88 | return str(artifact)
89 |
90 |
91 | def _sigstore_sign(global_args, sign_args):
92 | return [sys.executable, "-m", "sigstore", *global_args, "sign", *sign_args]
93 |
94 |
95 | def _sigstore_verify(global_args, verify_args):
96 | return [
97 | sys.executable,
98 | "-m",
99 | "sigstore",
100 | *global_args,
101 | "verify",
102 | "identity",
103 | *verify_args,
104 | ]
105 |
106 |
107 | def _fatal_help(msg):
108 | print(f"::error::❌ {msg}")
109 | sys.exit(1)
110 |
111 |
112 | # Allow inputs to be empty if the event type is release and release-signing-artifacts is
113 | # set to true. This allows projects without artifacts to still sign the source
114 | # archives in their releases.
115 | inputs = shlex.split(sys.argv[1]) if len(sys.argv) == 2 else []
116 | if not inputs and not _RELEASE_SIGNING_ARTIFACTS:
117 | _fatal_help(
118 | "inputs must be specified when release-signing-artifacts is disabled "
119 | "and the event type is not release"
120 | )
121 |
122 | # The arguments we pass into `sigstore-python` get built up in these lists.
123 | sigstore_global_args = []
124 | sigstore_sign_args = []
125 | sigstore_verify_args = []
126 |
127 | # The environment variables that we apply to `sigstore-python`.
128 | sigstore_python_env = {}
129 |
130 | # Flag to check whether we want enable the verify step.
131 | enable_verify = bool(os.getenv("GHA_SIGSTORE_PYTHON_VERIFY", "false").lower() == "true")
132 |
133 | # A list of paths to signing artifacts generated by `sigstore-python`. We want
134 | # to upload these as workflow artifacts after signing.
135 | signing_artifact_paths = []
136 |
137 | if _DEBUG:
138 | sigstore_python_env["SIGSTORE_LOGLEVEL"] = "DEBUG"
139 |
140 | identity_token = os.getenv("GHA_SIGSTORE_PYTHON_IDENTITY_TOKEN")
141 | if identity_token:
142 | sigstore_sign_args.extend(["--identity-token", identity_token])
143 |
144 | client_id = os.getenv("GHA_SIGSTORE_PYTHON_OIDC_CLIENT_ID")
145 | if client_id:
146 | sigstore_sign_args.extend(["--oidc-client-id", client_id])
147 |
148 | client_secret = os.getenv("GHA_SIGSTORE_PYTHON_OIDC_CLIENT_SECRET")
149 | if client_secret:
150 | sigstore_sign_args.extend(["--oidc-client-secret", client_secret])
151 |
152 | if os.getenv("GHA_SIGSTORE_PYTHON_STAGING", "false") != "false":
153 | sigstore_global_args.append("--staging")
154 |
155 | verify_cert_identity = os.getenv("GHA_SIGSTORE_PYTHON_VERIFY_CERT_IDENTITY")
156 | if enable_verify and not verify_cert_identity:
157 | _fatal_help("verify-cert-identity must be specified when verify is enabled")
158 | elif not enable_verify and verify_cert_identity:
159 | _fatal_help("verify-cert-identity cannot be specified without verify: true")
160 | elif verify_cert_identity:
161 | sigstore_verify_args.extend(["--cert-identity", verify_cert_identity])
162 |
163 | verify_oidc_issuer = os.getenv("GHA_SIGSTORE_PYTHON_VERIFY_OIDC_ISSUER")
164 | if enable_verify and not verify_oidc_issuer:
165 | _fatal_help("verify-oidc-issuer must be specified when verify is enabled")
166 | elif not enable_verify and verify_oidc_issuer:
167 | _fatal_help("verify-oidc-issuer cannot be specified without verify: true")
168 | elif verify_oidc_issuer:
169 | sigstore_verify_args.extend(["--cert-oidc-issuer", verify_oidc_issuer])
170 |
171 | if _RELEASE_SIGNING_ARTIFACTS:
172 | for filetype in ["zip", "tar.gz"]:
173 | artifact = _download_ref_asset(filetype)
174 | if artifact is not None:
175 | inputs.append(artifact)
176 |
177 | for input_ in inputs:
178 | # Forbid things that look like flags. This isn't a security boundary; just
179 | # a way to prevent (less motivated) users from breaking the action on themselves.
180 | if input_.startswith("-"):
181 | _fatal_help(f"input {input_} looks like a flag")
182 |
183 | # NOTE: We use a set here to deduplicate inputs, in case a glob expands
184 | # to the same input multiple times.
185 | files = {Path(f).resolve() for f in glob(input_, recursive=True)}
186 |
187 | # Prevent empty glob expansions, rather than silently allowing them.
188 | # Either behavior is technically correct but an empty glob indicates
189 | # user confusion, so we fail for them.
190 | if not files:
191 | _fatal_help(f"input {input_} doesn't expand to one or more filenames")
192 |
193 | for file_ in files:
194 | if not file_.is_file():
195 | _fatal_help(f"input {file_} does not look like a file")
196 |
197 | # Also upload artifact being signed for.
198 | signing_artifact_paths.append(str(file_))
199 |
200 | if "--bundle" not in sigstore_sign_args:
201 | signing_artifact_paths.append(f"{file_}.sigstore.json")
202 |
203 | sigstore_sign_args.extend([str(f) for f in files])
204 | sigstore_verify_args.extend([str(f) for f in files])
205 |
206 | _debug(f"signing: sigstore-python {[str(a) for a in sigstore_sign_args]}")
207 |
208 | sign_status = subprocess.run(
209 | _sigstore_sign(sigstore_global_args, sigstore_sign_args),
210 | text=True,
211 | stdout=subprocess.PIPE,
212 | stderr=subprocess.STDOUT,
213 | env={**os.environ, **sigstore_python_env},
214 | )
215 |
216 | _debug(sign_status.stdout)
217 |
218 | if sign_status.returncode == 0:
219 | _summary("🎉 sigstore-python signing exited successfully")
220 | else:
221 | _summary("❌ sigstore-python failed to sign package")
222 |
223 | verify_status = None
224 | if sign_status.returncode == 0 and enable_verify:
225 | _debug(f"verifying: sigstore-python {[str(a) for a in sigstore_verify_args]}")
226 |
227 | verify_status = subprocess.run(
228 | _sigstore_verify(sigstore_global_args, sigstore_verify_args),
229 | text=True,
230 | stdout=subprocess.PIPE,
231 | stderr=subprocess.STDOUT,
232 | env={**os.environ, **sigstore_python_env},
233 | )
234 |
235 | _debug(verify_status.stdout)
236 |
237 | if verify_status is None:
238 | # Don't add anything to the summary if verification is disabled.
239 | if enable_verify:
240 | _summary("❌ sigstore-python verification skipped due to failed signing")
241 | elif verify_status.returncode == 0:
242 | _summary("🎉 sigstore-python verification exited successfully")
243 | else:
244 | _summary("❌ sigstore-python failed to verify package")
245 |
246 |
247 | _log(sign_status.stdout)
248 | _summary(_template("sigstore-python-sign").substitute(output=sign_status.stdout))
249 |
250 | if verify_status is not None:
251 | _log(verify_status.stdout)
252 | _summary(
253 | _template("sigstore-python-verify").substitute(output=verify_status.stdout)
254 | )
255 |
256 | if sign_status.returncode != 0:
257 | assert verify_status is None
258 | sys.exit(sign_status.returncode)
259 |
260 | # Now populate the `GHA_SIGSTORE_PYTHON_INTERNAL_SIGNING_ARTIFACTS` environment
261 | # variable so that later steps know which files to upload as workflow artifacts.
262 | #
263 | # In GitHub Actions, environment variables can be made to persist across
264 | # workflow steps by appending to the file at `GITHUB_ENV`.
265 | _github_env = os.getenv("GITHUB_ENV")
266 | assert _github_env is not None
267 | with Path(_github_env).open("a") as gh_env:
268 | # Multiline values must match the following syntax:
269 | #
270 | # {name}<<{delimiter}
271 | # {value}
272 | # {delimiter}
273 | #
274 | # We use a random delimiter to avoid potential conflicts with our input;
275 | # see: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
276 | delim = os.urandom(16).hex()
277 | print(f"GHA_SIGSTORE_PYTHON_INTERNAL_SIGNING_ARTIFACTS<<{delim}", file=gh_env)
278 | print("\n".join(signing_artifact_paths), file=gh_env)
279 | print(delim, file=gh_env)
280 |
281 |
282 | # If signing didn't fail, then we check the verification status, if present.
283 | if verify_status is not None:
284 | sys.exit(verify_status.returncode)
285 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 The Sigstore Authors
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name: "gh-action-sigstore-python"
16 | author: "Sigstore Authors "
17 | description: "Use sigstore-python to sign Python packages"
18 | inputs:
19 | inputs:
20 | description: "the files to sign, whitespace separated"
21 | required: false
22 | default: ""
23 | identity-token:
24 | description: "the OIDC identity token to use"
25 | required: false
26 | default: ""
27 | oidc-client-id:
28 | description: "the custom OpenID Connect client ID to use during OAuth2"
29 | required: false
30 | default: ""
31 | oidc-client-secret:
32 | description: "the custom OpenID Connect client secret to use during OAuth2"
33 | required: false
34 | default: ""
35 | staging:
36 | description: "use sigstore's staging instances, instead of the default production instances"
37 | required: false
38 | default: "false"
39 | verify:
40 | description: "verify the generated signatures after signing"
41 | required: false
42 | default: "false"
43 | verify-cert-identity:
44 | description: |
45 | verify the identity in the signing certificate's Subject Alternative Name
46 |
47 | required if `verify` is enabled; has no effect otherwise.
48 | required: false
49 | default: ""
50 | verify-oidc-issuer:
51 | description: |
52 | verify the issuer extension of the signing certificate
53 |
54 | required if `verify` is enabled; has no effect otherwise.
55 | required: false
56 | default: ""
57 | upload-signing-artifacts:
58 | description: "upload all signing artifacts as workflow artifacts"
59 | required: false
60 | default: "false"
61 | release-signing-artifacts:
62 | description: "attach all signing artifacts as release assets"
63 | required: false
64 | default: "true"
65 | internal-be-careful-debug:
66 | description: "run with debug logs (default false)"
67 | required: false
68 | default: "false"
69 |
70 | runs:
71 | using: "composite"
72 | steps:
73 | - name: Set up sigstore-python
74 | id: setup
75 | run: |
76 | # NOTE: Sourced, not executed as a script.
77 | source "${GITHUB_ACTION_PATH}/setup/setup.bash"
78 | env:
79 | GHA_SIGSTORE_PYTHON_INTERNAL_BE_CAREFUL_DEBUG: "${{ inputs.internal-be-careful-debug }}"
80 | shell: bash
81 |
82 | - name: Run sigstore-python
83 | id: sigstore-python
84 | run: |
85 | "${VENV_PYTHON_PATH}" \
86 | "${GITHUB_ACTION_PATH}/action.py" \
87 | "${GHA_SIGSTORE_PYTHON_INPUTS}"
88 | env:
89 | # The year is 2023, and nonsense like this is still necessary on Windows.
90 | PYTHONUTF8: "1"
91 | VENV_PYTHON_PATH: "${{ steps.setup.outputs.venv-python-path }}"
92 | GHA_SIGSTORE_PYTHON_IDENTITY_TOKEN: "${{ inputs.identity-token }}"
93 | GHA_SIGSTORE_PYTHON_OIDC_CLIENT_ID: "${{ inputs.oidc-client-id }}"
94 | GHA_SIGSTORE_PYTHON_OIDC_CLIENT_SECRET: "${{ inputs.oidc-client-secret }}"
95 | GHA_SIGSTORE_PYTHON_STAGING: "${{ inputs.staging }}"
96 | GHA_SIGSTORE_PYTHON_VERIFY: "${{ inputs.verify }}"
97 | GHA_SIGSTORE_PYTHON_VERIFY_CERT_IDENTITY: "${{ inputs.verify-cert-identity }}"
98 | GHA_SIGSTORE_PYTHON_VERIFY_OIDC_ISSUER: "${{ inputs.verify-oidc-issuer }}"
99 | GHA_SIGSTORE_PYTHON_RELEASE_SIGNING_ARTIFACTS: "${{ inputs.release-signing-artifacts }}"
100 | GHA_SIGSTORE_PYTHON_INTERNAL_BE_CAREFUL_DEBUG: "${{ inputs.internal-be-careful-debug }}"
101 | GHA_SIGSTORE_PYTHON_INPUTS: "${{ inputs.inputs }}"
102 | shell: bash
103 |
104 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
105 | if: inputs.upload-signing-artifacts == 'true'
106 | with:
107 | name: "signing-artifacts-${{ github.job }}"
108 | path: "${{ env.GHA_SIGSTORE_PYTHON_INTERNAL_SIGNING_ARTIFACTS }}"
109 |
110 | - uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2
111 | if: inputs.release-signing-artifacts == 'true' && github.event_name == 'release' && github.event.action == 'published'
112 | with:
113 | files: "${{ env.GHA_SIGSTORE_PYTHON_INTERNAL_SIGNING_ARTIFACTS }}"
114 |
--------------------------------------------------------------------------------
/requirements/dev.in:
--------------------------------------------------------------------------------
1 | flake8
2 | isort
3 | black
4 | mypy
5 | types-requests
6 |
--------------------------------------------------------------------------------
/requirements/dev.txt:
--------------------------------------------------------------------------------
1 | # This file was autogenerated by uv via the following command:
2 | # uv pip compile --generate-hashes --prerelease=allow --output-file=requirements/dev.txt requirements/dev.in
3 | black==25.1.0 \
4 | --hash=sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171 \
5 | --hash=sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7 \
6 | --hash=sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da \
7 | --hash=sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2 \
8 | --hash=sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc \
9 | --hash=sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666 \
10 | --hash=sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f \
11 | --hash=sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b \
12 | --hash=sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32 \
13 | --hash=sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f \
14 | --hash=sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717 \
15 | --hash=sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299 \
16 | --hash=sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0 \
17 | --hash=sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18 \
18 | --hash=sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0 \
19 | --hash=sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3 \
20 | --hash=sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355 \
21 | --hash=sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096 \
22 | --hash=sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e \
23 | --hash=sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9 \
24 | --hash=sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba \
25 | --hash=sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f
26 | # via -r requirements/dev.in
27 | click==8.1.8 \
28 | --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \
29 | --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a
30 | # via black
31 | flake8==7.2.0 \
32 | --hash=sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343 \
33 | --hash=sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426
34 | # via -r requirements/dev.in
35 | isort==6.0.1 \
36 | --hash=sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450 \
37 | --hash=sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615
38 | # via -r requirements/dev.in
39 | mccabe==0.7.0 \
40 | --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
41 | --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
42 | # via flake8
43 | mypy==1.15.0 \
44 | --hash=sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e \
45 | --hash=sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22 \
46 | --hash=sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f \
47 | --hash=sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2 \
48 | --hash=sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f \
49 | --hash=sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b \
50 | --hash=sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5 \
51 | --hash=sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f \
52 | --hash=sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43 \
53 | --hash=sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e \
54 | --hash=sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c \
55 | --hash=sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828 \
56 | --hash=sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba \
57 | --hash=sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee \
58 | --hash=sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d \
59 | --hash=sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b \
60 | --hash=sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445 \
61 | --hash=sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e \
62 | --hash=sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13 \
63 | --hash=sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5 \
64 | --hash=sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd \
65 | --hash=sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf \
66 | --hash=sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357 \
67 | --hash=sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b \
68 | --hash=sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036 \
69 | --hash=sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559 \
70 | --hash=sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3 \
71 | --hash=sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f \
72 | --hash=sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464 \
73 | --hash=sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980 \
74 | --hash=sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078 \
75 | --hash=sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5
76 | # via -r requirements/dev.in
77 | mypy-extensions==1.1.0 \
78 | --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \
79 | --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558
80 | # via
81 | # black
82 | # mypy
83 | packaging==25.0 \
84 | --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
85 | --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
86 | # via black
87 | pathspec==0.12.1 \
88 | --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \
89 | --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712
90 | # via black
91 | platformdirs==4.3.7 \
92 | --hash=sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94 \
93 | --hash=sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351
94 | # via black
95 | pycodestyle==2.13.0 \
96 | --hash=sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9 \
97 | --hash=sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae
98 | # via flake8
99 | pyflakes==3.3.2 \
100 | --hash=sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a \
101 | --hash=sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b
102 | # via flake8
103 | types-requests==2.32.0.20250328 \
104 | --hash=sha256:72ff80f84b15eb3aa7a8e2625fffb6a93f2ad5a0c20215fc1dcfa61117bcb2a2 \
105 | --hash=sha256:c9e67228ea103bd811c96984fac36ed2ae8da87a36a633964a21f199d60baf32
106 | # via -r requirements/dev.in
107 | typing-extensions==4.13.2 \
108 | --hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \
109 | --hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef
110 | # via mypy
111 | urllib3==2.4.0 \
112 | --hash=sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466 \
113 | --hash=sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813
114 | # via types-requests
115 |
--------------------------------------------------------------------------------
/requirements/main.in:
--------------------------------------------------------------------------------
1 | sigstore ~= 3.6
2 | requests ~= 2.28
3 |
--------------------------------------------------------------------------------
/requirements/main.txt:
--------------------------------------------------------------------------------
1 | # This file was autogenerated by uv via the following command:
2 | # uv pip compile --prerelease=allow --output-file=requirements/main.txt requirements/main.in
3 | annotated-types==0.7.0
4 | # via pydantic
5 | betterproto==2.0.0b6
6 | # via sigstore-protobuf-specs
7 | certifi==2025.1.31
8 | # via requests
9 | cffi==1.17.1
10 | # via cryptography
11 | charset-normalizer==3.4.1
12 | # via requests
13 | cryptography==44.0.2
14 | # via
15 | # pyopenssl
16 | # rfc3161-client
17 | # sigstore
18 | dnspython==2.7.0
19 | # via email-validator
20 | email-validator==2.2.0
21 | # via pydantic
22 | grpclib==0.4.8rc2
23 | # via betterproto
24 | h2==4.2.0
25 | # via grpclib
26 | hpack==4.1.0
27 | # via h2
28 | hyperframe==6.1.0
29 | # via h2
30 | id==1.5.0
31 | # via sigstore
32 | idna==3.10
33 | # via
34 | # email-validator
35 | # requests
36 | markdown-it-py==3.0.0
37 | # via rich
38 | mdurl==0.1.2
39 | # via markdown-it-py
40 | multidict==6.4.3
41 | # via grpclib
42 | platformdirs==4.3.7
43 | # via sigstore
44 | pyasn1==0.6.1
45 | # via sigstore
46 | pycparser==2.22
47 | # via cffi
48 | pydantic==2.11.3
49 | # via
50 | # sigstore
51 | # sigstore-rekor-types
52 | pydantic-core==2.33.1
53 | # via pydantic
54 | pygments==2.19.1
55 | # via rich
56 | pyjwt==2.10.1
57 | # via sigstore
58 | pyopenssl==25.0.0
59 | # via sigstore
60 | python-dateutil==2.9.0.post0
61 | # via betterproto
62 | requests==2.32.3
63 | # via
64 | # -r requirements/main.in
65 | # id
66 | # sigstore
67 | rfc3161-client==1.0.1
68 | # via sigstore
69 | rfc8785==0.1.4
70 | # via sigstore
71 | rich==14.0.0
72 | # via sigstore
73 | securesystemslib==1.3.0
74 | # via tuf
75 | sigstore==3.6.2
76 | # via -r requirements/main.in
77 | sigstore-protobuf-specs==0.3.2
78 | # via sigstore
79 | sigstore-rekor-types==0.0.18
80 | # via sigstore
81 | six==1.17.0
82 | # via python-dateutil
83 | tuf==6.0.0
84 | # via sigstore
85 | typing-extensions==4.13.2
86 | # via
87 | # pydantic
88 | # pydantic-core
89 | # typing-inspection
90 | typing-inspection==0.4.0
91 | # via pydantic
92 | urllib3==2.4.0
93 | # via
94 | # requests
95 | # tuf
96 |
--------------------------------------------------------------------------------
/setup/setup.bash:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Copyright 2022 The Sigstore Authors
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -eo pipefail
18 |
19 | die() {
20 | echo "::error::${1}"
21 | exit 1
22 | }
23 |
24 | debug() {
25 | if [[ "${GHA_SIGSTORE_PYTHON_INTERNAL_BE_CAREFUL_DEBUG:-false}" != "false" || \
26 | "${ACTIONS_STEP_DEBUG:-false}" == 'true' ]]
27 | then
28 | echo -e "\033[93mDEBUG: ${1}\033[0m" >&2
29 | fi
30 | }
31 |
32 | debug "Python: $(python -V)"
33 | debug "pip: $(python -m pip --version)"
34 |
35 | # NOTE: This file is meant to be sourced, not executed as a script.
36 | if [[ "${0}" == "${BASH_SOURCE[0]}" ]]; then
37 | die "Internal error: setup harness was executed instead of being sourced?"
38 | fi
39 |
40 | # Check the Python version, making sure it's new enough (3.9+)
41 | # The installation step immediately below will technically catch this,
42 | # but doing it explicitly gives us the opportunity to produce a better
43 | # error message.
44 | vers=$(python -V | cut -d ' ' -f2)
45 | maj_vers=$(cut -d '.' -f1 <<< "${vers}")
46 | min_vers=$(cut -d '.' -f2 <<< "${vers}")
47 |
48 | [[ "${maj_vers}" == "3" && "${min_vers}" -ge 9 ]] || die "Bad Python version: ${vers}"
49 |
50 | # If the user didn't explicitly configure a Python version with
51 | # `actions/setup-python`, then we might be using the distribution's Python and
52 | # therefore be subject to PEP 668. We use a virtual environment unconditionally
53 | # to prevent that kind of confusion.
54 | python -m venv "${GITHUB_ACTION_PATH}/.action-env"
55 |
56 | # Annoying: Windows venvs use a different structure, for unknown reasons.
57 | if [[ -d "${GITHUB_ACTION_PATH}/.action-env/bin" ]]; then
58 | VENV_PYTHON_PATH="${GITHUB_ACTION_PATH}/.action-env/bin/python"
59 | else
60 | VENV_PYTHON_PATH="${GITHUB_ACTION_PATH}/.action-env/Scripts/python"
61 | fi
62 |
63 | "${VENV_PYTHON_PATH}" -m pip install --requirement "${GITHUB_ACTION_PATH}/requirements/main.txt"
64 |
65 | debug "sigstore-python: $("${VENV_PYTHON_PATH}" -m sigstore --version)"
66 |
67 | # Finally, propagate VENV_PYTHON_PATH so we can actually kick-start
68 | # the extension from it.
69 | echo "venv-python-path=${VENV_PYTHON_PATH}" >> "${GITHUB_OUTPUT}"
70 |
--------------------------------------------------------------------------------
/templates/sigstore-python-sign.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Raw sigstore-python sign
output
5 |
6 |
7 | ```
8 | $output
9 | ```
10 |
11 |
12 |
--------------------------------------------------------------------------------
/templates/sigstore-python-verify.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Raw sigstore-python verify
output
5 |
6 |
7 | ```
8 | $output
9 | ```
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/another1.txt:
--------------------------------------------------------------------------------
1 | Another input.
2 |
--------------------------------------------------------------------------------
/test/another2.txt:
--------------------------------------------------------------------------------
1 | Yet another input.
2 |
--------------------------------------------------------------------------------
/test/artifact.txt:
--------------------------------------------------------------------------------
1 | Hello, World!
2 |
--------------------------------------------------------------------------------
/test/artifact1.txt:
--------------------------------------------------------------------------------
1 | Hello, World 1!
2 |
--------------------------------------------------------------------------------
/test/artifact2.txt:
--------------------------------------------------------------------------------
1 | Hello, World 2!
2 |
--------------------------------------------------------------------------------
/test/more white space.txt:
--------------------------------------------------------------------------------
1 | This is another input with a whitespace filename.
2 |
--------------------------------------------------------------------------------
/test/subdir/hello1.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigstore/gh-action-sigstore-python/42bbcff08f93bc51a1e4b48a19b633ab975d10a6/test/subdir/hello1.txt
--------------------------------------------------------------------------------
/test/subdir/hello2.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigstore/gh-action-sigstore-python/42bbcff08f93bc51a1e4b48a19b633ab975d10a6/test/subdir/hello2.txt
--------------------------------------------------------------------------------
/test/subdir/hello3.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigstore/gh-action-sigstore-python/42bbcff08f93bc51a1e4b48a19b633ab975d10a6/test/subdir/hello3.txt
--------------------------------------------------------------------------------
/test/white space.txt:
--------------------------------------------------------------------------------
1 | This input has a filename with whitespace in it.
2 |
--------------------------------------------------------------------------------