Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions src/macaron/slsa_analyzer/checks/provenance_witness_l1_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from sqlalchemy.orm import Mapped, mapped_column

from macaron.database.table_definitions import CheckFacts
from macaron.errors import MacaronError
from macaron.slsa_analyzer.analyze_context import AnalyzeContext
from macaron.slsa_analyzer.checks.base_check import BaseCheck
from macaron.slsa_analyzer.checks.check_result import CheckResultData, CheckResultType, Confidence, JustificationType
Expand All @@ -27,6 +28,10 @@
logger: logging.Logger = logging.getLogger(__name__)


class WitnessProvenanceException(MacaronError):
"""When there is an error while processing a Witness provenance."""


class WitnessProvenanceAvailableFacts(CheckFacts):
"""The ORM mapping for justifications in provenance l3 check."""

Expand Down Expand Up @@ -66,20 +71,32 @@ def verify_artifact_assets(
-------
bool
True if verification succeeds and False otherwise.

Raises
------
WitnessProvenanceException
If a subject is not a file attested by the Witness product attestor.
"""
# A look-up table to verify:
# 1. if the name of the artifact appears in any subject of the witness provenance, then
# 2. if the digest of the artifact could be found
look_up: dict[str, dict[str, InTotoV01Subject]] = {}

for subject in subjects:
if subject["name"] not in look_up:
look_up[subject["name"]] = {}
look_up[subject["name"]][subject["digest"]["sha256"]] = subject
if not subject["name"].startswith("https://witness.dev/attestations/product/v0.1/file:"):
raise WitnessProvenanceException(
f"{subject['name']} is not a file attested by the Witness product attestor."
)

# Get the artifact name, which should be the last part of the artifact subject value.
_, _, artifact_filename = subject["name"].rpartition("/")
Copy link
Member

@nicallen nicallen Jul 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code splitting this string is assuming that the name is of the form https://witness.dev/attestations/product/v0.1/file:path/to/file. I know that this function is currently only called on a list that comes from extract_build_artifacts_from_witness_subjects, which currently only produces subjects of that form, but if that changes in future this code would silently break. I think it would be safer for this code to validate that it is a witness file subject before extracting the filename and throw an exception if it is not (which currently should never happen).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I have added the validation here - c57936b

if artifact_filename not in look_up:
look_up[artifact_filename] = {}
look_up[artifact_filename][subject["digest"]["sha256"]] = subject

for asset in artifact_assets:
if asset.name not in look_up:
message = f"Could not find subject with name {asset.name} in the provenance."
message = f"Could not find subject for asset {asset.name} in the provenance."
logger.info(message)
return False

Expand Down Expand Up @@ -169,7 +186,16 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData:
)
subjects = extract_build_artifacts_from_witness_subjects(provenance.payload)

if not verify_artifact_assets(artifact_assets, subjects):
try:
verify_status = verify_artifact_assets(artifact_assets, subjects)
except WitnessProvenanceException as err:
logger.error(err)
return CheckResultData(
result_tables=result_tables,
result_type=CheckResultType.UNKNOWN,
)

if not verify_status:
return CheckResultData(
result_tables=result_tables,
result_type=CheckResultType.FAILED,
Expand Down
15 changes: 0 additions & 15 deletions src/macaron/slsa_analyzer/provenance/witness/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,6 @@ def is_witness_provenance_payload(
return isinstance(payload, InTotoV01Payload) and payload.statement["predicateType"] in predicate_types


class WitnessProvenanceSubject(NamedTuple):
"""A helper class to store elements of the ``subject`` list in the provenances."""

#: The ``"name"`` field of each ``subject``.
subject_name: str
#: The SHA256 digest of the corresponding asset to the subject.
sha256_digest: str

@property
def artifact_name(self) -> str:
"""Get the artifact name, which should be the last part of the subject."""
_, _, artifact_name = self.subject_name.rpartition("/")
return artifact_name


def extract_repo_url(witness_payload: InTotoPayload) -> str | None:
"""Extract the repo URL from the witness provenance payload.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ Policy("example_maven_app_policy", component_id, "Policy for github Maven projec
check_passed(component_id, "mcn_build_service_1"),
check_passed(component_id, "mcn_build_script_1"),
check_passed(component_id, "mcn_provenance_available_1"),
check_passed(component_id, "mcn_provenance_expectation_1").
check_passed(component_id, "mcn_provenance_expectation_1"),
// We expect mcn_provenance_witness_level_one_1 to fail because at the moment
// it tries to discover the witness provenance even when the provenance is provided as input.
// TODO: address this policy once the issue with mcn_provenance_witness_level_one_1 is addressed.
check_failed(component_id, "mcn_provenance_witness_level_one_1").

apply_policy_to("example_maven_app_policy", component_id) :-
is_repo(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"component_satisfies_policy": [
[
"176",
"1",
"pkg:maven/io.github.behnazh-w.demo/example-maven-app@1.0-SNAPSHOT?type=jar",
"example_maven_app_policy"
],
Expand All @@ -11,11 +11,11 @@
"example_maven_app_policy"
]
],
"component_violates_policy": [],
"failed_policies": [],
"passed_policies": [
[
"example_maven_app_policy"
]
]
],
"component_violates_policy": [],
"failed_policies": []
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"uri": "pkg:maven/io.github.behnazh-w.demo/example-maven-app@1.0-SNAPSHOT?type=jar",
"digest": {
"sha256": "19986144a60f3d16d1e8d96bc1807c42bb7c91068ab3018b85033f62c2845921"
}
},
{
"uri": "pkg:maven/io.github.behnazh-w.demo/example-maven-app@1.0?type=jar",
"digest": {
"sha256": "759a3c7f76ef2c467cedb814ed3fd38cb9125126664f66f9a62d1cfa0e54b6b7"
}
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"uri": "pkg:maven/io.github.behnazh-w.demo/example-maven-app@1.0-SNAPSHOT?type=jar",
"digest": {
"sha256": "19986144a60f3d16d1e8d96bc1807c42bb7c91068ab3018b85033f62c2845921"
}
},
{
"uri": "pkg:maven/io.github.behnazh-w.demo/example-maven-app@1.0?type=jar",
"digest": {
"sha256": "759a3c7f76ef2c467cedb814ed3fd38cb9125126664f66f9a62d1cfa0e54b6b7"
}
}
],
"predicateType": "https://slsa.dev/verification_summary/v1",
"predicate": {
"verifier": {
"id": "https://github.com/oracle/macaron",
"version": {
"macaron": "0.11.0"
}
},
"timeVerified": "2024-07-23T05:34:41.564563+00:00",
"resourceUri": "",
"policy": {
"content": "/* Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved. */\n/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */\n\n#include \"prelude.dl\"\n\nPolicy(\"example_maven_app_policy\", component_id, \"Policy for github Maven project with Witness and GitHub provenances\") :-\n check_passed(component_id, \"mcn_build_service_1\"),\n check_passed(component_id, \"mcn_build_script_1\"),\n check_passed(component_id, \"mcn_provenance_available_1\"),\n check_passed(component_id, \"mcn_provenance_expectation_1\"),\n // We expect mcn_provenance_witness_level_one_1 to fail because at the moment\n // it tries to discover the witness provenance even when the provenance is provided as input.\n // TODO: address this policy once the issue with mcn_provenance_witness_level_one_1 is addressed.\n check_failed(component_id, \"mcn_provenance_witness_level_one_1\").\n\napply_policy_to(\"example_maven_app_policy\", component_id) :-\n is_repo(\n _, // repo_id\n \"github.com/behnazh-w/example-maven-app\", // http URL to the repo but without the \"http://\"\n component_id\n ).\n"
},
"verificationResult": "PASSED",
"verifiedLevels": []
}
],
"predicateType": "https://slsa.dev/verification_summary/v1",
"predicate": {
"verifier": {
"id": "https://github.com/oracle/macaron",
"version": {
"macaron": "0.9.0"
}
},
"timeVerified": "2024-05-07T05:32:42.105941+00:00",
"resourceUri": "",
"policy": {
"content": "/* Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved. */\n/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */\n\n#include \"prelude.dl\"\n\nPolicy(\"example_maven_app_policy\", component_id, \"Policy for github Maven project with Witness and GitHub provenances\") :-\n check_passed(component_id, \"mcn_build_service_1\"),\n check_passed(component_id, \"mcn_build_script_1\"),\n check_passed(component_id, \"mcn_provenance_available_1\"),\n check_passed(component_id, \"mcn_provenance_expectation_1\").\n\napply_policy_to(\"example_maven_app_policy\", component_id) :-\n is_repo(\n _, // repo_id\n \"github.com/behnazh-w/example-maven-app\", // http URL to the repo but without the \"http://\"\n component_id\n ).\n"
},
"verificationResult": "PASSED",
"verifiedLevels": []
}
}
Loading