From b3e3aa98a3379d15eac5c8a6528fdb1b5d63d0a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:04:43 +0200 Subject: [PATCH 01/13] build(deps): bump cryptography from 43.0.3 to 44.0.0 (#1233) Bumps [cryptography](https://github.com/pyca/cryptography) from 43.0.3 to 44.0.0. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/43.0.3...44.0.0) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1d1c55d6..02dabee8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ "Topic :: Security :: Cryptography", ] dependencies = [ - "cryptography >= 42, < 44", + "cryptography >= 42, < 45", "id >= 1.1.0", "importlib_resources ~= 5.7; python_version < '3.11'", "pyasn1 ~= 0.6", From 9ef52515ee8f7b6c53285f4d32a9758a419294be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:20:34 +0200 Subject: [PATCH 02/13] build(deps): bump rfc3161-client from 0.0.4 to 0.1.1 (#1246) Bumps [rfc3161-client](https://github.com/trailofbits/rfc3161-client) from 0.0.4 to 0.1.1. - [Release notes](https://github.com/trailofbits/rfc3161-client/releases) - [Changelog](https://github.com/trailofbits/rfc3161-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/trailofbits/rfc3161-client/compare/v0.0.4...v0.1.1) --- updated-dependencies: - dependency-name: rfc3161-client dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 02dabee8..69c8da38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dependencies = [ "rich ~= 13.0", "rfc8785 ~= 0.1.2", # NOTE(dm): Under very active development, so strictly pinned. - "rfc3161-client == 0.0.4", + "rfc3161-client == 0.1.1", # NOTE(ww): Both under active development, so strictly pinned. "sigstore-protobuf-specs == 0.3.2", "sigstore-rekor-types == 0.0.18", From 540ed9351a8735989bb38099929b6dbe000cf1c5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:48:51 +0000 Subject: [PATCH 03/13] Update pinned requirements for v3.6.0 (#1249) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: William Woodruff --- install/requirements.in | 2 +- install/requirements.txt | 97 +++++++++++++++++++++++++++++++++------- 2 files changed, 82 insertions(+), 17 deletions(-) diff --git a/install/requirements.in b/install/requirements.in index 0d4c42b1..760ed1b7 100644 --- a/install/requirements.in +++ b/install/requirements.in @@ -1 +1 @@ -sigstore==3.5.3 +sigstore==3.6.0 diff --git a/install/requirements.txt b/install/requirements.txt index 7ef65046..28a93061 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -222,6 +222,7 @@ cryptography==43.0.3 \ --hash=sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7 # via # pyopenssl + # rfc3161-client # sigstore dnspython==2.7.0 \ --hash=sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86 \ @@ -264,6 +265,21 @@ markdown-it-py==3.0.0 \ --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb # via rich +maturin==1.7.8 \ + --hash=sha256:1ce48d007438b895f8665314b6748ac0dab31e4f32049a60b52281dd2dccbdde \ + --hash=sha256:2b2bdee0c3a84696b3a809054c43ead1a04b7b3321cbd5b8f5676e4ba4691d0f \ + --hash=sha256:403eebf1afa6f19b49425f089e39c53b8e597bc86a47f3a76e828dc78d27fa80 \ + --hash=sha256:649c6ef3f0fa4c5f596140d761dc5a4d577c485cc32fb5b9b344a8280352880d \ + --hash=sha256:6cafb17bf57822bdc04423d9e3e766d42918d474848fe9833e397267514ba891 \ + --hash=sha256:a4f58c2a53c2958a1bf090960b08b28e676136cd88ac2f5dfdcf1b14ea54ec06 \ + --hash=sha256:b2d4e0f674ca29864e6b86c2eb9fee8236d1c7496c25f7300e34229272468f4c \ + --hash=sha256:b8188b71259fc2bc568d9c8acc186fcfed96f42539bcb55b8e6f4ec26e411f37 \ + --hash=sha256:c23664d19dadcbf800ef70f26afb2e0485a985c62889930934f019c565534c23 \ + --hash=sha256:c5d6c0c631d1fc646cd3834795e6cfd72ab4271d289df7e0f911261a02bec75f \ + --hash=sha256:c6950fd2790acd93265e1501cea66f9249cff19724654424ca75a3b17ebb315b \ + --hash=sha256:cc92a62953205e8945b6cfe6943d6a8576a4442d30d9c67141f944f4f4640e62 \ + --hash=sha256:f98288d5c382bacf0c076871dfd50c38f1eb2248f417551e98dd6f47f6ee8afa + # via rfc3161-client mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba @@ -374,9 +390,9 @@ pycparser==2.22 \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc # via cffi -pydantic[email]==2.10.2 \ - --hash=sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa \ - --hash=sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e +pydantic[email]==2.10.3 \ + --hash=sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d \ + --hash=sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9 # via # sigstore # sigstore-rekor-types @@ -505,6 +521,21 @@ requests==2.32.3 \ # id # sigstore # tuf +rfc3161-client==0.0.4 \ + --hash=sha256:07ce4ad1c35f3a0849a34efc78bb00b8520581c92d9cf3658539dd4604007d91 \ + --hash=sha256:0d2bb5be5c6937b15842221489d2564bc2492dfedc8c5b34ce97319e4618782d \ + --hash=sha256:3a9107572f92a0b2d6bb2e8eb0a635cebffa03d33bbd6eae69e8240b1f982922 \ + --hash=sha256:6609bb872f87da30448697234a0044a2cbe81ecc789cbd89d662b68b2d4a2021 \ + --hash=sha256:7415c816418f46d94a36a875ef0dfdcc6e2c3684383388ca92f3d2bb246766de \ + --hash=sha256:7698fbe46fc056d7aca2e790f68ae2e1ec8c2cb794d5a82e8ce583d9c48dfd91 \ + --hash=sha256:84707145debb6e6d94ca498ee9e4440cf31b0733b2c6931dfc200659967272d8 \ + --hash=sha256:8b98affc17fa4a6349cd045b6c48573f8998f254e1f1d6e8156d957cbbff8000 \ + --hash=sha256:8f9418cffb4b64c6d20505e1f48fadcf68dbafe5ce387cd57a19798ffb5a0677 \ + --hash=sha256:b97a1a73f71f5cc7b5459ca042a9267569369357da7ab9747f65d1feacbd2f19 \ + --hash=sha256:bf90cf5185ab9d7a6aa374a2ecea1b507a1326176881af1fe1e9ce067d5601bf \ + --hash=sha256:c7eefcc139e0c4ee98ea6ceaa272f11cbcaf28dfa39f61558803f173990d5dbd \ + --hash=sha256:f6d0b61b4b188d3e9607ef376762ab7c46af3c67e182ac984bfaf8f5e738e1c6 + # via sigstore rfc8785==0.1.4 \ --hash=sha256:520d690b448ecf0703691c76e1a34a24ddcd4fc5bc41d589cb7c58ec651bcd48 \ --hash=sha256:e545841329fe0eee4f6a3b44e7034343100c12b4ec566dc06ca9735681deb4da @@ -513,26 +544,60 @@ rich==13.9.4 \ --hash=sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098 \ --hash=sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90 # via sigstore -securesystemslib==1.1.0 \ - --hash=sha256:100bf04e60b260e1c7c51e3232647697fde2c5ca5772fda4932d841d3fb6dd0e \ - --hash=sha256:27143a8e04b5573636f260f21d7e26b48bcedcf394e6f74ec31e9a5287e0c38b +securesystemslib==1.2.0 \ + --hash=sha256:34fa63e3296a0540b122a13bf51722ecd015be00c1d2ed45b23442e718920e76 \ + --hash=sha256:fa63abcb1cf4dba4f2df964f623baa45bc39029980d7a0a2119d90731942afc6 # via tuf -sigstore==3.5.3 \ - --hash=sha256:2547bca442201bdf07f7de2c654a0dbe5051b90fac3f8580bf1875d11b5ad498 \ - --hash=sha256:5f91e101a51bbf442a37f7c1aeea8d4501c9565680c04e06f35be5b53bc02c55 - # via -r install/requirements.in +sigstore==3.6.0 \ + --hash=sha256:cd84e1acaec163d4b3a837dab160fb73259bf67939b2deea3d12c40f09a6ac21 \ + --hash=sha256:e354867c21674864b4014a390e28685e1decc925be5ba1af3055f217d6d27e2c + # via -r requirements.in sigstore-protobuf-specs==0.3.2 \ --hash=sha256:50c99fa6747a3a9c5c562a43602cf76df0b199af28f0e9d4319b6775630425ea \ --hash=sha256:cae041b40502600b8a633f43c257695d0222a94efa1e5110a7ec7ada78c39d99 # via sigstore -sigstore-rekor-types==0.0.13 \ - --hash=sha256:377fee942d5fc66437a4f54599472157149affaece9bbc7deb05e5b42f34ceba \ - --hash=sha256:63e9306a26931ed74411911948c250da7c5adc51c53507227738170424e6ae2d +sigstore-rekor-types==0.0.18 \ + --hash=sha256:19aef25433218ebf9975a1e8b523cc84aaf3cd395ad39a30523b083ea7917ec5 \ + --hash=sha256:b62bf38c5b1a62bc0d7fe0ee51a0709e49311d137c7880c329882a8f4b2d1d78 # via sigstore -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 # via python-dateutil +tomli==2.2.1 \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 + # via maturin tuf==5.1.0 \ --hash=sha256:1865737bf8e05893ae31b4511617da7f02cf070562fa3c931074d29ef5fb46d7 \ --hash=sha256:6494848d2720ced600e0d7ee23b4986623ddad1148ad8e54ffe308db18b762fe From 6a4edb5dfb9b481a603def8ef4279053f4ac6bf4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:49:31 -0500 Subject: [PATCH 04/13] build(deps): bump the actions group with 2 updates (#1253) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/conformance.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 73672899..14addb38 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -24,7 +24,7 @@ jobs: - name: install sigstore-python run: python -m pip install . - - uses: sigstore/sigstore-conformance@6bd1c54e236c9517da56f7344ad16cc00439fe19 # v0.0.13 + - uses: sigstore/sigstore-conformance@b0635d4101f11dbd18a50936568a1f7f55b17760 # v0.0.14 with: entrypoint: ${{ github.workspace }}/test/integration/sigstore-python-conformance xfail: "test_verify_with_trust_root test_verify_dsse_bundle_with_trust_root" # see issue 821 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9fa6835..715f34db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -131,7 +131,7 @@ jobs: # Confusingly, this action also supports updating releases, not # just creating them. This is what we want here, since we've manually # created the release that triggered the action. - uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0 + uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0 with: # smoketest-artifacts/ contains the signatures and certificates. files: | From 49c0706666a1bd1effa6cfcaa91bf9bd05acccb8 Mon Sep 17 00:00:00 2001 From: dm Date: Wed, 11 Dec 2024 21:02:02 +0100 Subject: [PATCH 05/13] Update Sigstore Timestamp using dependabot (#1225) Co-authored-by: William Woodruff --- .github/dependabot.yml | 11 +++++++++++ .github/go.mod | 11 +++++++++++ .github/workflows/ci.yml | 5 ++--- 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 .github/go.mod diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0f9f7511..29011578 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -27,3 +27,14 @@ updates: actions: patterns: - "*" + + - package-ecosystem: gomod + directory: "/.github" + schedule: + interval: daily + open-pull-requests-limit: 1 + rebase-strategy: "disabled" + groups: + actions: + patterns: + - "*" \ No newline at end of file diff --git a/.github/go.mod b/.github/go.mod new file mode 100644 index 00000000..56f2af9d --- /dev/null +++ b/.github/go.mod @@ -0,0 +1,11 @@ +module sigstore/sigstore-python + +go 1.23 + +require ( + // We don't have a Go module here but this file is picked up by dependabot + // and this will automatically update the dependency when needed. + + github.com/sigstore/timestamp-authority v1.2.3 + +) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 142214a5..de62ba21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,8 @@ jobs: - name: test (timestamp-authority) if: ${{ matrix.conf.os == 'ubuntu-latest' }} run: | - wget https://github.com/sigstore/timestamp-authority/releases/download/${SIGSTORE_TIMESTAMP}/timestamp-server-linux-amd64 -O /tmp/timestamp-server + SIGSTORE_TIMESTAMP_VERSION=$(grep "github.com/sigstore/timestamp-authority" .github/go.mod | awk '{print $2}') + wget https://github.com/sigstore/timestamp-authority/releases/download/${SIGSTORE_TIMESTAMP_VERSION}/timestamp-server-linux-amd64 -O /tmp/timestamp-server chmod +x /tmp/timestamp-server # Run the TSA in background /tmp/timestamp-server serve --port 3000 --disable-ntp-monitoring & @@ -70,8 +71,6 @@ jobs: # having pytest show skipped tests and verifying ours are running make test TEST_ARGS="-m timestamp_authority -rs" | tee output ! grep -q "skipping test that requires a Timestamp Authority" output || (echo "ERROR: Found skip message" && exit 1) - env: - SIGSTORE_TIMESTAMP: "v1.2.3" - name: test (interactive) if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork From 9996ae8f673027d75db0485f3d0e1e4d243d7ad7 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 11 Dec 2024 18:11:42 -0500 Subject: [PATCH 06/13] bump rfc3161-client (#1251) --- CHANGELOG.md | 6 ++++++ pyproject.toml | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c6778e..6cbd3643 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ All versions prior to 0.9.0 are untracked. ## [Unreleased] +### Fixed + +* Relaxed the transitive dependency on `cryptography` to allow v43 and v44 + to be resolved + ([#1251](https://github.com/sigstore/sigstore-python/pull/1251)) + ## [3.6.0] ### Added diff --git a/pyproject.toml b/pyproject.toml index 69c8da38..0d593f49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,8 +36,7 @@ dependencies = [ "requests", "rich ~= 13.0", "rfc8785 ~= 0.1.2", - # NOTE(dm): Under very active development, so strictly pinned. - "rfc3161-client == 0.1.1", + "rfc3161-client ~= 0.1.2", # NOTE(ww): Both under active development, so strictly pinned. "sigstore-protobuf-specs == 0.3.2", "sigstore-rekor-types == 0.0.18", From 0c1fafe02b91a7bdac8102ab3d801493983c1129 Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Wed, 11 Dec 2024 20:15:55 -0500 Subject: [PATCH 07/13] Remove vestigial `attestations: true` (#1254) --- .github/workflows/release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 715f34db..a14ec10c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -115,7 +115,6 @@ jobs: uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3 with: packages-dir: built-packages/ - attestations: true release-github: needs: [build, generate-provenance] From 491f3f8dd0b1983d23364673cc7e0b24aeb3a5dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:28:42 -0500 Subject: [PATCH 08/13] build(deps): update ruff requirement from <0.8.3 to <0.8.4 (#1256) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0d593f49..443765c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ lint = [ "mypy ~= 1.1", # NOTE(ww): ruff is under active development, so we pin conservatively here # and let Dependabot periodically perform this update. - "ruff < 0.8.3", + "ruff < 0.8.4", "types-requests", "types-pyOpenSSL", ] From 6a98ddd55405793bb91f2ee6675896a1fd5549b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 20:00:28 +0000 Subject: [PATCH 09/13] build(deps): bump github/codeql-action in the actions group (#1260) --- .github/workflows/scorecards-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index 10e19c75..d2628fd0 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@babb554ede22fd5605947329c4d04d8e7a0b8155 # v3.27.7 + uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 with: sarif_file: results.sarif From 25e0ad4ea392ce39d9d4abd1cb1c13bc0ef8de5b Mon Sep 17 00:00:00 2001 From: dm Date: Mon, 16 Dec 2024 18:11:28 +0100 Subject: [PATCH 10/13] Restructure documentation (#1255) * Draft a POLICY Signed-off-by: Alexis * Update documentation Signed-off-by: Alexis * Add missing license Signed-off-by: Alexis * Remove pdoc dependency Signed-off-by: Alexis * Update documentation Signed-off-by: Alexis * Improve documentation Signed-off-by: Alexis * Explicit failure reasons Signed-off-by: Alexis * Remove API folder * Add api folder * Update policy.md Signed-off-by: Alexis * Remove API folder Signed-off-by: Alexis * Add api folder Signed-off-by: Alexis * Update policy.md Signed-off-by: Alexis --------- Signed-off-by: Alexis --- .github/workflows/docs.yml | 4 +- .gitignore | 1 + Makefile | 9 +- README.md | 253 ++------------------------------- docs/advanced/custom_trust.md | 22 +++ docs/advanced/offline.md | 43 ++++++ docs/api/errors.md | 2 + docs/api/hashes.md | 2 + docs/api/index.md | 6 + docs/api/models.md | 2 + docs/api/oidc.md | 2 + docs/api/sign.md | 2 + docs/api/verify/policy.md | 2 + docs/api/verify/verifier.md | 2 + docs/assets/images/favicon.png | Bin 0 -> 16133 bytes docs/assets/images/logo.png | Bin 0 -> 15933 bytes docs/index.md | 44 ++++++ docs/installation.md | 51 +++++++ docs/policy.md | 145 +++++++++++++++++++ docs/scripts/gen_ref_pages.py | 84 +++++++++++ docs/signing.md | 133 +++++++++++++++++ docs/stylesheets/custom.css | 5 + docs/verify.md | 95 +++++++++++++ mkdocs.yml | 83 +++++++++++ pyproject.toml | 2 +- 25 files changed, 742 insertions(+), 252 deletions(-) create mode 100644 docs/advanced/custom_trust.md create mode 100644 docs/advanced/offline.md create mode 100644 docs/api/errors.md create mode 100644 docs/api/hashes.md create mode 100644 docs/api/index.md create mode 100644 docs/api/models.md create mode 100644 docs/api/oidc.md create mode 100644 docs/api/sign.md create mode 100644 docs/api/verify/policy.md create mode 100644 docs/api/verify/verifier.md create mode 100644 docs/assets/images/favicon.png create mode 100644 docs/assets/images/logo.png create mode 100644 docs/index.md create mode 100644 docs/installation.md create mode 100644 docs/policy.md create mode 100644 docs/scripts/gen_ref_pages.py create mode 100644 docs/signing.md create mode 100644 docs/stylesheets/custom.css create mode 100644 docs/verify.md create mode 100644 mkdocs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 10e555a8..3308498a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,9 +15,7 @@ jobs: - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: - # NOTE: We use 3.10+ typing syntax via future, which pdoc only - # understands if it's actually run with Python 3.10 or newer. - python-version: ">= 3.10" + python-version: "3.x" cache: "pip" cache-dependency-path: pyproject.toml diff --git a/.gitignore b/.gitignore index 5bf6416f..6e03bf7e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.cache/ env/ pip-wheel-metadata/ *.egg-info/ diff --git a/Makefile b/Makefile index 61032b67..f2fd8258 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,8 @@ SHELL := /bin/bash PY_MODULE := sigstore ALL_PY_SRCS := $(shell find $(PY_MODULE) -name '*.py') \ - $(shell find test -name '*.py') + $(shell find test -name '*.py') \ + $(shell find docs/scripts -name '*.py') \ # Optionally overriden by the user, if they're using a virtual environment manager. VENV ?= env @@ -67,7 +68,8 @@ lint: $(VENV)/pyvenv.cfg ruff check $(ALL_PY_SRCS) && \ mypy $(PY_MODULE) && \ bandit -c pyproject.toml -r $(PY_MODULE) && \ - interrogate --fail-under 100 -c pyproject.toml $(PY_MODULE) + interrogate --fail-under 100 -c pyproject.toml $(PY_MODULE) && \ + python docs/scripts/gen_ref_pages.py --check .PHONY: reformat reformat: $(VENV)/pyvenv.cfg @@ -97,7 +99,8 @@ gen-x509-testcases: $(VENV)/pyvenv.cfg .PHONY: doc doc: $(VENV)/pyvenv.cfg . $(VENV_BIN)/activate && \ - pdoc --output-directory html $(PY_MODULE) + python docs/scripts/gen_ref_pages.py --overwrite && \ + mkdocs build --strict --site-dir html .PHONY: package package: $(VENV)/pyvenv.cfg diff --git a/README.md b/README.md index b92d87ba..fad13d61 100644 --- a/README.md +++ b/README.md @@ -18,21 +18,13 @@ else! * [Features](#features) * [Installation](#installation) - * [GitHub Actions](#github-actions) * [Usage](#usage) * [Signing](#signing) * [Verifying](#verifying) * [Generic identities](#generic-identities) * [Signatures from GitHub Actions](#signatures-from-github-actions) * [Advanced usage](#advanced-usage) -* [Example uses](#example-uses) - * [Signing with ambient credentials](#signing-with-ambient-credentials) - * [Signing with an email identity](#signing-with-an-email-identity) - * [Signing with an explicit identity token](#signing-with-an-explicit-identity-token) - * [Verifying against a bundle](#verifying-against-a-bundle) - * [Offline verification](#offline-verification) - * [Verifying a digest instead of a file](#verifying-a-digest-instead-of-a-file) - * [Verifying signatures from GitHub Actions](#verifying-signatures-from-github-actions) +* [Documentation](#documentation) * [Licensing](#licensing) * [Community](#community) * [Contributing](#contributing) @@ -55,44 +47,17 @@ else! python -m pip install sigstore ``` -Optionally, to install `sigstore` and all its dependencies with [hash-checking mode](https://pip.pypa.io/en/stable/topics/secure-installs/#hash-checking-mode) enabled, run the following: - -```console -python -m pip install -r https://raw.githubusercontent.com/sigstore/sigstore-python/main/install/requirements.txt -``` - -This installs the requirements file located [here](https://github.com/sigstore/sigstore-python/blob/main/install/requirements.txt), which is kept up-to-date. - -### GitHub Actions - -`sigstore-python` has [an official GitHub Action](https://github.com/sigstore/gh-action-sigstore-python)! - -You can install it from the -[GitHub Marketplace](https://github.com/marketplace/actions/gh-action-sigstore-python), or -add it to your CI manually: - -```yaml -jobs: - sigstore-python: - steps: - - uses: sigstore/gh-action-sigstore-python@v3.0.0 - with: - inputs: foo.txt -``` - -See the -[action documentation](https://github.com/sigstore/gh-action-sigstore-python/blob/main/README.md) -for more details and usage examples. +See the [installation](https://sigstore.github.io/sigstore-python/installation) page in the documentation for more +installation options. ## Usage -For Python API usage, see our [documentation](https://sigstore.github.io/sigstore-python/). +For Python API usage, see our [API](https://sigstore.github.io/sigstore-python/api/). -You can run `sigstore` as a standalone program, or via `python -m`: +You can run `sigstore` as a standalone program: ```console sigstore --help -python -m sigstore --help ``` Top-level: @@ -236,12 +201,7 @@ Output options: ### Verifying -#### Generic identities - -This is the most common verification done with `sigstore`, and therefore -the one you probably want: you can use it to verify that a signature was -produced by a particular identity (like `hamilcar@example.com`), as attested -to by a particular OIDC provider (like `https://github.com/login/oauth`). +#### Identities ``` @@ -281,11 +241,6 @@ Verification options: #### Signatures from GitHub Actions -If your signatures are coming from GitHub Actions (e.g., a workflow -that uses its [ambient credentials](#signing-with-ambient-credentials)), -then you can use the `sigstore verify github` subcommand to verify -claims more precisely than `sigstore verify identity` allows: - ``` usage: sigstore verify github [-h] [-v] [--certificate FILE] @@ -330,195 +285,9 @@ Verification options: ``` -## Advanced usage - -### Configuring a custom root of trust ("BYO PKI") - -Apart from the default and "staging" Sigstore instances, `sigstore` also -supports "BYO PKI" setups, where a user maintains their own Sigstore -instance services. - -These are supported via the `--trust-config` flag, which accepts a -JSON-formatted file conforming to the `ClientTrustConfig` message -in the [Sigstore protobuf specs](https://github.com/sigstore/protobuf-specs). -This file configures the entire Sigstore instance state, *including* the URIs -used to access the CA and artifact transparency services as well as the -cryptographic root of trust itself. - -To use a custom client config, prepend `--trust-config` to any `sigstore` -command: - -```console -$ sigstore --trust-config custom.trustconfig.json sign foo.txt -$ sigstore --trust-config custom.trustconfig.json verify identity foo.txt ... -``` - -## Example uses +## Documentation -`sigstore` supports a wide variety of workflows and usages. Some common ones are -provided below. - -### Signing with ambient credentials - -For environments that support OpenID Connect, `sigstore` supports ambient credential -detection. This includes many popular CI platforms and cloud providers. See the full list of -supported environments [here](https://github.com/di/id#supported-environments). - -Sign a single file (`foo.txt`) using an ambient OpenID Connect credential, -saving the bundle to `foo.txt.sigstore.json`: - -```console -$ python -m sigstore sign foo.txt -``` - -### Signing with an email identity - -`sigstore` can use an OAuth2 + OpenID flow to establish an email identity, -allowing you to request signing certificates that attest to control over -that email. - -Sign a single file (`foo.txt`) using the OAuth2 flow, saving the -bundle to `foo.txt.sigstore.json`: - -```console -$ python -m sigstore sign foo.txt -``` - -By default, `sigstore` attempts to do -[ambient credential detection](#signing-with-ambient-credentials), which may preempt -the OAuth2 flow. To force the OAuth2 flow, you can explicitly disable ambient detection: - -```console -$ python -m sigstore sign --oidc-disable-ambient-providers foo.txt -``` - -### Signing with an explicit identity token - -If you can't use an ambient credential or the OAuth2 flow, you can pass a pre-created -identity token directly into `sigstore sign`: - -```console -$ python -m sigstore sign --identity-token YOUR-LONG-JWT-HERE foo.txt -``` - -Note that passing a custom identity token does not circumvent Fulcio's requirements, -namely the Fulcio's supported identity providers and the claims expected within the token. - -### Verifying against a bundle - -By default, `sigstore verify identity` will attempt to find a `.sigstore.json` -or `.sigstore` in the same directory as the file being verified: - -```console -# looks for foo.txt.sigstore.json -$ python -m sigstore verify identity foo.txt \ - --cert-identity 'hamilcar@example.com' \ - --cert-oidc-issuer 'https://github.com/login/oauth' -``` - -Multiple files can be verified at once: - -```console -# looks for {foo,bar}.txt.sigstore.json -$ python -m sigstore verify identity foo.txt bar.txt \ - --cert-identity 'hamilcar@example.com' \ - --cert-oidc-issuer 'https://github.com/login/oauth' -``` - -### Offline verification - -> [!IMPORTANT] -> Because `--offline` disables trust root updates, `sigstore-python` falls back -> to the latest cached trust root or, if none exists, the trust root baked -> into `sigstore-python` itself. Like with any other offline verification, -> this means that users may miss trust root changes (such as new root keys, -> or revocations) unless they separately keep the trust root up-to-date. -> -> Users who need to operationalize offline verification may wish to do this -> by distributing their own trust configuration; see -> [Configuring a custom root of trust](#configuring-a-custom-root-of-trust-byo-pki). - -During verification, there are two kinds of network access that `sigstore-python` -*can* perform: - -1. When verifying against "detached" materials (e.g. separate `.crt` and `.sig` - files), `sigstore-python` can perform an online transparency log lookup. -2. By default, during all verifications, `sigstore-python` will attempt to - refresh the locally cached root of trust via a TUF update. - -When performing bundle verification (i.e. `.sigstore` or `.sigstore.json`), -(1) does not apply. However, (2) can still result in online accesses. - -To perform **fully** offline verification, pass `--offline` to your -`sigstore verify` subcommand: - -```bash -$ python -m sigstore verify identity foo.txt \ - --offline \ - --cert-identity 'hamilcar@example.com' \ - --cert-oidc-issuer 'https://github.com/login/oauth' -``` - -Alternatively, users may choose to bypass TUF entirely by passing -an entire trust configuration to `sigstore-python` via `--trust-config`: - -```bash -$ python -m sigstore --trust-config public.trustconfig.json verify identity ... -``` - -This will similarly result in fully offline operation, as the trust -configuration contains a full trust root. - -### Verifying a digest instead of a file - -`sigstore-python` supports verifying digests directly, without requiring the artifact to be -present. The digest should be prefixed with the `sha256:` string: - -```console -$ python -m sigstore verify identity sha256:ce8ab2822671752e201ea1e19e8c85e73d497e1c315bfd9c25f380b7625d1691 \ - --cert-identity 'hamilcar@example.com' \ - --cert-oidc-issuer 'https://github.com/login/oauth' - --bundle 'foo.txt.sigstore.json' -``` - -### Verifying signatures from GitHub Actions - -`sigstore verify github` can be used to verify claims specific to signatures coming from GitHub -Actions. `sigstore-python` signs releases via GitHub Actions, so the examples below are working -examples of how you can verify a given `sigstore-python` release. - -When using `sigstore verify github`, you must pass `--cert-identity` or `--repository`, or both. -Unlike `sigstore verify identity`, `--cert-oidc-issuer` is **not** required (since it's -inferred to be GitHub Actions). - -Verifying with `--cert-identity`: - -```console -$ python -m sigstore verify github sigstore-0.10.0-py3-none-any.whl \ - --bundle sigstore-0.10.0-py3-none-any.whl.bundle \ - --cert-identity https://github.com/sigstore/sigstore-python/.github/workflows/release.yml@refs/tags/v0.10.0 -``` - -Verifying with `--repository`: - -```console -$ python -m sigstore verify github sigstore-0.10.0-py3-none-any.whl \ - --bundle sigstore-0.10.0-py3-none-any.whl.bundle \ - --repository sigstore/sigstore-python -``` - -Additional GitHub Actions specific claims can be verified like so: - -```console -$ python -m sigstore verify github sigstore-0.10.0-py3-none-any.whl \ - --bundle sigstore-0.10.0-py3-none-any.whl.bundle \ - --cert-identity https://github.com/sigstore/sigstore-python/.github/workflows/release.yml@refs/tags/v0.10.0 \ - --trigger release \ - --sha 66581529803929c3ccc45334632ccd90f06e0de4 \ - --name Release \ - --repository sigstore/sigstore-python \ - --ref refs/tags/v0.10.0 -``` +`sigstore` documentation is available on [https://sigstore.github.io/sigstore-python](https://sigstore.github.io/sigstore-python) ## Licensing @@ -544,9 +313,3 @@ Everyone interacting with this project is expected to follow the Should you discover any security issues, please refer to sigstore's [security process](https://github.com/sigstore/.github/blob/main/SECURITY.md). - -### SLSA Provenance -This project emits a SLSA provenance on its release! This enables you to verify the integrity -of the downloaded artifacts and ensured that the binary's code really comes from this source code. - -To do so, please follow the instructions [here](https://github.com/slsa-framework/slsa-github-generator#verification-of-provenance). diff --git a/docs/advanced/custom_trust.md b/docs/advanced/custom_trust.md new file mode 100644 index 00000000..87949993 --- /dev/null +++ b/docs/advanced/custom_trust.md @@ -0,0 +1,22 @@ +# Custom Root of Trust + +### Configuring a custom root of trust ("BYO PKI") + +Apart from the default and "staging" Sigstore instances, `sigstore` also +supports "BYO PKI" setups, where a user maintains their own Sigstore +instance services. + +These are supported via the `--trust-config` flag, which accepts a +JSON-formatted file conforming to the `ClientTrustConfig` message +in the [Sigstore protobuf specs](https://github.com/sigstore/protobuf-specs). +This file configures the entire Sigstore instance state, *including* the URIs +used to access the CA and artifact transparency services as well as the +cryptographic root of trust itself. + +To use a custom client config, prepend `--trust-config` to any `sigstore` +command: + +```console +$ sigstore --trust-config custom.trustconfig.json sign foo.txt +$ sigstore --trust-config custom.trustconfig.json verify identity foo.txt ... +``` \ No newline at end of file diff --git a/docs/advanced/offline.md b/docs/advanced/offline.md new file mode 100644 index 00000000..bc107dbc --- /dev/null +++ b/docs/advanced/offline.md @@ -0,0 +1,43 @@ +# Offline Verification + +!!! danger + Because `--offline` disables trust root updates, `sigstore-python` falls back + to the latest cached trust root or, if none exists, the trust root baked + into `sigstore-python` itself. Like with any other offline verification, + this means that users may miss trust root changes (such as new root keys, + or revocations) unless they separately keep the trust root up-to-date. + + Users who need to operationalize offline verification may wish to do this + by distributing their own trust configuration; see + [Customn root of trust](./custom_trust.md). + +During verification, there are two kinds of network access that `sigstore-python` +*can* perform: + +1. When verifying against "detached" materials (e.g. separate `.crt` and `.sig` + files), `sigstore-python` can perform an online transparency log lookup. +2. By default, during all verifications, `sigstore-python` will attempt to + refresh the locally cached root of trust via a TUF update. + +When performing bundle verification (i.e. `.sigstore` or `.sigstore.json`), +(1) does not apply. However, (2) can still result in online accesses. + +To perform **fully** offline verification, pass `--offline` to your +`sigstore verify` subcommand: + +```bash +$ sigstore verify identity foo.txt \ + --offline \ + --cert-identity 'hamilcar@example.com' \ + --cert-oidc-issuer 'https://github.com/login/oauth' +``` + +Alternatively, users may choose to bypass TUF entirely by passing +an entire trust configuration to `sigstore-python` via `--trust-config`: + +```bash +$ sigstore --trust-config public.trustconfig.json verify identity ... +``` + +This will similarly result in fully offline operation, as the trust +configuration contains a full trust root. \ No newline at end of file diff --git a/docs/api/errors.md b/docs/api/errors.md new file mode 100644 index 00000000..81a2bab9 --- /dev/null +++ b/docs/api/errors.md @@ -0,0 +1,2 @@ +:::sigstore.errors + \ No newline at end of file diff --git a/docs/api/hashes.md b/docs/api/hashes.md new file mode 100644 index 00000000..bf00a7f6 --- /dev/null +++ b/docs/api/hashes.md @@ -0,0 +1,2 @@ +:::sigstore.hashes + \ No newline at end of file diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 00000000..122945b1 --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,6 @@ +!!! note + + The API reference is automatically generated from the docstrings + +:::sigstore + \ No newline at end of file diff --git a/docs/api/models.md b/docs/api/models.md new file mode 100644 index 00000000..9a75e028 --- /dev/null +++ b/docs/api/models.md @@ -0,0 +1,2 @@ +:::sigstore.models + \ No newline at end of file diff --git a/docs/api/oidc.md b/docs/api/oidc.md new file mode 100644 index 00000000..7a30ccc0 --- /dev/null +++ b/docs/api/oidc.md @@ -0,0 +1,2 @@ +:::sigstore.oidc + \ No newline at end of file diff --git a/docs/api/sign.md b/docs/api/sign.md new file mode 100644 index 00000000..a29710fc --- /dev/null +++ b/docs/api/sign.md @@ -0,0 +1,2 @@ +:::sigstore.sign + \ No newline at end of file diff --git a/docs/api/verify/policy.md b/docs/api/verify/policy.md new file mode 100644 index 00000000..0b6d133b --- /dev/null +++ b/docs/api/verify/policy.md @@ -0,0 +1,2 @@ +:::sigstore.verify.policy + \ No newline at end of file diff --git a/docs/api/verify/verifier.md b/docs/api/verify/verifier.md new file mode 100644 index 00000000..fc002d8b --- /dev/null +++ b/docs/api/verify/verifier.md @@ -0,0 +1,2 @@ +:::sigstore.verify.verifier + \ No newline at end of file diff --git a/docs/assets/images/favicon.png b/docs/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..b1f05e42a2d8abf19a61c07bfd0807a26f5b0bfa GIT binary patch literal 16133 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 literal 0 HcmV?d00001 diff --git a/docs/assets/images/logo.png b/docs/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5519b417cfd70bc97b2c8106e4e0258b467bb67b GIT binary patch literal 15933 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 b2?SL~#IWVFLXF9-|0~tgHqxrb+C}{z{k-fS literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..acca7405 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,44 @@ +# Home + +## Introduction + +`sigstore` is a Python tool for generating and verifying [Sigstore] signatures. +You can use it to sign and verify Python package distributions, or anything +else! + +## Features + +* Support for keyless signature generation and verification with [Sigstore](https://www.sigstore.dev/) +* Support for signing with ["ambient" OpenID Connect identities](./signing.md#signing-with-ambient-credentials) +* A comprehensive [CLI](#using-sigstore) and corresponding + [importable Python API](./api/index.md) + +## Installing `sigstore` + +```console +python -m pip install sigstore +``` + +See [installation](./installation.md) for more detailed installation instructions or options. + +## Using `sigstore` + +You can run `sigstore` as a standalone program, or via `python -m`: + +```console +sigstore --help +python -m sigstore --help +``` + +- Use `sigstore` to [sign](./signing.md) +- Use `sigstore` to [verify](./verify.md) + +## SLSA Provenance + +This project emits a [SLSA] provenance on its release! This enables you to verify the integrity +of the downloaded artifacts and ensured that the binary's code really comes from this source code. + +To do so, please follow the instructions [here](https://github.com/slsa-framework/slsa-github-generator#verification-of-provenance). + +[SLSA]: https://slsa.dev/ +[Sigstore]: https://www.sigstore.dev/ \ No newline at end of file diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 00000000..51154f19 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,51 @@ +# Installation + +## With `pip` + +`sigstore` requires Python 3.9 or newer, and can be installed directly via `pip`: + +```console +python -m pip install sigstore +``` + +Optionally, to install `sigstore` and all its dependencies with [hash-checking mode](https://pip.pypa.io/en/stable/topics/secure-installs/#hash-checking-mode) enabled, run the following: + +```console +python -m pip install -r https://raw.githubusercontent.com/sigstore/sigstore-python/main/install/requirements.txt +``` + +This installs the requirements file located [here](https://github.com/sigstore/sigstore-python/blob/main/install/requirements.txt), which is kept up-to-date. + +## With `uv` + +!!! warning + + `sigstore` depends on `betterproto` pre-releases versions, which are by default not resolved by `uv`. + +```console +uv pip install --prerelease=allow sigstore +``` + +`sigstore` can also be used as tool: + +```console +uvx --prerelease=allow sigstore --help +``` + +## GitHub Actions + +`sigstore-python` has [an official GitHub Action](https://github.com/sigstore/gh-action-sigstore-python)! + +You can install it from the [GitHub Marketplace](https://github.com/marketplace/actions/gh-action-sigstore-python), or +add it to your CI manually: + +```yaml +jobs: + sigstore-python: + steps: + - uses: sigstore/gh-action-sigstore-python@v3.0.0 + with: + inputs: foo.txt +``` + +See the [action documentation](https://github.com/sigstore/gh-action-sigstore-python/blob/main/README.md) for more details and usage examples. \ No newline at end of file diff --git a/docs/policy.md b/docs/policy.md new file mode 100644 index 00000000..88050466 --- /dev/null +++ b/docs/policy.md @@ -0,0 +1,145 @@ +# Policies + +This document describes the set of policies followed by `sigstore-python` +when signing or verifying a bundle. + +`sigstore-python` follows the [Sigstore: Client Spec] and this document +outline mimic the one from the spec. + +## Signing + +### Authentication + +`sigstore-python` supports several authentication mechanisms : + +- An OAuth flow: this mode is preferred for interactive workflows. +- An _ambient_ detection: this mode is preferred for un-attended workflows + (i.e., continuous integration system) + +### Key generation + +`sigstore-python` uses [ECDSA] as its signing algorithm. + +### Certificate Issuance + +_using Fulcio_ + +### Signing + +When needed, the payload pre-hashing algorithm is `SHA2_256`. + +### Timestamping + +If Timestamp Authorities have been provided in the Signing Config, a +Timestamp Request using the hash of the signature is automatically sent to the +provided Timestamp Authorities. + +This step allows to attest of the signature time. + +### Submission of Signing Metadata to Transparency Service + +The Transparency Service, [rekor], is used by `sigstore-python` to provide a +public, immutable record of signing events. This step is crucial for ensuring +the integrity and transparency of the signing process. + +!!! warning + + This step is performed before the `Timestamping` step in the worfklow. + +### Signing Choices + +Here's a summary of the key choices in the `sigstore-python` signing process: + +| Option | `sigstore-python` | +|-------------------------------|------------------------------| +| Digital signature algorithm | ECDSA | +| Signature metadata format | ??? | +| Payload pre-hashing algorithm | SHA2 (256) | +| Long-lived signing keys | not used | +| Timestamping | Used if provided | +| Transparency | Always used (rekor) | +| Other workflows | no other workflows supported | + +## Verification + +`sigstore-python` supports configuring the verification process using policies +but this must be done using the [api](./api/index.md). By default, the CLI uses +the [`Identity`][sigstore.verify.policy] verification policy. + +### Establishing a Time for the Signature + +If the bundle contains one or more signed times from Timestamping Authorities, +they will be used as the time source. In this case, a Timestamp Authority +configuration must be provided in the `ClientTrustConfig`. When verifying +Timestamp Authorities Responses, at least one must be valid. + +If there is a Transparency Service Timestamp, this is also used as a source +of trusted time. + +The verification will fail if no sources of time are found. + +### Certificate + +For a signature to be considered valid, it must meet two key criteria: + +- The signature must have an associated timestamp. +- Every certificate in the chain, from the signing certificate up to the root + certificate, must be valid at the time of signing. + +This approach is known as the “hybrid model” of certificate verification, as +described by [Braun et al.]. + +This validation process is repeated for each available source of trusted time. +The signature is only considered valid if it passes the validation checks +against all of these time sources. + +#### SignedCertificateTimestamp + +The `SignedCertificateTimestamp` is extracted from the leaf certificate and +verified using the verification key from the Certificate Transparency Log. + +#### Identity Verification Policy + +The system verifies that the signing certificate conforms to the Sigstore X. 509 +profile as well as `Identity Policy`. + +### Transparency Log Entry + +The Verifier now verifies the inclusion proof and signed checkpoint for the +log entry using [rekor]. + +If there is an inclusion promise, this is also verified. + +#### Time insertion check + +The system verifies that the transparency log entry’s insertion timestamp falls +within the certificate’s validity period. + +If the insertion timestamp is outside the certificate’s validity period, it +could indicate potential backdating or use of an expired certificate, and the +verification will fail. + + +### Signature Verification + +The next verification step is to verify the actual signature. This ensures +that the signed content has not been tampered with and was indeed signed by the +claimed entity. + +The verification process differs slightly depending on the type of signed +content: + +- DSSE: The entire envelope structure is used as the verification payload. +- Artifacts: The raw bytes of the artifacts serve as the verification payload. + +#### Final step + +Finally, a last consistency check is performed to verify that the constructed +payload is indeed the one that has been signed. This step is ussed to prevent +variants of [CVE-2022-36056]. + +[Sigstore: Client Spec]: https://docs.google.com/document/d/1kbhK2qyPPk8SLavHzYSDM8-Ueul9_oxIMVFuWMWKz0E/edit?usp=sharing +[ECDSA]: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +[rekor]: https://github.com/sigstore/rekor +[Braun et al.]: https://research.tue.nl/en/publications/how-to-avoid-the-breakdown-of-public-key-infrastructures-forward- +[CVE-2022-36056]: https://github.com/sigstore/cosign/security/advisories/GHSA-8gw7-4j42-w388 \ No newline at end of file diff --git a/docs/scripts/gen_ref_pages.py b/docs/scripts/gen_ref_pages.py new file mode 100644 index 00000000..6a5572b8 --- /dev/null +++ b/docs/scripts/gen_ref_pages.py @@ -0,0 +1,84 @@ +# Copyright 2022 The Sigstore Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import argparse +import shutil +import sys +from pathlib import Path + +root = Path(__file__).parent.parent.parent +src = root / "sigstore" +api_root = root / "docs" / "api" + + +def main(args: argparse.Namespace) -> None: + """Main script.""" + if args.overwrite: + shutil.rmtree(api_root, ignore_errors=True) + elif not args.check and api_root.exists(): + print(f"API root {api_root} already exists, skipping.") + sys.exit(0) + + seen = set() + for path in src.rglob("*.py"): + module_path = path.relative_to(src).with_suffix("") + full_doc_path = api_root / path.relative_to(src).with_suffix(".md") + + # Exclude private entries + if any(part.startswith("_") for part in module_path.parts): + continue + + if args.check: + if not full_doc_path.is_file(): + print(f"File {full_doc_path} does not exist.", file=sys.stderr) + sys.exit(1) + + full_doc_path.parent.mkdir(parents=True, exist_ok=True) + with full_doc_path.open("w") as f: + f.write(f":::sigstore.{str(module_path).replace('/', '.')}\n ") + + seen.add(full_doc_path) + + # Add the root + with (api_root / "index.md").open("w") as f: + f.write("""!!! note + + The API reference is automatically generated from the docstrings + +:::sigstore + """) + + seen.add(api_root / "index.md") + + if args.check: + if diff := set(api_root.rglob("*.md")).symmetric_difference(seen): + print(f"Found leftover documentation file: {diff}", file=sys.stderr) + sys.exit(1) + else: + print("API doc generated.") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate the structure for the API documentation." + ) + parser.add_argument("--overwrite", action="store_true", default=False) + parser.add_argument("--check", action="store_true", default=False) + + arguments = parser.parse_args() + + if arguments.check and arguments.overwrite: + print("You can't specify both --check and --overwrite.", file=sys.stderr) + sys.exit(1) + + main(arguments) diff --git a/docs/signing.md b/docs/signing.md new file mode 100644 index 00000000..4b7b5729 --- /dev/null +++ b/docs/signing.md @@ -0,0 +1,133 @@ +# Signing + +!!! warning + + By default signing an artifact creates a public record in `Rekor` which is publicly available. + The transparency log entry is browsable at `https://search.sigstore.dev/?logIndex=` + and disclose the signing identity. + +## Identities + +### Signing with ambient credentials + +For environments that support OpenID Connect, `sigstore` supports ambient credential +detection. This includes many popular CI platforms and cloud providers. See the full list of +supported environments [here](https://github.com/di/id#supported-environments). + +### Signing with an email identity + +`sigstore` can use an OAuth2 + OpenID flow to establish an email identity, +allowing you to request signing certificates that attest to control over +that email. + +By default, `sigstore` attempts to do [ambient credential detection](#signing-with-ambient-credentials), which may preempt +the OAuth2 flow. To force the OAuth2 flow, you can explicitly disable ambient detection: + +```console +$ sigstore sign --oidc-disable-ambient-providers foo.txt +``` + +### Signing with an explicit identity token + +If you can't use an ambient credential or the OAuth2 flow, you can pass a pre-created +identity token directly into `sigstore sign`: + +```console +$ sigstore sign --identity-token YOUR-LONG-JWT-HERE foo.txt +``` + +Note that passing a custom identity token does not circumvent Fulcio's requirements, +namely the Fulcio's supported identity providers and the claims expected within the token. + +!!! note + + The examples in the section belows are using ambient credential detection. + When no credentials are detected, it opens a browser to perform an interactive OAuth2 authentication flow. + +## Signing an artifact + +The easiest option to sign an artifact with `sigstore` is to use the `sign` command. + +For example, signing `sigstore-python` [README.md](https://github.com/sigstore/sigstore-python/blob/main/README.md). + +```console +$ sigstore sign README.md + +Waiting for browser interaction... +Using ephemeral certificate: +-----BEGIN CERTIFICATE----- +MIIC2TCCAl+gAwIBAgIUdqkRnuxTr6bgdKtNiItu3+y8UkIwCgYIKoZIzj0EAwMw +NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl +cm1lZGlhdGUwHhcNMjQxMjEyMDk1NTU5WhcNMjQxMjEyMTAwNTU5WjAAMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEjb33vsuuNr4phkmpkUvMB19rnXLtS9QqZGT+ +kDetyi9+wYv/g2oOFDfEm7UHPLUeZJ6Bad8Zd7H/JqGUhuJ7gaOCAX4wggF6MA4G +A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUJpNq +0mPqLw1ypudG98REMY7mjyowHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y +ZD8wLgYDVR0RAQH/BCQwIoEgYWxleGlzLmNoYWxsYW5kZUB0cmFpbG9mYml0cy5j +b20wKQYKKwYBBAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMCsG +CisGAQQBg78wAQgEHQwbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGKBgor +BgEEAdZ5AgQCBHwEegB4AHYA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p +7o4AAAGTukvv5QAABAMARzBFAiEA3oqdIinnZ9rGb7CTxQ60G6xi6l3T+z6vkSr2 +ERAnIp4CIHbx61camOWU8dClH2WMUfguQ11+D82IQQBnHF968g22MAoGCCqGSM49 +BAMDA2gAMGUCMQDdf8S5Y/UhAp2vd2eo+RsjtfsasXSI51kO1ppNz42rSa6b5djW +8+we6/OzVQW+THYCMBaBHPNntloKD040Pce6f8W3HpydbUzshJ24Emt/EaTPqH/g +gYd2xz5hd4vQ7Ysmsg== +-----END CERTIFICATE----- + +Transparency log entry created at index: 155016378 +MEQCIHVjH0I3iarhB5hD0MEE4AZ7GpCPZhXpdsVsSFlZIynVAiA10qzWt9FBC5pjD6+1kLRS14F+muVD1NJZNw6b+/WADQ== +Sigstore bundle written to README.md.sigstore.json + +``` + +The log entry is available at : [https://search.sigstore.dev/?logIndex=155016378](https://search.sigstore.dev/?logIndex=155016378) + +## Attest + +`sigstore` can be used to generate attestations for software artifacts using [SLSA]. + +!!! info "What is SLSA?" + + Supply-chain Levels for Software Artifacts, or SLSA ("salsa"). + It’s a security framework, a checklist of standards and controls to prevent tampering, improve integrity, and secure packages and infrastructure. It’s how you get from "safe enough" to being as resilient as possible, at any link in the chain. + + +At the moment, `sigstore` supports the following predicates types: + +- [https://slsa.dev/provenance/v1](https://slsa.dev/spec/v1.0/provenance) +- [https://slsa.dev/provenance/v0.2](https://slsa.dev/spec/v0.2/provenance) + +Example : + +```console +$ sigstore attest \ + --predicate-type "https://slsa.dev/provenance/v1" \ + --predicate ./test/assets/integration/attest/slsa_predicate_v1_0.json \ + ./README.md + +Waiting for browser interaction... +Using ephemeral certificate: +-----BEGIN CERTIFICATE----- +MIIC2TCCAmCgAwIBAgIUI1GUnwGV69rXWAixrFmwAcZ7j7IwCgYIKoZIzj0EAwMw +NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl +cm1lZGlhdGUwHhcNMjQxMjEyMTAxODUwWhcNMjQxMjEyMTAyODUwWjAAMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEZPieQV37ByUyf+zWMGjXmom+kM4INxPcO1Kf +DhjV3RmhTAlKOYXGU38O/KUNka5BLTb4f5r1bNwGhiEf9qcmNqOCAX8wggF7MA4G +A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUUexC +qnLoKejMCAAgNxN77wSlIHkwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y +ZD8wLgYDVR0RAQH/BCQwIoEgYWxleGlzLmNoYWxsYW5kZUB0cmFpbG9mYml0cy5j +b20wKQYKKwYBBAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMCsG +CisGAQQBg78wAQgEHQwbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGLBgor +BgEEAdZ5AgQCBH0EewB5AHcA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p +7o4AAAGTumDcJAAABAMASDBGAiEAprGPiBTcRK8ZFM+x3HLE+2s82xPAecHfJo9F +RXNI+CMCIQCYzRBQtTehd+LLmwkXjPJEsJ5CpI7q1uDhhspyplVSLjAKBggqhkjO +PQQDAwNnADBkAjAjO7BG9Gx6ggm1/IP75l+LzUnAP/DP0BOBeM0/lXZN3BBUvtdq ++oTUzmmY/VpCWggCMEcCMn4UDIF/jBrVhES8ks57T8LjRX6xacpn9ufpkTlnKs6w +S8/kL6jEREOcdnpOSQ== +-----END CERTIFICATE----- + +Transparency log entry created at index: 155019253 +Sigstore bundle written to README.md.sigstore.json +``` + +[SLSA]: https://slsa.dev/ \ No newline at end of file diff --git a/docs/stylesheets/custom.css b/docs/stylesheets/custom.css new file mode 100644 index 00000000..a06a30cc --- /dev/null +++ b/docs/stylesheets/custom.css @@ -0,0 +1,5 @@ +/* From https://github.com/sigstore/community/blob/main/artwork/Sigstore_BrandGuide_March2023.pdf */ +:root { + --md-primary-fg-color: #2e2f71; + --md-primary-bg-color: #f9f7ef; +} \ No newline at end of file diff --git a/docs/verify.md b/docs/verify.md new file mode 100644 index 00000000..d256a391 --- /dev/null +++ b/docs/verify.md @@ -0,0 +1,95 @@ +# Verifying + +## Generic identities + +This is the most common verification done with `sigstore`, and therefore +the one you probably want: you can use it to verify that a signature was +produced by a particular identity (like `hamilcar@example.com`), as attested +to by a particular OIDC provider (like `https://github.com/login/oauth`). + +```console +$ sigstore verify identity --cert-identity --cert-oidc-issuer FILE_OR_DIGEST +``` + +The following command will verify that the bundle `tests/assets/bundle.txt.sigstore` was signed by `a@tny.town` using +the staging infrastructure of `sigstore`. + +```console +$ sigstore --staging verify identity --cert-identity "a@tny.town" --cert-oidc-issuer "https://github.com/login/oauth" test/assets/bundle.txt +``` + +## Verifying from GitHub Actions + +If your signatures are coming from GitHub Actions (e.g., a workflow that uses its [ambient credentials](./signing.md#signing-with-ambient-credentials)), +then you can use the `sigstore verify github` subcommand to verify +claims more precisely than `sigstore verify identity` allows. + +`sigstore verify github` can be used to verify claims specific to signatures coming from GitHub +Actions. `sigstore-python` signs releases via GitHub Actions, so the examples below are working +examples of how you can verify a given `sigstore-python` release. + +When using `sigstore verify github`, you must pass `--cert-identity` or `--repository`, or both. +Unlike `sigstore verify identity`, `--cert-oidc-issuer` is **not** required (since it's +inferred to be GitHub Actions). + +Verifying with `--cert-identity`: + +```console +$ sigstore verify github sigstore-0.10.0-py3-none-any.whl \ + --bundle sigstore-0.10.0-py3-none-any.whl.bundle \ + --cert-identity https://github.com/sigstore/sigstore-python/.github/workflows/release.yml@refs/tags/v0.10.0 +``` + +Verifying with `--repository`: + +```console +$ sigstore verify github sigstore-0.10.0-py3-none-any.whl \ + --bundle sigstore-0.10.0-py3-none-any.whl.bundle \ + --repository sigstore/sigstore-python +``` + +Additional GitHub Actions specific claims can be verified like so: + +```console +$ sigstore verify github sigstore-0.10.0-py3-none-any.whl \ + --bundle sigstore-0.10.0-py3-none-any.whl.bundle \ + --cert-identity https://github.com/sigstore/sigstore-python/.github/workflows/release.yml@refs/tags/v0.10.0 \ + --trigger release \ + --sha 66581529803929c3ccc45334632ccd90f06e0de4 \ + --name Release \ + --repository sigstore/sigstore-python \ + --ref refs/tags/v0.10.0 +``` + +## Verifying against a bundle + +By default, `sigstore verify identity` will attempt to find a `.sigstore.json` +or `.sigstore` in the same directory as the file being verified: + +```console +# looks for foo.txt.sigstore.json +$ sigstore verify identity foo.txt \ + --cert-identity 'hamilcar@example.com' \ + --cert-oidc-issuer 'https://github.com/login/oauth' +``` + +Multiple files can be verified at once: + +```console +# looks for {foo,bar}.txt.sigstore.json +$ python -m sigstore verify identity foo.txt bar.txt \ + --cert-identity 'hamilcar@example.com' \ + --cert-oidc-issuer 'https://github.com/login/oauth' +``` + +## Verifying a digest instead of a file + +`sigstore-python` supports verifying digests directly, without requiring the artifact to be +present. The digest should be prefixed with the `sha256:` string: + +```console +$ sigstore verify identity sha256:ce8ab2822671752e201ea1e19e8c85e73d497e1c315bfd9c25f380b7625d1691 \ + --cert-identity 'hamilcar@example.com' \ + --cert-oidc-issuer 'https://github.com/login/oauth' + --bundle 'foo.txt.sigstore.json' +``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..aaba03cd --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,83 @@ +# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json + +site_name: sigstore-python +site_url: https://sigstore.github.io/sigstore-python +repo_url: https://github.com/sigstore/sigstore-python +site_description: sigstore-python, a Sigstore client written in Python +repo_name: sigstore-python +edit_uri: edit/main/docs/ +theme: + name: material + icon: + repo: fontawesome/brands/github + logo: assets/images/logo.png + features: + - content.action.edit + - content.code.copy + - header.autohide + - navigation.instant + - navigation.instant.progress + - navigation.footer + - search.highlight + - search.suggest + palette: + primary: custom + font: + text: Inter +extra_css: + - stylesheets/custom.css +nav: + - Home: index.md + - Installation: installation.md + - Signing: signing.md + - Verifying: verify.md + - Policy: policy.md + - Advanced: + - Custom Root of Trust: advanced/custom_trust.md + - Offline Verification: advanced/offline.md + # begin-api-section + - API: + - api/index.md + - Models: api/models.md + - Errors: api/errors.md + - Hashes: api/hashes.md + - OIDC: api/oidc.md + - Sign: api/sign.md + - Verify: + - Policy: api/verify/policy.md + - Verifier: api/verify/verifier.md + # end-api-section +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences +copyright: sigstore © 2024 +plugins: + - search + - social + - mkdocstrings: + handlers: + python: + options: + members_order: source + unwrap_annotated: true + modernize_annotations: true + merge_init_into_class: true + docstring_section_style: spacy + signature_crossrefs: true + show_symbol_type_toc: true + filters: + - '!^_' +validation: + omitted_files: warn + unrecognized_links: warn + anchors: warn + not_found: warn + +extra: + generator: false + social: + - icon: fontawesome/brands/slack + link: https://sigstore.slack.com + - icon: fontawesome/brands/x-twitter + link: https://twitter.com/projectsigstore \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 443765c4..01c5bf1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ lint = [ "types-requests", "types-pyOpenSSL", ] -doc = ["pdoc"] +doc = ["mkdocs-material[imaging]", "mkdocstrings-python"] dev = ["build", "bump >= 1.3.2", "sigstore[doc,test,lint]"] [tool.coverage.run] From 36a958f7d30075683d39f182324aecbe3fc0c479 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 01:59:02 +0000 Subject: [PATCH 11/13] build(deps): bump actions/upload-artifact (#1261) Bumps the actions group in /.github/actions/upload-coverage with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `actions/upload-artifact` from 4.4.3 to 4.5.0 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882...6f51ac03b9356f520e9adb1b1b7802705f340c2b) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/upload-coverage/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/upload-coverage/action.yml b/.github/actions/upload-coverage/action.yml index a22e1997..73850940 100644 --- a/.github/actions/upload-coverage/action.yml +++ b/.github/actions/upload-coverage/action.yml @@ -20,7 +20,7 @@ runs: fi id: coverage-uuid shell: bash - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: coverage-data-${{ steps.coverage-uuid.outputs.COVERAGE_UUID }} include-hidden-files: 'true' From 0c6deea4f7ae7339e53df5d35d53b25a003e5cb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 02:06:38 +0000 Subject: [PATCH 12/13] build(deps): bump actions/upload-artifact in the actions group (#1262) Bumps the actions group with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `actions/upload-artifact` from 4.4.3 to 4.5.0 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882...6f51ac03b9356f520e9adb1b1b7802705f340c2b) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: William Woodruff --- .github/workflows/release.yml | 4 ++-- .github/workflows/scorecards-analysis.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a14ec10c..2e7ccc20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,14 +74,14 @@ jobs: done - name: Upload built packages - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: built-packages path: ./dist/ if-no-files-found: warn - name: Upload smoketest-artifacts - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: smoketest-artifacts path: smoketest-artifacts/ diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index d2628fd0..b3b5037e 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -44,7 +44,7 @@ jobs: # Upload the results as artifacts (optional). - name: "Upload artifact" - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: SARIF file path: results.sarif From 896cfe13105495e6dc6f8faf23e1007da35edeeb Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Thu, 19 Dec 2024 17:07:55 +0000 Subject: [PATCH 13/13] sigstore: prep 3.6.1 (#1263) Signed-off-by: William Woodruff --- CHANGELOG.md | 5 ++++- sigstore/__init__.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cbd3643..7104a674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ All versions prior to 0.9.0 are untracked. ## [Unreleased] +## [3.6.1] + ### Fixed * Relaxed the transitive dependency on `cryptography` to allow v43 and v44 @@ -591,7 +593,8 @@ This is a corrective release for [2.1.1]. -[Unreleased]: https://github.com/sigstore/sigstore-python/compare/v3.6.0...HEAD +[Unreleased]: https://github.com/sigstore/sigstore-python/compare/v3.6.1...HEAD +[3.6.0]: https://github.com/sigstore/sigstore-python/compare/v3.6.0...v3.6.1 [3.6.0]: https://github.com/sigstore/sigstore-python/compare/v3.5.3...v3.6.0 [3.5.3]: https://github.com/sigstore/sigstore-python/compare/v3.5.2...v3.5.3 [3.5.2]: https://github.com/sigstore/sigstore-python/compare/v3.5.1...v3.5.2 diff --git a/sigstore/__init__.py b/sigstore/__init__.py index 82953f0d..c5e18e0b 100644 --- a/sigstore/__init__.py +++ b/sigstore/__init__.py @@ -25,4 +25,4 @@ * `sigstore.sign`: creation of Sigstore signatures """ -__version__ = "3.6.0" +__version__ = "3.6.1"