Skip to content

Commit

Permalink
Add CLI integration tests for sign subcommand (#1134)
Browse files Browse the repository at this point in the history
Co-authored-by: William Woodruff <william@trailofbits.com>
  • Loading branch information
facutuesca and woodruffw committed Sep 18, 2024
1 parent b7171ca commit 9327c9b
Showing 1 changed file with 333 additions and 0 deletions.
333 changes: 333 additions & 0 deletions test/integration/cli/test_sign.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
# Copyright 2024 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.
from pathlib import Path
from typing import List, Optional

import pytest

from sigstore.models import Bundle
from sigstore.verify import Verifier
from sigstore.verify.policy import UnsafeNoOp


def get_cli_params(
artifact_paths: List[Path],
overwrite: bool = False,
no_default_files: bool = False,
output_directory: Optional[Path] = None,
bundle_path: Optional[Path] = None,
signature_path: Optional[Path] = None,
certificate_path: Optional[Path] = None,
) -> List[str]:
cli_params = ["--staging", "sign"]
if output_directory is not None:
cli_params.extend(["--output-directory", str(output_directory)])
if bundle_path is not None:
cli_params.extend(["--bundle", str(bundle_path)])
if signature_path is not None:
cli_params.extend(["--signature", str(signature_path)])
if certificate_path is not None:
cli_params.extend(["--certificate", str(certificate_path)])
if overwrite:
cli_params.append("--overwrite")
if no_default_files:
cli_params.append("--no-default-files")

cli_params.extend([str(p) for p in artifact_paths])

return cli_params


@pytest.mark.staging
@pytest.mark.ambient_oidc
def test_sign_success_default_output_bundle(capsys, sigstore, asset):
artifact = asset("a.txt")
expected_output_bundle = artifact.with_name("a.txt.sigstore.json")

assert not expected_output_bundle.exists()
sigstore(
*get_cli_params(
artifact_paths=[artifact],
)
)

assert expected_output_bundle.exists()
verifier = Verifier.staging()
with open(expected_output_bundle, "r") as bundle_file, open(
artifact, "rb"
) as input_file:
bundle = Bundle.from_json(bundle_file.read())
verifier.verify_artifact(
input_=input_file.read(), bundle=bundle, policy=UnsafeNoOp()
)

expected_output_bundle.unlink()

captures = capsys.readouterr()
assert captures.out.endswith(
f"Sigstore bundle written to {str(expected_output_bundle)}\n"
)


@pytest.mark.staging
@pytest.mark.ambient_oidc
def test_sign_success_custom_outputs(capsys, sigstore, asset, tmp_path):
artifact = asset("a.txt")
output_bundle = tmp_path / "bundle.json"
output_cert = tmp_path / "cert.cert"
output_signature = tmp_path / "signature.sig"

sigstore(
*get_cli_params(
artifact_paths=[artifact],
bundle_path=output_bundle,
certificate_path=output_cert,
signature_path=output_signature,
)
)

assert output_bundle.exists()
assert output_cert.exists()
assert output_signature.exists()

captures = capsys.readouterr()
assert captures.out.endswith(
f"Signature written to {str(output_signature)}\nCertificate written to {str(output_cert)}\nSigstore bundle written to {str(output_bundle)}\n"
)


@pytest.mark.staging
@pytest.mark.ambient_oidc
def test_sign_success_custom_output_dir(capsys, sigstore, asset, tmp_path):
artifact = asset("a.txt")
expected_output_bundle = tmp_path / "a.txt.sigstore.json"

sigstore(
*get_cli_params(
artifact_paths=[artifact],
output_directory=tmp_path,
)
)

assert expected_output_bundle.exists()

captures = capsys.readouterr()
assert captures.out.endswith(
f"Sigstore bundle written to {str(expected_output_bundle)}\n"
)


@pytest.mark.staging
@pytest.mark.ambient_oidc
def test_sign_success_no_default_files(capsys, sigstore, asset, tmp_path):
artifact = asset("a.txt")
default_output_bundle = tmp_path / "a.txt.sigstore.json"
output_cert = tmp_path / "cert.cert"
output_signature = tmp_path / "sig.sig"

sigstore(
*get_cli_params(
artifact_paths=[artifact],
signature_path=output_signature,
certificate_path=output_cert,
no_default_files=True,
)
)
assert output_cert.exists()
assert output_signature.exists()
assert not default_output_bundle.exists()

captures = capsys.readouterr()
assert captures.out.endswith(
f"Signature written to {str(output_signature)}\nCertificate written to {str(output_cert)}\n"
)


@pytest.mark.staging
@pytest.mark.ambient_oidc
def test_sign_overwrite_existing_bundle(capsys, sigstore, asset):
artifact = asset("a.txt")
expected_output_bundle = artifact.with_name("a.txt.sigstore.json")

assert not expected_output_bundle.exists()
sigstore(
*get_cli_params(
artifact_paths=[artifact],
)
)

assert expected_output_bundle.exists()

sigstore(
*get_cli_params(
artifact_paths=[artifact],
overwrite=True,
)
)
assert expected_output_bundle.exists()

with pytest.raises(SystemExit) as e:
sigstore(
*get_cli_params(
artifact_paths=[artifact],
overwrite=False,
)
)
assert e.value.code == 2

captures = capsys.readouterr()
assert captures.err.endswith(
f"Refusing to overwrite outputs without --overwrite: {str(expected_output_bundle)}\n"
)

expected_output_bundle.unlink()


def test_sign_fails_with_default_files_and_bundle_options(capsys, sigstore, asset):
artifact = asset("a.txt")
output_bundle = artifact.with_name("a.txt.sigstore.json")

with pytest.raises(SystemExit) as e:
sigstore(
*get_cli_params(
artifact_paths=[artifact],
bundle_path=output_bundle,
no_default_files=True,
)
)
assert e.value.code == 2

captures = capsys.readouterr()
assert captures.err.endswith(
"--no-default-files may not be combined with --bundle.\n"
)


def test_sign_fails_with_multiple_inputs_and_custom_output(capsys, sigstore, asset):
artifact = asset("a.txt")

with pytest.raises(SystemExit) as e:
sigstore(
*get_cli_params(
artifact_paths=[artifact, artifact],
bundle_path=artifact.with_name("a.txt.sigstore.json"),
)
)
assert e.value.code == 2
captures = capsys.readouterr()
assert captures.err.endswith(
"Error: --signature, --certificate, and --bundle can't be used with explicit outputs for multiple inputs.\n"
)

with pytest.raises(SystemExit) as e:
sigstore(
*get_cli_params(
artifact_paths=[artifact, artifact],
certificate_path=artifact.with_name("a.txt.cert"),
)
)
assert e.value.code == 2
captures = capsys.readouterr()
assert captures.err.endswith(
"Error: --signature, --certificate, and --bundle can't be used with explicit outputs for multiple inputs.\n"
)

with pytest.raises(SystemExit) as e:
sigstore(
*get_cli_params(
artifact_paths=[artifact, artifact],
signature_path=artifact.with_name("a.txt.sig"),
)
)
assert e.value.code == 2
captures = capsys.readouterr()
assert captures.err.endswith(
"Error: --signature, --certificate, and --bundle can't be used with explicit outputs for multiple inputs.\n"
)


def test_sign_fails_with_output_dir_and_custom_output_files(capsys, sigstore, asset):
artifact = asset("a.txt")

with pytest.raises(SystemExit) as e:
sigstore(
*get_cli_params(
artifact_paths=[artifact],
bundle_path=artifact.with_name("a.txt.sigstore.json"),
output_directory=artifact.parent,
)
)
assert e.value.code == 2
captures = capsys.readouterr()
assert captures.err.endswith(
"Error: --signature, --certificate, and --bundle can't be used with an explicit output directory.\n"
)

with pytest.raises(SystemExit) as e:
sigstore(
*get_cli_params(
artifact_paths=[artifact],
certificate_path=artifact.with_name("a.txt.cert"),
output_directory=artifact.parent,
)
)
assert e.value.code == 2
captures = capsys.readouterr()
assert captures.err.endswith(
"Error: --signature, --certificate, and --bundle can't be used with an explicit output directory.\n"
)

with pytest.raises(SystemExit) as e:
sigstore(
*get_cli_params(
artifact_paths=[artifact],
signature_path=artifact.with_name("a.txt.sig"),
output_directory=artifact.parent,
)
)
assert e.value.code == 2
captures = capsys.readouterr()
assert captures.err.endswith(
"Error: --signature, --certificate, and --bundle can't be used with an explicit output directory.\n"
)


def test_sign_fails_without_both_output_cert_and_signature(capsys, sigstore, asset):
artifact = asset("a.txt")

with pytest.raises(SystemExit) as e:
sigstore(
*get_cli_params(
artifact_paths=[artifact],
certificate_path=artifact.with_name("a.txt.cert"),
)
)
assert e.value.code == 2
captures = capsys.readouterr()
assert captures.err.endswith(
"Error: --signature and --certificate must be used together.\n"
)

with pytest.raises(SystemExit) as e:
sigstore(
*get_cli_params(
artifact_paths=[artifact],
signature_path=artifact.with_name("a.txt.sig"),
)
)
assert e.value.code == 2
captures = capsys.readouterr()
assert captures.err.endswith(
"Error: --signature and --certificate must be used together.\n"
)

0 comments on commit 9327c9b

Please sign in to comment.