diff --git a/README.md b/README.md index b77e8c28..8fcf16fb 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 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 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 @@ -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 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 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 d63cbd1d..a02ee24a 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -286,6 +286,13 @@ def _parser() -> argparse.ArgumentParser: formatter_class=argparse.ArgumentDefaultsHelpFormatter, parents=[parent_parser], ) + attest.add_argument( + "--rekor-version", + type=int, + 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", metavar="FILE", @@ -346,6 +353,13 @@ def _parser() -> argparse.ArgumentParser: formatter_class=argparse.ArgumentDefaultsHelpFormatter, parents=[parent_parser], ) + sign.add_argument( + "--rekor-version", + type=int, + 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") oidc_options.add_argument( @@ -1193,11 +1207,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 e4da68de..2a9d1125 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -315,10 +315,15 @@ def __str__(self) -> str: """Returns the variant's string value.""" return self.value - def __init__(self, inner: trustroot_v1.SigningConfig): + def __init__( + self, inner: trustroot_v1.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 @@ -331,8 +336,13 @@ def __init__(self, inner: trustroot_v1.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") @@ -396,7 +406,11 @@ def _get_valid_services( if config.selector == trustroot_v1.ServiceSelector.EXACT and config.count else 1 ) - if len(result) < count: + + if ( + config.selector == trustroot_v1.ServiceSelector.EXACT + and len(result) < count + ): raise ValueError( f"Expected {count} services in signing config, found {len(result)}" ) @@ -633,6 +647,9 @@ def __init__(self, inner: trustroot_v1.ClientTrustConfig) -> None: """ self._inner = inner + # This can be used to enforce a specific rekor major version in signingconfig + self.force_tlog_version: int | None = None + @property def trusted_root(self) -> TrustedRoot: """ @@ -645,4 +662,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 26b7278e..ff408ae9 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -189,11 +189,6 @@ def test_get_valid_services(self, services, versions, config, expected_result): @pytest.mark.parametrize( "services, versions, config", [ - ( # ANY selector without services - [], - [1], - ServiceConfiguration(selector=ServiceSelector.ANY), - ), ( # EXACT selector without enough services [_service_v1_op1], [1],