Skip to content
Open
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
29 changes: 29 additions & 0 deletions src/macaron/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,41 @@ def verify_policy(verify_policy_args: argparse.Namespace) -> int:
show_prelude(verify_policy_args.database)
return os.EX_OK

policy_content = None
if verify_policy_args.file:
if not os.path.isfile(verify_policy_args.file):
logger.critical('The policy file "%s" does not exist.', verify_policy_args.file)
return os.EX_OSFILE

with open(verify_policy_args.file, encoding="utf-8") as file:
policy_content = file.read()
elif verify_policy_args.existing_policy:
policy_dir = os.path.join(macaron.MACARON_PATH, "resources", "policies", "datalog")
policy_suffix = ".dl"
template_suffix = f"{policy_suffix}.template"
available_policies = [
os.path.splitext(policy)[0].replace(policy_suffix, "")
for policy in os.listdir(policy_dir)
if policy.endswith(template_suffix)
]
if verify_policy_args.existing_policy not in available_policies:
logger.error(
"The policy %s is not available. Available policies are: %s",
verify_policy_args.existing_policy,
available_policies,
)
return os.EX_USAGE
policy_path = os.path.join(policy_dir, f"{verify_policy_args.existing_policy}{template_suffix}")
with open(policy_path, encoding="utf-8") as file:
policy_content = file.read()
try:
PackageURL.from_string(verify_policy_args.package_url)
policy_content = policy_content.replace("<PACKAGE_PURL>", verify_policy_args.package_url)
except ValueError as err:
logger.error("The package url %s is not valid. Error: %s", verify_policy_args.package_url, err)
return os.EX_USAGE

if policy_content:
result = run_policy_engine(verify_policy_args.database, policy_content)
vsa = generate_vsa(policy_content=policy_content, policy_result=result)
# Retrieve the console handler previously configured via the access_handler.
Expand Down Expand Up @@ -573,7 +600,9 @@ def main(argv: list[str] | None = None) -> None:
vp_group = vp_parser.add_mutually_exclusive_group(required=True)

vp_parser.add_argument("-d", "--database", required=True, type=str, help="Path to the database.")
vp_parser.add_argument("-purl", "--package-url", help="PackageURL for policy template.")
vp_group.add_argument("-f", "--file", type=str, help="Path to the Datalog policy.")
vp_group.add_argument("-e", "--existing-policy", help="Name of the existing policy to run.")
vp_group.add_argument("-s", "--show-prelude", action="store_true", help="Show policy prelude.")

# Find the repo and commit of a passed PURL, or the commit of a passed PURL and repo.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include "prelude.dl"

Policy("github_actions_vulns", component_id, "GitHub Actions Vulnerability Detection") :-
check_passed(component_id, "mcn_githubactions_vulnerabilities_1").

apply_policy_to("github_actions_vulns", component_id) :-
is_component(component_id, purl),
match("<PACKAGE_PURL>@.*", purl).
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include "prelude.dl"

Policy("check-dependencies", component_id, "Check the dependencies of component.") :-
transitive_dependency(component_id, dependency),
check_passed(component_id, "mcn_detect_malicious_metadata_1"),
check_passed(dependency, "mcn_detect_malicious_metadata_1").

apply_policy_to("check-dependencies", component_id) :-
is_component(component_id, purl),
match("<PACKAGE_PURL>@.*", purl).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the user provides a PURL that contains version as well? Then the policy won't match the PURL.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does removing @.* resolve the issue?

from

match("<PACKAGE_PURL>@.*", purl).

to

match("<PACKAGE_PURL>", purl).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that would mean we can't use wildcards, which are useful for creating more generic policies that apply to all versions. Alternatively, we could allow the wildcard within the PURL argument itself 🤔

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "prelude.dl"

Policy("check-component", component_id, "Check component artifacts.") :-
check_passed(component_id, "mcn_detect_malicious_metadata_1").


apply_policy_to("check-component", component_id) :-
is_component(component_id, purl),
match("<PACKAGE_PURL>@.*", purl).
61 changes: 61 additions & 0 deletions tests/policy_engine/test_existing_policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright (c) 2025 - 2025, 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 tests the existing-policy flag supported by the policy engine."""

import argparse
import os
from pathlib import Path
from unittest.mock import MagicMock, patch

from macaron.__main__ import verify_policy


def test_verify_existing_policy_success(tmp_path: Path) -> None:
"""When an existing policy is provided and package-url is valid, verify_policy returns EX_OK."""
db_file = tmp_path / "macaron.db"
db_file.write_text("")

# Use a MagicMock for the handler.
mock_handler = MagicMock()

# Fake run_policy_engine and generate_vsa that returns a fixed result.
fake_run = MagicMock(return_value={"passed_policies": [["check-component"]], "failed_policies": []})
fake_generate_vsa = MagicMock(return_value=None)

# Fake PolicyReporter class: when called, returns an instance with generate method.
fake_policy_reporter_cls = MagicMock()
fake_policy_reporter_inst = MagicMock()
fake_policy_reporter_inst.generate.return_value = None
fake_policy_reporter_cls.return_value = fake_policy_reporter_inst

with (
patch("macaron.__main__.run_policy_engine", fake_run),
patch("macaron.__main__.generate_vsa", fake_generate_vsa),
patch("macaron.__main__.access_handler.get_handler", return_value=mock_handler),
patch("macaron.__main__.PolicyReporter", fake_policy_reporter_cls),
):
policy_args = argparse.Namespace(
database=str(db_file),
show_prelude=False,
file=None,
existing_policy="malware-detection",
package_url="pkg:pypi/django",
)
result = verify_policy(policy_args)
assert result == os.EX_OK


def test_verify_existing_policy_not_found(tmp_path: Path) -> None:
"""Requesting a non-existent policy returns usage error."""
db_file = tmp_path / "macaron.db"
db_file.write_text("")
policy_args = argparse.Namespace(
database=str(db_file),
show_prelude=False,
file=None,
existing_policy="no-such-policy",
package_url="pkg:pypi/django",
)
result = verify_policy(policy_args)
assert result == os.EX_USAGE
Loading