Skip to content

Commit dff8431

Browse files
committed
chore: account for witness provenance
Signed-off-by: Ben Selwyn-Smith <[email protected]>
1 parent 94b02c0 commit dff8431

File tree

6 files changed

+49
-17
lines changed

6 files changed

+49
-17
lines changed

src/macaron/database/db_custom_types.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88

99
from sqlalchemy import JSON, String, TypeDecorator
1010

11-
from macaron.slsa_analyzer.provenance.intoto import InTotoPayload, validate_intoto_payload
11+
from macaron.slsa_analyzer.provenance.intoto import (
12+
InTotoPayload,
13+
InTotoV01Payload,
14+
InTotoV1Payload,
15+
validate_intoto_payload,
16+
)
1217

1318

1419
class RFC3339DateTime(TypeDecorator): # pylint: disable=W0223
@@ -118,7 +123,8 @@ def process_bind_param(self, value: None | InTotoPayload, dialect: Any) -> None
118123
if not isinstance(value, InTotoPayload):
119124
raise TypeError("ProvenancePayload type expects an InTotoPayload.")
120125

121-
return value.statement.get("predicate")
126+
payload_type = value.__class__.__name__
127+
return {"payload_type": payload_type, "payload": value.statement}
122128

123129
def process_result_value(self, value: None | dict, dialect: Any) -> None | InTotoPayload:
124130
"""Process when loading an InTotoPayload object from the SQLite db.
@@ -132,4 +138,13 @@ def process_result_value(self, value: None | dict, dialect: Any) -> None | InTot
132138
if not isinstance(value, dict):
133139
raise TypeError("ProvenancePayload type expects a dict.")
134140

141+
if "payload_type" not in value or "payload" not in value:
142+
raise TypeError("Missing keys in dict for ProvenancePayload type.")
143+
144+
payload = value["payload"]
145+
if value["payload_type"] == "InTotoV01Payload":
146+
return InTotoV01Payload(statement=payload)
147+
if value["payload_type"] == "InTotoV1Payload":
148+
return InTotoV1Payload(statement=payload)
149+
135150
return validate_intoto_payload(value)

src/macaron/database/table_definitions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ class Provenance(ORMBase):
479479
component: Mapped["Component"] = relationship(back_populates="provenance")
480480

481481
#: The SLSA version.
482-
version: Mapped[str] = mapped_column(String, nullable=True)
482+
slsa_version: Mapped[str] = mapped_column(String, nullable=True)
483483

484484
#: The SLSA level.
485485
slsa_level: Mapped[int] = mapped_column(Integer, default=0)

src/macaron/provenance/provenance_verifier.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,21 @@ def verify_npm_provenance(purl: PackageURL, provenance: list[InTotoPayload]) ->
9191
found_signed_subject = None
9292
for signed_subject in signed_subjects:
9393
name = signed_subject.get("name")
94-
if name and name == str(purl):
95-
found_signed_subject = signed_subject
96-
break
94+
if not name or not check_purls_equivalent(purl, PackageURL.from_string(name)):
95+
continue
96+
found_signed_subject = signed_subject
97+
break
9798

9899
if not found_signed_subject:
99100
return False
100101

101102
found_unsigned_subject = None
102103
for unsigned_subject in unsigned_subjects:
103104
name = unsigned_subject.get("name")
104-
if name and name == str(purl):
105-
found_unsigned_subject = unsigned_subject
106-
break
105+
if not name or not check_purls_equivalent(purl, PackageURL.from_string(name)):
106+
continue
107+
found_unsigned_subject = unsigned_subject
108+
break
107109

108110
if not found_unsigned_subject:
109111
return False
@@ -127,6 +129,19 @@ def verify_npm_provenance(purl: PackageURL, provenance: list[InTotoPayload]) ->
127129
return True
128130

129131

132+
def check_purls_equivalent(original_purl: PackageURL, new_purl: PackageURL) -> bool:
133+
"""Check if `new_purl` is equivalent to `original_purl`, excluding versions if the original has none."""
134+
if (
135+
original_purl.type != new_purl.type
136+
or original_purl.name != new_purl.name
137+
or original_purl.namespace != new_purl.namespace
138+
):
139+
return False
140+
if original_purl.version and original_purl.version != new_purl.version:
141+
return False
142+
return True
143+
144+
130145
def verify_ci_provenance(analyze_ctx: AnalyzeContext, ci_info: CIInfo, download_path: str) -> bool:
131146
"""Try to verify the CI provenance in terms of SLSA level 3 requirements.
132147
@@ -370,7 +385,9 @@ def determine_provenance_slsa_level(
370385
predicate = provenance_payload.statement.get("predicate")
371386
build_type = None
372387
if predicate:
373-
build_type = json_extract(predicate, ["buildType"], str)
388+
build_type = json_extract(predicate, ["buildDefinition", "buildType"], str)
389+
if not build_type:
390+
build_type = json_extract(predicate, ["buildType"], str)
374391

375392
if build_type == "https://github.com/slsa-framework/slsa-github-generator/generic@v1" and verified_l3:
376393
# 3. Provenance is created by the SLSA GitHub generator and verified.

src/macaron/slsa_analyzer/analyzer.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -485,13 +485,15 @@ def run_single(
485485
if verified and all(verified):
486486
provenance_l3_verified = True
487487

488-
slsa_level = determine_provenance_slsa_level(
489-
analyze_ctx, provenance_payload, provenance_is_verified, provenance_l3_verified
490-
)
491488
slsa_version = None
492489
if provenance_payload:
490+
analyze_ctx.dynamic_data["is_inferred_prov"] = False
493491
slsa_version = extract_predicate_version(provenance_payload)
494492

493+
slsa_level = determine_provenance_slsa_level(
494+
analyze_ctx, provenance_payload, provenance_is_verified, provenance_l3_verified
495+
)
496+
495497
analyze_ctx.dynamic_data["provenance_info"] = table_definitions.Provenance(
496498
component=component,
497499
repository_url=provenance_repo_url,
@@ -502,8 +504,7 @@ def run_single(
502504
slsa_version=slsa_version,
503505
# TODO Add release tag, release digest.
504506
)
505-
if provenance_payload:
506-
analyze_ctx.dynamic_data["is_inferred_prov"] = False
507+
507508
analyze_ctx.dynamic_data["validate_malware_switch"] = validate_malware_switch
508509

509510
if parsed_purl and parsed_purl.type in self.local_artifact_repo_mapper:

src/macaron/slsa_analyzer/checks/provenance_verified_check.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData:
7272
if provenance_info and provenance_info.provenance_payload:
7373
predicate = provenance_info.provenance_payload.statement.get("predicate")
7474
if predicate:
75-
build_type = json_extract(predicate, ["buildType"], str)
75+
build_type = json_extract(predicate, ["buildDefinition", "buildType"], str)
7676

7777
slsa_level = 0
7878
if provenance_info:

tests/integration/cases/urllib3_expectation_dir/policy.dl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ Policy("test_policy", component_id, "") :-
2020
check_passed(component_id, "mcn_provenance_verified_1"),
2121
provenance_verified_check(_, build_level, _),
2222
build_level = 3,
23-
check_failed(component_id, "mcn_infer_artifact_pipeline_1"),
2423
check_failed(component_id, "mcn_provenance_witness_level_one_1"),
2524
check_failed(component_id, "mcn_trusted_builder_level_three_1"),
2625
is_repo_url(component_id, "https://github.com/urllib3/urllib3"),

0 commit comments

Comments
 (0)