Skip to content

chore: add provenance as an input CLI option #654

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/source/pages/cli_usage/command_analyze.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ Options

The path to provenance expectation file or directory.

.. option:: -pf PROVENANCE_FILE, --provenance-file PROVENANCE_FILE

The path to the provenance file in in-toto format.

.. option:: -c CONFIG_PATH, --config-path CONFIG_PATH

The path to the user configuration.
Expand Down
11 changes: 11 additions & 0 deletions scripts/dev_scripts/integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,17 @@ then
log_fail
fi

echo -e "\n----------------------------------------------------------------------------------"
echo "Test providing an invalid provenance file as input."
echo -e "----------------------------------------------------------------------------------\n"
$RUN_MACARON analyze -rp https://github.com/apache/maven --provenance-file $WORKSPACE/golang/internal/cue_validator/resources/invalid_provenance.json --skip-deps

if [ $? -eq 0 ];
then
echo -e "Expect non-zero status code but got $?."
log_fail
fi

# Testing the CUE provenance expectation verifier.
echo -e "\n----------------------------------------------------------------------------------"
echo "Test verifying CUE provenance expectation for ossf/scorecard"
Expand Down
25 changes: 23 additions & 2 deletions src/macaron/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
from macaron.slsa_analyzer.analyzer import Analyzer
from macaron.slsa_analyzer.git_service import GIT_SERVICES
from macaron.slsa_analyzer.package_registry import PACKAGE_REGISTRIES
from macaron.slsa_analyzer.provenance.intoto.errors import LoadIntotoAttestationError
from macaron.slsa_analyzer.provenance.loader import load_provenance_payload
from macaron.vsa.vsa import generate_vsa

logger: logging.Logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -78,7 +80,6 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None
analyzer.reporters.append(JSONReporter())

run_config = {}

if analyzer_single_args.config_path:
# Get user config from yaml file
loaded_config = YamlLoader.load(analyzer_single_args.config_path)
Expand Down Expand Up @@ -129,7 +130,20 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None
"dependencies": [],
}

status_code = analyzer.run(run_config, analyzer_single_args.sbom_path, analyzer_single_args.skip_deps)
prov_payload = None
if analyzer_single_args.provenance_file:
try:
prov_payload = load_provenance_payload(analyzer_single_args.provenance_file)
except LoadIntotoAttestationError as error:
logger.error("Error while loading the input provenance file: %s", error)
sys.exit(os.EX_DATAERR)

status_code = analyzer.run(
run_config,
analyzer_single_args.sbom_path,
analyzer_single_args.skip_deps,
prov_payload=prov_payload,
)
sys.exit(status_code)


Expand Down Expand Up @@ -365,6 +379,13 @@ def main(argv: list[str] | None = None) -> None:
help=("The path to provenance expectation file or directory."),
)

single_analyze_parser.add_argument(
"-pf",
"--provenance-file",
required=False,
help=("The path to the provenance file in in-toto format."),
)

group.add_argument(
"-c",
"--config-path",
Expand Down
14 changes: 9 additions & 5 deletions src/macaron/slsa_analyzer/analyze_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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 import InTotoPayload
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
Expand All @@ -31,17 +32,19 @@ class ChecksOutputs(TypedDict):
"""Data computed at runtime by checks."""

git_service: BaseGitService
"""The git service information for this repository."""
"""The git service information for the target software component."""
build_spec: BuildSpec
"""The build spec inferred for this repository."""
"""The build spec inferred for the target software component."""
ci_services: list[CIInfo]
"""The CI services information for this repository."""
"""The CI services information for the target software component."""
is_inferred_prov: bool
"""True if we cannot find the provenance and Macaron need to infer the provenance."""
expectation: Expectation | None
"""The expectation to verify the provenance for this repository."""
"""The expectation to verify the provenance for the target software component."""
package_registries: list[PackageRegistryInfo]
"""The package registries for this repository."""
"""The package registries for the target software component."""
provenance: InTotoPayload | None
"""The provenance payload for the target software component."""


class AnalyzeContext:
Expand Down Expand Up @@ -92,6 +95,7 @@ def __init__(
package_registries=[],
is_inferred_prov=True,
expectation=None,
provenance=None,
)

@property
Expand Down
22 changes: 19 additions & 3 deletions src/macaron/slsa_analyzer/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from macaron.slsa_analyzer.git_service.base_git_service import NoneGitService
from macaron.slsa_analyzer.package_registry import PACKAGE_REGISTRIES
from macaron.slsa_analyzer.provenance.expectations.expectation_registry import ExpectationRegistry
from macaron.slsa_analyzer.provenance.intoto import InTotoV01Payload
from macaron.slsa_analyzer.provenance.intoto import InTotoPayload, InTotoV01Payload
from macaron.slsa_analyzer.provenance.slsa import SLSAProvenanceData
from macaron.slsa_analyzer.registry import registry
from macaron.slsa_analyzer.specs.ci_spec import CIInfo
Expand Down Expand Up @@ -111,7 +111,13 @@ def __init__(self, output_path: str, build_log_path: str) -> None:
# Create database tables: all checks have been registered so all tables should be mapped now
self.db_man.create_tables()

def run(self, user_config: dict, sbom_path: str = "", skip_deps: bool = False) -> int:
def run(
self,
user_config: dict,
sbom_path: str = "",
skip_deps: bool = False,
prov_payload: InTotoPayload | None = None,
) -> int:
"""Run the analysis and write results to the output path.

This method handles the configuration file and writes the result html reports including dependencies.
Expand All @@ -125,6 +131,8 @@ def run(self, user_config: dict, sbom_path: str = "", skip_deps: bool = False) -
The path to the SBOM.
skip_deps : bool
Flag to skip dependency resolution.
prov_payload : InToToPayload | None
The provenance intoto payload for the main software component.

Returns
-------
Expand Down Expand Up @@ -154,7 +162,11 @@ def run(self, user_config: dict, sbom_path: str = "", skip_deps: bool = False) -
)

# Analyze the main target.
main_record = self.run_single(main_config, analysis)
main_record = self.run_single(
main_config,
analysis,
prov_payload=prov_payload,
)

if main_record.status != SCMStatus.AVAILABLE or not main_record.context:
logger.info("Analysis has failed.")
Expand Down Expand Up @@ -255,6 +267,7 @@ def run_single(
config: Configuration,
analysis: Analysis,
existing_records: dict[str, Record] | None = None,
prov_payload: InTotoPayload | None = None,
) -> Record:
"""Run the checks for a single repository target.

Expand All @@ -269,6 +282,8 @@ def run_single(
The current analysis instance.
existing_records : dict[str, Record] | None
The mapping of existing records that the analysis has run successfully.
prov_payload : InToToPayload | None
The provenance intoto payload for the analyzed software component.

Returns
-------
Expand Down Expand Up @@ -306,6 +321,7 @@ def run_single(
analyze_ctx.dynamic_data["expectation"] = self.expectations.get_expectation_for_target(
analyze_ctx.component.purl.split("@")[0]
)
analyze_ctx.dynamic_data["provenance"] = prov_payload
analyze_ctx.check_results = self.perform_checks(analyze_ctx)

return Record(
Expand Down