Skip to content

feat: add support for GitHub provenances passed as input #732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2024
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
26 changes: 19 additions & 7 deletions scripts/dev_scripts/integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -761,28 +761,40 @@ $RUN_POLICY -f $POLICY_FILE -d "$WORKSPACE/output/macaron.db" || log_fail
check_or_update_expected_output $COMPARE_POLICIES $POLICY_RESULT $POLICY_EXPECTED || log_fail

echo -e "\n----------------------------------------------------------------------------------"
echo "behnazh-w/example-maven-app as a local repository"
echo "Test Witness provenance as an input, Cue expectation validation, Policy CLI and VSA generation."
echo "behnazh-w/example-maven-app as a local and remote repository"
echo "Test the Witness and GitHub provenances as an input, Cue expectation validation, Policy CLI and VSA generation."
echo -e "----------------------------------------------------------------------------------\n"
RUN_POLICY="macaron verify-policy"
POLICY_FILE=$WORKSPACE/tests/policy_engine/resources/policies/example-maven-project/policy.dl
POLICY_RESULT=$WORKSPACE/output/policy_report.json
POLICY_EXPECTED=$WORKSPACE/tests/policy_engine/expected_results/example-maven-project/example_maven_project_policy_report.json
VSA_RESULT=$WORKSPACE/output/vsa.intoto.jsonl
VSA_PAYLOAD_EXPECTED=$WORKSPACE/tests/vsa/integration/local_witness_example-maven-project/vsa_payload.json
EXPECTATION_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/expectations/cue/resources/valid_expectations/example-maven-project.cue
PROVENANCE_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/resources/valid_provenances/example-maven-project.json
VSA_PAYLOAD_EXPECTED=$WORKSPACE/tests/vsa/integration/example-maven-project/vsa_payload.json

# Test the local repo with Witness provenance.
WITNESS_EXPECTATION_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/expectations/cue/resources/valid_expectations/witness-example-maven-project.cue
WITNESS_PROVENANCE_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/resources/valid_provenances/witness-example-maven-project.json

# Cloning the repository locally
git clone https://github.com/behnazh-w/example-maven-app.git $WORKSPACE/output/git_repos/local_repos/example-maven-app || log_fail

$RUN_MACARON analyze -pf $PROVENANCE_FILE -pe $EXPECTATION_FILE -purl pkg:maven/io.github.behnazh-w.demo/[email protected]?type=jar --repo-path example-maven-app --skip-deps || log_fail
# Check the Witness provenance.
$RUN_MACARON analyze -pf $WITNESS_PROVENANCE_FILE -pe $WITNESS_EXPECTATION_FILE -purl pkg:maven/io.github.behnazh-w.demo/[email protected]?type=jar --repo-path example-maven-app --skip-deps || log_fail

# Test the remote repo with GitHub provenance.
GITHUB_EXPECTATION_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/expectations/cue/resources/valid_expectations/github-example-maven-project.cue
GITHUB_PROVENANCE_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/resources/valid_provenances/github-example-maven-project.json

# Check the GitHub provenance.
$RUN_MACARON analyze -pf $GITHUB_PROVENANCE_FILE -pe $GITHUB_EXPECTATION_FILE -purl pkg:maven/io.github.behnazh-w.demo/[email protected]?type=jar --skip-deps || log_fail

# Verify the policy and VSA for all the software components generated from behnazh-w/example-maven-app repo.
$RUN_POLICY -f $POLICY_FILE -d "$WORKSPACE/output/macaron.db" || log_fail

check_or_update_expected_output $COMPARE_POLICIES $POLICY_RESULT $POLICY_EXPECTED || log_fail
check_or_update_expected_output "$COMPARE_POLICIES" "$POLICY_RESULT" "$POLICY_EXPECTED" || log_fail
check_or_update_expected_output "$COMPARE_VSA" "$VSA_RESULT" "$VSA_PAYLOAD_EXPECTED" || log_fail


# Testing the Repo Finder's remote calls.
# This requires the 'packageurl' Python module
echo -e "\n----------------------------------------------------------------------------------"
Expand Down
26 changes: 19 additions & 7 deletions scripts/dev_scripts/integration_tests_docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -161,25 +161,37 @@ python $COMPARE_POLICIES $POLICY_RESULT $POLICY_EXPECTED || log_fail
python "$COMPARE_VSA" "$VSA_RESULT" "$VSA_PAYLOAD_EXPECTED" || log_fail

echo -e "\n----------------------------------------------------------------------------------"
echo "behnazh-w/example-maven-app as a local repository"
echo "Test Witness provenance as an input, Cue expectation validation, Policy CLI and VSA generation."
echo "behnazh-w/example-maven-app as a local and remote repository"
echo "Test the Witness and GitHub provenances as an input, Cue expectation validation, Policy CLI and VSA generation."
echo -e "----------------------------------------------------------------------------------\n"
RUN_POLICY="macaron verify-policy"
POLICY_FILE=$WORKSPACE/tests/policy_engine/resources/policies/example-maven-project/policy.dl
POLICY_RESULT=$WORKSPACE/output/policy_report.json
POLICY_EXPECTED=$WORKSPACE/tests/policy_engine/expected_results/example-maven-project/example_maven_project_policy_report.json
VSA_RESULT=$WORKSPACE/output/vsa.intoto.jsonl
VSA_PAYLOAD_EXPECTED=$WORKSPACE/tests/vsa/integration/local_witness_example-maven-project/vsa_payload.json
EXPECTATION_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/expectations/cue/resources/valid_expectations/example-maven-project.cue
PROVENANCE_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/resources/valid_provenances/example-maven-project.json
VSA_PAYLOAD_EXPECTED=$WORKSPACE/tests/vsa/integration/example-maven-project/vsa_payload.json

# Test the local repo with Witness provenance.
WITNESS_EXPECTATION_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/expectations/cue/resources/valid_expectations/witness-example-maven-project.cue
WITNESS_PROVENANCE_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/resources/valid_provenances/witness-example-maven-project.json

# Cloning the repository locally
git clone https://github.com/behnazh-w/example-maven-app.git $WORKSPACE/output/git_repos/local_repos/example-maven-app || log_fail

$RUN_MACARON_SCRIPT analyze -pf $PROVENANCE_FILE -pe $EXPECTATION_FILE -purl pkg:maven/io.github.behnazh-w.demo/[email protected]?type=jar --repo-path example-maven-app --skip-deps || log_fail
# Check the Witness provenance.
$RUN_MACARON_SCRIPT analyze -pf $WITNESS_PROVENANCE_FILE -pe $WITNESS_EXPECTATION_FILE -purl pkg:maven/io.github.behnazh-w.demo/[email protected]?type=jar --repo-path example-maven-app --skip-deps || log_fail

# Test the remote repo with GitHub provenance.
GITHUB_EXPECTATION_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/expectations/cue/resources/valid_expectations/github-example-maven-project.cue
GITHUB_PROVENANCE_FILE=$WORKSPACE/tests/slsa_analyzer/provenance/resources/valid_provenances/github-example-maven-project.json

# Check the GitHub provenance.
$RUN_MACARON_SCRIPT analyze -pf $GITHUB_PROVENANCE_FILE -pe $GITHUB_EXPECTATION_FILE -purl pkg:maven/io.github.behnazh-w.demo/[email protected]?type=jar --skip-deps || log_fail

# Verify the policy and VSA for all the software components generated from behnazh-w/example-maven-app repo.
$RUN_MACARON_SCRIPT verify-policy -f $POLICY_FILE -d "$WORKSPACE/output/macaron.db" || log_fail

python $COMPARE_POLICIES $POLICY_RESULT $POLICY_EXPECTED || log_fail
python "$COMPARE_POLICIES" "$POLICY_RESULT" "$POLICY_EXPECTED" || log_fail
python "$COMPARE_VSA" "$VSA_RESULT" "$VSA_PAYLOAD_EXPECTED" || log_fail

echo -e "\n----------------------------------------------------------------------------------"
Expand Down
24 changes: 21 additions & 3 deletions src/macaron/artifact/maven.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@

"""This module declares types and utilities for Maven artifacts."""

from collections.abc import Sequence

from packageurl import PackageURL

from macaron.config.defaults import defaults
from macaron.slsa_analyzer.provenance.intoto import InTotoPayload
from macaron.slsa_analyzer.provenance.intoto.v01 import InTotoV01Subject
from macaron.slsa_analyzer.provenance.intoto.v1 import InTotoV1ResourceDescriptor
from macaron.slsa_analyzer.provenance.slsa import extract_build_artifacts_from_slsa_subjects, is_slsa_provenance_payload
from macaron.slsa_analyzer.provenance.witness import (
extract_build_artifacts_from_witness_subjects,
is_witness_provenance_payload,
Expand Down Expand Up @@ -46,15 +50,29 @@ def get_subject_in_provenance_matching_purl(
if purl.type != "maven":
return None

if not is_witness_provenance_payload(
artifact_subjects: Sequence[InTotoV01Subject | InTotoV1ResourceDescriptor] = []
if is_witness_provenance_payload(
payload=provenance_payload,
predicate_types=load_witness_verifier_config().predicate_types,
):
artifact_subjects = extract_build_artifacts_from_witness_subjects(provenance_payload)
elif is_slsa_provenance_payload(
payload=provenance_payload,
predicate_types=defaults.get_list(
"slsa.verifier",
"predicate_types",
fallback=[],
),
):
artifact_subjects = extract_build_artifacts_from_slsa_subjects(provenance_payload)
else:
return None
artifact_subjects = extract_build_artifacts_from_witness_subjects(provenance_payload)

for subject in artifact_subjects:
_, _, artifact_filename = subject["name"].rpartition("/")
subject_name = subject["name"]
if not subject_name:
continue
_, _, artifact_filename = subject_name.rpartition("/")
subject_purl = create_maven_purl_from_artifact_filename(
artifact_filename=artifact_filename,
group_id=purl.namespace,
Expand Down
3 changes: 3 additions & 0 deletions src/macaron/config/defaults.ini
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,9 @@ max_download_size = 70000000
timeout = 120
# The allowed hostnames for URL file links for provenance download
url_link_hostname_allowlist =
predicate_types =
https://slsa.dev/provenance/v0.2
https://slsa.dev/provenance/v1

# Witness provenance. See: https://github.com/testifysec/witness.
[provenance.witness]
Expand Down
2 changes: 2 additions & 0 deletions src/macaron/slsa_analyzer/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ def run_single(
analyze_ctx.component.purl.split("@")[0]
)
analyze_ctx.dynamic_data["provenance"] = provenance_payload
if provenance_payload:
analyze_ctx.dynamic_data["is_inferred_prov"] = False
analyze_ctx.check_results = self.perform_checks(analyze_ctx)

return Record(
Expand Down
21 changes: 20 additions & 1 deletion src/macaron/slsa_analyzer/provenance/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import configparser
import gzip
import json
import logging
import zlib
from urllib.parse import urlparse

Expand All @@ -15,6 +16,8 @@
from macaron.slsa_analyzer.provenance.intoto.errors import LoadIntotoAttestationError, ValidateInTotoPayloadError
from macaron.util import JsonType, send_get_http_raw

logger: logging.Logger = logging.getLogger(__name__)


def _try_read_url_link_file(file_content: bytes) -> str | None:
parser = configparser.ConfigParser()
Expand Down Expand Up @@ -70,7 +73,15 @@ def _load_provenance_file_content(
"Cannot deserialize the file content as JSON.",
) from error

provenance_payload = provenance.get("payload", None)
# The GitHub Attestation stores the DSSE envelope in `dsseEnvelope` property.
dsse_envelope = provenance.get("dsseEnvelope", None)
if dsse_envelope:
provenance_payload = dsse_envelope.get("payload", None)
logger.debug("Found dsseEnvelope property in the provenance.")
else:
# Some provenances, such as Witness may not include the DSSE envelope `dsseEnvelope`
# property but contain its value directly.
provenance_payload = provenance.get("payload", None)
if not provenance_payload:
raise LoadIntotoAttestationError(
'Cannot find the "payload" field in the decoded provenance.',
Expand Down Expand Up @@ -104,6 +115,14 @@ def load_provenance_file(filepath: str) -> dict[str, JsonType]:
a "URL" field inside an "InternetShortcut" section), it will be transparently
downloaded.

Note: We have observed that GitHub provenances store the DSSE envelope using the
`dsseEnvelope` property in the bundle. The bundle also includes Sigstore verification
material, such as `publicKey` and `x509CertificateChain`. However, provenances generated by
Witness and SLSA GitHub generator store the DSSE envelope content only.
This function supports both types of provenances. See the Sigstore bundle schema, which is
used in GitHub provenances:
https://github.com/sigstore/protobuf-specs/blob/2bfc122984e8c30fc83f5892b2947af7d113b411/gen/jsonschema/schemas/Bundle.schema.json

Parameters
----------
filepath : str
Expand Down
57 changes: 56 additions & 1 deletion src/macaron/slsa_analyzer/provenance/slsa/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2023 - 2023, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.

"""This module implements SLSA provenance abstractions."""
Expand All @@ -7,6 +7,8 @@

from macaron.slsa_analyzer.asset import AssetLocator
from macaron.slsa_analyzer.provenance.intoto import InTotoPayload
from macaron.slsa_analyzer.provenance.intoto.v01 import InTotoV01Subject
from macaron.slsa_analyzer.provenance.intoto.v1 import InTotoV1ResourceDescriptor


class SLSAProvenanceData(NamedTuple):
Expand All @@ -16,3 +18,56 @@ class SLSAProvenanceData(NamedTuple):
asset: AssetLocator
#: The provenance payload.
payload: InTotoPayload


def extract_build_artifacts_from_slsa_subjects(
payload: InTotoPayload,
) -> list[InTotoV01Subject | InTotoV1ResourceDescriptor]:
"""Extract subjects that are build artifacts from the ``"subject"`` field of the provenance.

Each artifact subject is assumed to have a sha256 digest. If a sha256 digest is not present for
a subject, that subject is ignored.

Parameters
----------
payload : InTotoPayload
The provenance payload.

Returns
-------
list[InTotoV01Subject | InTotoV1ResourceDescriptor]
A list of subjects in the ``"subject"`` field of the provenance that are build artifacts.
"""
subjects = payload.statement["subject"]
artifact_subjects = []
for subject in subjects:
digest = subject["digest"]
if not digest:
continue
sha256 = digest.get("sha256")
if not sha256 or not isinstance(sha256, str):
continue
artifact_subjects.append(subject)

return artifact_subjects


def is_slsa_provenance_payload(
payload: InTotoPayload,
predicate_types: list[str],
) -> bool:
"""Check if the given provenance payload is a SLSA provenance payload.

Parameters
----------
payload : InTotoPayload
The provenance payload.
predicate_types : list[str]
The allowed values for the ``"predicateType"`` field of the provenance payload.

Returns
-------
bool
``True`` if the payload is a witness provenance payload, ``False`` otherwise.
"""
return payload.statement["predicateType"] in predicate_types
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
[
"1",
"pkg:maven/io.github.behnazh-w.demo/[email protected]?type=jar",
"gh_witness_provenance_policy"
"example_maven_app_policy"
],
[
"2",
"pkg:maven/io.github.behnazh-w.demo/[email protected]?type=jar",
"example_maven_app_policy"
]
],
"passed_policies": [
[
"gh_witness_provenance_policy"
"example_maven_app_policy"
]
],
"component_violates_policy": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

#include "prelude.dl"

Policy("gh_witness_provenance_policy", component_id, "Policy for github Maven project with witness provenances") :-
Policy("example_maven_app_policy", component_id, "Policy for github Maven project with Witness and GitHub provenances") :-
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").

apply_policy_to("gh_witness_provenance_policy", component_id) :-
apply_policy_to("example_maven_app_policy", component_id) :-
is_repo(
_, // repo_id
"github.com/behnazh-w/example-maven-app", // http URL to the repo but without the "http://"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
target: "pkg:maven/io.github.behnazh-w.demo/example-maven-app",
predicate: {
buildDefinition: {
externalParameters: {
workflow: {
ref: "refs/heads/main",
repository: "https://github.com/behnazh-w/example-maven-app",
path: ".github/workflows/main.yaml"
}
}
}
}
}
Loading