Skip to content

Commit fae998b

Browse files
authored
fix: check if repository is available in provenance available check (#467)
If a repository is not available for an artifact/analysis target identified by a PURL string, the `mcn_provenance_available_1` check throws an exception. This PR fixes this bug by checking if the repository is available before running the check. Signed-off-by: behnazh-w <[email protected]>
1 parent 4282c6c commit fae998b

File tree

2 files changed

+96
-57
lines changed

2 files changed

+96
-57
lines changed

src/macaron/slsa_analyzer/checks/provenance_available_check.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,14 @@ def run_check(self, ctx: AnalyzeContext, check_result: CheckResult) -> CheckResu
450450

451451
# We look for the provenances in the package registries first, then CI services.
452452
# (Note the short-circuit evaluation with OR.)
453+
# The current provenance discovery mechanism for package registries requires a
454+
# repository to be available. Moreover, the repository path in Witness provenance
455+
# contents are checked to match the target repository path.
456+
# TODO: handle cases where a PURL string is provided for a software component but
457+
# no repository is available.
458+
if not ctx.component.repository:
459+
check_result["justification"] = ["Unable to find provenances because no repository is available."]
460+
return CheckResultType.FAILED
453461
try:
454462
provenance_assets = self.find_provenance_assets_on_package_registries(
455463
repo_fs_path=ctx.component.repository.fs_path,

tests/slsa_analyzer/checks/test_provenance_available_check.py

Lines changed: 88 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
"""This modules contains tests for the provenance available check."""
55

66

7+
from pathlib import Path
8+
9+
import pytest
10+
711
from macaron.code_analyzer.call_graph import BaseNode, CallGraph
12+
from macaron.database.table_definitions import Repository
813
from macaron.slsa_analyzer.checks.check_result import CheckResult, CheckResultType
914
from macaron.slsa_analyzer.checks.provenance_available_check import ProvenanceAvailableCheck
1015
from macaron.slsa_analyzer.ci_service.circleci import CircleCI
@@ -16,8 +21,6 @@
1621
from macaron.slsa_analyzer.specs.ci_spec import CIInfo
1722
from tests.conftest import MockAnalyzeContext
1823

19-
from ...macaron_testcase import MacaronTestCase
20-
2124

2225
class MockGitHubActions(GitHubActions):
2326
"""Mock the GitHubActions class."""
@@ -47,58 +50,86 @@ def download_asset(self, url: str, download_path: str) -> bool:
4750
return False
4851

4952

50-
class TestProvAvailableCheck(MacaronTestCase):
51-
"""Test the provenance available check."""
52-
53-
def test_provenance_available_check(self) -> None:
54-
"""Test the provenance available check."""
55-
check = ProvenanceAvailableCheck()
56-
check_result = CheckResult(justification=[]) # type: ignore
57-
github_actions = MockGitHubActions()
58-
api_client = MockGhAPIClient({"headers": {}, "query": []})
59-
github_actions.api_client = api_client
60-
github_actions.load_defaults()
61-
jenkins = Jenkins()
62-
jenkins.load_defaults()
63-
travis = Travis()
64-
travis.load_defaults()
65-
circle_ci = CircleCI()
66-
circle_ci.load_defaults()
67-
gitlab_ci = GitLabCI()
68-
gitlab_ci.load_defaults()
69-
70-
ci_info = CIInfo(
71-
service=github_actions,
72-
bash_commands=[],
73-
callgraph=CallGraph(BaseNode(), ""),
74-
provenance_assets=[],
75-
latest_release={},
76-
provenances=[],
77-
)
78-
79-
# Repo has provenances.
80-
ctx = MockAnalyzeContext(macaron_path=MacaronTestCase.macaron_path, output_dir="")
81-
ctx.dynamic_data["ci_services"] = [ci_info]
82-
assert check.run_check(ctx, check_result) == CheckResultType.PASSED
83-
84-
# Repo doesn't have a provenance.
85-
api_client.release = {"assets": [{"name": "attestation.intoto", "url": "URL", "size": 10}]}
86-
assert check.run_check(ctx, check_result) == CheckResultType.FAILED
87-
88-
api_client.release = {"assets": [{"name": "attestation.intoto.jsonl", "url": "URL", "size": 10}]}
89-
90-
# Test Jenkins.
91-
ci_info["service"] = jenkins
92-
assert check.run_check(ctx, check_result) == CheckResultType.FAILED
93-
94-
# Test Travis.
95-
ci_info["service"] = travis
96-
assert check.run_check(ctx, check_result) == CheckResultType.FAILED
97-
98-
# Test Circle CI.
99-
ci_info["service"] = circle_ci
100-
assert check.run_check(ctx, check_result) == CheckResultType.FAILED
101-
102-
# Test GitLab CI.
103-
ci_info["service"] = gitlab_ci
104-
assert check.run_check(ctx, check_result) == CheckResultType.FAILED
53+
@pytest.mark.parametrize(
54+
("repository", "expected"),
55+
[
56+
(None, CheckResultType.FAILED),
57+
(Repository(complete_name="github.com/package-url/purl-spec", fs_path=""), CheckResultType.PASSED),
58+
],
59+
)
60+
def test_provenance_available_check_with_repos(macaron_path: Path, repository: Repository, expected: str) -> None:
61+
"""Test the provenance available check on different types of repositories."""
62+
check = ProvenanceAvailableCheck()
63+
check_result = CheckResult(justification=[]) # type: ignore
64+
github_actions = MockGitHubActions()
65+
api_client = MockGhAPIClient({"headers": {}, "query": []})
66+
github_actions.api_client = api_client
67+
github_actions.load_defaults()
68+
69+
ci_info = CIInfo(
70+
service=github_actions,
71+
bash_commands=[],
72+
callgraph=CallGraph(BaseNode(), ""),
73+
provenance_assets=[],
74+
latest_release={},
75+
provenances=[],
76+
)
77+
78+
# Set up the context object with provenances.
79+
ctx = MockAnalyzeContext(macaron_path=macaron_path, output_dir="")
80+
ctx.component.repository = repository
81+
ctx.dynamic_data["ci_services"] = [ci_info]
82+
assert check.run_check(ctx, check_result) == expected
83+
84+
85+
def test_provenance_available_check_on_ci(macaron_path: Path) -> None:
86+
"""Test the provenance available check on different types of CI services."""
87+
check = ProvenanceAvailableCheck()
88+
check_result = CheckResult(justification=[]) # type: ignore
89+
github_actions = MockGitHubActions()
90+
api_client = MockGhAPIClient({"headers": {}, "query": []})
91+
github_actions.api_client = api_client
92+
github_actions.load_defaults()
93+
jenkins = Jenkins()
94+
jenkins.load_defaults()
95+
travis = Travis()
96+
travis.load_defaults()
97+
circle_ci = CircleCI()
98+
circle_ci.load_defaults()
99+
gitlab_ci = GitLabCI()
100+
gitlab_ci.load_defaults()
101+
102+
ci_info = CIInfo(
103+
service=github_actions,
104+
bash_commands=[],
105+
callgraph=CallGraph(BaseNode(), ""),
106+
provenance_assets=[],
107+
latest_release={},
108+
provenances=[],
109+
)
110+
111+
# Set up the context object with provenances.
112+
ctx = MockAnalyzeContext(macaron_path=macaron_path, output_dir="")
113+
ctx.dynamic_data["ci_services"] = [ci_info]
114+
115+
# Repo doesn't have a provenance.
116+
api_client.release = {"assets": [{"name": "attestation.intoto", "url": "URL", "size": 10}]}
117+
assert check.run_check(ctx, check_result) == CheckResultType.FAILED
118+
119+
api_client.release = {"assets": [{"name": "attestation.intoto.jsonl", "url": "URL", "size": 10}]}
120+
121+
# Test Jenkins.
122+
ci_info["service"] = jenkins
123+
assert check.run_check(ctx, check_result) == CheckResultType.FAILED
124+
125+
# Test Travis.
126+
ci_info["service"] = travis
127+
assert check.run_check(ctx, check_result) == CheckResultType.FAILED
128+
129+
# Test Circle CI.
130+
ci_info["service"] = circle_ci
131+
assert check.run_check(ctx, check_result) == CheckResultType.FAILED
132+
133+
# Test GitLab CI.
134+
ci_info["service"] = gitlab_ci
135+
assert check.run_check(ctx, check_result) == CheckResultType.FAILED

0 commit comments

Comments
 (0)