Skip to content

Commit 13206e5

Browse files
authored
feat: add a new check to map artifacts to pipelines (#471)
This PR adds a new check, `mcn_infer_artifact_pipeline_1` to detect a potential pipeline from which an artifact is published. When a verifiable provenance is found for an artifact, the result of this check can be discarded. Otherwise, we check whether a CI workflow run has automatically published the artifact. This check supports Maven artifacts built using Gradle or Maven and published on Maven Central only. Support for other registries and ecosystems will be added in the future. Signed-off-by: behnazh-w <[email protected]>
1 parent 5815139 commit 13206e5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2191
-752
lines changed

docs/source/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ the requirements that are currently supported by Macaron.
7474
* - 3
7575
- **Build as code** - If a trusted builder is not present, this requirement determines that the build definition and configuration executed by the build service is verifiably derived from text file definitions stored in a version control system.
7676
- Identify and validate the CI service(s) used to build and deploy/publish an artifact.
77+
* - 3
78+
- **Infer artifact publish pipeline** - When a provenance is not available, checks whether a CI workflow run has automatically published the artifact.
79+
- Identify a workflow run that has triggered the deploy step determined by the ``Build as code`` check.
7780
* - 3
7881
- **Provenance Level three** - Check whether the target has SLSA provenance level 3.
7982
- Use the `slsa-verifier <https://github.com/slsa-framework/slsa-verifier>`_ to attest to the subjects in the SLSA provenance that accompanies an artifact.

docs/source/pages/developers_guide/apidoc/macaron.slsa_analyzer.checks.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ macaron.slsa\_analyzer.checks.check\_result module
4949
:undoc-members:
5050
:show-inheritance:
5151

52+
macaron.slsa\_analyzer.checks.infer\_artifact\_pipeline\_check module
53+
---------------------------------------------------------------------
54+
55+
.. automodule:: macaron.slsa_analyzer.checks.infer_artifact_pipeline_check
56+
:members:
57+
:undoc-members:
58+
:show-inheritance:
59+
5260
macaron.slsa\_analyzer.checks.provenance\_available\_check module
5361
-----------------------------------------------------------------
5462

docs/source/pages/developers_guide/apidoc/macaron.slsa_analyzer.package_registry.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ macaron.slsa\_analyzer.package\_registry.jfrog\_maven\_registry module
1717
:undoc-members:
1818
:show-inheritance:
1919

20+
macaron.slsa\_analyzer.package\_registry.maven\_central\_registry module
21+
------------------------------------------------------------------------
22+
23+
.. automodule:: macaron.slsa_analyzer.package_registry.maven_central_registry
24+
:members:
25+
:undoc-members:
26+
:show-inheritance:
27+
2028
macaron.slsa\_analyzer.package\_registry.package\_registry module
2129
-----------------------------------------------------------------
2230

docs/source/pages/supported_technologies/index.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ Package Registries
4141
* - `JFrog Artifactory <https://jfrog.com/artifactory>`_
4242
- Only projects built with Gradle and publishing to a JFrog Artifactory repo following `Maven layout <https://maven.apache.org/repository/layout.html>`_
4343
- :doc:`page </pages/supported_technologies/jfrog>`
44+
* - `Maven Central Artifactory <https://central.sonatype.com>`_
45+
- Only projects built with Gradle or Maven and published to the Maven Central Artifactory.
46+
- :doc:`page </pages/supported_technologies/maven_central>`
4447

4548
-----------
4649
Provenances
@@ -70,3 +73,4 @@ See also
7073

7174
jfrog
7275
witness
76+
maven_central
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.. Copyright (c) 2023 - 2023, Oracle and/or its affiliates. All rights reserved.
2+
.. Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
3+
4+
=============
5+
Maven Central
6+
=============

scripts/dev_scripts/integration_tests.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,16 @@ python $COMPARE_JSON_OUT $JSON_RESULT $JSON_EXPECTED || log_fail
267267

268268
# python $COMPARE_DEPS $DEP_RESULT $DEP_EXPECTED || log_fail
269269

270+
echo -e "\n----------------------------------------------------------------------------------"
271+
echo "google/guava: Analyzing with PURL and repository path without dependency resolution."
272+
echo -e "----------------------------------------------------------------------------------\n"
273+
JSON_EXPECTED=$WORKSPACE/tests/e2e/expected_results/purl/com_google_guava/guava/guava.json
274+
JSON_RESULT=$WORKSPACE/output/reports/maven/com_google_guava/guava/guava.json
275+
$RUN_MACARON analyze -purl pkg:maven/com.google.guava/[email protected]?type=jar -rp https://github.com/google/guava -b master -d d8633ac8539dae52c8361f79c7a0dbd9ad6dd2c4 --skip-deps || log_fail
276+
277+
python $COMPARE_JSON_OUT $JSON_RESULT $JSON_EXPECTED || log_fail
278+
279+
270280
# Running Macaron using local paths.
271281
echo -e "\n=================================================================================="
272282
echo "Run integration tests with local paths for apache/maven..."

src/macaron/config/defaults.ini

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,10 +375,20 @@ predicate_types =
375375
https://witness.testifysec.com/attestation-collection/v0.1
376376
artifact_extensions =
377377
jar
378-
379378
# Package registries.
379+
[package_registry]
380+
# The allowed time range (in seconds) from a deploy workflow run start time to publish time.
381+
publish_time_range = 3600
382+
380383
# [package_registry.jfrog.maven]
381384
# In this example, the Maven repo can be accessed at `https://internal.registry.org/repo-name`.
382385
# hostname = internal.registry.org
383386
# repo = repo-name
384387
# download_timeout = 120
388+
389+
[package_registry.maven_central]
390+
# Maven Central host name.
391+
hostname = search.maven.org
392+
# The search REST API. See https://central.sonatype.org/search/rest-api-guide/
393+
search_endpoint = solrsearch/select
394+
request_timeout = 20

src/macaron/dependency_analyzer/cyclonedx.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ def convert_components_to_artifacts(
174174
version=component.get("version") or "",
175175
group=component.get("group") or "",
176176
name=component.get("name") or "",
177+
purl=component.get("purl") or "",
177178
url="",
178179
note="",
179180
available=SCMStatus.AVAILABLE,

src/macaron/dependency_analyzer/dependency_resolver.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class DependencyInfo(TypedDict):
3434
version: str
3535
group: str
3636
name: str
37+
purl: str
3738
url: str
3839
note: str
3940
available: SCMStatus
@@ -262,6 +263,7 @@ def merge_configs(
262263
Configuration(
263264
{
264265
"id": key,
266+
"purl": value.get("purl"),
265267
"path": value.get("url"),
266268
"branch": "",
267269
"digest": "",

src/macaron/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,7 @@ class InvalidPURLError(MacaronError):
5050

5151
class DuplicateError(MacaronError):
5252
"""The class for errors for duplicated data."""
53+
54+
55+
class InvalidHTTPResponseError(MacaronError):
56+
"""Happens when the HTTP response is invalid or unexpected."""

src/macaron/parsers/bashparser.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ class BashCommands(TypedDict):
3333
"""CI service type."""
3434
commands: list[list[str]]
3535
"""Parsed bash commands."""
36+
job_name: str
37+
"""The name of the job where commands were called."""
38+
step_name: str
39+
"""The name of the step where commands were called."""
3640

3741

3842
def parse_file(file_path: str, macaron_path: str = "") -> dict:
@@ -119,6 +123,8 @@ def extract_bash_from_ci(
119123
recursive: bool = False,
120124
repo_path: str = "",
121125
working_dir: str = "",
126+
job_name: str = "",
127+
step_name: str = "",
122128
) -> Iterable[BashCommands]:
123129
"""Parse the bash scripts triggered from CI.
124130
@@ -140,6 +146,10 @@ def extract_bash_from_ci(
140146
working_dir : str
141147
The working directory from which the bash script has run.
142148
Empty value is considered as the root of the repo.
149+
job_name: str
150+
The name of the job where commands were called.
151+
step_name: str
152+
The name of the step where commands were called.
143153
144154
Yields
145155
------
@@ -152,7 +162,14 @@ def extract_bash_from_ci(
152162
parsed_parent = parse(bash_content)
153163
caller_commands = parsed_parent.get("commands", [])
154164
if caller_commands:
155-
yield BashCommands(caller_path=ci_file, CI_path=ci_file, CI_type=ci_type, commands=caller_commands)
165+
yield BashCommands(
166+
caller_path=ci_file,
167+
CI_path=ci_file,
168+
CI_type=ci_type,
169+
commands=caller_commands,
170+
job_name=job_name,
171+
step_name=step_name,
172+
)
156173

157174
# Parse the bash script files called from the current script.
158175
if recursive and repo_path:
@@ -170,4 +187,6 @@ def extract_bash_from_ci(
170187
CI_path=ci_file,
171188
CI_type=ci_type,
172189
commands=callee_commands,
190+
job_name=job_name,
191+
step_name=step_name,
173192
)

src/macaron/slsa_analyzer/checks/build_as_code_check.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def _check_build_tool(
188188
ctx.component.repository.branch_name,
189189
ctx.component.repository.commit_sha,
190190
ctx.component.repository.commit_date,
191-
os.path.basename(callee.caller_path),
191+
callee.caller_path,
192192
)
193193

194194
# TODO: include in the justification multiple cases of external action usage
@@ -253,7 +253,7 @@ def _check_build_tool(
253253
ctx.component.repository.branch_name,
254254
ctx.component.repository.commit_sha,
255255
ctx.component.repository.commit_date,
256-
os.path.basename(bash_cmd["CI_path"]),
256+
bash_cmd["CI_path"],
257257
)
258258

259259
justification_cmd: list[str | dict[str, str]] = [
@@ -283,6 +283,8 @@ def _check_build_tool(
283283
"sha1"
284284
] = ctx.component.repository.commit_sha
285285
predicate["invocation"]["configSource"]["entryPoint"] = trigger_link
286+
predicate["buildConfig"]["jobID"] = bash_cmd["job_name"]
287+
predicate["buildConfig"]["stepID"] = bash_cmd["step_name"]
286288
predicate["metadata"]["buildInvocationId"] = html_url
287289
check_result["result_tables"].append(
288290
BuildAsCodeFacts(

0 commit comments

Comments
 (0)