Skip to content
Draft
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
13 changes: 13 additions & 0 deletions src/macaron/provenance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,16 @@
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.

"""This package contains the provenance tools for software components."""

from dataclasses import dataclass

from macaron.slsa_analyzer.provenance.intoto import InTotoPayload


@dataclass(frozen=True)
class ProvenanceAsset:
"""This class exists to hold a provenance payload with the original asset's name and URL."""

payload: InTotoPayload
name: str
url: str
12 changes: 1 addition & 11 deletions src/macaron/provenance/provenance_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
import logging
import os
import tempfile
from dataclasses import dataclass
from functools import partial

from packageurl import PackageURL
from pydriller import Git

from macaron.artifact.local_artifact import get_local_artifact_hash
from macaron.config.defaults import defaults
from macaron.provenance import ProvenanceAsset
from macaron.repo_finder.commit_finder import AbstractPurlType, determine_abstract_purl_type
from macaron.repo_finder.repo_finder_deps_dev import DepsDevRepoFinder
from macaron.repo_finder.repo_utils import get_repo_tags
Expand All @@ -30,7 +30,6 @@
)
from macaron.slsa_analyzer.package_registry.npm_registry import NPMAttestationAsset
from macaron.slsa_analyzer.package_registry.pypi_registry import find_or_create_pypi_asset
from macaron.slsa_analyzer.provenance.intoto import InTotoPayload
from macaron.slsa_analyzer.provenance.intoto.errors import LoadIntotoAttestationError
from macaron.slsa_analyzer.provenance.loader import load_provenance_payload
from macaron.slsa_analyzer.provenance.slsa import SLSAProvenanceData
Expand All @@ -41,15 +40,6 @@
logger: logging.Logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class ProvenanceAsset:
"""This class exists to hold a provenance payload with the original asset's name and URL."""

payload: InTotoPayload
name: str
url: str


class ProvenanceFinder:
"""This class is used to find and retrieve provenance files from supported registries."""

Expand Down
3 changes: 2 additions & 1 deletion src/macaron/slsa_analyzer/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,9 +527,10 @@ def run_single(
# Try to discover GitHub attestation for the target software component.
artifact_hash = get_artifact_hash(parsed_purl, local_artifact_dirs, package_registries_info)
if artifact_hash:
provenance_payload = git_service.get_attestation_payload(
provenance_asset = git_service.get_attestation(
analyze_ctx.component.repository.full_name, artifact_hash
)
provenance_payload = provenance_asset.payload if provenance_asset else None
if provenance_payload:
try:
provenance_repo_url, provenance_commit_digest = extract_repo_and_commit_from_provenance(
Expand Down
12 changes: 7 additions & 5 deletions src/macaron/slsa_analyzer/git_service/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,8 +648,8 @@ def download_asset(self, url: str, download_path: str) -> bool:

return download_file_with_size_limit(url, headers, download_path, timeout, size_limit)

def get_attestation(self, full_name: str, artifact_hash: str) -> dict:
"""Download and return the attestation associated with the passed artifact hash, if any.
def get_attestation(self, full_name: str, artifact_hash: str) -> tuple[str | None, dict]:
"""Download and return the attestation url and content associated with the passed artifact hash, if any.

Parameters
----------
Expand All @@ -660,12 +660,14 @@ def get_attestation(self, full_name: str, artifact_hash: str) -> dict:

Returns
-------
dict
The attestation data, or an empty dict if not found.
tuple[str|None,dict]
The attestation url and data, or None and an empty dict if not found.
"""
url = f"{GhAPIClient._REPO_END_POINT}/{full_name}/attestations/sha256:{artifact_hash}"
response_data = send_get_http(url, self.headers)
return response_data or {}
if not response_data:
return (None, {})
return (url, response_data)


def get_default_gh_client(access_token: str) -> GhAPIClient:
Expand Down
21 changes: 13 additions & 8 deletions src/macaron/slsa_analyzer/git_service/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
from macaron.config.global_config import global_config
from macaron.errors import ConfigurationError, RepoCheckOutError
from macaron.json_tools import json_extract
from macaron.provenance import ProvenanceAsset
from macaron.slsa_analyzer import git_url
from macaron.slsa_analyzer.git_service.api_client import GhAPIClient, get_default_gh_client
from macaron.slsa_analyzer.git_service.base_git_service import BaseGitService
from macaron.slsa_analyzer.provenance.intoto import InTotoPayload, ValidateInTotoPayloadError, validate_intoto_payload
from macaron.slsa_analyzer.provenance.intoto import ValidateInTotoPayloadError, validate_intoto_payload
from macaron.slsa_analyzer.provenance.loader import decode_provenance

logger: logging.Logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -96,7 +97,7 @@ def check_out_repo(self, git_obj: Git, branch: str, digest: str, offline_mode: b

return git_obj

def get_attestation_payload(self, repository_name: str, artifact_hash: str) -> InTotoPayload | None:
def get_attestation(self, repository_name: str, artifact_hash: str) -> ProvenanceAsset | None:
"""Get the GitHub attestation associated with the given PURL, or None if it cannot be found.

The schema of GitHub attestation can be found on the API page:
Expand All @@ -111,22 +112,26 @@ def get_attestation_payload(self, repository_name: str, artifact_hash: str) -> I

Returns
-------
InTotoPayload | None
The attestation payload, if found.
ProvenanceAsset | None
The provenance asset, if found.
"""
git_attestation_dict = self.api_client.get_attestation(repository_name, artifact_hash)
attestation_url, git_attestation_dict = self.api_client.get_attestation(repository_name, artifact_hash)

if not git_attestation_dict:
if not attestation_url or not git_attestation_dict:
return None

git_attestation_list = json_extract(git_attestation_dict, ["attestations"], list)
if not git_attestation_list:
return None

payload = decode_provenance(git_attestation_list[0])

validated_payload = None
try:
return validate_intoto_payload(payload)
validated_payload = validate_intoto_payload(payload)
except ValidateInTotoPayloadError as error:
logger.debug("Invalid attestation payload: %s", error)
return None
if not validated_payload:
return None

return ProvenanceAsset(validated_payload, artifact_hash, attestation_url)
Loading