Skip to content

Commit 38aa519

Browse files
authored
ci: add OMS conformance suite as CI stage (#670)
Add a conformance workflow that runs the 79-test OMS conformance suite from ossf/model-signing-spec against the Python client on every push, PR, and on a Mon/Thu schedule. The conformance adapter (test/conformance/python-conformance) translates the OMS adapter protocol (sign-model / verify-model) to model_signing CLI calls, covering key, certificate, and sigstore verification methods. Signed-off-by: Sachin Sampras M <sampras343@gmail.com>
1 parent c2b9470 commit 38aa519

2 files changed

Lines changed: 227 additions & 0 deletions

File tree

‎.github/workflows/conformance.yml‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2025 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: Conformance
16+
17+
on:
18+
push:
19+
branches: [main]
20+
pull_request:
21+
branches: [main]
22+
types: [opened, synchronize]
23+
schedule:
24+
- cron: '0 8 * * 1,4' # Mon/Thu 8 AM UTC
25+
workflow_dispatch:
26+
27+
permissions: {}
28+
29+
defaults:
30+
run:
31+
shell: bash
32+
33+
jobs:
34+
conformance:
35+
name: OMS conformance suite
36+
runs-on: ubuntu-latest
37+
permissions:
38+
contents: read
39+
40+
steps:
41+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
42+
with:
43+
persist-credentials: false
44+
45+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
46+
with:
47+
python-version: '3.12'
48+
49+
- name: Install model-signing from source
50+
run: pip install .
51+
52+
- name: Run OMS conformance suite
53+
uses: ossf/model-signing-spec/conformance@main
54+
with:
55+
entrypoint: ${{ github.workspace }}/test/conformance/python-conformance
56+
xfail: |
57+
test_verify[negative/certificate-no-code-signing-eku_fail
58+
test_verify[negative/certificate-not-yet-valid_fail
59+
test_verify[negative/certificate-expired_fail
60+
test_verify[negative/certificate-all-expired_fail
61+
test_verify[negative/certificate-expired-intermediate-expired-leaf_fail
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2025 The Sigstore Authors.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""python-conformance — model-signing conformance adapter.
17+
18+
Translates the OMS conformance protocol to the model_signing Python CLI.
19+
20+
Usage:
21+
python-conformance sign-model --method key|certificate|sigstore \
22+
--model-path DIR --output-bundle FILE [FLAGS...]
23+
24+
python-conformance verify-model --method key|certificate|sigstore \
25+
--model-path DIR --bundle FILE [FLAGS...]
26+
"""
27+
28+
from __future__ import annotations
29+
30+
import argparse
31+
import os
32+
import subprocess
33+
import sys
34+
35+
36+
def _python() -> str:
37+
return os.environ.get("PYTHON_BIN", sys.executable)
38+
39+
40+
def cmd_sign(args: argparse.Namespace) -> int:
41+
method = args.method
42+
cmd = [_python(), "-m", "model_signing", "sign", method,
43+
"--signature", args.output_bundle]
44+
45+
if method == "key":
46+
if not args.private_key:
47+
print("ERROR: --private-key required for method=key", file=sys.stderr)
48+
return 2
49+
cmd += ["--private_key", args.private_key]
50+
51+
elif method == "certificate":
52+
if not args.private_key:
53+
print("ERROR: --private-key required for method=certificate", file=sys.stderr)
54+
return 2
55+
if not args.signing_cert:
56+
print("ERROR: --signing-cert required for method=certificate", file=sys.stderr)
57+
return 2
58+
cmd += ["--private_key", args.private_key]
59+
cmd += ["--signing_certificate", args.signing_cert]
60+
for chain_cert in (args.cert_chain or []):
61+
cmd += ["--certificate_chain", chain_cert]
62+
63+
elif method == "sigstore":
64+
token = args.identity_token or os.environ.get("SIGSTORE_ID_TOKEN", "")
65+
if token:
66+
cmd += ["--identity_token", token]
67+
if args.use_staging:
68+
cmd += ["--use_staging"]
69+
70+
else:
71+
print(f"ERROR: Unknown method: {method}", file=sys.stderr)
72+
return 2
73+
74+
for ignore_path in (args.ignore_paths or []):
75+
cmd += ["--ignore-paths", ignore_path]
76+
77+
cmd += [args.model_path]
78+
79+
result = subprocess.run(cmd, timeout=300)
80+
return result.returncode
81+
82+
83+
def cmd_verify(args: argparse.Namespace) -> int:
84+
method = args.method
85+
cmd = [_python(), "-m", "model_signing", "verify", method,
86+
"--signature", args.bundle]
87+
88+
if method == "key":
89+
if not args.public_key:
90+
print("ERROR: --public-key required for method=key", file=sys.stderr)
91+
return 2
92+
cmd += ["--public_key", args.public_key]
93+
94+
elif method == "certificate":
95+
for chain_cert in (args.cert_chain or []):
96+
cmd += ["--certificate_chain", chain_cert]
97+
98+
elif method == "sigstore":
99+
if args.identity:
100+
cmd += ["--identity", args.identity]
101+
if args.identity_provider:
102+
cmd += ["--identity_provider", args.identity_provider]
103+
if args.use_staging:
104+
cmd += ["--use_staging"]
105+
106+
else:
107+
print(f"ERROR: Unknown method: {method}", file=sys.stderr)
108+
return 2
109+
110+
for ignore_path in (args.ignore_paths or []):
111+
cmd += ["--ignore-paths", ignore_path]
112+
113+
if args.ignore_unsigned_files:
114+
cmd += ["--ignore_unsigned_files"]
115+
116+
cmd += [args.model_path]
117+
118+
result = subprocess.run(cmd, timeout=300)
119+
return result.returncode
120+
121+
122+
def main() -> int:
123+
parser = argparse.ArgumentParser(
124+
prog="python-conformance",
125+
description="model-signing conformance adapter (Python)",
126+
)
127+
subparsers = parser.add_subparsers(dest="command", required=True)
128+
129+
sign_parser = subparsers.add_parser("sign-model")
130+
sign_parser.add_argument("--method", required=True,
131+
choices=["key", "certificate", "sigstore"])
132+
sign_parser.add_argument("--model-path", required=True)
133+
sign_parser.add_argument("--output-bundle", required=True)
134+
sign_parser.add_argument("--private-key")
135+
sign_parser.add_argument("--signing-cert")
136+
sign_parser.add_argument("--cert-chain", action="append", default=[])
137+
sign_parser.add_argument("--ignore-paths", action="append",
138+
dest="ignore_paths", default=[])
139+
sign_parser.add_argument("--identity-token")
140+
sign_parser.add_argument("--use-staging", action="store_true")
141+
142+
verify_parser = subparsers.add_parser("verify-model")
143+
verify_parser.add_argument("--method", required=True,
144+
choices=["key", "certificate", "sigstore"])
145+
verify_parser.add_argument("--model-path", required=True)
146+
verify_parser.add_argument("--bundle", required=True)
147+
verify_parser.add_argument("--public-key")
148+
verify_parser.add_argument("--cert-chain", action="append", default=[])
149+
verify_parser.add_argument("--ignore-paths", action="append",
150+
dest="ignore_paths", default=[])
151+
verify_parser.add_argument("--ignore-unsigned-files", action="store_true")
152+
verify_parser.add_argument("--identity")
153+
verify_parser.add_argument("--identity-provider")
154+
verify_parser.add_argument("--use-staging", action="store_true")
155+
156+
args = parser.parse_args()
157+
158+
if args.command == "sign-model":
159+
return cmd_sign(args)
160+
elif args.command == "verify-model":
161+
return cmd_verify(args)
162+
return 1
163+
164+
165+
if __name__ == "__main__":
166+
sys.exit(main())

0 commit comments

Comments
 (0)