Skip to content

Commit 3be6cce

Browse files
committed
feat: add support for JFrog Maven package registry
Signed-off-by: Nathan Nguyen <[email protected]>
1 parent 2062e7e commit 3be6cce

File tree

16 files changed

+606
-18
lines changed

16 files changed

+606
-18
lines changed

src/macaron/__main__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from macaron.policy_engine.policy_engine import run_policy_engine, show_prelude
2323
from macaron.slsa_analyzer.analyzer import Analyzer
2424
from macaron.slsa_analyzer.git_service import GIT_SERVICES
25+
from macaron.slsa_analyzer.package_registry import PACKAGE_REGISTRIES
2526

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

@@ -142,6 +143,8 @@ def perform_action(action_args: argparse.Namespace) -> None:
142143
try:
143144
for git_service in GIT_SERVICES:
144145
git_service.load_defaults()
146+
for package_registry in PACKAGE_REGISTRIES:
147+
package_registry.load_defaults()
145148
except ConfigurationError as error:
146149
logger.error(error)
147150
sys.exit(os.EX_USAGE)

src/macaron/config/defaults.ini

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,9 @@ provenance_extensions =
337337
max_download_size = 70000000
338338
# This is the timeout (in seconds) to run the SLSA verifier.
339339
timeout = 120
340+
341+
# Package registries.
342+
# [package_registry.jfrog.maven]
343+
# In this example, the Maven package registry can be accessed at https://internal.registry.org/maven-repo.
344+
# repo_url = https://internal.registry.org
345+
# repo_key = maven-repo

src/macaron/slsa_analyzer/analyze_context.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from macaron.slsa_analyzer.slsa_req import ReqName, SLSAReq, get_requirements_dict
2222
from macaron.slsa_analyzer.specs.build_spec import BuildSpec
2323
from macaron.slsa_analyzer.specs.ci_spec import CIInfo
24+
from macaron.slsa_analyzer.specs.package_registry_data import PackageRegistryData
2425

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

@@ -38,6 +39,8 @@ class ChecksOutputs(TypedDict):
3839
"""True if we cannot find the provenance and Macaron need to infer the provenance."""
3940
expectation: Expectation | None
4041
"""The expectation to verify the provenance for this repository."""
42+
package_registries: list[PackageRegistryData]
43+
"""The package registries for this repository."""
4144

4245

4346
class AnalyzeContext:
@@ -118,6 +121,7 @@ def __init__(
118121
git_service=NoneGitService(),
119122
build_spec=BuildSpec(tools=[]),
120123
ci_services=[],
124+
package_registries=[],
121125
is_inferred_prov=True,
122126
expectation=None,
123127
)
@@ -134,7 +138,7 @@ def provenances(self) -> dict:
134138
"""
135139
try:
136140
ci_services = self.dynamic_data["ci_services"]
137-
result = {}
141+
result = {} # key: ci service's name; values: list of provenances (in Json)
138142
for ci_info in ci_services:
139143
result[ci_info["service"].name] = ci_info["provenances"]
140144
return result

src/macaron/slsa_analyzer/analyzer.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@
4141
from macaron.slsa_analyzer.database_store import store_analysis_to_db, store_analyze_context_to_db
4242
from macaron.slsa_analyzer.git_service import GIT_SERVICES, BaseGitService
4343
from macaron.slsa_analyzer.git_service.base_git_service import NoneGitService
44+
from macaron.slsa_analyzer.package_registry import PACKAGE_REGISTRIES
4445
from macaron.slsa_analyzer.provenance.expectations.expectation_registry import ExpectationRegistry
4546
from macaron.slsa_analyzer.registry import registry
4647
from macaron.slsa_analyzer.specs.ci_spec import CIInfo
4748
from macaron.slsa_analyzer.specs.inferred_provenance import Provenance
49+
from macaron.slsa_analyzer.specs.package_registry_data import PackageRegistryData
4850

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

@@ -664,7 +666,7 @@ def perform_checks(self, analyze_ctx: AnalyzeContext) -> dict[str, CheckResult]:
664666
ci_service.load_defaults()
665667
ci_service.set_api_client()
666668

667-
if ci_service.is_detected(analyze_ctx.git_obj.path):
669+
if ci_service.is_detected(analyze_ctx.git_obj.path, analyze_ctx.dynamic_data["git_service"]):
668670
logger.info("The repo uses %s CI service.", ci_service.name)
669671

670672
# Parse configuration files and generate IRs.
@@ -684,6 +686,19 @@ def perform_checks(self, analyze_ctx: AnalyzeContext) -> dict[str, CheckResult]:
684686
)
685687
)
686688

689+
# Determine the package registries.
690+
# We match the repo against package registries through build tools.
691+
build_tools = analyze_ctx.dynamic_data["build_spec"]["tools"]
692+
for package_registry in PACKAGE_REGISTRIES:
693+
for build_tool in build_tools:
694+
if package_registry.is_detected(build_tool):
695+
analyze_ctx.dynamic_data["package_registries"].append(
696+
PackageRegistryData(
697+
build_tool=build_tool,
698+
package_registry=package_registry,
699+
)
700+
)
701+
687702
# TODO: Get the list of skipped checks from user configuration
688703
skipped_checks: list[SkippedInfo] = []
689704

src/macaron/slsa_analyzer/build_tool/gradle.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import logging
1010
import os
11+
import subprocess # nosec B404
1112

1213
from macaron.config.defaults import defaults
1314
from macaron.config.global_config import global_config
@@ -135,3 +136,31 @@ def get_dep_analyzer(self, repo_path: str) -> CycloneDxGradle:
135136
)
136137

137138
raise DependencyAnalyzerError(f"Unsupported SBOM generator for Gradle: {tool_name}.")
139+
140+
def get_group_id(self, project_path: str) -> str | None:
141+
"""Get the group id of a Java repository.
142+
143+
Parameters
144+
----------
145+
project_path : str
146+
Path to the repository.
147+
"""
148+
try:
149+
result = subprocess.run( # nosec B603
150+
["./gradlew", "properties"],
151+
capture_output=True,
152+
cwd=project_path,
153+
check=False,
154+
)
155+
except (subprocess.CalledProcessError, OSError) as error:
156+
logger.info("Could not capture the group id of the repo at %s", project_path)
157+
logger.debug("Error: %s", error)
158+
return None
159+
160+
lines = result.stdout.decode().split("\n")
161+
for line in lines:
162+
if line.startswith("group: "):
163+
return line.replace("group: ", "")
164+
165+
logger.debug("Could not capture the group id of the repo at %s", project_path)
166+
return None

src/macaron/slsa_analyzer/checks/provenance_available_check.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@
1313
from macaron.database.database_manager import ORMBase
1414
from macaron.database.table_definitions import CheckFactsTable
1515
from macaron.slsa_analyzer.analyze_context import AnalyzeContext
16+
from macaron.slsa_analyzer.build_tool.gradle import Gradle
1617
from macaron.slsa_analyzer.checks.base_check import BaseCheck
1718
from macaron.slsa_analyzer.checks.check_result import CheckResult, CheckResultType
1819
from macaron.slsa_analyzer.ci_service.base_ci_service import NoneCIService
20+
from macaron.slsa_analyzer.package_registry import JFrogMavenRegistry
1921
from macaron.slsa_analyzer.registry import registry
2022
from macaron.slsa_analyzer.slsa_req import ReqName
23+
from macaron.slsa_analyzer.specs.package_registry_data import PackageRegistryData
2124

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

@@ -82,6 +85,63 @@ def run_check(self, ctx: AnalyzeContext, check_result: CheckResult) -> CheckResu
8285
CheckResultType
8386
The result type of the check (e.g. PASSED).
8487
"""
88+
provenance_extensions = defaults.get_list(
89+
"slsa.verifier",
90+
"provenance_extensions",
91+
fallback=["intoto.jsonl"],
92+
)
93+
94+
# We look for the provenances in the package registries first, then CI services.
95+
package_registry_data_entries = ctx.dynamic_data["package_registries"]
96+
97+
for package_registry_data_entry in package_registry_data_entries:
98+
match package_registry_data_entry:
99+
case PackageRegistryData(
100+
build_tool=Gradle() as gradle,
101+
package_registry=JFrogMavenRegistry() as jfrog_registry,
102+
):
103+
group_id = gradle.get_group_id(ctx.repo_path)
104+
if not group_id:
105+
continue
106+
107+
artifact_ids = jfrog_registry.get_artifact_ids(group_id)
108+
109+
provenance_assets = []
110+
111+
for artifact_id in artifact_ids:
112+
latest_version = jfrog_registry.get_latest_version(group_id, artifact_id)
113+
if not latest_version:
114+
continue
115+
116+
provenance_filenames = jfrog_registry.retrieve_provenance_filenames(
117+
group_id,
118+
artifact_id,
119+
latest_version,
120+
provenance_extensions,
121+
)
122+
123+
for provenance_filename in provenance_filenames:
124+
provenance_assets.append(
125+
{
126+
"asset_name": provenance_filename,
127+
"asset_url": jfrog_registry.get_asset_url(
128+
group_id,
129+
artifact_id,
130+
latest_version,
131+
provenance_filename,
132+
),
133+
}
134+
)
135+
136+
package_registry_data_entry.provenance_assets.extend(provenance_assets)
137+
check_result["justification"].append("Found provenance in release assets:")
138+
check_result["justification"].extend([prov["asset_name"] for prov in provenance_assets])
139+
check_result["result_tables"] = [ProvenanceAvailableTable(**prov) for prov in provenance_assets]
140+
141+
return CheckResultType.PASSED
142+
case _:
143+
continue
144+
85145
ci_services = ctx.dynamic_data["ci_services"]
86146
for ci_info in ci_services:
87147
ci_service = ci_info["service"]
@@ -95,7 +155,7 @@ def run_check(self, ctx: AnalyzeContext, check_result: CheckResult) -> CheckResu
95155
ci_info["latest_release"] = release
96156

97157
# Get the provenance assets.
98-
for prov_ext in defaults.get_list("slsa.verifier", "provenance_extensions"):
158+
for prov_ext in provenance_extensions:
99159
assets = ci_service.api_client.get_assets(release, ext=prov_ext)
100160
if not assets:
101161
continue

0 commit comments

Comments
 (0)