Skip to content

Commit 1bb9029

Browse files
committed
Use TrustConfig to initialize components
This change makes almost all code paths now use TrustConfig to choose the sigstore instance (urls, keys, validity periods, etc). * OIDC url now comes from signingconfig too * Some production()/staging() methods remain because they're used by tests or special cases like "fix-bundle" * Likewise some hard coded urls are left in the code since they are used by some special case Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
1 parent 91bebba commit 1bb9029

File tree

8 files changed

+42
-128
lines changed

8 files changed

+42
-128
lines changed

sigstore/_cli.py

Lines changed: 31 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from sigstore._internal.fulcio.client import ExpiredCertificate
4040
from sigstore._internal.rekor import _hashedrekord_from_parts
4141
from sigstore._internal.rekor.client import RekorClient
42-
from sigstore._internal.trust import ClientTrustConfig, TrustedRoot
42+
from sigstore._internal.trust import ClientTrustConfig
4343
from sigstore._utils import sha256_digest
4444
from sigstore.dsse import StatementBuilder, Subject
4545
from sigstore.dsse._predicate import (
@@ -51,7 +51,6 @@
5151
from sigstore.hashes import Hashed
5252
from sigstore.models import Bundle, InvalidBundle
5353
from sigstore.oidc import (
54-
DEFAULT_OAUTH_ISSUER_URL,
5554
ExpiredIdentity,
5655
IdentityToken,
5756
Issuer,
@@ -229,8 +228,8 @@ def _add_shared_oidc_options(
229228
"--oidc-issuer",
230229
metavar="URL",
231230
type=str,
232-
default=os.getenv("SIGSTORE_OIDC_ISSUER", DEFAULT_OAUTH_ISSUER_URL),
233-
help="The OpenID Connect issuer to use (conflicts with --staging)",
231+
default=os.getenv("SIGSTORE_OIDC_ISSUER", None),
232+
help="The OpenID Connect issuer to use",
234233
)
235234
group.add_argument(
236235
"--oauth-force-oob",
@@ -630,11 +629,7 @@ def _get_identity_token(args: argparse.Namespace) -> None:
630629
"""
631630
Output the OIDC authentication token
632631
"""
633-
634-
trust_config: ClientTrustConfig | None = None
635-
if args.trust_config:
636-
trust_config = ClientTrustConfig.from_json(args.trust_config.read_text())
637-
identity = _get_identity(args, trust_config)
632+
identity = _get_identity(args, _get_trust_config(args))
638633
if identity:
639634
print(identity)
640635
else:
@@ -654,19 +649,8 @@ def _sign_common(
654649
not, it will use a hashedrekord.
655650
"""
656651
# Select the signing context to use.
657-
if args.trust_config:
658-
trust_config = ClientTrustConfig.from_json(args.trust_config.read_text())
659-
signing_ctx = SigningContext._from_trust_config(trust_config)
660-
elif args.staging:
661-
_logger.debug("sign: staging instances requested")
662-
trust_config = None # signingconfig 0.2 is not in staging TUF yet
663-
signing_ctx = SigningContext.staging()
664-
else:
665-
# If the user didn't request the staging instance or pass in an
666-
# explicit client trust config, we're using the public good (i.e.
667-
# production) instance.
668-
trust_config = None # signingconfig 0.2 is not in staging TUF yet
669-
signing_ctx = SigningContext.production()
652+
trust_config = _get_trust_config(args)
653+
signing_ctx = SigningContext.from_trust_config(trust_config)
670654

671655
# The order of precedence for identities is as follows:
672656
#
@@ -1022,14 +1006,8 @@ def _collect_verification_state(
10221006
f"Missing verification materials for {(hashed)}: {', '.join(missing)}",
10231007
)
10241008

1025-
if args.staging:
1026-
_logger.debug("verify: staging instances requested")
1027-
verifier = Verifier.staging(offline=args.offline)
1028-
elif args.trust_config:
1029-
trust_config = ClientTrustConfig.from_json(args.trust_config.read_text())
1030-
verifier = Verifier._from_trust_config(trust_config)
1031-
else:
1032-
verifier = Verifier.production(offline=args.offline)
1009+
trust_config = _get_trust_config(args)
1010+
verifier = Verifier(trusted_root=trust_config.trusted_root)
10331011

10341012
all_materials = []
10351013
for file_or_hashed, materials in input_map.items():
@@ -1180,8 +1158,23 @@ def _verify_common(
11801158
return None
11811159

11821160

1161+
def _get_trust_config(args: argparse.Namespace) -> ClientTrustConfig:
1162+
"""
1163+
Return the client trust configuration (Sigstore service URLs, key material and lifetimes)
1164+
1165+
The configuration may come from explicit argument (--trust-config) or from the TUF
1166+
repository of the used Sigstore instance.
1167+
"""
1168+
if args.trust_config:
1169+
return ClientTrustConfig.from_json(args.trust_config.read_text())
1170+
elif args.staging:
1171+
return ClientTrustConfig.staging(offline=args.offline)
1172+
else:
1173+
return ClientTrustConfig.production(offline=args.offline)
1174+
1175+
11831176
def _get_identity(
1184-
args: argparse.Namespace, trust_config: ClientTrustConfig | None
1177+
args: argparse.Namespace, trust_config: ClientTrustConfig
11851178
) -> Optional[IdentityToken]:
11861179
token = None
11871180
if not args.oidc_disable_ambient_providers:
@@ -1191,14 +1184,10 @@ def _get_identity(
11911184
if token:
11921185
return IdentityToken(token)
11931186

1194-
if trust_config is not None:
1195-
issuer = Issuer.from_trust_config(trust_config)
1196-
if args.staging:
1197-
issuer = Issuer.staging()
1198-
elif args.oidc_issuer == DEFAULT_OAUTH_ISSUER_URL:
1199-
issuer = Issuer.production()
1200-
else:
1187+
if args.oidc_issuer is not None:
12011188
issuer = Issuer(args.oidc_issuer)
1189+
else:
1190+
issuer = Issuer.from_trust_config(trust_config)
12021191

12031192
if args.oidc_client_secret is None:
12041193
args.oidc_client_secret = "" # nosec: B105
@@ -1215,6 +1204,7 @@ def _get_identity(
12151204
def _fix_bundle(args: argparse.Namespace) -> None:
12161205
# NOTE: We could support `--trusted-root` here in the future,
12171206
# for custom Rekor instances.
1207+
12181208
rekor = RekorClient.staging() if args.staging else RekorClient.production()
12191209

12201210
raw_bundle = RawBundle.from_dict(json.loads(args.bundle.read_bytes()))
@@ -1251,13 +1241,10 @@ def _fix_bundle(args: argparse.Namespace) -> None:
12511241

12521242

12531243
def _update_trust_root(args: argparse.Namespace) -> None:
1254-
# Simply creating the TrustedRoot in online mode is enough to perform
1244+
# Simply creating the TrustConfig in online mode is enough to perform
12551245
# a metadata update.
1256-
if args.staging:
1257-
trusted_root = TrustedRoot.staging(offline=False)
1258-
else:
1259-
trusted_root = TrustedRoot.production(offline=False)
12601246

1247+
config = _get_trust_config(args)
12611248
_console.print(
1262-
f"Trust root updated: {len(trusted_root.get_fulcio_certs())} Fulcio certificates"
1249+
f"Trust root & signing config updated: {len(config.trusted_root.get_fulcio_certs())} Fulcio certificates"
12631250
)

sigstore/_internal/fulcio/client.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@
3939

4040
_logger = logging.getLogger(__name__)
4141

42-
DEFAULT_FULCIO_URL = "https://fulcio.sigstore.dev"
43-
STAGING_FULCIO_URL = "https://fulcio.sigstage.dev"
4442
SIGNING_CERT_ENDPOINT = "/api/v2/signingCert"
4543
TRUST_BUNDLE_ENDPOINT = "/api/v2/trustBundle"
4644

@@ -163,7 +161,7 @@ def get(self) -> FulcioTrustBundleResponse:
163161
class FulcioClient:
164162
"""The internal Fulcio client"""
165163

166-
def __init__(self, url: str = DEFAULT_FULCIO_URL) -> None:
164+
def __init__(self, url: str) -> None:
167165
"""Initialize the client"""
168166
_logger.debug(f"Fulcio client using URL: {url}")
169167
self.url = url
@@ -180,20 +178,6 @@ def __del__(self) -> None:
180178
"""
181179
self.session.close()
182180

183-
@classmethod
184-
def production(cls) -> FulcioClient:
185-
"""
186-
Returns a `FulcioClient` for the Sigstore production instance of Fulcio.
187-
"""
188-
return cls(DEFAULT_FULCIO_URL)
189-
190-
@classmethod
191-
def staging(cls) -> FulcioClient:
192-
"""
193-
Returns a `FulcioClient` for the Sigstore staging instance of Fulcio.
194-
"""
195-
return cls(STAGING_FULCIO_URL)
196-
197181
@property
198182
def signing_cert(self) -> FulcioSigningCert:
199183
"""

sigstore/_internal/trust.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -611,10 +611,12 @@ def from_tuf(
611611
else:
612612
raise e
613613

614-
return _ClientTrustConfig(
615-
ClientTrustConfig.ClientTrustConfigType.CONFIG_0_1,
616-
inner_tr,
617-
inner_sc,
614+
return cls(
615+
_ClientTrustConfig(
616+
ClientTrustConfig.ClientTrustConfigType.CONFIG_0_1,
617+
inner_tr,
618+
inner_sc,
619+
)
618620
)
619621

620622
def __init__(self, inner: _ClientTrustConfig) -> None:

sigstore/oidc.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@
3535
from sigstore._internal.trust import ClientTrustConfig
3636
from sigstore.errors import Error, NetworkError
3737

38-
DEFAULT_OAUTH_ISSUER_URL = "https://oauth2.sigstore.dev/auth"
39-
STAGING_OAUTH_ISSUER_URL = "https://oauth2.sigstage.dev/auth"
40-
4138
# See: https://github.com/sigstore/fulcio/blob/b2186c0/pkg/config/config.go#L182-L201
4239
_KNOWN_OIDC_ISSUERS = {
4340
"https://accounts.google.com": "email",
@@ -272,20 +269,6 @@ def __init__(self, base_url: str) -> None:
272269
except ValueError as exc:
273270
raise IssuerError(f"OIDC issuer returned invalid configuration: {exc}")
274271

275-
@classmethod
276-
def production(cls) -> Issuer:
277-
"""
278-
Returns an `Issuer` configured against Sigstore's production-level services.
279-
"""
280-
return cls(DEFAULT_OAUTH_ISSUER_URL)
281-
282-
@classmethod
283-
def staging(cls) -> Issuer:
284-
"""
285-
Returns an `Issuer` configured against Sigstore's staging-level services.
286-
"""
287-
return cls(STAGING_OAUTH_ISSUER_URL)
288-
289272
@classmethod
290273
def from_trust_config(cls, trust_config: ClientTrustConfig) -> Issuer:
291274
"""

sigstore/sign.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -324,29 +324,7 @@ def __init__(
324324
self._tsa_clients = tsa_clients or []
325325

326326
@classmethod
327-
def production(cls) -> SigningContext:
328-
"""
329-
Return a `SigningContext` instance configured against Sigstore's production-level services.
330-
"""
331-
return cls(
332-
fulcio=FulcioClient.production(),
333-
rekor=RekorClient.production(),
334-
trusted_root=TrustedRoot.production(),
335-
)
336-
337-
@classmethod
338-
def staging(cls) -> SigningContext:
339-
"""
340-
Return a `SignerContext` instance configured against Sigstore's staging-level services.
341-
"""
342-
return cls(
343-
fulcio=FulcioClient.staging(),
344-
rekor=RekorClient.staging(),
345-
trusted_root=TrustedRoot.staging(),
346-
)
347-
348-
@classmethod
349-
def _from_trust_config(cls, trust_config: ClientTrustConfig) -> SigningContext:
327+
def from_trust_config(cls, trust_config: ClientTrustConfig) -> SigningContext:
350328
"""
351329
Create a `SigningContext` from the given `ClientTrustConfig`.
352330

sigstore/verify/verifier.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
verify_sct,
4747
)
4848
from sigstore._internal.timestamp import TimestampSource, TimestampVerificationResult
49-
from sigstore._internal.trust import ClientTrustConfig, KeyringPurpose, TrustedRoot
49+
from sigstore._internal.trust import KeyringPurpose, TrustedRoot
5050
from sigstore._utils import base64_encode_pem_cert, sha256_digest
5151
from sigstore.errors import VerificationError
5252
from sigstore.hashes import Hashed
@@ -113,17 +113,6 @@ def staging(cls, *, offline: bool = False) -> Verifier:
113113
trusted_root=TrustedRoot.staging(offline=offline),
114114
)
115115

116-
@classmethod
117-
def _from_trust_config(cls, trust_config: ClientTrustConfig) -> Verifier:
118-
"""
119-
Create a `Verifier` from the given `ClientTrustConfig`.
120-
121-
@api private
122-
"""
123-
return cls(
124-
trusted_root=trust_config.trusted_root,
125-
)
126-
127116
def _verify_signed_timestamp(
128117
self, timestamp_response: TimeStampResponse, signature: bytes
129118
) -> TimestampVerificationResult | None:

test/unit/internal/oidc/test_issuer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ def test_get_identity_token_bad_code(monkeypatch):
3333
monkeypatch.setattr("builtins.input", lambda _: "hunter2")
3434

3535
with pytest.raises(IdentityError, match=r"^Token request failed with .+$"):
36-
Issuer.staging().identity_token(force_oob=True)
36+
Issuer("https://oauth2.sigstage.dev/auth").identity_token(force_oob=True)

test/unit/test_sign.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,6 @@
2929
from sigstore.verify.policy import UnsafeNoOp
3030

3131

32-
class TestSigningContext:
33-
@pytest.mark.production
34-
def test_production(self):
35-
assert SigningContext.production() is not None
36-
37-
def test_staging(self, mock_staging_tuf):
38-
assert SigningContext.staging() is not None
39-
40-
4132
@pytest.mark.parametrize("env", ["staging", "production"])
4233
@pytest.mark.ambient_oidc
4334
def test_sign_rekor_entry_consistent(sign_ctx_and_ident_for_env):
@@ -185,7 +176,7 @@ def sig_ctx(self, asset, tsa_url) -> SigningContext:
185176

186177
trust_config._inner.signing_config.tsa_urls[0] = tsa_url
187178

188-
return SigningContext._from_trust_config(trust_config)
179+
return SigningContext.from_trust_config(trust_config)
189180

190181
@pytest.fixture
191182
def identity(self, staging):

0 commit comments

Comments
 (0)