Skip to content

Commit

Permalink
feat: find SLSA provenance v0.2 published on npm registry (#551)
Browse files Browse the repository at this point in the history
This PR adds a feature to detect SLSA provenance v0.2 for npm packages that are published on the npm registry. 

Note that
- This PR does not add verification of these provenances. The verification will be handled separately.
- Support for SLSA provenance v1 will be added in future.
- A test for an npm package is added to the integration tests. If making REST Calls to the npm registry is not possible, developers can skip that test by setting NO_NPM environment variable before running make integration-test.

Signed-off-by: behnazh-w <[email protected]>
  • Loading branch information
behnazh-w authored Nov 23, 2023
1 parent 470dd74 commit 0c3bdda
Show file tree
Hide file tree
Showing 21 changed files with 1,086 additions and 97 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ test-go:
go test ./golang/...

# Run the integration tests.
# Note: to disable npm tests set `NO_NPM` environment variable to `TRUE`.
.PHONY: integration-test
integration-test:
scripts/dev_scripts/integration_tests.sh $(REPO_PATH) "${HOME}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ macaron.slsa\_analyzer.package\_registry.maven\_central\_registry module
:undoc-members:
:show-inheritance:

macaron.slsa\_analyzer.package\_registry.npm\_registry module
-------------------------------------------------------------

.. automodule:: macaron.slsa_analyzer.package_registry.npm_registry
:members:
:undoc-members:
:show-inheritance:

macaron.slsa\_analyzer.package\_registry.package\_registry module
-----------------------------------------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Subpackages

macaron.slsa_analyzer.provenance.expectations
macaron.slsa_analyzer.provenance.intoto
macaron.slsa_analyzer.provenance.slsa
macaron.slsa_analyzer.provenance.witness

Submodules
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
macaron.slsa\_analyzer.provenance.slsa package
==============================================

.. automodule:: macaron.slsa_analyzer.provenance.slsa
:members:
:undoc-members:
:show-inheritance:
11 changes: 8 additions & 3 deletions docs/source/pages/supported_technologies/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@ Package Registries
- Projects built with Gradle and published to a JFrog Artifactory repo following `Maven layout <https://maven.apache.org/repository/layout.html>`_
- :doc:`page </pages/supported_technologies/jfrog>`
* - `Maven Central Artifactory <https://central.sonatype.com>`_
- Projects built with Gradle or Maven and published to the Maven Central Artifactory.
- Projects built with Gradle or Maven and published on the Maven Central Artifactory.
- :doc:`page </pages/supported_technologies/maven_central>`
* - `npm Registry <https://registry.npmjs.org>`_
- Projects built with npm or Yarn and published on the npm registry.
- :doc:`page </pages/supported_technologies/npm_registry>`

-----------
Provenances
Expand All @@ -86,8 +89,9 @@ Provenances
- Documentation
* - `SLSA <https://slsa.dev>`_
-
* `SLSA provenance version 0.2 <https://slsa.dev/spec/v0.2/provenance>`_.
* The provenance should be published as a GitHub release asset
* | `SLSA provenance version 0.2 <https://slsa.dev/spec/v0.2/provenance>`_. The provenance should be published in one of the following ways:
| - as a GitHub release asset.
| - on the `npm registry <https://registry.npmjs.org>`_.
- :doc:`page </pages/supported_technologies/jfrog>`
* - `Witness <https://github.com/testifysec/witness>`_
-
Expand All @@ -106,3 +110,4 @@ See also
jfrog
witness
maven_central
npm_registry
6 changes: 6 additions & 0 deletions docs/source/pages/supported_technologies/npm_registry.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.. Copyright (c) 2023 - 2023, Oracle and/or its affiliates. All rights reserved.
.. Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
============
npm Registry
============
18 changes: 18 additions & 0 deletions scripts/dev_scripts/integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ function check_or_update_expected_output() {
fi
}

# Check if npm-related tests should be disabled.
if [[ "$NO_NPM" == "TRUE" ]]; then
echo "Note: NO_NPM environment variable is set to TRUE, so npm tests will be skipped."
fi
NO_NPM_TEST=$NO_NPM


function log_fail() {
printf "Error: FAILED integration test (line ${BASH_LINENO}) %s\n" $@
RESULT_CODE=1
Expand Down Expand Up @@ -66,6 +73,17 @@ $RUN_MACARON analyze -rp https://github.com/micronaut-projects/micronaut-core -b

check_or_update_expected_output $COMPARE_JSON_OUT $JSON_RESULT $JSON_EXPECTED || log_fail

if [[ -z "$NO_NPM_TEST" ]]; then
echo -e "\n----------------------------------------------------------------------------------"
echo "sigstore/[email protected]: Analyzing the PURL when automatic dependency resolution is skipped."
echo -e "----------------------------------------------------------------------------------\n"
JSON_EXPECTED=$WORKSPACE/tests/e2e/expected_results/purl/npm/sigstore/mock/mock.json
JSON_RESULT=$WORKSPACE/output/reports/npm/_sigstore/mock/mock.json
$RUN_MACARON analyze -purl pkg:npm/@sigstore/[email protected] -rp https://github.com/sigstore/sigstore-js -b main -d ebdcfdfbdfeb9c9aeee6df53674ef230613629f5 --skip-deps || log_fail

check_or_update_expected_output $COMPARE_JSON_OUT $JSON_RESULT $JSON_EXPECTED || log_fail
fi

echo -e "\n----------------------------------------------------------------------------------"
echo "gitlab.com/tinyMediaManager/tinyMediaManager: Analyzing the repo path and the branch name when automatic dependency resolution is skipped."
echo -e "----------------------------------------------------------------------------------\n"
Expand Down
9 changes: 9 additions & 0 deletions src/macaron/config/defaults.ini
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,12 @@ hostname = search.maven.org
# The search REST API. See https://central.sonatype.org/search/rest-api-guide/
search_endpoint = solrsearch/select
request_timeout = 20

[package_registry.npm]
# Set `enabled=False` to disable making REST API calls to the npm registry.
enabled = True
# npm registry host name.
hostname = registry.npmjs.org
# The attestation REST API.
attestation_endpoint = -/npm/v1/attestations
request_timeout = 20
17 changes: 11 additions & 6 deletions src/macaron/slsa_analyzer/analyze_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import logging
import os
from collections import defaultdict
from typing import TypedDict

from macaron.database.table_definitions import Component, SLSALevel
Expand All @@ -16,6 +17,8 @@
from macaron.slsa_analyzer.git_service.base_git_service import NoneGitService
from macaron.slsa_analyzer.levels import SLSALevels
from macaron.slsa_analyzer.provenance.expectations.expectation import Expectation
from macaron.slsa_analyzer.provenance.intoto.v01 import InTotoV01Statement
from macaron.slsa_analyzer.provenance.intoto.v1 import InTotoV1Statement
from macaron.slsa_analyzer.slsa_req import ReqName, SLSAReqStatus, create_requirement_status_dict
from macaron.slsa_analyzer.specs.build_spec import BuildSpec
from macaron.slsa_analyzer.specs.ci_spec import CIInfo
Expand Down Expand Up @@ -91,25 +94,27 @@ def __init__(
)

@property
def provenances(self) -> dict:
def provenances(self) -> dict[str, list[InTotoV01Statement | InTotoV1Statement]]:
"""Return the provenances data as a dictionary.
Returns
-------
dict
dict[str, list[InTotoV01Statement | InTotoV1Statement] ]
A dictionary in which each key is a CI service's name and each value is
the corresponding provenance payload.
"""
try:
ci_services = self.dynamic_data["ci_services"]
result = {}

# By default, initialize every key with an empty list.
result: dict[str, list[InTotoV01Statement | InTotoV1Statement]] = defaultdict(lambda: [])
for ci_info in ci_services:
result[ci_info["service"].name] = [payload.statement for payload in ci_info["provenances"]]
result[ci_info["service"].name].extend(payload.statement for payload in ci_info["provenances"])
package_registry_entries = self.dynamic_data["package_registries"]
for package_registry_entry in package_registry_entries:
result[package_registry_entry.package_registry.name] = [
result[package_registry_entry.package_registry.name].extend(
provenance.payload.statement for provenance in package_registry_entry.provenances
]
)
return result
except KeyError:
return {}
Expand Down
2 changes: 2 additions & 0 deletions src/macaron/slsa_analyzer/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,8 @@ def to_analysis_target(config: Configuration, available_domains: list[str]) -> A
parsed_purl = config.get_value("purl")
else:
try:
# Note that PackageURL.from_string sanitizes the unsafe characters in the purl string,
# which is user-controllable, by calling urllib's `urlsplit` function.
parsed_purl = PackageURL.from_string(config.get_value("purl"))
except ValueError as error:
raise InvalidPURLError(f"Invalid input PURL: {config.get_value('purl')}") from error
Expand Down
Loading

0 comments on commit 0c3bdda

Please sign in to comment.