Skip to content

Commit dc943b5

Browse files
committed
Move attestation processing to helper function
1 parent d119a50 commit dc943b5

File tree

1 file changed

+89
-73
lines changed

1 file changed

+89
-73
lines changed

warehouse/forklift/legacy.py

Lines changed: 89 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,92 @@ def _is_duplicate_file(db_session, filename, hashes):
371371
return None
372372

373373

374+
def _process_attestations(request, metrics, artifact_path: Path):
375+
"""
376+
Process any attestations included in a file upload request
377+
378+
Attestations, if present, will be parsed and verified against the uploaded
379+
artifact. Attestations are only allowed when uploading via a Trusted
380+
Publisher, because a Trusted Publisher provides the identity that will be
381+
used to verify the attestations.
382+
Currently, only GitHub Actions Trusted Publishers are supported, and
383+
attestations are discarded after verification.
384+
"""
385+
386+
# Check that if the file was uploaded with attestations, verification
387+
# passes
388+
if "attestations" in request.POST:
389+
publisher = request.oidc_publisher
390+
if not publisher or not publisher.publisher_name == "GitHub":
391+
raise _exc_with_message(
392+
HTTPBadRequest,
393+
"Attestations are currently only supported when using Trusted "
394+
"Publishing with GitHub Actions.",
395+
)
396+
try:
397+
attestations = TypeAdapter(list[Attestation]).validate_json(
398+
request.POST["attestations"]
399+
)
400+
except ValidationError as e:
401+
# Log invalid (malformed) attestation upload
402+
metrics.increment("warehouse.upload.attestations.malformed")
403+
raise _exc_with_message(
404+
HTTPBadRequest,
405+
f"Error while decoding the included attestation: {e}",
406+
)
407+
408+
if len(attestations) > 1:
409+
metrics.increment(
410+
"warehouse.upload.attestations." "failed_multiple_attestations"
411+
)
412+
raise _exc_with_message(
413+
HTTPBadRequest,
414+
"Only a single attestation per-file is supported at the moment.",
415+
)
416+
417+
verification_policy = publisher.publisher_verification_policy(
418+
request.oidc_claims
419+
)
420+
for attestation_model in attestations:
421+
try:
422+
# For now, attestations are not stored, just verified
423+
predicate_type, _ = attestation_model.verify(
424+
Verifier.production(),
425+
verification_policy,
426+
artifact_path,
427+
)
428+
except VerificationError as e:
429+
# Log invalid (failed verification) attestation upload
430+
metrics.increment("warehouse.upload.attestations.failed_verify")
431+
raise _exc_with_message(
432+
HTTPBadRequest,
433+
f"Could not verify the uploaded artifact using the included "
434+
f"attestation: {e}",
435+
)
436+
except Exception as e:
437+
sentry_sdk.capture_message(
438+
f"Unexpected error while verifying attestation: {e}"
439+
)
440+
raise _exc_with_message(
441+
HTTPBadRequest,
442+
f"Unknown error while trying to verify included "
443+
f"attestations: {e}",
444+
)
445+
446+
if predicate_type != "https://docs.pypi.org/attestations/publish/v1":
447+
metrics.increment(
448+
"warehouse.upload.attestations." "failed_unsupported_predicate_type"
449+
)
450+
raise _exc_with_message(
451+
HTTPBadRequest,
452+
f"Attestation with unsupported predicate type: "
453+
f"{predicate_type}",
454+
)
455+
456+
# Log successful attestation upload
457+
metrics.increment("warehouse.upload.attestations.ok")
458+
459+
374460
@view_config(
375461
route_name="forklift.legacy.file_upload",
376462
uses_session=True,
@@ -1069,79 +1155,9 @@ def file_upload(request):
10691155
k: h.hexdigest().lower() for k, h in metadata_file_hashes.items()
10701156
}
10711157

1072-
# Check that if the file was uploaded with attestations, verification
1073-
# passes
1074-
if "attestations" in request.POST:
1075-
publisher = request.oidc_publisher
1076-
if not publisher or not publisher.publisher_name == "GitHub":
1077-
raise _exc_with_message(
1078-
HTTPBadRequest,
1079-
"Attestations are currently only supported when using Trusted "
1080-
"Publishing with GitHub Actions.",
1081-
)
1082-
try:
1083-
attestations = TypeAdapter(list[Attestation]).validate_json(
1084-
request.POST["attestations"]
1085-
)
1086-
except ValidationError as e:
1087-
# Log invalid (malformed) attestation upload
1088-
metrics.increment("warehouse.upload.attestations.malformed")
1089-
raise _exc_with_message(
1090-
HTTPBadRequest,
1091-
f"Error while decoding the included attestation: {e}",
1092-
)
1093-
1094-
if len(attestations) > 1:
1095-
metrics.increment(
1096-
"warehouse.upload.attestations." "failed_multiple_attestations"
1097-
)
1098-
raise _exc_with_message(
1099-
HTTPBadRequest,
1100-
"Only a single attestation per-file is supported at the moment.",
1101-
)
1102-
1103-
verification_policy = publisher.publisher_verification_policy(
1104-
request.oidc_claims
1105-
)
1106-
for attestation_model in attestations:
1107-
try:
1108-
# For now, attestations are not stored, just verified
1109-
predicate_type, _ = attestation_model.verify(
1110-
Verifier.production(),
1111-
verification_policy,
1112-
Path(temporary_filename),
1113-
)
1114-
except VerificationError as e:
1115-
# Log invalid (failed verification) attestation upload
1116-
metrics.increment("warehouse.upload.attestations.failed_verify")
1117-
raise _exc_with_message(
1118-
HTTPBadRequest,
1119-
f"Could not verify the uploaded artifact using the included "
1120-
f"attestation: {e}",
1121-
)
1122-
except Exception as e:
1123-
sentry_sdk.capture_message(
1124-
f"Unexpected error while verifying attestation: {e}"
1125-
)
1126-
raise _exc_with_message(
1127-
HTTPBadRequest,
1128-
f"Unknown error while trying to verify included "
1129-
f"attestations: {e}",
1130-
)
1131-
1132-
if predicate_type != "https://docs.pypi.org/attestations/publish/v1":
1133-
metrics.increment(
1134-
"warehouse.upload.attestations."
1135-
"failed_unsupported_predicate_type"
1136-
)
1137-
raise _exc_with_message(
1138-
HTTPBadRequest,
1139-
f"Attestation with unsupported predicate type: "
1140-
f"{predicate_type}",
1141-
)
1142-
1143-
# Log successful attestation upload
1144-
metrics.increment("warehouse.upload.attestations.ok")
1158+
_process_attestations(
1159+
request=request, metrics=metrics, artifact_path=Path(temporary_filename)
1160+
)
11451161

11461162
# TODO: This should be handled by some sort of database trigger or a
11471163
# SQLAlchemy hook or the like instead of doing it inline in this

0 commit comments

Comments
 (0)