Skip to content
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

feat: add problog rule inference to build as code check #260

Draft
wants to merge 29 commits into
base: staging
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9415acc
chore(deps): add problog dependency to pyproject.toml
sophie-bates May 29, 2023
8cc866d
feat: split build_as_code_check into subchecks and aggregate certaint…
sophie-bates May 31, 2023
6bfaa0c
refactor: specify build as code subcheck dependencies and invoke thro…
sophie-bates Jun 7, 2023
41372fa
fix: update test_gha_workflow_deployment so that it passes the ci_par…
sophie-bates Jun 7, 2023
ca0a398
chore: convert problog result dictionary to use str keys
sophie-bates Jun 7, 2023
6fcfbd1
feat: perform intermediate querying on deploy method subchecks to det…
sophie-bates Jun 7, 2023
35ae23b
feat: add sub-check for workflow trigger event type
sophie-bates Jun 9, 2023
c7502c6
chore: store workflow info object for each node in GitHub Actions cal…
sophie-bates Jun 10, 2023
e48003f
chore: check that deploy action doesn't have a repository url specified
sophie-bates Jun 11, 2023
0bc69bd
feat: add sub-check for test publish to pypi
sophie-bates Jun 11, 2023
8c6f80d
chore: restructure problog predicate functions
sophie-bates Jun 11, 2023
9e80d79
chore: verify sub-check dependencies in ProbLog predicates rather tha…
sophie-bates Jun 11, 2023
74da2c9
feat: add API client for PyPI
sophie-bates Jun 12, 2023
d920307
chore: get project name from poetry config file
sophie-bates Jun 12, 2023
ee392e3
chore: extract project name from pip config files
sophie-bates Jun 12, 2023
067a2c0
chore: setup PyPI registry_service with the PyPI API client
sophie-bates Jun 12, 2023
6a04c43
feat: implement sub-check to compare PyPI project timestamp with GHA …
sophie-bates Jun 13, 2023
ddb2d0c
chore: fix poetry is_detected logic to pass snapshots
sophie-bates Jun 13, 2023
7d9a3aa
chore: update poetry snapshot
sophie-bates Jun 13, 2023
08e58bd
chore: remove setup.py file parsing from pip build tool detection
sophie-bates Jun 19, 2023
03621ae
chore: add evidence to BuildAsCodeTable and update ProbLog rules
sophie-bates Jun 19, 2023
ca00db4
chore: fix repository_url check
sophie-bates Jun 20, 2023
c04d887
chore: fix logging of sub-task results
sophie-bates Jun 20, 2023
24be921
chore: update ProbLog rules likelihood values
sophie-bates Jun 20, 2023
5c641b8
feat: add sub-task to check for secrets used in same workflow step as…
sophie-bates Jun 20, 2023
2ebdea2
chore: store workflow_file in deploy_action and deploy_command checks…
sophie-bates Jun 21, 2023
a84c2e4
chore: add tox -e release as supported deploy tool
sophie-bates Jun 21, 2023
48ca217
chore: include Poetry projects for deploy_action check
sophie-bates Jun 21, 2023
44b36c8
chore: update release workflow trigger sub-task to penalize certainty…
sophie-bates Jun 30, 2023
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
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies = [
"jinja2 >=3.1.2,<4.0.0",
"SQLAlchemy >=2.0.0,<3.0.0",
"defusedxml >=0.7.1,<1.0.0",
"problog >=2.2.4,<3.0.0"
]
keywords = []
# https://pypi.org/classifiers/
Expand Down Expand Up @@ -187,6 +188,7 @@ module = [
"gitdb.*",
"yamale.*",
"defusedxml.*",
"problog.*"
]
ignore_missing_imports = true

Expand Down
2 changes: 2 additions & 0 deletions src/macaron/config/defaults.ini
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ publisher =
twine
flit
conda
tox
# These are the Python interpreters that may be used to load modules.
interpreter =
python
Expand All @@ -250,6 +251,7 @@ build_arg =
deploy_arg =
publish
upload
release
[builder.pip.ci.deploy]
github_actions = pypa/gh-action-pypi-publish

Expand Down
9 changes: 7 additions & 2 deletions src/macaron/parsers/bashparser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2022 - 2022, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2022 - 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/.

"""This module is a Python wrapper for the compiled bashparser binary.
Expand Down Expand Up @@ -33,6 +33,7 @@ class BashCommands(TypedDict):
"""CI service type."""
commands: list[list[str]]
"""Parsed bash commands."""
workflow_info: dict


def parse_file(file_path: str, macaron_path: str = "") -> dict:
Expand Down Expand Up @@ -115,6 +116,7 @@ def extract_bash_from_ci(
bash_content: str,
ci_file: str,
ci_type: str,
workflow_info: dict,
macaron_path: str = "",
recursive: bool = False,
repo_path: str = "",
Expand Down Expand Up @@ -152,7 +154,9 @@ def extract_bash_from_ci(
parsed_parent = parse(bash_content)
caller_commands = parsed_parent.get("commands", [])
if caller_commands:
yield BashCommands(caller_path=ci_file, CI_path=ci_file, CI_type=ci_type, commands=caller_commands)
yield BashCommands(
caller_path=ci_file, CI_path=ci_file, CI_type=ci_type, commands=caller_commands, workflow_info=workflow_info
)

# Parse the bash script files called from the current script.
if recursive and repo_path:
Expand All @@ -171,4 +175,5 @@ def extract_bash_from_ci(
CI_path=ci_file,
CI_type=ci_type,
commands=callee_commands,
workflow_info=workflow_info,
)
1 change: 1 addition & 0 deletions src/macaron/slsa_analyzer/build_tool/base_build_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def __init__(self, name: str) -> None:
}
self.build_log: list[str] = []
self.wrapper_files: list[str] = []
self.project_name: str = ""

def __str__(self) -> str:
return self.name
Expand Down
19 changes: 19 additions & 0 deletions src/macaron/slsa_analyzer/build_tool/pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"""

import logging
import os
import tomllib

from macaron.config.defaults import defaults
from macaron.dependency_analyzer import DependencyAnalyzer, NoneDependencyAnalyzer
Expand Down Expand Up @@ -49,6 +51,23 @@ def is_detected(self, repo_path: str) -> bool:
"""
for file in self.build_configs:
if file_exists(repo_path, file):
# Find project name value from the config file.
# TODO: improve this approach, support setup.py
file_path = os.path.join(repo_path, file)
if file == "pyproject.toml":
try:
with open(file_path, "rb") as toml_file:
try:
data = tomllib.load(toml_file)
project = data.get("project", {})
if project:
# Store the project name
self.project_name = project.get("name", "")
logger.info("Package name: %s", self.project_name)
except tomllib.TOMLDecodeError:
logger.debug("Failed to read the %s file: invalid toml file.", file)
except FileNotFoundError:
logger.debug("Failed to read the %s file.", file)
return True
return False

Expand Down
14 changes: 8 additions & 6 deletions src/macaron/slsa_analyzer/build_tool/poetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ def is_detected(self, repo_path: str) -> bool:

if files_detected:
# If a package_lock file exists, and a config file is present, Poetry build tool is detected.
# TODO: package_lock_exists check removed for now so poetry # tool name is stored.
if package_lock_exists:
return True
logger.info("Lock file found.") # return True
# TODO: this implementation assumes one build type, so when multiple build types are supported, this
# needs to be updated.
# Take the highest level file, if there are two at the same level, take the first in the list.
Expand All @@ -76,16 +77,17 @@ def is_detected(self, repo_path: str) -> bool:
try:
data = tomllib.load(toml_file)
# Check for the existence of a [tool.poetry] section.
if ("tool" in data) and ("poetry" in data["tool"]):
poetry_tool = data.get("tool", {}).get("poetry", {})
if poetry_tool:
# Store the project name
self.project_name = poetry_tool.get("name")
return True
except tomllib.TOMLDecodeError:
logger.error("Failed to read the %s file: invalid toml file.", conf)
return False
return False
except FileNotFoundError:
logger.error("Failed to read the %s file.", conf)
return False

if package_lock_exists:
return True
return False

def prepare_config_files(self, wrapper_path: str, build_dir: str) -> bool:
Expand Down
1 change: 1 addition & 0 deletions src/macaron/slsa_analyzer/checks/base_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def run(self, target: AnalyzeContext, skipped_info: Optional[SkippedInfo] = None
justification=[],
result_type=CheckResultType.SKIPPED,
result_tables=[],
confidence_score=0.0,
)

if skipped_info:
Expand Down
Loading