Skip to content

Commit

Permalink
feat: add a new check to report the build tool (#914)
Browse files Browse the repository at this point in the history
This pull request adds a new check that identifies whether a supported build tool configuration exists in the associated source code repository of a software component.

Signed-off-by: behnazh-w <[email protected]>
  • Loading branch information
behnazh-w authored Nov 4, 2024
1 parent 7d3c63e commit f39784a
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 2 deletions.
3 changes: 3 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ the requirements that are currently supported by Macaron.
* - Check ID
- SLSA requirement
- Concrete check
* - ``mcn_build_tool_1``
- **Build tool exists** - The source code repository includes configurations for a supported build tool used to produce the software component.
- Detect the build tool used in the source code repository to build the software component.
* - ``mcn_build_script_1``
- **Scripted build** - All build steps were fully defined in a “build script”.
- Identify and validate build script(s).
Expand Down
85 changes: 85 additions & 0 deletions src/macaron/slsa_analyzer/checks/build_tool_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Copyright (c) 2024 - 2024, 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/.

"""This module contains the implementation of the build tool detection check."""


import logging

from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column

from macaron.database.table_definitions import CheckFacts
from macaron.slsa_analyzer.analyze_context import AnalyzeContext
from macaron.slsa_analyzer.checks.base_check import BaseCheck, CheckResultType
from macaron.slsa_analyzer.checks.check_result import CheckResultData, Confidence, JustificationType
from macaron.slsa_analyzer.registry import registry
from macaron.slsa_analyzer.slsa_req import ReqName

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


class BuildToolFacts(CheckFacts):
"""The ORM mapping for the facts collected by the build tool check."""

__tablename__ = "_build_tool_check"

#: The primary key.
id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003

#: The build tool name.
build_tool_name: Mapped[str] = mapped_column(String, nullable=False, info={"justification": JustificationType.TEXT})

#: The language of the artifact built by build tool.
language: Mapped[str] = mapped_column(String, nullable=False, info={"justification": JustificationType.TEXT})

__mapper_args__ = {
"polymorphic_identity": "_build_tool_check",
}


class BuildToolCheck(BaseCheck):
"""This check detects the build tool used in the source code repository to build the software component."""

def __init__(self) -> None:
"""Initialize instance."""
check_id = "mcn_build_tool_1"
description = "Detect the build tool used in the source code repository to build the software component."
depends_on: list[tuple[str, CheckResultType]] = [("mcn_version_control_system_1", CheckResultType.PASSED)]
eval_reqs = [ReqName.SCRIPTED_BUILD]
super().__init__(check_id=check_id, description=description, depends_on=depends_on, eval_reqs=eval_reqs)

def run_check(self, ctx: AnalyzeContext) -> CheckResultData:
"""Implement the check in this method.
Parameters
----------
ctx : AnalyzeContext
The object containing processed data for the target repo.
Returns
-------
CheckResultData
The result of the check.
"""
if not ctx.component.repository:
logger.info("Unable to find a Git repository for %s", ctx.component.purl)
return CheckResultData(result_tables=[], result_type=CheckResultType.FAILED)

build_tools = ctx.dynamic_data["build_spec"]["tools"]
if not build_tools:
return CheckResultData(result_tables=[], result_type=CheckResultType.FAILED)

result_tables: list[CheckFacts] = []
for tool in build_tools:
result_tables.append(
BuildToolFacts(build_tool_name=tool.name, language=tool.language.value, confidence=Confidence.HIGH)
)

return CheckResultData(
result_tables=result_tables,
result_type=CheckResultType.PASSED,
)


registry.register(BuildToolCheck())
5 changes: 3 additions & 2 deletions src/macaron/slsa_analyzer/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,10 @@ def _validate_check(check: Any) -> bool:
True if check is valid, else False.
"""
if not isinstance(check, BaseCheck):
class_name = check.__name__ if isinstance(check, type) else type(check).__name__
logger.error(
"The registered Check is of type %s. Please register a child class of BaseCheck.",
type(check).__name__,
"The registered Check %s is not a valid instance of BaseCheck.",
class_name,
)
return False

Expand Down
3 changes: 3 additions & 0 deletions tests/integration/cases/facebook_yoga_yarn_classic/policy.dl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Policy("test_policy", component_id, "") :-
check_passed(component_id, "mcn_build_script_1"),
check_passed(component_id, "mcn_build_service_1"),
check_passed(component_id, "mcn_version_control_system_1"),
check_passed(component_id, "mcn_build_tool_1"),
build_tool_check(yarn_id, "yarn", "javascript"),
check_facts(yarn_id, _, component_id,_,_),
check_failed(component_id, "mcn_infer_artifact_pipeline_1"),
check_failed(component_id, "mcn_provenance_available_1"),
check_failed(component_id, "mcn_provenance_derived_commit_1"),
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/cases/google_guava/policy.dl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ Policy("test_policy", component_id, "") :-
// the logic in the mcn_infer_artifact_pipeline_1 check.
check_failed(component_id, "mcn_infer_artifact_pipeline_1"),
check_passed(component_id, "mcn_version_control_system_1"),
check_passed(component_id, "mcn_build_tool_1"),
build_tool_check(maven_id, "maven", "java"),
check_facts(maven_id, _, component_id,_,_),
check_failed(component_id, "mcn_provenance_available_1"),
check_failed(component_id, "mcn_provenance_derived_commit_1"),
check_failed(component_id, "mcn_provenance_derived_repo_1"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Policy("test_policy", component_id, "") :-
check_passed(component_id, "mcn_build_script_1"),
check_passed(component_id, "mcn_build_service_1"),
check_passed(component_id, "mcn_version_control_system_1"),
check_passed(component_id, "mcn_build_tool_1"),
build_tool_check(maven_id, "maven", "java"),
check_facts(maven_id, _, component_id,_,_),
check_failed(component_id, "mcn_infer_artifact_pipeline_1"),
check_failed(component_id, "mcn_provenance_available_1"),
check_failed(component_id, "mcn_provenance_derived_commit_1"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Policy("test_policy", component_id, "") :-
check_passed(component_id, "mcn_version_control_system_1"),
check_passed(component_id, "mcn_provenance_available_1"),
check_passed(component_id, "mcn_provenance_derived_repo_1"),
check_passed(component_id, "mcn_build_tool_1"),
build_tool_check(gradle_id, "gradle", "java"),
check_facts(gradle_id, _, component_id,_,_),
check_passed(component_id, "mcn_provenance_level_three_1"),
check_failed(component_id, "mcn_infer_artifact_pipeline_1"),
check_failed(component_id, "mcn_provenance_derived_commit_1"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ Policy("test_policy", component_id, "") :-
check_passed(component_id, "mcn_build_service_1"),
check_passed(component_id, "mcn_trusted_builder_level_three_1"),
check_passed(component_id, "mcn_version_control_system_1"),
check_passed(component_id, "mcn_build_tool_1"),
build_tool_check(go_id, "go", "go"),
check_facts(go_id, _, component_id,_,_),
check_passed(component_id, "mcn_provenance_available_1"),
check_passed(component_id, "mcn_provenance_derived_commit_1"),
check_passed(component_id, "mcn_provenance_derived_repo_1"),
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/cases/timyarkov_docker_test/policy.dl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Policy("test_policy", component_id, "") :-
check_passed(component_id, "mcn_build_script_1"),
check_passed(component_id, "mcn_build_service_1"),
check_passed(component_id, "mcn_version_control_system_1"),
check_passed(component_id, "mcn_build_tool_1"),
build_tool_check(docker_id, "docker", "docker"),
check_facts(docker_id, _, component_id,_,_),
check_failed(component_id, "mcn_infer_artifact_pipeline_1"),
check_failed(component_id, "mcn_provenance_available_1"),
check_failed(component_id, "mcn_provenance_derived_commit_1"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ Policy("test_policy", component_id, "") :-
check_passed(component_id, "mcn_build_script_1"),
check_passed(component_id, "mcn_build_service_1"),
check_passed(component_id, "mcn_version_control_system_1"),
check_passed(component_id, "mcn_build_tool_1"),
build_tool_check(gradle_id, "gradle", "java"),
check_facts(gradle_id, _, component_id,_,_),
build_tool_check(maven_id, "maven", "java"),
check_facts(maven_id, _, component_id,_,_),
check_failed(component_id, "mcn_infer_artifact_pipeline_1"),
check_failed(component_id, "mcn_provenance_available_1"),
check_failed(component_id, "mcn_provenance_derived_commit_1"),
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/cases/uiv-lib_uiv/policy.dl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Policy("test_policy", component_id, "") :-
check_passed(component_id, "mcn_build_script_1"),
check_passed(component_id, "mcn_build_service_1"),
check_passed(component_id, "mcn_version_control_system_1"),
check_passed(component_id, "mcn_build_tool_1"),
build_tool_check(npm_id, "npm", "javascript"),
check_facts(npm_id, _, component_id,_,_),
check_failed(component_id, "mcn_infer_artifact_pipeline_1"),
check_failed(component_id, "mcn_provenance_available_1"),
check_failed(component_id, "mcn_provenance_derived_commit_1"),
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/cases/urllib3_expectation_dir/policy.dl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Policy("test_policy", component_id, "") :-
check_passed(component_id, "mcn_provenance_derived_commit_1"),
check_passed(component_id, "mcn_provenance_derived_repo_1"),
check_passed(component_id, "mcn_provenance_expectation_1"),
check_passed(component_id, "mcn_build_tool_1"),
build_tool_check(pip_id, "pip", "python"),
check_facts(pip_id, _, component_id,_,_),
check_failed(component_id, "mcn_infer_artifact_pipeline_1"),
check_failed(component_id, "mcn_provenance_witness_level_one_1"),
check_failed(component_id, "mcn_trusted_builder_level_three_1"),
Expand Down
47 changes: 47 additions & 0 deletions tests/slsa_analyzer/checks/test_build_tool_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (c) 2024 - 2024, 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/.

"""This module contains the tests for the build tool detection Check."""

from pathlib import Path

import pytest

from macaron.slsa_analyzer.build_tool.base_build_tool import BaseBuildTool
from macaron.slsa_analyzer.checks.build_tool_check import BuildToolCheck
from macaron.slsa_analyzer.checks.check_result import CheckResultType
from tests.conftest import MockAnalyzeContext


@pytest.mark.parametrize(
"build_tool_name",
[
"maven",
"gradle",
"poetry",
"pip",
"npm",
"docker",
"go",
],
)
def test_build_tool_check_pass(
macaron_path: Path,
build_tools: dict[str, BaseBuildTool],
build_tool_name: str,
) -> None:
"""Test the build tool detection check passes."""
ctx = MockAnalyzeContext(macaron_path=macaron_path, output_dir="")
ctx.dynamic_data["build_spec"]["tools"] = [build_tools[build_tool_name]]
check = BuildToolCheck()
assert check.run_check(ctx).result_type == CheckResultType.PASSED


def test_build_tool_check_fail(
macaron_path: Path,
) -> None:
"""Test the build tool detection check fails."""
ctx = MockAnalyzeContext(macaron_path=macaron_path, output_dir="")
ctx.dynamic_data["build_spec"]["tools"] = []
check = BuildToolCheck()
assert check.run_check(ctx).result_type == CheckResultType.FAILED

0 comments on commit f39784a

Please sign in to comment.