Skip to content
Draft
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0314d9f
adding composer.lock download functionality to trivy sca
faizanH May 12, 2025
df03719
add test case
faizanH May 12, 2025
627c5e8
formatting
faizanH May 12, 2025
e96fe48
updating dind to have php and curl
faizanH May 12, 2025
deeaae7
adding required packages for PHP
faizanH May 12, 2025
8dcd3fe
adding one more PHP requirement
faizanH May 12, 2025
18ac459
trying a multi stage build
faizanH May 12, 2025
47ec973
testing
faizanH May 12, 2025
8fafd6c
adding more php depdencies
faizanH May 12, 2025
0416d29
removing incorrect package
faizanH May 13, 2025
7dc3078
removing improper import
faizanH May 13, 2025
8d62552
invoke the script
faizanH May 13, 2025
bf65480
attempting to create docker container in plugin directly
faizanH May 14, 2025
ac6c8f8
remove php from dockerfile
faizanH May 14, 2025
bce7432
adding config in settings.py
faizanH May 14, 2025
f165e10
adding logs for debugging
faizanH May 14, 2025
dfe927a
commenting out return
faizanH May 14, 2025
acb39fb
trying to get logs to appear to show file directory
faizanH May 16, 2025
880247c
Merge branch 'main' into feature/generate-php-lockfile
faizanH May 16, 2025
32ab4e2
tring to enter container
faizanH May 16, 2025
b849677
slight tweaks
faizanH May 23, 2025
a60c99d
debug
faizanH May 23, 2025
22eb123
adding more debugs
faizanH May 27, 2025
4ebbe82
remove possible error causing
faizanH May 27, 2025
83b314c
adding more debugging
faizanH May 27, 2025
14eac8c
using different temp volume
faizanH May 27, 2025
8cab6cf
trying different approach
faizanH May 28, 2025
2107bcf
drawing inspo from other plugin
faizanH May 29, 2025
d8e3de7
Merge branch 'main' into feature/generate-php-lockfile
faizanH Aug 27, 2025
83de2b2
debugging
mdfleury-wbd Aug 27, 2025
9d9750a
testing working dir
mdfleury-wbd Aug 28, 2025
d21e59c
changing path again
mdfleury-wbd Aug 28, 2025
1e73976
another try
mdfleury-wbd Aug 28, 2025
0d26b9b
adding missed file
mdfleury-wbd Aug 28, 2025
3b53779
renaming npm file
mdfleury-wbd Aug 28, 2025
c077fba
using mnt
mdfleury-wbd Aug 28, 2025
76be831
trying working mnt
mdfleury-wbd Sep 3, 2025
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
109 changes: 109 additions & 0 deletions backend/engine/plugins/lib/trivy_common/generate_composer_locks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import subprocess
import os
from glob import glob
from engine.plugins.lib import utils
import docker
import docker.errors

logger = utils.setup_logging("trivy_sca")

cmd = [
"composer",
"install",
"--no-scripts", # Skip execution of scripts
"--no-audit", # Don't run an audit
"&&",
"ls",
"-l",
"composer.lock"
]

docker_client = docker.from_env()


def install_package_files(include_dev: bool, path: str, root_path: str):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since this function doesn't report errors up, we should document the fact that this is "best effort", i.e. if the composer.lock couldn't be generated then we will continue anyway.

# Create a composer.lock file if it doesn't already exist
logger.info(
f"Generating composer.lock for {path.replace(root_path, '')} (including dev dependencies: {include_dev}"
Copy link
Collaborator

Choose a reason for hiding this comment

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

(minor) Missing end paren at the end of the log message.

)
if not include_dev:
cmd.append("--no-dev")

# Run Composer in a container
COMPOSER_IMG = "composer:latest"
container_name = "composer_runner"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Container names must be unique, so we should append a UUID.

host_working_dir = path
container_mount_path = "/app"

container = docker_client.containers.run(
COMPOSER_IMG,
name=container_name,
command=cmd,
volumes={
host_working_dir: {"bind": container_mount_path, "mode": "rw"},
},
working_dir=container_mount_path,
auto_remove=False,
stdout=True,
stderr=True,
detach=True,
)


exit_code = container.wait()
logs = container.logs().decode("utf-8")

logger.error(f'exit code: {exit_code}')
logger.error(f'logs: {logs}')


# container.remove()


return


def check_composer_package_files(path: str, include_dev: bool) -> tuple:
"""
Main Function
Find all of the composer.json files in the repo and build lock files for them if they dont have one already.
Parses the results and returns them with the errors.
"""

errors = []
alerts = []

# Find and loop through all the composer.json files in the path
files = glob(f"{path}/**/composer.json", recursive=True)

logger.info("Found %d composer.json files", len(files))

# If there are no composer.json files, exit function
if len(files) == 0:
return errors, alerts

# Build a set of all directories containing package files
paths = set()
for filename in files:
paths.add(os.path.dirname(filename))

# Loop through paths that have a package file
for sub_path in paths:
lockfile = os.path.join(sub_path, "composer.lock")
lockfile_missing = not os.path.exists(lockfile)
# Generate a lock file if does not exist in path that has a composer.json
if lockfile_missing:
msg = (
f"No composer.lock file was found in path {sub_path.replace(path, '')}."
" Please consider creating a composer.lock file for this project."
)
logger.warning(msg)
alerts.append(msg)
r = install_package_files(include_dev, sub_path, path)
# if r.returncode != 0:
# error = r.stderr.decode("utf-8")
# logger.error(error)
# errors.append(error)
# return errors, alerts
# Return the results
return errors, alerts
20 changes: 13 additions & 7 deletions backend/engine/plugins/trivy_sca/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import subprocess
from engine.plugins.lib.trivy_common.generate_locks import check_package_files
from engine.plugins.lib.trivy_common.generate_composer_locks import check_composer_package_files
from engine.plugins.lib.utils import convert_string_to_json
from engine.plugins.lib.trivy_common.parsing_util import parse_output
from engine.plugins.lib.utils import setup_logging
Expand Down Expand Up @@ -38,9 +39,18 @@ def main():
args = parse_args()
include_dev = args.engine_vars.get("include_dev", False)
results = []
alerts = []
errors = []

# Generate Lock files (without installing npm packages)
lock_file_errors, lock_file_alerts = check_package_files(args.path, include_dev, False)
# Generate Lock files (and install npm packages for license info)
lock_file_errors, lock_file_alerts = check_package_files(args.path, include_dev, True)
alerts.extend(lock_file_alerts)
errors.extend(lock_file_errors)

# Run Composer Install for exact version numbers
compose_lock_errors, compose_lock_alerts = check_composer_package_files(args.path, include_dev)
alerts.extend(compose_lock_alerts)
errors.extend(compose_lock_errors)

# Scan local lock files
output = execute_trivy_lock_scan(args.path, include_dev)
Expand All @@ -54,11 +64,7 @@ def main():
results.extend(result)

# Return results
print(
json.dumps(
{"success": not bool(results), "details": results, "errors": lock_file_errors, "alerts": lock_file_alerts}
)
)
print(json.dumps({"success": not bool(results), "details": results, "errors": errors, "alerts": alerts}))
Copy link
Collaborator

Choose a reason for hiding this comment

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

(minor) Since our JSON results can be quite large, we should switch from using json.dumps (which renders the whole JSON to a string first) to json.dump that renders the output directly to the output, e.g.:

json.dump({ ... }, sys.stdout)



if __name__ == "__main__":
Expand Down
1 change: 1 addition & 0 deletions backend/engine/plugins/trivy_sca/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "Trivy SCA Scanner",
"type": "vulnerability",
"image": "$ECR/artemis/dind:latest",
"docker": true,
"build_images": false,
"enabled": true,
"writable": true
Expand Down
21 changes: 21 additions & 0 deletions backend/engine/tests/test_plugin_trivy_sca.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from oci import remover
from engine.plugins.trivy_sca import main as Trivy
from engine.plugins.lib.trivy_common.generate_locks import check_package_files
from engine.plugins.lib.trivy_common.generate_composer_locks import check_composer_package_files
from engine.plugins.lib.utils import convert_string_to_json
from engine.plugins.lib.utils import setup_logging

Expand Down Expand Up @@ -122,6 +123,16 @@ def test_lock_file_exists(self):
actual = check_package_files("/mocked/path/", False, False)
self.assertEqual(len(actual[1]), 0, "There should NOT be a warning of a lock file missing")

def test_compose_lock_file_exists(self):
with patch(f"{GENERATE_LOCKS_PREFIX}glob") as mock_glob:
mock_glob.return_value = ["/mocked/path/compose.json"]
with patch(f"{GENERATE_LOCKS_PREFIX}os.path.exists", return_value=True):
with patch(f"{GENERATE_LOCKS_PREFIX}subprocess.run") as mock_proc:
mock_proc.stderr = mock_proc.stdout = None
mock_proc.return_value = CompletedProcess(args="", returncode=0)
actual = check_composer_package_files("/mocked/path/", False)
self.assertEqual(len(actual[1]), 0, "There should NOT be a warning of a lock file missing")

def test_lock_file_missing(self):
with patch(f"{GENERATE_LOCKS_PREFIX}glob") as mock_glob:
mock_glob.return_value = ["/mocked/path/package.json"]
Expand All @@ -133,6 +144,16 @@ def test_lock_file_missing(self):
actual = check_package_files("/mocked/path/", False, False)
self.assertEqual(len(actual[1]), 1, "There should be a warning of a lock file missing")

def test_compose_lock_file_missing(self):
with patch(f"{GENERATE_LOCKS_PREFIX}glob") as mock_glob:
mock_glob.return_value = ["/mocked/path/compose.json"]
with patch(f"{GENERATE_LOCKS_PREFIX}os.path.exists", return_value=False):
with patch(f"{GENERATE_LOCKS_PREFIX}subprocess.run") as mock_proc:
mock_proc.stderr = mock_proc.stdout = None
mock_proc.return_value = CompletedProcess(args="", returncode=0)
actual = check_composer_package_files("/mocked/path/", False)
self.assertEqual(len(actual[1]), 1, "There should be a warning of a lock file missing")

def test_check_output(self):
check_output_list = Trivy.parse_output(self.demo_results_dict)
self.assertIn(TEST_CHECK_OUTPUT_PACKAGE_LOCK, check_output_list)
Expand Down
Loading