Skip to content

Commit

Permalink
Improve unexpected success handling (#108)
Browse files Browse the repository at this point in the history
* Add raises() contextmanager

This allows managing the "unexpected success" case, and printing
the same details as in "unexpected failure" case.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>

* Change the failure output slightly

Try to print a usable shell command:
 * use str() so that we don't get "PosixPath()" in the args list
 * join the arguments into a single string

This means that I can now copy paste the command and reproduce the
problem by just being in the same directory with assets.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>

---------

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
  • Loading branch information
jku authored Oct 13, 2023
1 parent 3c63132 commit 0f4e883
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 22 deletions.
31 changes: 26 additions & 5 deletions test/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations
from contextlib import contextmanager

import os
import subprocess
Expand All @@ -12,9 +13,7 @@
CERTIFICATE_OIDC_ISSUER = "https://token.actions.githubusercontent.com"

_CLIENT_ERROR_MSG = """
!!! CLIENT FAILURE !!!
Arguments: {args}
Command: {command}
Exit code: {exitcode}
!!! STDOUT !!!
Expand All @@ -33,6 +32,10 @@ class ClientFail(Exception):
pass


class ClientUnexpectedSuccess(Exception):
pass


class VerificationMaterials:
"""
A wrapper around verification materials. Materials can be either bundles
Expand Down Expand Up @@ -114,13 +117,15 @@ def __init__(self, entrypoint: str, identity_token: str) -> None:
"""
self.entrypoint = entrypoint
self.identity_token = identity_token
self.completed_process: subprocess.CompletedProcess | None = None

def run(self, *args) -> None:
"""
Execute a command against the Sigstore client.
"""
self.completed_process = None
try:
subprocess.run(
self.completed_process = subprocess.run(
[self.entrypoint, *args],
text=True,
stdout=subprocess.PIPE,
Expand All @@ -130,12 +135,28 @@ def run(self, *args) -> None:
except subprocess.CalledProcessError as cpe:
msg = _CLIENT_ERROR_MSG.format(
exitcode=cpe.returncode,
args=cpe.args,
command=" ".join(map(str, cpe.args)),
stdout=cpe.stdout,
stderr=cpe.stderr,
)
raise ClientFail(msg)

@contextmanager
def raises(self):
try:
yield
except ClientFail:
pass
else:
assert self.completed_process
msg = _CLIENT_ERROR_MSG.format(
exitcode=self.completed_process.returncode,
command=" ".join(map(str, self.completed_process.args)),
stdout=self.completed_process.stdout,
stderr=self.completed_process.stderr,
)
raise ClientUnexpectedSuccess(msg)

@singledispatchmethod
def sign(self, materials: VerificationMaterials, artifact: os.PathLike) -> None:
"""
Expand Down
16 changes: 8 additions & 8 deletions test/test_bundle.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from test.client import BundleMaterials, ClientFail, SigstoreClient
from test.client import BundleMaterials, SigstoreClient
from test.conftest import _MakeMaterialsByType

import pytest # type: ignore
Expand Down Expand Up @@ -29,7 +29,7 @@ def test_verify_rejects_root(
materials: BundleMaterials
input_path, materials = make_materials_by_type("has_root_in_chain.txt", BundleMaterials)

with pytest.raises(ClientFail):
with client.raises():
client.verify(materials, input_path)


Expand Down Expand Up @@ -77,7 +77,7 @@ def test_verify_rejects_staging_cert(
input_path, materials = make_materials_by_type("a.txt", BundleMaterials)
materials.bundle = Path("a.txt.staging.sigstore")

with pytest.raises(ClientFail):
with client.raises():
client.verify(materials, input_path)


Expand All @@ -93,7 +93,7 @@ def test_verify_rejects_invalid_set(
input_path, materials = make_materials_by_type("a.txt", BundleMaterials)
materials.bundle = Path("a.txt.invalid_set.sigstore")

with pytest.raises(ClientFail):
with client.raises():
client.verify(materials, input_path)


Expand All @@ -108,7 +108,7 @@ def test_verify_rejects_invalid_signature(
input_path, materials = make_materials_by_type("a.txt", BundleMaterials)
materials.bundle = Path("a.txt.invalid_signature.sigstore")

with pytest.raises(ClientFail):
with client.raises():
client.verify(materials, input_path)


Expand All @@ -124,7 +124,7 @@ def test_verify_rejects_invalid_key(
input_path, materials = make_materials_by_type("a.txt", BundleMaterials)
materials.bundle = Path("a.txt.invalid_key.sigstore")

with pytest.raises(ClientFail):
with client.raises():
client.verify(materials, input_path)


Expand All @@ -139,7 +139,7 @@ def test_verify_rejects_invalid_inclusion_proof(
input_path, materials = make_materials_by_type("a.txt", BundleMaterials)
materials.bundle = Path("a.txt.invalid_inclusion_proof.sigstore")

with pytest.raises(ClientFail):
with client.raises():
client.verify(materials, input_path)


Expand All @@ -154,5 +154,5 @@ def test_verify_rejects_different_materials(
input_path, materials = make_materials_by_type("b.txt", BundleMaterials)
materials.bundle = Path("a.txt.good.sigstore")

with pytest.raises(ClientFail):
with client.raises():
client.verify(materials, input_path)
4 changes: 2 additions & 2 deletions test/test_certificate_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest # type: ignore

from .client import ClientFail, SignatureCertificateMaterials, SigstoreClient
from .client import SignatureCertificateMaterials, SigstoreClient


def test_verify_invalid_certificate_chain(client: SigstoreClient) -> None:
Expand All @@ -22,5 +22,5 @@ def test_verify_invalid_certificate_chain(client: SigstoreClient) -> None:
materials.certificate = certificate_path
materials.signature = signature_path

with pytest.raises(ClientFail):
with client.raises():
client.verify(materials, artifact_path)
14 changes: 7 additions & 7 deletions test/test_signature_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest # type: ignore

from .client import ClientFail, SignatureCertificateMaterials, SigstoreClient
from .client import SignatureCertificateMaterials, SigstoreClient


@pytest.mark.signing
Expand All @@ -26,7 +26,7 @@ def test_verify_empty(client: SigstoreClient, make_materials: _MakeMaterials) ->
blank_path.touch()

# Verify with an empty artifact.
with pytest.raises(ClientFail):
with client.raises():
client.verify(materials, blank_path)

# Verify with correct inputs.
Expand Down Expand Up @@ -55,7 +55,7 @@ def test_verify_mismatch(client: SigstoreClient, make_materials: _MakeMaterials)
assert b_materials.exists()

# Verify with a mismatching artifact.
with pytest.raises(ClientFail):
with client.raises():
client.verify(a_materials, b_artifact_path)

# Verify with correct inputs.
Expand All @@ -82,31 +82,31 @@ def test_verify_sigcrt(
blank_path.touch()

# Verify with an empty signature.
with pytest.raises(ClientFail):
with client.raises():
blank_sig = SignatureCertificateMaterials()
blank_sig.signature = blank_path
blank_sig.certificate = a_materials.certificate

client.verify(blank_sig, a_artifact_path)

# Verify with an empty certificate.
with pytest.raises(ClientFail):
with client.raises():
blank_crt = SignatureCertificateMaterials()
blank_crt.signature = a_materials.signature
blank_crt.certificate = blank_path

client.verify(blank_crt, a_artifact_path)

# Verify with a mismatching certificate.
with pytest.raises(ClientFail):
with client.raises():
crt_mismatch = SignatureCertificateMaterials()
crt_mismatch.certificate = b_materials.certificate
crt_mismatch.signature = a_materials.signature

client.verify(crt_mismatch, a_artifact_path)

# Verify with a mismatching signature.
with pytest.raises(ClientFail):
with client.raises():
sig_mismatch = SignatureCertificateMaterials()
sig_mismatch.certificate = a_materials.certificate
sig_mismatch.signature = b_materials.signature
Expand Down

0 comments on commit 0f4e883

Please sign in to comment.