From 74fdbfd6e5b66e505530b9ba4631d20b46093fbe Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Tue, 22 Jul 2025 11:31:37 +0300 Subject: [PATCH 1/3] cli: Add --rekor-version to sign arguments This should not be needed... but it could be handy if * SigningConfig already contains rekor v2 * user for some reason does not want rekor v2 entries in the bundle This option only does anything if there are multiple Rekor versions listed in SigningConfig. The test is changed since the "ANY" selector is now considered to not be an error if there are 0 services: * This is not a problem since for both TSAs and tlogs we have a check that there is at least one service * This improves the error message when --rekor-version is used with a version that is not found in signingconfig Signed-off-by: Jussi Kukkonen --- sigstore/_cli.py | 21 ++++++++++++++++++--- sigstore/_internal/trust.py | 21 +++++++++++++++++---- test/unit/internal/test_trust.py | 5 ----- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index 4ed1219b7..51bbe0a9f 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -288,6 +288,11 @@ def _parser() -> argparse.ArgumentParser: formatter_class=argparse.ArgumentDefaultsHelpFormatter, parents=[parent_parser], ) + attest.add_argument( + "--rekor-version", + type=int, + help="Force the rekor transparency log version (in case there are multiple in signing configuration)", + ) attest.add_argument( "files", metavar="FILE", @@ -348,6 +353,11 @@ def _parser() -> argparse.ArgumentParser: formatter_class=argparse.ArgumentDefaultsHelpFormatter, parents=[parent_parser], ) + sign.add_argument( + "--rekor-version", + type=int, + help="Force the rekor transparency log version (in case there are multiple in signing configuration)", + ) oidc_options = sign.add_argument_group("OpenID Connect options") oidc_options.add_argument( @@ -1195,11 +1205,16 @@ def _get_trust_config(args: argparse.Namespace) -> ClientTrustConfig: offline = getattr(args, "offline", False) if args.trust_config: - return ClientTrustConfig.from_json(args.trust_config.read_text()) + trust_config = ClientTrustConfig.from_json(args.trust_config.read_text()) elif args.staging: - return ClientTrustConfig.staging(offline=offline) + trust_config = ClientTrustConfig.staging(offline=offline) else: - return ClientTrustConfig.production(offline=offline) + trust_config = ClientTrustConfig.production(offline=offline) + + # Enforce rekor version if --rekor-version is used + trust_config.force_tlog_version = getattr(args, "rekor_version", None) + + return trust_config def _get_identity( diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index d335d87ed..a69d1c173 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -335,10 +335,13 @@ def __str__(self) -> str: """Returns the variant's string value.""" return self.value - def __init__(self, inner: _SigningConfig): + def __init__(self, inner: _SigningConfig, tlog_version: int | None = None): """ Construct a new `SigningConfig`. + tlog_version is an optional argument that enforces that only specified + versions of rekor are included in the transparency logs. + @api private """ self._inner = inner @@ -351,8 +354,13 @@ def __init__(self, inner: _SigningConfig): # Create lists of service protos that are valid, selected by the service # configuration & supported by this client + if tlog_version is None: + tlog_versions = REKOR_VERSIONS + else: + tlog_versions = [tlog_version] + self._tlogs = self._get_valid_services( - self._inner.rekor_tlog_urls, REKOR_VERSIONS, self._inner.rekor_tlog_config + self._inner.rekor_tlog_urls, tlog_versions, self._inner.rekor_tlog_config ) if not self._tlogs: raise Error("No valid Rekor transparency log found in signing config") @@ -415,7 +423,7 @@ def _get_valid_services( # handle EXACT and ANY selectors count = config.count if config.selector == ServiceSelector.EXACT else 1 - if len(result) < count: + if config.selector == ServiceSelector.EXACT and len(result) < count: raise ValueError( f"Expected {count} services in signing config, found {len(result)}" ) @@ -655,6 +663,9 @@ def __init__(self, inner: _ClientTrustConfig) -> None: self._inner = inner self._verify() + # This can be used to enforce a specific rekor major version in signingconfig + self.force_tlog_version: int | None = None + def _verify(self) -> None: """ Performs various feats of heroism to ensure that the client trust config @@ -681,4 +692,6 @@ def signing_config(self) -> SigningConfig: """ Return the interior root of trust, as a `SigningConfig`. """ - return SigningConfig(self._inner.signing_config) + return SigningConfig( + self._inner.signing_config, tlog_version=self.force_tlog_version + ) diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index 4eef7f68c..db7e76579 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -188,11 +188,6 @@ def test_get_valid_services(self, services, versions, config, expected_result): @pytest.mark.parametrize( "services, versions, config", [ - ( # ANY selector without services - [], - [1], - ServiceConfiguration(ServiceSelector.ANY), - ), ( # EXACT selector without enough services [_service_v1_op1], [1], From 3c7fa400ee308ee8ed5fad681ce99e0b4e641a68 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Tue, 22 Jul 2025 14:52:27 +0300 Subject: [PATCH 2/3] README: Update help output Signed-off-by: Jussi Kukkonen --- README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b77e8c282..c416472e0 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,8 @@ optional arguments: ``` -usage: sigstore sign [-h] [-v] [--identity-token TOKEN] [--oidc-client-id ID] +usage: sigstore sign [-h] [-v] [--rekor-version REKOR_VERSION] + [--identity-token TOKEN] [--oidc-client-id ID] [--oidc-client-secret SECRET] [--oidc-disable-ambient-providers] [--oidc-issuer URL] [--oauth-force-oob] [--no-default-files] @@ -109,6 +110,10 @@ optional arguments: -h, --help show this help message and exit -v, --verbose run with additional debug logging; supply multiple times to increase verbosity (default: 0) + --rekor-version REKOR_VERSION + Force the rekor transparency log version (in case + there are multiple in signing configuration) (default: + None) OpenID Connect options: --identity-token TOKEN @@ -151,9 +156,9 @@ Output options: ``` -usage: sigstore attest [-h] [-v] --predicate FILE --predicate-type TYPE - [--identity-token TOKEN] [--oidc-client-id ID] - [--oidc-client-secret SECRET] +usage: sigstore attest [-h] [-v] [--rekor-version REKOR_VERSION] --predicate + FILE --predicate-type TYPE [--identity-token TOKEN] + [--oidc-client-id ID] [--oidc-client-secret SECRET] [--oidc-disable-ambient-providers] [--oidc-issuer URL] [--oauth-force-oob] [--bundle FILE] [--overwrite] FILE [FILE ...] @@ -165,6 +170,10 @@ optional arguments: -h, --help show this help message and exit -v, --verbose run with additional debug logging; supply multiple times to increase verbosity (default: 0) + --rekor-version REKOR_VERSION + Force the rekor transparency log version (in case + there are multiple in signing configuration) (default: + None) DSSE options: --predicate FILE Path to the predicate file (default: None) From dc213769b48df848250d8b396d602833ffbe5ec6 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Wed, 23 Jul 2025 16:41:09 +0300 Subject: [PATCH 3/3] cli: Improve help output for --rekor-version Avoid saying "default: None", mention the valid values instead. Signed-off-by: Jussi Kukkonen --- README.md | 22 +++++++++++----------- sigstore/_cli.py | 8 ++++++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c416472e0..8fcf16fbf 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ optional arguments: ``` -usage: sigstore sign [-h] [-v] [--rekor-version REKOR_VERSION] +usage: sigstore sign [-h] [-v] [--rekor-version VERSION] [--identity-token TOKEN] [--oidc-client-id ID] [--oidc-client-secret SECRET] [--oidc-disable-ambient-providers] [--oidc-issuer URL] @@ -110,10 +110,10 @@ optional arguments: -h, --help show this help message and exit -v, --verbose run with additional debug logging; supply multiple times to increase verbosity (default: 0) - --rekor-version REKOR_VERSION - Force the rekor transparency log version (in case - there are multiple in signing configuration) (default: - None) + --rekor-version VERSION + Force the rekor transparency log version. Valid values + are [1, 2]. By default the highest available version + is used OpenID Connect options: --identity-token TOKEN @@ -156,8 +156,8 @@ Output options: ``` -usage: sigstore attest [-h] [-v] [--rekor-version REKOR_VERSION] --predicate - FILE --predicate-type TYPE [--identity-token TOKEN] +usage: sigstore attest [-h] [-v] [--rekor-version VERSION] --predicate FILE + --predicate-type TYPE [--identity-token TOKEN] [--oidc-client-id ID] [--oidc-client-secret SECRET] [--oidc-disable-ambient-providers] [--oidc-issuer URL] [--oauth-force-oob] [--bundle FILE] [--overwrite] @@ -170,10 +170,10 @@ optional arguments: -h, --help show this help message and exit -v, --verbose run with additional debug logging; supply multiple times to increase verbosity (default: 0) - --rekor-version REKOR_VERSION - Force the rekor transparency log version (in case - there are multiple in signing configuration) (default: - None) + --rekor-version VERSION + Force the rekor transparency log version. Valid values + are [1, 2]. By default the highest available version + is used DSSE options: --predicate FILE Path to the predicate file (default: None) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index 51bbe0a9f..fc7dae376 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -291,7 +291,9 @@ def _parser() -> argparse.ArgumentParser: attest.add_argument( "--rekor-version", type=int, - help="Force the rekor transparency log version (in case there are multiple in signing configuration)", + metavar="VERSION", + default=argparse.SUPPRESS, + help="Force the rekor transparency log version. Valid values are [1, 2]. By default the highest available version is used", ) attest.add_argument( "files", @@ -356,7 +358,9 @@ def _parser() -> argparse.ArgumentParser: sign.add_argument( "--rekor-version", type=int, - help="Force the rekor transparency log version (in case there are multiple in signing configuration)", + metavar="VERSION", + default=argparse.SUPPRESS, + help="Force the rekor transparency log version. Valid values are [1, 2]. By default the highest available version is used", ) oidc_options = sign.add_argument_group("OpenID Connect options")