Skip to content

Commit 014c8d2

Browse files
authored
fix: accept from-provenance repos as scm authentic (#1131)
Signed-off-by: Ben Selwyn-Smith <[email protected]>
1 parent 3548af0 commit 014c8d2

File tree

9 files changed

+155
-53
lines changed

9 files changed

+155
-53
lines changed

docs/source/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ the requirements that are currently supported by Macaron.
9090
- If there is no commit, this check will fail.
9191
* - ``mcn_scm_authenticity_check_1``
9292
- **Source repo authenticity** - Check whether the claims of a source code repository made by a package can be corroborated.
93-
- If the source code repository contains conflicting evidence regarding its claim of the source code repository, this check will fail. If no source code repository or corroborating evidence is found, or if the build system is unsupported, the check will return ``UNKNOWN`` as the result. This check currently supports only Maven artifacts.
93+
- If the source code repository contains conflicting evidence regarding its claim of the source code repository, this check will fail. If no source code repository or corroborating evidence is found, or if the build system is unsupported, the check will return ``UNKNOWN`` as the result. This check supports Maven artifacts, and other artifacts that have a repository that is confirmed to be from a provenance file.
9494
* - ``mcn_detect_malicious_metadata_1``
9595
- **Malicious code detection** - Check whether the source code or package metadata has indicators of compromise.
9696
- This check performs analysis on PyPI package metadata to detect malicious behavior. It also reports known malware from other ecosystems.

src/macaron/repo_verifier/repo_verifier.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
# Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved.
22
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
33

44
"""This module contains code to verify whether a reported repository can be linked back to the artifact."""
55
import logging
66

77
from macaron.repo_verifier.repo_verifier_base import (
88
RepositoryVerificationResult,
9-
RepositoryVerificationStatus,
10-
RepoVerifierBase,
9+
RepoVerifierFromProvenance,
10+
RepoVerifierToolSpecific,
1111
)
1212
from macaron.repo_verifier.repo_verifier_gradle import RepoVerifierGradle
1313
from macaron.repo_verifier.repo_verifier_maven import RepoVerifierMaven
@@ -22,6 +22,7 @@ def verify_repo(
2222
version: str,
2323
reported_repo_url: str,
2424
reported_repo_fs: str,
25+
provenance_repo_url: str | None,
2526
build_tool: BaseBuildTool,
2627
) -> RepositoryVerificationResult:
2728
"""Verify whether the repository links back to the artifact.
@@ -38,6 +39,8 @@ def verify_repo(
3839
The reported repository URL.
3940
reported_repo_fs : str
4041
The reported repository filesystem path.
42+
provenance_repo_url : str | None
43+
The URL of the repository from a provenance file, or None if it, or the provenance, is not present.
4144
build_tool : BaseBuildTool
4245
The build tool used to build the package.
4346
@@ -47,7 +50,7 @@ def verify_repo(
4750
The result of the repository verification
4851
"""
4952
# TODO: Add support for other build tools.
50-
verifier_map: dict[type[BaseBuildTool], type[RepoVerifierBase]] = {
53+
verifier_map: dict[type[BaseBuildTool], type[RepoVerifierToolSpecific]] = {
5154
Maven: RepoVerifierMaven,
5255
Gradle: RepoVerifierGradle,
5356
# Poetry(): RepoVerifierPoetry,
@@ -60,16 +63,27 @@ def verify_repo(
6063

6164
verifier_cls = verifier_map.get(type(build_tool))
6265
if not verifier_cls:
63-
return RepositoryVerificationResult(
64-
status=RepositoryVerificationStatus.UNKNOWN, reason="unsupported_type", build_tool=build_tool
66+
# For unsupported types fallback to the default implementation that can verify based on the from-provenance
67+
# repository URL.
68+
verifier = RepoVerifierFromProvenance(
69+
namespace=namespace,
70+
name=name,
71+
version=version,
72+
reported_repo_url=reported_repo_url,
73+
reported_repo_fs=reported_repo_fs,
74+
provenance_repo_url=provenance_repo_url,
75+
build_tool=build_tool,
76+
)
77+
else:
78+
# Otherwise, use the correct repo verifier.
79+
verifier = verifier_cls(
80+
namespace=namespace,
81+
name=name,
82+
version=version,
83+
reported_repo_url=reported_repo_url,
84+
reported_repo_fs=reported_repo_fs,
85+
provenance_repo_url=provenance_repo_url,
6586
)
6687

67-
verifier = verifier_cls(
68-
namespace=namespace,
69-
name=name,
70-
version=version,
71-
reported_repo_url=reported_repo_url,
72-
reported_repo_fs=reported_repo_fs,
73-
)
74-
88+
# Perform verification.
7589
return verifier.verify_repo()

src/macaron/repo_verifier/repo_verifier_base.py

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved.
22
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
33

44
"""This module contains the base class and core data models for repository verification."""
@@ -94,10 +94,21 @@ class RepositoryVerificationResult:
9494
class RepoVerifierBase(abc.ABC):
9595
"""The base class to verify whether a reported repository links back to the artifact."""
9696

97-
@property
9897
@abc.abstractmethod
99-
def build_tool(self) -> BaseBuildTool:
100-
"""Define the build tool used to build the package."""
98+
def verify_repo(self) -> RepositoryVerificationResult:
99+
"""Verify whether the repository links back to the artifact.
100+
101+
Returns
102+
-------
103+
RepositoryVerificationResult
104+
The result of the repository verification
105+
"""
106+
107+
108+
class RepoVerifierFromProvenance(RepoVerifierBase):
109+
"""An implementation of the base verifier that verifies a repository if the URL comes from provenance."""
110+
111+
DEFAULT_REASON = "from_provenance"
101112

102113
def __init__(
103114
self,
@@ -106,6 +117,8 @@ def __init__(
106117
version: str,
107118
reported_repo_url: str,
108119
reported_repo_fs: str,
120+
provenance_repo_url: str | None,
121+
build_tool: BaseBuildTool,
109122
):
110123
"""Instantiate the class.
111124
@@ -121,19 +134,82 @@ def __init__(
121134
The URL of the repository reported by the publisher.
122135
reported_repo_fs : str
123136
The file system path of the reported repository.
137+
provenance_repo_url : str | None
138+
The URL of the repository from a provenance file, or None if it, or the provenance, is not present.
139+
build_tool : BaseBuildTool
140+
The build tool used to build the package.
124141
"""
125142
self.namespace = namespace
126143
self.name = name
127144
self.version = version
128145
self.reported_repo_url = reported_repo_url
129146
self.reported_repo_fs = reported_repo_fs
147+
self.provenance_repo_url = provenance_repo_url
148+
self.build_tool = build_tool
130149

131-
@abc.abstractmethod
132150
def verify_repo(self) -> RepositoryVerificationResult:
133-
"""Verify whether the repository links back to the artifact.
151+
"""Verify whether the repository links back to the artifact from the provenance URL."""
152+
if self.provenance_repo_url:
153+
return RepositoryVerificationResult(
154+
status=RepositoryVerificationStatus.PASSED,
155+
reason=RepoVerifierFromProvenance.DEFAULT_REASON,
156+
build_tool=self.build_tool,
157+
)
134158

135-
Returns
136-
-------
137-
RepositoryVerificationResult
138-
The result of the repository verification
159+
return RepositoryVerificationResult(
160+
status=RepositoryVerificationStatus.UNKNOWN, reason="unsupported_type", build_tool=self.build_tool
161+
)
162+
163+
164+
class RepoVerifierToolSpecific(RepoVerifierFromProvenance, abc.ABC):
165+
"""An abstract subclass of the repo verifier that provides and calls a per-tool verification function.
166+
167+
From-provenance verification is inherited from the parent class.
168+
"""
169+
170+
@property
171+
@abc.abstractmethod
172+
def specific_tool(self) -> BaseBuildTool:
173+
"""Define the build tool used to build the package."""
174+
175+
def __init__(
176+
self,
177+
namespace: str | None,
178+
name: str,
179+
version: str,
180+
reported_repo_url: str,
181+
reported_repo_fs: str,
182+
provenance_repo_url: str | None,
183+
):
184+
"""Instantiate the class.
185+
186+
Parameters
187+
----------
188+
namespace : str
189+
The namespace of the artifact.
190+
name : str
191+
The name of the artifact.
192+
version : str
193+
The version of the artifact.
194+
reported_repo_url : str
195+
The URL of the repository reported by the publisher.
196+
reported_repo_fs : str
197+
The file system path of the reported repository.
198+
provenance_repo_url : str | None
199+
The URL of the repository from a provenance file, or None if it, or the provenance, is not present.
139200
"""
201+
super().__init__(
202+
namespace, name, version, reported_repo_url, reported_repo_fs, provenance_repo_url, self.specific_tool
203+
)
204+
205+
def verify_repo(self) -> RepositoryVerificationResult:
206+
"""Verify the repository as per the base class method."""
207+
result = super().verify_repo()
208+
if result.status == RepositoryVerificationStatus.PASSED:
209+
return result
210+
211+
return self.verify_by_tool()
212+
213+
@abc.abstractmethod
214+
def verify_by_tool(self) -> RepositoryVerificationResult:
215+
"""Verify the repository using build tool specific methods."""

src/macaron/repo_verifier/repo_verifier_gradle.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
# Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved.
22
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
33

4-
"""This module contains code to verify whether a repository with Gradle build system can be linked back to the artifact."""
4+
"""This module contains code to verify whether a Gradle-based repository can be linked back to the artifact."""
55
import logging
66
from pathlib import Path
77

88
from macaron.artifact.maven import is_valid_maven_group_id
99
from macaron.repo_verifier.repo_verifier_base import (
1010
RepositoryVerificationResult,
1111
RepositoryVerificationStatus,
12-
RepoVerifierBase,
12+
RepoVerifierToolSpecific,
1313
find_file_in_repo,
1414
)
1515
from macaron.repo_verifier.repo_verifier_maven import RepoVerifierMaven
@@ -19,10 +19,10 @@
1919
logger = logging.getLogger(__name__)
2020

2121

22-
class RepoVerifierGradle(RepoVerifierBase):
22+
class RepoVerifierGradle(RepoVerifierToolSpecific):
2323
"""A class to verify whether a repository with Gradle build tool links back to the artifact."""
2424

25-
build_tool = Gradle()
25+
specific_tool = Gradle()
2626

2727
def __init__(
2828
self,
@@ -31,6 +31,7 @@ def __init__(
3131
version: str,
3232
reported_repo_url: str,
3333
reported_repo_fs: str,
34+
provenance_repo_url: str | None,
3435
):
3536
"""Initialize a RepoVerifierGradle instance.
3637
@@ -46,18 +47,21 @@ def __init__(
4647
The URL of the repository reported by the publisher.
4748
reported_repo_fs : str
4849
The file system path of the reported repository.
50+
provenance_repo_url : str | None
51+
The URL of the repository from a provenance file, or None if it, or the provenance, is not present.
4952
"""
50-
super().__init__(namespace, name, version, reported_repo_url, reported_repo_fs)
53+
super().__init__(namespace, name, version, reported_repo_url, reported_repo_fs, provenance_repo_url)
5154

5255
self.maven_verifier = RepoVerifierMaven(
5356
namespace=namespace,
5457
name=name,
5558
version=version,
5659
reported_repo_url=reported_repo_url,
5760
reported_repo_fs=reported_repo_fs,
61+
provenance_repo_url=provenance_repo_url,
5862
)
5963

60-
def verify_repo(self) -> RepositoryVerificationResult:
64+
def verify_by_tool(self) -> RepositoryVerificationResult:
6165
"""Verify whether the reported repository links back to the artifact.
6266
6367
Returns

src/macaron/repo_verifier/repo_verifier_maven.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
# Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved.
22
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
33

4-
"""This module contains code to verify whether a reported repository with Maven build system can be linked back to the artifact."""
4+
"""This module contains code to verify whether a reported Maven-based repository can be linked back to the artifact."""
55
import logging
66
from pathlib import Path
77
from urllib.parse import urlparse
@@ -10,7 +10,7 @@
1010
from macaron.repo_verifier.repo_verifier_base import (
1111
RepositoryVerificationResult,
1212
RepositoryVerificationStatus,
13-
RepoVerifierBase,
13+
RepoVerifierToolSpecific,
1414
find_file_in_repo,
1515
)
1616
from macaron.slsa_analyzer.build_tool import Maven
@@ -22,12 +22,12 @@
2222
logger = logging.getLogger(__name__)
2323

2424

25-
class RepoVerifierMaven(RepoVerifierBase):
25+
class RepoVerifierMaven(RepoVerifierToolSpecific):
2626
"""A class to verify whether a repository with Maven build tool links back to the artifact."""
2727

28-
build_tool = Maven()
28+
specific_tool = Maven()
2929

30-
def verify_repo(self) -> RepositoryVerificationResult:
30+
def verify_by_tool(self) -> RepositoryVerificationResult:
3131
"""Verify whether the reported repository links back to the Maven artifact.
3232
3333
Returns

src/macaron/slsa_analyzer/analyzer.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -514,8 +514,6 @@ def run_single(
514514
if artifact_hash:
515515
provenance_payload = self.get_github_attestation_payload(analyze_ctx, git_service, artifact_hash)
516516

517-
if parsed_purl is not None:
518-
self._verify_repository_link(parsed_purl, analyze_ctx)
519517
self._determine_package_registries(analyze_ctx, package_registries_info)
520518

521519
provenance_l3_verified = False
@@ -576,6 +574,9 @@ def run_single(
576574
# TODO Add release digest.
577575
)
578576

577+
if parsed_purl is not None:
578+
self._verify_repository_link(parsed_purl, analyze_ctx)
579+
579580
analyze_ctx.dynamic_data["force_analyze_source"] = force_analyze_source
580581

581582
if local_artifact_dirs:
@@ -1232,7 +1233,7 @@ def _verify_repository_link(self, parsed_purl: PackageURL, analyze_ctx: AnalyzeC
12321233
logger.debug("The repository is not available. Skipping the repository verification.")
12331234
return
12341235

1235-
if parsed_purl.namespace is None or parsed_purl.version is None:
1236+
if parsed_purl.version is None:
12361237
logger.debug("The PURL is not complete. Skipping the repository verification.")
12371238
return
12381239

@@ -1242,13 +1243,18 @@ def _verify_repository_link(self, parsed_purl: PackageURL, analyze_ctx: AnalyzeC
12421243

12431244
analyze_ctx.dynamic_data["repo_verification"] = []
12441245

1246+
provenance_repo_url = None
1247+
if provenance_info := analyze_ctx.dynamic_data["provenance_info"]:
1248+
provenance_repo_url = provenance_info.repository_url
1249+
12451250
for build_tool in build_tools:
12461251
verification_result = verify_repo(
12471252
namespace=parsed_purl.namespace,
12481253
name=parsed_purl.name,
12491254
version=parsed_purl.version,
12501255
reported_repo_url=analyze_ctx.component.repository.remote_path,
12511256
reported_repo_fs=analyze_ctx.component.repository.fs_path,
1257+
provenance_repo_url=provenance_repo_url,
12521258
build_tool=build_tool,
12531259
)
12541260
analyze_ctx.dynamic_data["repo_verification"].append(verification_result)

0 commit comments

Comments
 (0)