Skip to content

Commit ace0dfb

Browse files
independent RekorV2Client
Signed-off-by: Ramon Petgrave <ramon.petgrave64@gmail.com>
1 parent b09afa3 commit ace0dfb

File tree

2 files changed

+112
-50
lines changed

2 files changed

+112
-50
lines changed

sigstore/_internal/rekor/client.py

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import rekor_types
2828
import requests
29+
from sigstore_protobuf_specs.dev.sigstore.rekor import v1 as rekor_v1
2930

3031
from sigstore._internal import USER_AGENT
3132
from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2
@@ -148,25 +149,12 @@ def get(
148149
return LogEntry._from_response(resp.json())
149150

150151
def post(
151-
self,
152-
proposed_entry: rekor_types.Hashedrekord
153-
| rekor_types.Dsse
154-
| v2.CreateEntryRequest,
152+
self, proposed_entry: rekor_types.Hashedrekord | rekor_types.Dsse
155153
) -> LogEntry:
156154
"""
157155
Submit a new entry for inclusion in the Rekor log.
158156
"""
159-
# There may be a bug in betterproto, where the V_0_0_2 is changed to V002.
160-
if isinstance(proposed_entry, v2.CreateEntryRequest):
161-
payload = proposed_entry.to_dict()
162-
if "hashedRekordRequestV002" in payload:
163-
payload["hashedRekordRequestV0_0_2"] = payload.pop(
164-
"hashedRekordRequestV002"
165-
)
166-
if "dsseRequestV002" in payload:
167-
payload["dsseRequestV0_0_2"] = payload.pop("dsseRequestV002")
168-
else:
169-
payload = proposed_entry.model_dump(mode="json", by_alias=True)
157+
payload = proposed_entry.model_dump(mode="json", by_alias=True)
170158
_logger.debug(f"proposed: {json.dumps(payload)}")
171159
resp: requests.Response = self.session.post(self.url, json=payload)
172160
try:
@@ -176,10 +164,7 @@ def post(
176164

177165
integrated_entry = resp.json()
178166
_logger.debug(f"integrated: {integrated_entry}")
179-
if isinstance(proposed_entry, v2.CreateEntryRequest):
180-
return LogEntry._from_dict_rekor(integrated_entry)
181-
else:
182-
return LogEntry._from_response(integrated_entry)
167+
return LogEntry._from_response(integrated_entry)
183168

184169
@property
185170
def retrieve(self) -> RekorEntriesRetrieve:
@@ -236,14 +221,11 @@ def post(
236221
class RekorClient:
237222
"""The internal Rekor client"""
238223

239-
def __init__(
240-
self, url: str, major_api_version: int = REKOR_V1_API_MAJOR_VERSION
241-
) -> None:
224+
def __init__(self, url: str) -> None:
242225
"""
243226
Create a new `RekorClient` from the given URL.
244227
"""
245-
self.url = f"{url}/api/v{major_api_version}"
246-
self.major_api_version = major_api_version
228+
self.url = f"{url}/api/v1"
247229
self.session = requests.Session()
248230
self.session.headers.update(
249231
{
@@ -281,3 +263,71 @@ def log(self) -> RekorLog:
281263
Returns a `RekorLog` adapter for making requests to a Rekor log.
282264
"""
283265
return RekorLog(f"{self.url}/log", session=self.session)
266+
267+
268+
class RekorV2Client:
269+
"""The internal Rekor client for the v2 API"""
270+
271+
# TODO: implement get_tile, get_entry_bundle, get_checkpoint.
272+
273+
def __init__(self, base_url: str) -> None:
274+
"""
275+
Create a new `RekorV2Client` from the given URL.
276+
"""
277+
self.url = f"{base_url}/api/v2"
278+
self.session = requests.Session()
279+
self.session.headers.update(
280+
{
281+
"Content-Type": "application/json",
282+
"Accept": "application/json",
283+
"User-Agent": USER_AGENT,
284+
}
285+
)
286+
287+
def __del__(self) -> None:
288+
"""
289+
Terminates the underlying network session.
290+
"""
291+
self.session.close()
292+
293+
def create_entry(
294+
self, request: v2.CreateEntryRequest
295+
) -> rekor_v1.TransparencyLogEntry:
296+
"""
297+
Submit a new entry for inclusion in the Rekor log.
298+
"""
299+
# There may be a bug in betterproto, where the V_0_0_2 is changed to V002.
300+
payload = request.to_dict()
301+
if "hashedRekordRequestV002" in payload:
302+
payload["hashedRekordRequestV0_0_2"] = payload.pop(
303+
"hashedRekordRequestV002"
304+
)
305+
if "dsseRequestV002" in payload:
306+
payload["dsseRequestV0_0_2"] = payload.pop("dsseRequestV002")
307+
_logger.debug(f"request: {json.dumps(payload)}")
308+
resp = self.session.post(f"{self.url}/log/entries", json=payload)
309+
310+
try:
311+
resp.raise_for_status()
312+
except requests.HTTPError as http_error:
313+
raise RekorClientError(http_error)
314+
315+
integrated_entry = resp.json()
316+
_logger.debug(f"integrated: {integrated_entry}")
317+
return LogEntry._from_dict_rekor(integrated_entry)
318+
319+
@classmethod
320+
def production(cls) -> RekorClient:
321+
"""
322+
Returns a `RekorClient` populated with the default Rekor production instance.
323+
"""
324+
return cls(
325+
DEFAULT_REKOR_URL,
326+
)
327+
328+
@classmethod
329+
def staging(cls) -> RekorClient:
330+
"""
331+
Returns a `RekorClient` populated with the default Rekor staging instance.
332+
"""
333+
return cls(STAGING_REKOR_URL)

sigstore/sign.py

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
from sigstore._internal.rekor.client import (
6565
REKOR_V1_API_MAJOR_VERSION,
6666
RekorClient,
67+
RekorV2Client,
6768
)
6869
from sigstore._internal.rekor_tiles.dev.sigstore.common import v1
6970
from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2
@@ -195,13 +196,19 @@ def _finalize_sign(
195196
self,
196197
cert: x509.Certificate,
197198
content: MessageSignature | dsse.Envelope,
198-
proposed_entry: rekor_types.Hashedrekord | rekor_types.Dsse,
199+
proposed_entry: rekor_types.Hashedrekord
200+
| rekor_types.Dsse
201+
| v2.CreateEntryRequest,
199202
) -> Bundle:
200203
"""
201204
Perform the common "finalizing" steps in a Sigstore signing flow.
202205
"""
203206
# Submit the proposed entry to the transparency log
204-
entry = self._signing_ctx._rekor.log.entries.post(proposed_entry)
207+
client = self._signing_ctx._rekor
208+
if isinstance(client, RekorV2Client):
209+
entry = client.create_entry(proposed_entry)
210+
else:
211+
entry = self._signing_ctx._rekor.log.entries.post(proposed_entry)
205212

206213
_logger.debug(f"Transparency log entry created with index: {entry.log_index}")
207214

@@ -311,24 +318,7 @@ def sign_artifact(
311318
)
312319

313320
# Create the proposed hashedrekord entry
314-
if self._signing_ctx._rekor.major_api_version == REKOR_V1_API_MAJOR_VERSION:
315-
proposed_entry = rekor_types.Hashedrekord(
316-
spec=rekor_types.hashedrekord.HashedrekordV001Schema(
317-
signature=rekor_types.hashedrekord.Signature(
318-
content=base64.b64encode(artifact_signature).decode(),
319-
public_key=rekor_types.hashedrekord.PublicKey(
320-
content=b64_cert.decode()
321-
),
322-
),
323-
data=rekor_types.hashedrekord.Data(
324-
hash=rekor_types.hashedrekord.Hash(
325-
algorithm=hashed_input._as_hashedrekord_algorithm(),
326-
value=hashed_input.digest.hex(),
327-
)
328-
),
329-
),
330-
)
331-
else:
321+
if isinstance(self._signing_ctx._rekor, RekorV2Client):
332322
proposed_entry = v2.CreateEntryRequest(
333323
hashed_rekord_request_v0_0_2=v2.HashedRekordRequestV002(
334324
digest=hashed_input.digest,
@@ -346,7 +336,23 @@ def sign_artifact(
346336
),
347337
)
348338
)
349-
339+
else:
340+
proposed_entry = rekor_types.Hashedrekord(
341+
spec=rekor_types.hashedrekord.HashedrekordV001Schema(
342+
signature=rekor_types.hashedrekord.Signature(
343+
content=base64.b64encode(artifact_signature).decode(),
344+
public_key=rekor_types.hashedrekord.PublicKey(
345+
content=b64_cert.decode()
346+
),
347+
),
348+
data=rekor_types.hashedrekord.Data(
349+
hash=rekor_types.hashedrekord.Hash(
350+
algorithm=hashed_input._as_hashedrekord_algorithm(),
351+
value=hashed_input.digest.hex(),
352+
)
353+
),
354+
),
355+
)
350356
return self._finalize_sign(cert, content, proposed_entry)
351357

352358

@@ -359,7 +365,7 @@ def __init__(
359365
self,
360366
*,
361367
fulcio: FulcioClient,
362-
rekor: RekorClient,
368+
rekor: RekorClient | RekorV2Client,
363369
trusted_root: TrustedRoot,
364370
tsa_clients: list[TimestampAuthorityClient] | None = None,
365371
):
@@ -407,12 +413,18 @@ def _from_trust_config(cls, trust_config: ClientTrustConfig) -> SigningContext:
407413
@api private
408414
"""
409415
signing_config = trust_config.signing_config
416+
if (
417+
signing_config._inner.rekor_tlog_urls[0].major_api_version
418+
== REKOR_V1_API_MAJOR_VERSION
419+
):
420+
rekor_client = RekorClient(
421+
signing_config.get_tlog_urls()[0],
422+
)
423+
else:
424+
rekor_client = RekorV2Client(signing_config.get_tlog_urls()[0])
410425
return cls(
411426
fulcio=FulcioClient(signing_config.get_fulcio_url()),
412-
rekor=RekorClient(
413-
signing_config.get_tlog_urls()[0],
414-
signing_config._inner.rekor_tlog_urls[0].major_api_version,
415-
),
427+
rekor=rekor_client,
416428
trusted_root=trust_config.trusted_root,
417429
tsa_clients=[
418430
TimestampAuthorityClient(url) for url in signing_config.get_tsa_urls()

0 commit comments

Comments
 (0)