From 380c7d5f6ab200423e931abe9f50add044c0ac18 Mon Sep 17 00:00:00 2001 From: Ben Selwyn-Smith Date: Fri, 9 Feb 2024 16:47:09 +1000 Subject: [PATCH] feat: use repository url provided as input for finding a commit (#622) This PR allows a user to provide to the command line: a repository URL with no digest alongside a PURL with a version. Signed-off-by: Ben Selwyn-Smith --- docs/source/pages/using.rst | 12 +- scripts/dev_scripts/integration_tests.sh | 9 + src/macaron/__main__.py | 26 +- .../tinyMediaManager.json | 253 ++++++++++++++++++ 4 files changed, 288 insertions(+), 12 deletions(-) create mode 100644 tests/e2e/expected_results/purl/org_tinymediamanager/tinyMediaManager.json diff --git a/docs/source/pages/using.rst b/docs/source/pages/using.rst index 773fb6833..28e8b9a50 100644 --- a/docs/source/pages/using.rst +++ b/docs/source/pages/using.rst @@ -125,13 +125,19 @@ Run the analysis using the PURL string as follows: ./run_macaron.sh analyze -purl -You can also provide the PURL string together with the repository path. In this case, the PURL string will be used as the unique identifier for the analysis target: +You can also provide the PURL string together with the repository path. In this case, the PURL string will be used as the unique identifier for the analysis target. If providing a PURL with a version, providing the repository path as well is sufficient for analysis to take place. If providing a PURL without a version, the branch and digest must also be provided alongside the repository path. Examples of both use cases follow. + +Analyzing a PURL (with an included version) and a repository path: .. code-block:: shell - ./run_macaron.sh analyze -purl -rp -b -d + ./run_macaron.sh analyze -purl -rp + +Analyzing a PURL (without an included version) and a repository path (with a digest and branch): -.. note:: When providing the PURL and the repository path, both the branch name and commit digest must be provided as well. +.. code-block:: shell + + ./run_macaron.sh analyze -purl -rp -b -d '''''''''''''''''''''''''''''''''''''' Providing an artifact as a PURL string diff --git a/scripts/dev_scripts/integration_tests.sh b/scripts/dev_scripts/integration_tests.sh index 2e17c4f18..2f3230c76 100755 --- a/scripts/dev_scripts/integration_tests.sh +++ b/scripts/dev_scripts/integration_tests.sh @@ -400,6 +400,15 @@ $RUN_MACARON analyze -purl pkg:maven/com.google.guava/guava@32.1.2-jre?type=jar check_or_update_expected_output $COMPARE_JSON_OUT $JSON_RESULT $JSON_EXPECTED || log_fail +echo -e "\n----------------------------------------------------------------------------------" +echo "org.tinymediamanager/tinyMediaManager: Analyzing the purl with a version, and a provided repo with no commit." +echo -e "----------------------------------------------------------------------------------\n" +JSON_EXPECTED=$WORKSPACE/tests/e2e/expected_results/purl/org_tinymediamanager/tinyMediaManager.json +JSON_RESULT=$WORKSPACE/output/reports/maven/org_tinymediamanager/tinyMediaManager/tinyMediaManager.json +$RUN_MACARON analyze -purl pkg:maven/org.tinymediamanager/tinyMediaManager@4.3.13 -rp https://gitlab.com/tinyMediaManager/tinyMediaManager --skip-deps || log_fail + +check_or_update_expected_output $COMPARE_JSON_OUT $JSON_RESULT $JSON_EXPECTED || log_fail + # Running Macaron using local paths. echo -e "\n==================================================================================" diff --git a/src/macaron/__main__.py b/src/macaron/__main__.py index 1dcf11b25..766f07c5a 100644 --- a/src/macaron/__main__.py +++ b/src/macaron/__main__.py @@ -11,6 +11,7 @@ from importlib import metadata as importlib_metadata from jinja2 import Environment, FileSystemLoader, select_autoescape +from packageurl import PackageURL import macaron from macaron.config.defaults import create_defaults, load_defaults @@ -92,15 +93,22 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None branch = analyzer_single_args.branch digest = analyzer_single_args.digest - if repo_path and purl and not digest: - # To provide the purl together with the repository path, the user must specify the branch and commit - # digest. - logger.error( - "Please provide the commit digest for the repo at %s that matches to the PURL string %s.", - repo_path, - purl, - ) - sys.exit(os.EX_USAGE) + if repo_path and purl: + # To provide the purl together with the repository path, the user must specify the commit digest unless the + # purl has a version. + try: + purl_object = PackageURL.from_string(purl) + except ValueError as error: + logger.debug("Could not parse PURL: %s", error) + sys.exit(os.EX_USAGE) + if not (purl_object.version or digest): + logger.error( + "Please provide the commit digest for the repo at %s that matches to the PURL string %s. Or " + "include the version in the PURL", + repo_path, + purl, + ) + sys.exit(os.EX_USAGE) # We need to use empty strings when the input values are of None type. This is because this dictionary will be # passed into the Configuration instance, where the existing values in Configuration.options are replaced by diff --git a/tests/e2e/expected_results/purl/org_tinymediamanager/tinyMediaManager.json b/tests/e2e/expected_results/purl/org_tinymediamanager/tinyMediaManager.json new file mode 100644 index 000000000..b705c7c30 --- /dev/null +++ b/tests/e2e/expected_results/purl/org_tinymediamanager/tinyMediaManager.json @@ -0,0 +1,253 @@ +{ + "metadata": { + "timestamps": "2024-02-02 15:39:30", + "has_passing_check": true + }, + "target": { + "info": { + "full_name": "pkg:maven/org.tinymediamanager/tinyMediaManager@4.3.13", + "local_cloned_path": "git_repos/gitlab_com/tinyMediaManager/tinyMediaManager", + "remote_path": "https://gitlab.com/tinyMediaManager/tinyMediaManager", + "branch": null, + "commit_hash": "968745f6b9d592af053f0ef5610357435a0e3b5a", + "commit_date": "2023-08-19T11:27:15+00:00" + }, + "provenances": { + "is_inferred": true, + "content": { + "gitlab_ci": [ + { + "_type": "https://in-toto.io/Statement/v0.1", + "subject": [], + "predicateType": "https://slsa.dev/provenance/v0.2", + "predicate": { + "builder": { + "id": "" + }, + "buildType": "", + "invocation": { + "configSource": { + "uri": "", + "digest": { + "sha1": "" + }, + "entryPoint": "" + }, + "parameters": {}, + "environment": {} + }, + "buildConfig": { + "jobID": "", + "stepID": "" + }, + "metadata": { + "buildInvocationId": "", + "buildStartedOn": "", + "buildFinishedOn": "", + "completeness": { + "parameters": "false", + "environment": "false", + "materials": "false" + }, + "reproducible": "false" + }, + "materials": [ + { + "uri": "", + "digest": {} + } + ] + } + } + ], + "Maven Central Registry": [] + } + }, + "checks": { + "summary": { + "DISABLED": 0, + "FAILED": 8, + "PASSED": 2, + "SKIPPED": 0, + "UNKNOWN": 0 + }, + "results": [ + { + "check_id": "mcn_build_script_1", + "check_description": "Check if the target repo has a valid build script.", + "slsa_requirements": [ + "Scripted Build - SLSA Level 1" + ], + "justification": [ + "The target repository uses build tool maven.", + "The target repository uses build tool docker." + ], + "result_type": "PASSED" + }, + { + "check_id": "mcn_version_control_system_1", + "check_description": "Check whether the target repo uses a version control system.", + "slsa_requirements": [ + "Version controlled - SLSA Level 2" + ], + "justification": [ + { + "This is a Git repository": "https://gitlab.com/tinyMediaManager/tinyMediaManager" + } + ], + "result_type": "PASSED" + }, + { + "check_id": "mcn_build_as_code_1", + "check_description": "The build definition and configuration executed by the build service is verifiably derived from text file definitions stored in a version control system.", + "slsa_requirements": [ + "Build as code - SLSA Level 3" + ], + "justification": [ + "The target repository does not use maven to deploy.", + "The target repository does not use docker to deploy." + ], + "result_type": "FAILED" + }, + { + "check_id": "mcn_build_service_1", + "check_description": "Check if the target repo has a valid build service.", + "slsa_requirements": [ + "Build service - SLSA Level 2" + ], + "justification": [ + "The target repository does not have a build service for maven.", + "The target repository does not have a build service for docker.", + "The target repository does not have a build service for at least one build tool." + ], + "result_type": "FAILED" + }, + { + "check_id": "mcn_infer_artifact_pipeline_1", + "check_description": "Detects potential pipelines from which an artifact is published.", + "slsa_requirements": [ + "Build as code - SLSA Level 3" + ], + "justification": [ + "Check mcn_infer_artifact_pipeline_1 is set to FAILED because mcn_build_as_code_1 FAILED." + ], + "result_type": "FAILED" + }, + { + "check_id": "mcn_provenance_available_1", + "check_description": "Check whether the target has intoto provenance.", + "slsa_requirements": [ + "Provenance - Available - SLSA Level 1", + "Provenance content - Identifies build instructions - SLSA Level 1", + "Provenance content - Identifies artifacts - SLSA Level 1", + "Provenance content - Identifies builder - SLSA Level 1" + ], + "justification": [ + "Could not find any SLSA or Witness provenances." + ], + "result_type": "FAILED" + }, + { + "check_id": "mcn_provenance_expectation_1", + "check_description": "Check whether the SLSA provenance for the produced artifact conforms to the expected value.", + "slsa_requirements": [ + "Provenance conforms with expectations - SLSA Level 3" + ], + "justification": [ + "Check mcn_provenance_expectation_1 is set to FAILED because mcn_provenance_available_1 FAILED." + ], + "result_type": "FAILED" + }, + { + "check_id": "mcn_provenance_level_three_1", + "check_description": "Check whether the target has SLSA provenance level 3.", + "slsa_requirements": [ + "Provenance - Non falsifiable - SLSA Level 3", + "Provenance content - Includes all build parameters - SLSA Level 3", + "Provenance content - Identifies entry point - SLSA Level 3", + "Provenance content - Identifies source code - SLSA Level 2" + ], + "justification": [ + "Check mcn_provenance_level_three_1 is set to FAILED because mcn_provenance_available_1 FAILED." + ], + "result_type": "FAILED" + }, + { + "check_id": "mcn_provenance_witness_level_one_1", + "check_description": "Check whether the target has a level-1 witness provenance.", + "slsa_requirements": [ + "Provenance - Available - SLSA Level 1", + "Provenance content - Identifies build instructions - SLSA Level 1", + "Provenance content - Identifies artifacts - SLSA Level 1", + "Provenance content - Identifies builder - SLSA Level 1" + ], + "justification": [ + "Check mcn_provenance_witness_level_one_1 is set to FAILED because mcn_provenance_available_1 FAILED." + ], + "result_type": "FAILED" + }, + { + "check_id": "mcn_trusted_builder_level_three_1", + "check_description": "Check whether the target uses a trusted SLSA level 3 builder.", + "slsa_requirements": [ + "Hermetic - SLSA Level 4", + "Isolated - SLSA Level 3", + "Parameterless - SLSA Level 4", + "Ephemeral environment - SLSA Level 3" + ], + "justification": [ + "Could not find a trusted level 3 builder as a GitHub Actions workflow." + ], + "result_type": "FAILED" + } + ] + } + }, + "dependencies": { + "analyzed_deps": 0, + "unique_dep_repos": 0, + "checks_summary": [ + { + "check_id": "mcn_build_as_code_1", + "num_deps_pass": 0 + }, + { + "check_id": "mcn_build_script_1", + "num_deps_pass": 0 + }, + { + "check_id": "mcn_build_service_1", + "num_deps_pass": 0 + }, + { + "check_id": "mcn_infer_artifact_pipeline_1", + "num_deps_pass": 0 + }, + { + "check_id": "mcn_provenance_available_1", + "num_deps_pass": 0 + }, + { + "check_id": "mcn_provenance_level_three_1", + "num_deps_pass": 0 + }, + { + "check_id": "mcn_provenance_expectation_1", + "num_deps_pass": 0 + }, + { + "check_id": "mcn_provenance_witness_level_one_1", + "num_deps_pass": 0 + }, + { + "check_id": "mcn_trusted_builder_level_three_1", + "num_deps_pass": 0 + }, + { + "check_id": "mcn_version_control_system_1", + "num_deps_pass": 0 + } + ], + "dep_status": [] + } +}