├── .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 | [![CI](https://github.com/sigstore/gh-action-sigstore-python/actions/workflows/ci.yml/badge.svg)](https://github.com/sigstore/gh-action-sigstore-python/actions/workflows/ci.yml) 5 | [![Self-test](https://github.com/sigstore/gh-action-sigstore-python/actions/workflows/selftest.yml/badge.svg)](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 | --------------------------------------------------------------------------------