Skip to content

Commit ea890a0

Browse files
ramonpetgrave64jku
authored andcommitted
Sign multiple artifacts in threads
This is (especially) useful with rekor 2 where the service only responds after log inclusion: We would prefer to get all signatures in the same inclusion batch. The change still affects both rekor v1 and rekor v2. This commit is a rebase of a bunch of Ramons commits. Signed-off-by: Ramon Petgrave <[email protected]> Signed-off-by: Jussi Kukkonen <[email protected]>
1 parent b7d2138 commit ea890a0

File tree

1 file changed

+66
-56
lines changed

1 file changed

+66
-56
lines changed

sigstore/_cli.py

Lines changed: 66 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import logging
2121
import os
2222
import sys
23+
from concurrent import futures
2324
from dataclasses import dataclass
2425
from pathlib import Path
2526
from typing import Any, NoReturn, TextIO, Union
@@ -667,63 +668,72 @@ def _sign_common(
667668
_invalid_arguments(args, "No identity token supplied or detected!")
668669

669670
with signing_ctx.signer(identity) as signer:
670-
for file, outputs in output_map.items():
671-
_logger.debug(f"signing for {file.name}")
672-
with file.open(mode="rb") as io:
673-
# The input can be indefinitely large, so we perform a streaming
674-
# digest and sign the prehash rather than buffering it fully.
675-
digest = sha256_digest(io)
676-
try:
677-
if predicate is None:
678-
result = signer.sign_artifact(input_=digest)
679-
else:
680-
subject = Subject(
681-
name=file.name, digest={"sha256": digest.digest.hex()}
682-
)
683-
predicate_type = args.predicate_type
684-
statement_builder = StatementBuilder(
685-
subjects=[subject],
686-
predicate_type=predicate_type,
687-
predicate=predicate,
688-
)
689-
result = signer.sign_dsse(statement_builder.build())
690-
except ExpiredIdentity as exp_identity:
691-
print("Signature failed: identity token has expired")
692-
raise exp_identity
693-
694-
except ExpiredCertificate as exp_certificate:
695-
print("Signature failed: Fulcio signing certificate has expired")
696-
raise exp_certificate
697-
698-
print("Using ephemeral certificate:")
699-
cert = result.signing_certificate
700-
cert_pem = cert.public_bytes(Encoding.PEM).decode()
701-
print(cert_pem)
702-
703-
print(
704-
f"Transparency log entry created at index: {result.log_entry.log_index}"
705-
)
671+
with futures.ThreadPoolExecutor(max_workers=10) as executor:
672+
673+
def _sign_file(file: Path, outputs: SigningOutputs) -> None:
674+
_logger.debug(f"signing for {file.name}")
675+
with file.open(mode="rb") as io:
676+
# The input can be indefinitely large, so we perform a streaming
677+
# digest and sign the prehash rather than buffering it fully.
678+
digest = sha256_digest(io)
679+
try:
680+
if predicate is None:
681+
result = signer.sign_artifact(input_=digest)
682+
else:
683+
subject = Subject(
684+
name=file.name, digest={"sha256": digest.digest.hex()}
685+
)
686+
predicate_type = args.predicate_type
687+
statement_builder = StatementBuilder(
688+
subjects=[subject],
689+
predicate_type=predicate_type,
690+
predicate=predicate,
691+
)
692+
result = signer.sign_dsse(statement_builder.build())
693+
except ExpiredIdentity as exp_identity:
694+
print("Signature failed: identity token has expired")
695+
raise exp_identity
696+
697+
except ExpiredCertificate as exp_certificate:
698+
print("Signature failed: Fulcio signing certificate has expired")
699+
raise exp_certificate
700+
701+
print("Using ephemeral certificate:")
702+
cert = result.signing_certificate
703+
cert_pem = cert.public_bytes(Encoding.PEM).decode()
704+
print(cert_pem)
705+
706+
print(
707+
f"Transparency log entry created at index: {result.log_entry.log_index}"
708+
)
706709

707-
sig_output: TextIO
708-
if outputs.signature is not None:
709-
sig_output = outputs.signature.open("w")
710-
else:
711-
sig_output = sys.stdout
712-
713-
signature = base64.b64encode(result.signature).decode()
714-
print(signature, file=sig_output)
715-
if outputs.signature is not None:
716-
print(f"Signature written to {outputs.signature}")
717-
718-
if outputs.certificate is not None:
719-
with outputs.certificate.open(mode="w") as io:
720-
print(cert_pem, file=io)
721-
print(f"Certificate written to {outputs.certificate}")
722-
723-
if outputs.bundle is not None:
724-
with outputs.bundle.open(mode="w") as io:
725-
print(result.to_json(), file=io)
726-
print(f"Sigstore bundle written to {outputs.bundle}")
710+
sig_output: TextIO
711+
if outputs.signature is not None:
712+
sig_output = outputs.signature.open("w")
713+
else:
714+
sig_output = sys.stdout
715+
716+
signature = base64.b64encode(result.signature).decode()
717+
print(signature, file=sig_output)
718+
if outputs.signature is not None:
719+
print(f"Signature written to {outputs.signature}")
720+
721+
if outputs.certificate is not None:
722+
with outputs.certificate.open(mode="w") as io:
723+
print(cert_pem, file=io)
724+
print(f"Certificate written to {outputs.certificate}")
725+
726+
if outputs.bundle is not None:
727+
with outputs.bundle.open(mode="w") as io:
728+
print(result.to_json(), file=io)
729+
print(f"Sigstore bundle written to {outputs.bundle}")
730+
731+
jobs = [
732+
executor.submit(_sign_file, file, outputs)
733+
for file, outputs in output_map.items()
734+
]
735+
for job in futures.as_completed(jobs):
736+
job.result()
727737

728738

729739
def _attest(args: argparse.Namespace) -> None:

0 commit comments

Comments
 (0)