Skip to content

Commit c7a94b2

Browse files
add sigstore signing integration test (#324)
* add integration test marker By default `hatch test` wont run integration tests, but they can be run with `hatch test -m integration`. Signed-off-by: Spencer Schrock <[email protected]> * add signing test using public OIDC beacon Signed-off-by: Spencer Schrock <[email protected]> * add integration workflow Signed-off-by: Spencer Schrock <[email protected]> --------- Signed-off-by: Spencer Schrock <[email protected]>
1 parent 2b45f9e commit c7a94b2

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

.github/workflows/integration.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2024 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: Run integration tests
16+
on:
17+
pull_request:
18+
branches: [main]
19+
types: [opened, synchronize]
20+
paths-ignore:
21+
- '**/*.md'
22+
- '*.md'
23+
workflow_dispatch:
24+
25+
permissions: {}
26+
27+
defaults:
28+
run:
29+
shell: bash
30+
31+
jobs:
32+
model-signing-integration-test:
33+
name: Signing with Python ${{ matrix.python-version }} on ${{ startsWith(matrix.os, 'macos-') && 'macOS' || startsWith(matrix.os, 'windows-') && 'Windows' || 'Linux' }}
34+
runs-on: ${{ matrix.os }}
35+
strategy:
36+
fail-fast: false # Don't cancel other jobs if one fails
37+
matrix:
38+
os: [ubuntu-latest, macos-latest, windows-latest]
39+
python-version: ['3.10', '3.11', '3.12', '3.13']
40+
steps:
41+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
42+
- name: Set up Hatch
43+
uses: pypa/hatch@257e27e51a6a5616ed08a39a408a21c35c9931bc
44+
- name: Run integration tests
45+
run: |
46+
set -euxo pipefail
47+
hatch test -c -py ${{ matrix.python-version }} -m integration

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ packages = ["src/model_signing"]
6161
installer = "pip"
6262
parallel = true
6363
randomize = true
64+
extra-args = ["-m", "not integration"]
6465

6566
[[tool.hatch.envs.hatch-test.matrix]]
6667
python = ["3.10", "3.11", "3.12", "3.13"]
@@ -119,6 +120,9 @@ skip_empty = true
119120

120121
# Add support for testing via the old `pytest .` way, too.
121122
[tool.pytest.ini_options]
123+
markers = [
124+
"integration: mark a test as an integration test.",
125+
]
122126
pythonpath = "src"
123127

124128
[tool.ruff]

tests/api_test.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Copyright 2024 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+
"""Tests for the top level API."""
16+
17+
from base64 import b64decode
18+
from datetime import datetime
19+
from datetime import timedelta
20+
import json
21+
import os
22+
import subprocess
23+
from tempfile import TemporaryDirectory
24+
import time
25+
26+
import pytest
27+
28+
from model_signing import api as model_signing_api
29+
30+
31+
_MIN_VALIDITY = timedelta(minutes=1)
32+
_MAX_RETRY_TIME = timedelta(minutes=5)
33+
_RETRY_SLEEP_SECS = 30
34+
35+
36+
class DangerousPublicOIDCBeacon:
37+
"""Fetches and validates tokens from Sigstore's testing beacon repo."""
38+
39+
def __init__(self):
40+
self._token = ""
41+
42+
def _fetch(self) -> None:
43+
# the git approach is apparently fresher than https://raw.githubusercontent.com
44+
# https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/issues/17
45+
git_url = "https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon.git"
46+
with TemporaryDirectory() as tmpdir:
47+
base_cmd = [
48+
"git",
49+
"clone",
50+
"--quiet",
51+
"--single-branch",
52+
"--branch=current-token",
53+
"--depth=1",
54+
]
55+
subprocess.run(base_cmd + [git_url, tmpdir], check=True)
56+
token_path = os.path.join(tmpdir, "oidc-token.txt")
57+
with open(token_path) as f:
58+
self._token = f.read().rstrip()
59+
60+
def _expiration(self) -> datetime:
61+
payload = self._token.split(".")[1]
62+
payload += "=" * (4 - len(payload) % 4)
63+
payload_json = json.loads(b64decode(payload))
64+
return datetime.fromtimestamp(payload_json["exp"])
65+
66+
67+
@pytest.fixture
68+
def sigstore_oidc_beacon_token():
69+
beacon = DangerousPublicOIDCBeacon()
70+
start = datetime.now()
71+
while True:
72+
now = datetime.now()
73+
deadline = now + _MIN_VALIDITY
74+
beacon._fetch()
75+
exp = beacon._expiration()
76+
if deadline < exp:
77+
return beacon._token
78+
if now > start + _MAX_RETRY_TIME:
79+
break
80+
time.sleep(_RETRY_SLEEP_SECS)
81+
pytest.fail("unable to fetch token within time limit")
82+
83+
84+
class TestSigstoreSigning:
85+
@pytest.mark.integration
86+
def test_sign_and_verify(
87+
self, sigstore_oidc_beacon_token, sample_model_folder, tmp_path
88+
):
89+
sc = model_signing_api.SigningConfig()
90+
sc.set_sigstore_signer(
91+
use_staging=True, identity_token=sigstore_oidc_beacon_token
92+
)
93+
signature_path = tmp_path / "model.sig"
94+
sc.sign(sample_model_folder, signature_path)
95+
expected_identity = "https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/.github/workflows/extremely-dangerous-oidc-beacon.yml@refs/heads/main"
96+
model_signing_api.verify(
97+
sample_model_folder,
98+
signature_path,
99+
identity=expected_identity,
100+
use_staging=True,
101+
)

0 commit comments

Comments
 (0)