Skip to content

Commit 9327c9b

Browse files
Add CLI integration tests for sign subcommand (sigstore#1134)
Co-authored-by: William Woodruff <william@trailofbits.com>
1 parent b7171ca commit 9327c9b

File tree

1 file changed

+333
-0
lines changed

1 file changed

+333
-0
lines changed

test/integration/cli/test_sign.py

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
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+
from pathlib import Path
15+
from typing import List, Optional
16+
17+
import pytest
18+
19+
from sigstore.models import Bundle
20+
from sigstore.verify import Verifier
21+
from sigstore.verify.policy import UnsafeNoOp
22+
23+
24+
def get_cli_params(
25+
artifact_paths: List[Path],
26+
overwrite: bool = False,
27+
no_default_files: bool = False,
28+
output_directory: Optional[Path] = None,
29+
bundle_path: Optional[Path] = None,
30+
signature_path: Optional[Path] = None,
31+
certificate_path: Optional[Path] = None,
32+
) -> List[str]:
33+
cli_params = ["--staging", "sign"]
34+
if output_directory is not None:
35+
cli_params.extend(["--output-directory", str(output_directory)])
36+
if bundle_path is not None:
37+
cli_params.extend(["--bundle", str(bundle_path)])
38+
if signature_path is not None:
39+
cli_params.extend(["--signature", str(signature_path)])
40+
if certificate_path is not None:
41+
cli_params.extend(["--certificate", str(certificate_path)])
42+
if overwrite:
43+
cli_params.append("--overwrite")
44+
if no_default_files:
45+
cli_params.append("--no-default-files")
46+
47+
cli_params.extend([str(p) for p in artifact_paths])
48+
49+
return cli_params
50+
51+
52+
@pytest.mark.staging
53+
@pytest.mark.ambient_oidc
54+
def test_sign_success_default_output_bundle(capsys, sigstore, asset):
55+
artifact = asset("a.txt")
56+
expected_output_bundle = artifact.with_name("a.txt.sigstore.json")
57+
58+
assert not expected_output_bundle.exists()
59+
sigstore(
60+
*get_cli_params(
61+
artifact_paths=[artifact],
62+
)
63+
)
64+
65+
assert expected_output_bundle.exists()
66+
verifier = Verifier.staging()
67+
with open(expected_output_bundle, "r") as bundle_file, open(
68+
artifact, "rb"
69+
) as input_file:
70+
bundle = Bundle.from_json(bundle_file.read())
71+
verifier.verify_artifact(
72+
input_=input_file.read(), bundle=bundle, policy=UnsafeNoOp()
73+
)
74+
75+
expected_output_bundle.unlink()
76+
77+
captures = capsys.readouterr()
78+
assert captures.out.endswith(
79+
f"Sigstore bundle written to {str(expected_output_bundle)}\n"
80+
)
81+
82+
83+
@pytest.mark.staging
84+
@pytest.mark.ambient_oidc
85+
def test_sign_success_custom_outputs(capsys, sigstore, asset, tmp_path):
86+
artifact = asset("a.txt")
87+
output_bundle = tmp_path / "bundle.json"
88+
output_cert = tmp_path / "cert.cert"
89+
output_signature = tmp_path / "signature.sig"
90+
91+
sigstore(
92+
*get_cli_params(
93+
artifact_paths=[artifact],
94+
bundle_path=output_bundle,
95+
certificate_path=output_cert,
96+
signature_path=output_signature,
97+
)
98+
)
99+
100+
assert output_bundle.exists()
101+
assert output_cert.exists()
102+
assert output_signature.exists()
103+
104+
captures = capsys.readouterr()
105+
assert captures.out.endswith(
106+
f"Signature written to {str(output_signature)}\nCertificate written to {str(output_cert)}\nSigstore bundle written to {str(output_bundle)}\n"
107+
)
108+
109+
110+
@pytest.mark.staging
111+
@pytest.mark.ambient_oidc
112+
def test_sign_success_custom_output_dir(capsys, sigstore, asset, tmp_path):
113+
artifact = asset("a.txt")
114+
expected_output_bundle = tmp_path / "a.txt.sigstore.json"
115+
116+
sigstore(
117+
*get_cli_params(
118+
artifact_paths=[artifact],
119+
output_directory=tmp_path,
120+
)
121+
)
122+
123+
assert expected_output_bundle.exists()
124+
125+
captures = capsys.readouterr()
126+
assert captures.out.endswith(
127+
f"Sigstore bundle written to {str(expected_output_bundle)}\n"
128+
)
129+
130+
131+
@pytest.mark.staging
132+
@pytest.mark.ambient_oidc
133+
def test_sign_success_no_default_files(capsys, sigstore, asset, tmp_path):
134+
artifact = asset("a.txt")
135+
default_output_bundle = tmp_path / "a.txt.sigstore.json"
136+
output_cert = tmp_path / "cert.cert"
137+
output_signature = tmp_path / "sig.sig"
138+
139+
sigstore(
140+
*get_cli_params(
141+
artifact_paths=[artifact],
142+
signature_path=output_signature,
143+
certificate_path=output_cert,
144+
no_default_files=True,
145+
)
146+
)
147+
assert output_cert.exists()
148+
assert output_signature.exists()
149+
assert not default_output_bundle.exists()
150+
151+
captures = capsys.readouterr()
152+
assert captures.out.endswith(
153+
f"Signature written to {str(output_signature)}\nCertificate written to {str(output_cert)}\n"
154+
)
155+
156+
157+
@pytest.mark.staging
158+
@pytest.mark.ambient_oidc
159+
def test_sign_overwrite_existing_bundle(capsys, sigstore, asset):
160+
artifact = asset("a.txt")
161+
expected_output_bundle = artifact.with_name("a.txt.sigstore.json")
162+
163+
assert not expected_output_bundle.exists()
164+
sigstore(
165+
*get_cli_params(
166+
artifact_paths=[artifact],
167+
)
168+
)
169+
170+
assert expected_output_bundle.exists()
171+
172+
sigstore(
173+
*get_cli_params(
174+
artifact_paths=[artifact],
175+
overwrite=True,
176+
)
177+
)
178+
assert expected_output_bundle.exists()
179+
180+
with pytest.raises(SystemExit) as e:
181+
sigstore(
182+
*get_cli_params(
183+
artifact_paths=[artifact],
184+
overwrite=False,
185+
)
186+
)
187+
assert e.value.code == 2
188+
189+
captures = capsys.readouterr()
190+
assert captures.err.endswith(
191+
f"Refusing to overwrite outputs without --overwrite: {str(expected_output_bundle)}\n"
192+
)
193+
194+
expected_output_bundle.unlink()
195+
196+
197+
def test_sign_fails_with_default_files_and_bundle_options(capsys, sigstore, asset):
198+
artifact = asset("a.txt")
199+
output_bundle = artifact.with_name("a.txt.sigstore.json")
200+
201+
with pytest.raises(SystemExit) as e:
202+
sigstore(
203+
*get_cli_params(
204+
artifact_paths=[artifact],
205+
bundle_path=output_bundle,
206+
no_default_files=True,
207+
)
208+
)
209+
assert e.value.code == 2
210+
211+
captures = capsys.readouterr()
212+
assert captures.err.endswith(
213+
"--no-default-files may not be combined with --bundle.\n"
214+
)
215+
216+
217+
def test_sign_fails_with_multiple_inputs_and_custom_output(capsys, sigstore, asset):
218+
artifact = asset("a.txt")
219+
220+
with pytest.raises(SystemExit) as e:
221+
sigstore(
222+
*get_cli_params(
223+
artifact_paths=[artifact, artifact],
224+
bundle_path=artifact.with_name("a.txt.sigstore.json"),
225+
)
226+
)
227+
assert e.value.code == 2
228+
captures = capsys.readouterr()
229+
assert captures.err.endswith(
230+
"Error: --signature, --certificate, and --bundle can't be used with explicit outputs for multiple inputs.\n"
231+
)
232+
233+
with pytest.raises(SystemExit) as e:
234+
sigstore(
235+
*get_cli_params(
236+
artifact_paths=[artifact, artifact],
237+
certificate_path=artifact.with_name("a.txt.cert"),
238+
)
239+
)
240+
assert e.value.code == 2
241+
captures = capsys.readouterr()
242+
assert captures.err.endswith(
243+
"Error: --signature, --certificate, and --bundle can't be used with explicit outputs for multiple inputs.\n"
244+
)
245+
246+
with pytest.raises(SystemExit) as e:
247+
sigstore(
248+
*get_cli_params(
249+
artifact_paths=[artifact, artifact],
250+
signature_path=artifact.with_name("a.txt.sig"),
251+
)
252+
)
253+
assert e.value.code == 2
254+
captures = capsys.readouterr()
255+
assert captures.err.endswith(
256+
"Error: --signature, --certificate, and --bundle can't be used with explicit outputs for multiple inputs.\n"
257+
)
258+
259+
260+
def test_sign_fails_with_output_dir_and_custom_output_files(capsys, sigstore, asset):
261+
artifact = asset("a.txt")
262+
263+
with pytest.raises(SystemExit) as e:
264+
sigstore(
265+
*get_cli_params(
266+
artifact_paths=[artifact],
267+
bundle_path=artifact.with_name("a.txt.sigstore.json"),
268+
output_directory=artifact.parent,
269+
)
270+
)
271+
assert e.value.code == 2
272+
captures = capsys.readouterr()
273+
assert captures.err.endswith(
274+
"Error: --signature, --certificate, and --bundle can't be used with an explicit output directory.\n"
275+
)
276+
277+
with pytest.raises(SystemExit) as e:
278+
sigstore(
279+
*get_cli_params(
280+
artifact_paths=[artifact],
281+
certificate_path=artifact.with_name("a.txt.cert"),
282+
output_directory=artifact.parent,
283+
)
284+
)
285+
assert e.value.code == 2
286+
captures = capsys.readouterr()
287+
assert captures.err.endswith(
288+
"Error: --signature, --certificate, and --bundle can't be used with an explicit output directory.\n"
289+
)
290+
291+
with pytest.raises(SystemExit) as e:
292+
sigstore(
293+
*get_cli_params(
294+
artifact_paths=[artifact],
295+
signature_path=artifact.with_name("a.txt.sig"),
296+
output_directory=artifact.parent,
297+
)
298+
)
299+
assert e.value.code == 2
300+
captures = capsys.readouterr()
301+
assert captures.err.endswith(
302+
"Error: --signature, --certificate, and --bundle can't be used with an explicit output directory.\n"
303+
)
304+
305+
306+
def test_sign_fails_without_both_output_cert_and_signature(capsys, sigstore, asset):
307+
artifact = asset("a.txt")
308+
309+
with pytest.raises(SystemExit) as e:
310+
sigstore(
311+
*get_cli_params(
312+
artifact_paths=[artifact],
313+
certificate_path=artifact.with_name("a.txt.cert"),
314+
)
315+
)
316+
assert e.value.code == 2
317+
captures = capsys.readouterr()
318+
assert captures.err.endswith(
319+
"Error: --signature and --certificate must be used together.\n"
320+
)
321+
322+
with pytest.raises(SystemExit) as e:
323+
sigstore(
324+
*get_cli_params(
325+
artifact_paths=[artifact],
326+
signature_path=artifact.with_name("a.txt.sig"),
327+
)
328+
)
329+
assert e.value.code == 2
330+
captures = capsys.readouterr()
331+
assert captures.err.endswith(
332+
"Error: --signature and --certificate must be used together.\n"
333+
)

0 commit comments

Comments
 (0)