diff --git a/backend/Dockerfiles/Dockerfile.dind b/backend/Dockerfiles/Dockerfile.dind index 478e68b0..e25089cd 100644 --- a/backend/Dockerfiles/Dockerfile.dind +++ b/backend/Dockerfiles/Dockerfile.dind @@ -3,8 +3,8 @@ FROM docker:20.10.14-dind ARG MAINTAINER LABEL maintainer=$MAINTAINER -ARG TRIVY_VER=v0.16.0 -ARG TRIVY_COMMIT=0285a89c7cce9b2d07bd5826cd2fed68420ca546 +ARG TRIVY_VER=v0.48.0 +ARG TRIVY_COMMIT=01edbda3472ebbbe71f341d1741194ec35de7d69 ARG SNYK_VER=v1.889.0 # Copy Artemis libraries into /src for installation @@ -17,6 +17,7 @@ COPY ./libs/ /src/ # - Symlink python3 to python for Analyzer Engine benefit RUN apk update && apk add git unzip python3 py3-pip libgcc libstdc++ && \ apk upgrade && \ + apk add npm && \ pip3 install --upgrade pip setuptools boto3 && \ pip3 install boto3 && \ ln -s /usr/bin/python3 /usr/bin/python && \ diff --git a/backend/Makefile b/backend/Makefile index 83b7fa5d..46d45c19 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -202,8 +202,8 @@ SEVERITY_LEVELS := critical,high PHP_SCANNER_VER := 1.0.0 # Trivy -TRIVY_COMMIT := 0285a89c7cce9b2d07bd5826cd2fed68420ca546 -TRIVY_VER := v0.16.0 +TRIVY_COMMIT := 01edbda3472ebbbe71f341d1741194ec35de7d69 +TRIVY_VER := v0.48.0 # Snyk SNYK_VER := v1.889.0 diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 1909ec6e..b4bd6305 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -222,33 +222,33 @@ }, "cryptography": { "hashes": [ - "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf", - "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84", - "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e", - "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8", - "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7", - "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1", - "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88", - "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86", - "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179", - "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81", - "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20", - "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548", - "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d", - "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d", - "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5", - "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1", - "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147", - "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936", - "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797", - "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696", - "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72", - "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da", - "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723" + "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960", + "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a", + "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc", + "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a", + "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf", + "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1", + "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39", + "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406", + "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a", + "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a", + "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c", + "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be", + "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15", + "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2", + "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d", + "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157", + "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003", + "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248", + "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a", + "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec", + "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309", + "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7", + "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==41.0.5" + "version": "==41.0.7" }, "cwe": { "hashes": [ diff --git a/backend/engine/plugins/lib/trivy_common/generate_locks.py b/backend/engine/plugins/lib/trivy_common/generate_locks.py new file mode 100644 index 00000000..e10f7c5e --- /dev/null +++ b/backend/engine/plugins/lib/trivy_common/generate_locks.py @@ -0,0 +1,73 @@ +import subprocess +import os +from glob import glob +from engine.plugins.lib import utils +from engine.plugins.lib.write_npmrc import handle_npmrc_creation + +logger = utils.setup_logging("trivy_sca") + + +def install_package_files(include_dev, path, root_path): + # Create a package-lock.json file if it doesn't already exist + logger.info( + f'Generating package-lock.json for {path.replace(root_path, "")} (including dev dependencies: {include_dev})' + ) + cmd = [ + "npm", + "install", + "--package-lock-only", # Generate the needed lockfile + "--legacy-bundling", # Don't dedup dependencies so that we can correctly trace their root in package.json + "--legacy-peer-deps", # Ignore peer dependencies, which is the NPM 6.x behavior + "--no-audit", # Don't run an audit + ] + if not include_dev: + cmd.append("--only=prod") + return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=path, check=False) + + +def check_package_files(path: str, include_dev: bool) -> tuple: + """ + Main Function + Find all of the package.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 package.json files in the path + files = glob("%s/**/package.json" % path, recursive=True) + + logger.info("Found %d package.json files", len(files)) + + # If there are no package.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)) + + # Write a .npmrc file based on the set of package.json files found + handle_npmrc_creation(logger, paths) + + # Loop through paths that have a package file and generate a package-lock.json for them (if does not exist) + for sub_path in paths: + lockfile = os.path.join(sub_path, "package-lock.json") + lockfile_missing = not os.path.exists(lockfile) + if lockfile_missing: + msg = ( + f"No package-lock.json file was found in path {sub_path.replace(path, '')}." + " Please consider creating a package-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 diff --git a/backend/engine/plugins/lib/trivy_common/parsing_util.py b/backend/engine/plugins/lib/trivy_common/parsing_util.py new file mode 100644 index 00000000..7b7598ee --- /dev/null +++ b/backend/engine/plugins/lib/trivy_common/parsing_util.py @@ -0,0 +1,89 @@ +""" +trivy output parser +""" +import json +from typing import NamedTuple +from engine.plugins.lib import utils + +logger = utils.setup_logging("trivy") + +DESC_REMEDIATION_SPLIT = "## Recommendation" + + +def parse_output(output: list) -> list: + results = [] + for item in output: + source = item["Target"] + component_type = convert_type(item.get("Type", "N/A")) + if item.get("Vulnerabilities") is None: + continue + cve_set = set() + for vuln in item["Vulnerabilities"]: + vuln_id = vuln.get("VulnerabilityID") + if vuln_id in cve_set: + continue + cve_set.add(vuln_id) + description_result = get_description_and_remediation(vuln.get("Description"), vuln.get("FixedVersion")) + + component = vuln.get("PkgName") + if vuln.get("InstalledVersion"): + component = f'{component}-{vuln.get("InstalledVersion")}' + results.append( + { + "component": component, + "source": source, + "id": vuln_id, + "description": description_result.description, + "severity": vuln.get("Severity", "").lower(), + "remediation": description_result.remediation, + "inventory": { + "component": { + "name": vuln.get("PkgName"), + "version": vuln.get("InstalledVersion"), + "type": component_type, + }, + "advisory_ids": sorted( + list(set(filter(None, [vuln_id, vuln.get("PrimaryURL")] + vuln.get("References", [])))) + ), + }, + } + ) + return results + + +def convert_type(component_type: str) -> str: + if component_type == "bundler": + return "gem" + return component_type.lower() + + +def get_description_and_remediation(description, fixed_version) -> NamedTuple: + """ + gets the description and remediation fields after pulling them from the vuln and appending/removing additional info + :param fixed_version: + :param description: + :return: NamedTuple containing the description and remediation + """ + result = NamedTuple("DescriptionResult", [("description", str), ("remediation", str)]) + if not description: + description = "" + remediation = "" + if DESC_REMEDIATION_SPLIT in description: + des_split = description.split(DESC_REMEDIATION_SPLIT) + remediation = des_split[1].strip() + description = des_split[0].strip() + if fixed_version: + remediation = f"Fixed Version: {fixed_version}. {remediation}".strip() + result.description = description + result.remediation = remediation + return result + + +def convert_output(output_str: str): + if not output_str: + return None + try: + return json.loads(output_str) + except json.JSONDecodeError as e: + logger.error(e) + return None diff --git a/backend/engine/plugins/node_dependencies/write_npmrc.py b/backend/engine/plugins/lib/write_npmrc.py similarity index 72% rename from backend/engine/plugins/node_dependencies/write_npmrc.py rename to backend/engine/plugins/lib/write_npmrc.py index 328ab88e..811dd8bc 100644 --- a/backend/engine/plugins/node_dependencies/write_npmrc.py +++ b/backend/engine/plugins/lib/write_npmrc.py @@ -3,10 +3,8 @@ NODE_CRED_KEY = "node-dep-creds" -log = utils.setup_logging("node_dependencies") - -def handle_npmrc_creation(paths: set, home_dir=None) -> bool: +def handle_npmrc_creation(logger, paths: set, home_dir=None) -> bool: """ Main npmrc creation function. Checks if the .npmrc file exists, and if not, gets and writes the private registries currently in the package.jsons @@ -16,18 +14,18 @@ def handle_npmrc_creation(paths: set, home_dir=None) -> bool: home_dir = os.environ["HOME"] npmrc = os.path.join(home_dir, ".npmrc") if os.path.exists(npmrc): - log.info("%s already exists, skipping", npmrc) + logger.info("%s already exists, skipping", npmrc) return False - scope_list = get_config_matches_in_packages(paths) + scope_list = get_config_matches_in_packages(logger, paths) if not scope_list: - log.info("No supported private packages found. Skipping .npmrc creation.") + logger.info("No supported private packages found. Skipping .npmrc creation.") return False - write_npmrc(npmrc, scope_list) + write_npmrc(logger, npmrc, scope_list) return True -def get_config_matches_in_packages(paths: set) -> list: +def get_config_matches_in_packages(logger, paths: set) -> list: """ - Gets a list of the private scopes supported from Secrets Manager. - Checks to see if the package.jsons in our paths have any supported private scopes. @@ -35,9 +33,9 @@ def get_config_matches_in_packages(paths: set) -> list: return: List of private scopes """ # get list of configs to check for - configs = get_scope_configs() + configs = get_scope_configs(logger) if configs is None: - log.warning("List of configs is empty. Skipping .npmrc creation.") + logger.warning("List of configs is empty. Skipping .npmrc creation.") return None private_scope_list = [] @@ -50,18 +48,18 @@ def get_config_matches_in_packages(paths: set) -> list: contents = f.read() for config in configs: if f'"@{config["scope"]}/' in contents: - log.info(f"%s has @%s packages", package_file, config["scope"]) + logger.info(f"%s has @%s packages", package_file, config["scope"]) private_scope_list.append(config) return private_scope_list -def get_scope_configs(): +def get_scope_configs(logger): """ grabs the list of private registries from secrets manager """ - creds = utils.get_secret(NODE_CRED_KEY, log) + creds = utils.get_secret(NODE_CRED_KEY, logger) if not creds: - log.error("Unable to retrieve Node registry configs.") + logger.error("Unable to retrieve Node registry configs.") return None return creds @@ -81,9 +79,9 @@ def build_npm_config(scope, registry, token, username, email, **kwargs): ) -def write_npmrc(npmrc, scope_list: list) -> None: +def write_npmrc(logger, npmrc, scope_list: list) -> None: # Write the configs for flagged scopes to npmrc with open(npmrc, "a") as f: for scope in scope_list: - log.info(f"Writing {scope['scope']} config to %s", npmrc) + logger.info(f"Writing {scope['scope']} config to %s", npmrc) f.write(build_npm_config(**scope)) diff --git a/backend/engine/plugins/node_dependencies/main.py b/backend/engine/plugins/node_dependencies/main.py index ae9b1497..dd24b7c8 100644 --- a/backend/engine/plugins/node_dependencies/main.py +++ b/backend/engine/plugins/node_dependencies/main.py @@ -9,7 +9,7 @@ from engine.plugins.lib.line_numbers.resolver import LineNumberResolver from engine.plugins.node_dependencies.audit import npm_audit from engine.plugins.node_dependencies.parse import parse_advisory -from engine.plugins.node_dependencies.write_npmrc import handle_npmrc_creation +from engine.plugins.lib.write_npmrc import handle_npmrc_creation log = utils.setup_logging("node_dependencies") @@ -35,7 +35,7 @@ def check_package_files(path: str, include_dev: bool = False) -> tuple: paths.add(os.path.dirname(filename)) # Write a .npmrc file based on the set of package.json files found - handle_npmrc_creation(paths) + handle_npmrc_creation(log, paths) for sub_path in paths: absolute_package_file = f"{sub_path}/package.json" diff --git a/backend/engine/plugins/trivy/main.py b/backend/engine/plugins/trivy/main.py index 002727ed..3fe5a049 100644 --- a/backend/engine/plugins/trivy/main.py +++ b/backend/engine/plugins/trivy/main.py @@ -1,114 +1,17 @@ """ -trivy plugin +trivy image scanning plugin """ import json import subprocess -from typing import NamedTuple - from engine.plugins.lib import utils +from engine.plugins.lib.trivy_common.parsing_util import convert_output +from engine.plugins.lib.trivy_common.parsing_util import parse_output logger = utils.setup_logging("trivy") -DESC_REMEDIATION_SPLIT = "## Recommendation" -NO_RESULTS_TEXT = "no supported file was detected" -JSON_FILE = "trivy.json" - - -def parse_output(output: list) -> list: - results = [] - for item in output: - source = item["Target"] - component_type = convert_type(item["Type"]) - if item.get("Vulnerabilities") is None: - continue - cve_set = set() - for vuln in item["Vulnerabilities"]: - vuln_id = vuln.get("VulnerabilityID") - if vuln_id in cve_set: - continue - cve_set.add(vuln_id) - description_result = get_description_and_remediation(vuln.get("Description"), vuln.get("FixedVersion")) - - component = vuln.get("PkgName") - if vuln.get("InstalledVersion"): - component = f'{component}-{vuln.get("InstalledVersion")}' - results.append( - { - "component": component, - "source": source, - "id": vuln_id, - "description": description_result.description, - "severity": vuln.get("Severity", "").lower(), - "remediation": description_result.remediation, - "inventory": { - "component": { - "name": vuln.get("PkgName"), - "version": vuln.get("InstalledVersion"), - "type": component_type, - }, - "advisory_ids": sorted( - list(set(filter(None, [vuln_id, vuln.get("PrimaryURL")] + vuln.get("References", [])))) - ), - }, - } - ) - return results - - -def convert_type(component_type: str) -> str: - if component_type == "bundler": - return "gem" - return component_type.lower() - - -def get_description_and_remediation(description, fixed_version) -> NamedTuple: - """ - gets the description and remediation fields after pulling them from the vuln and appending/removing additional info - :param fixed_version: - :param description: - :return: NamedTuple containing the description and remediation - """ - result = NamedTuple("DescriptionResult", [("description", str), ("remediation", str)]) - if not description: - description = "" - remediation = "" - if DESC_REMEDIATION_SPLIT in description: - des_split = description.split(DESC_REMEDIATION_SPLIT) - remediation = des_split[1].strip() - description = des_split[0].strip() - if fixed_version: - remediation = f"Fixed Version: {fixed_version}. {remediation}".strip() - result.description = description - result.remediation = remediation - return result - - -def convert_output(output_str: str): - if not output_str: - return None - try: - return json.loads(output_str) - except json.JSONDecodeError as e: - logger.error(e) - return None - - -def execute_trivy_lock_scan(path: str): - proc = subprocess.run(["trivy", "-q", "fs", "-f", "json", path], capture_output=True, check=False) - if proc.returncode != 0: - logger.warning(proc.stderr.decode("utf-8")) - return None - if proc.stdout.decode("utf-8") == "null": - logger.warning("No response returned. No files to parse.") - return None - if proc.stdout and NO_RESULTS_TEXT not in proc.stdout.decode("utf-8"): - return proc.stdout.decode("utf-8") - logger.error(proc.stderr.decode("utf-8")) - return None - def execute_trivy_image_scan(image: str): - proc = subprocess.run(["trivy", "-q", "image", "-f", "json", image], capture_output=True, check=False) + proc = subprocess.run(["trivy", "image", image, "--format", "json"], capture_output=True, check=False) if proc.returncode != 0: logger.warning(proc.stderr.decode("utf-8")) return None @@ -128,7 +31,6 @@ def process_docker_images(images: list): "dockerfile": "/Dockerfiles" } """ - outputs = [] for image in images: if not image.get("status"): @@ -142,10 +44,12 @@ def process_docker_images(images: list): image["dockerfile"], ) else: - # The tag-id is placed at the Target. The tag-id is auto-generated and is of no use to the end user. - # Placing the dockerfile location allows the user to better identify the location of the vulns + """ + The tag-id is placed at the Target. The tag-id is auto-generated and is of no use to the end user. + Placing the dockerfile location allows the user to better identify the location of the vulns + """ items = [] - for item in output: + for item in output["Results"]: if image["tag-id"] in item["Target"]: item["Target"] = image["dockerfile"] else: @@ -174,15 +78,7 @@ def main(): logger.info("Executing Trivy") args = utils.parse_args() results = [] - # Scan local lock files - output = execute_trivy_lock_scan(args.path) - output = convert_output(output) - if not output: - logger.warning("Lock file output is None. Continuing.") - else: - result = parse_output(output) - logger.info("Lock file output parsed. Success: %s", not bool(result)) - results.extend(result) + # Scan Images image_outputs = build_scan_parse_images(args.images) results.extend(image_outputs) diff --git a/backend/engine/plugins/trivy/settings.json b/backend/engine/plugins/trivy/settings.json index 8deaf662..c0e2fd87 100644 --- a/backend/engine/plugins/trivy/settings.json +++ b/backend/engine/plugins/trivy/settings.json @@ -1,5 +1,5 @@ { - "name": "Trivy Scanner", + "name": "Trivy Image Scanner", "type": "vulnerability", "image": "$ECR/artemis/dind:latest", "build_images": true, diff --git a/backend/engine/plugins/trivy_sca/__init__.py b/backend/engine/plugins/trivy_sca/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/engine/plugins/trivy_sca/main.py b/backend/engine/plugins/trivy_sca/main.py new file mode 100644 index 00000000..df07c93b --- /dev/null +++ b/backend/engine/plugins/trivy_sca/main.py @@ -0,0 +1,63 @@ +""" +trivy SCA plugin +""" +import json +import subprocess +from engine.plugins.lib import utils +from engine.plugins.lib.trivy_common.generate_locks import check_package_files +from engine.plugins.lib.trivy_common.parsing_util import convert_output +from engine.plugins.lib.trivy_common.parsing_util import parse_output + +logger = utils.setup_logging("trivy_sca") + +NO_RESULTS_TEXT = "no supported file was detected" + + +def execute_trivy_lock_scan(path: str, include_dev: bool): + logger.info(f"Scanning lock-files. Dev-dependencies: {include_dev}") + args = ["trivy", "fs", path, "--format", "json"] + if include_dev: + args.append("--include-dev-deps") + proc = subprocess.run(args, capture_output=True, check=False) + if proc.returncode != 0: + logger.warning(proc.stderr.decode("utf-8")) + return None + if proc.stdout.decode("utf-8") == "null": + logger.warning("No response returned. No files to parse.") + return None + if proc.stdout and NO_RESULTS_TEXT not in proc.stdout.decode("utf-8"): + return proc.stdout.decode("utf-8") + logger.error(proc.stderr.decode("utf-8")) + return None + + +def main(): + logger.info("Executing Trivy SCA") + args = utils.parse_args() + include_dev = args.engine_vars.get("include_dev", False) + results = [] + + # Generate Lock files + lock_file_errors, lock_file_alerts = check_package_files(args.path, include_dev) + + # Scan local lock files + output = execute_trivy_lock_scan(args.path, include_dev) + logger.debug(output) + output = convert_output(output) + if not output: + logger.warning("Lock file output is None. Continuing.") + else: + result = parse_output(output["Results"]) + logger.info("Lock file output parsed. Success: %s", bool(result)) + results.extend(result) + + # Return results + print( + json.dumps( + {"success": not bool(results), "details": results, "errors": lock_file_errors, "alerts": lock_file_alerts} + ) + ) + + +if __name__ == "__main__": + main() diff --git a/backend/engine/plugins/trivy_sca/settings.json b/backend/engine/plugins/trivy_sca/settings.json new file mode 100644 index 00000000..c8641287 --- /dev/null +++ b/backend/engine/plugins/trivy_sca/settings.json @@ -0,0 +1,7 @@ +{ + "name": "Trivy SCA Scanner", + "type": "vulnerability", + "image": "$ECR/artemis/dind:latest", + "build_images": false, + "enabled": true +} diff --git a/backend/engine/tests/test_plugin_node_dependencies.py b/backend/engine/tests/test_plugin_node_dependencies.py index 0aa74004..fd4587fa 100644 --- a/backend/engine/tests/test_plugin_node_dependencies.py +++ b/backend/engine/tests/test_plugin_node_dependencies.py @@ -6,13 +6,16 @@ import pytest +from engine.plugins.lib import utils from engine.plugins.lib.cve import find_cves from engine.plugins.lib.line_numbers.resolver import LineNumberResolver -from engine.plugins.node_dependencies import write_npmrc +from engine.plugins.lib import write_npmrc from engine.plugins.node_dependencies.audit import npm_audit from engine.plugins.node_dependencies.main import _extract_advisories, _load_lockfile from engine.plugins.node_dependencies.parse import _find_versions_v1, _find_versions_v2, parse_advisory +log = utils.setup_logging("node_dependencies") + AUDIT_PREFIX = "engine.plugins.node_dependencies.audit." CVE_PREFIX = "engine.plugins.node_dependencies.parse." @@ -496,7 +499,7 @@ def test_write_npmrc(self): with TemporaryDirectory() as working_dir: npmrc = os.path.join(working_dir, ".npmrc") - write_npmrc.write_npmrc(npmrc, scope_list) + write_npmrc.write_npmrc(log, npmrc, scope_list) with open(npmrc) as npm_file: result = npm_file.read() @@ -510,7 +513,7 @@ def test_get_config_matches_in_packages_scope1(self, mock_creds): expected_result = [TEST_SCOPE_CONFIG[0]] - result = write_npmrc.get_config_matches_in_packages(paths) + result = write_npmrc.get_config_matches_in_packages(log, paths) self.assertEqual(expected_result, result) @@ -522,7 +525,7 @@ def test_get_config_matches_in_packages_scope2(self, mock_creds): expected_result = [TEST_SCOPE_CONFIG[1]] - result = write_npmrc.get_config_matches_in_packages(paths) + result = write_npmrc.get_config_matches_in_packages(log, paths) self.assertEqual(expected_result, result) @@ -536,14 +539,14 @@ def test_handle_npmrc_creation(self, mock_creds): with TemporaryDirectory() as working_dir: npmrc = os.path.join(working_dir, ".npmrc") - write_npmrc.handle_npmrc_creation(paths, home_dir=working_dir) + write_npmrc.handle_npmrc_creation(log, paths, home_dir=working_dir) with open(npmrc) as npm_file: result = npm_file.read() self.assertEqual(expected_result, result) def test_handle_npmrc_creation_file_exists(self): - result = write_npmrc.handle_npmrc_creation(set(), os.path.join(NODE_DIR, "private_scope")) + result = write_npmrc.handle_npmrc_creation(log, set(), os.path.join(NODE_DIR, "private_scope")) self.assertFalse(result) @@ -552,6 +555,6 @@ def test_handle_npmrc_creation_file_no_private_packages(self, mock_creds): self.assertEqual(write_npmrc.get_scope_configs, mock_creds) mock_creds.return_value = TEST_SCOPE_CONFIG - result = write_npmrc.handle_npmrc_creation(set(), NODE_DIR) + result = write_npmrc.handle_npmrc_creation(log, set(), NODE_DIR) self.assertFalse(result) diff --git a/backend/engine/tests/test_trivy.py b/backend/engine/tests/test_trivy.py index f94f5272..347932ca 100644 --- a/backend/engine/tests/test_trivy.py +++ b/backend/engine/tests/test_trivy.py @@ -10,6 +10,8 @@ TEST_DIR = os.path.dirname(os.path.abspath(__file__)) +NODE_DIR = os.path.join(TEST_DIR, "data", "node") + TEST_ROOT = os.path.abspath(os.path.join(TEST_DIR, "..", "..")) TEST_DATA = os.path.join(TEST_DIR, "data") @@ -20,73 +22,6 @@ TEST_IMAGE = os.path.join(TEST_DATA, "image", "test_file_for_docker") -TEST_CHECK_OUTPUT_GEMLOCK_FILE = { - "component": "rest-client-1.7.3", - "source": "ruby/dependency_vulnerability_samples/Gemfile.lock", - "id": "CVE-2015-1820", - "description": "REST client for Ruby (aka rest-client) before 1.8.0 allows remote attackers to conduct session " - "fixation attacks or obtain sensitive cookie information by leveraging passage of cookies set in a " - "response to a redirect.", - "severity": "critical", - "remediation": "Fixed Version: 1.8.0.", - "inventory": { - "component": {"name": "rest-client", "version": "1.7.3", "type": "gem"}, - "advisory_ids": [ - "CVE-2015-1820", - "http://www.openwall.com/lists/oss-security/2015/03/24/3", - "http://www.securityfocus.com/bid/73295", - "https://avd.aquasec.com/nvd/cve-2015-1820", - "https://bugzilla.redhat.com/show_bug.cgi?id=1205291", - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-1820", - "https://github.com/advisories/GHSA-3fhf-6939-qg8p", - "https://github.com/rest-client/rest-client/issues/369", - "https://nvd.nist.gov/vuln/detail/CVE-2015-1820", - ], - }, -} - -TEST_CHECK_OUTPUT_PACKAGE_LOCK = { - "component": "braces-1.8.5", - "source": "node/dependency_vulnerability_samples/package-lock.json", - "id": "GHSA-g95f-p29q-9xw4", - "description": "Versions of `braces` prior to 2.3.1 are vulnerable to Regular Expression Denial of Service (" - "ReDoS). Untrusted input may cause catastrophic backtracking while matching regular expressions. " - "This can cause the application to be unresponsive leading to Denial of Service.", - "severity": "low", - "remediation": "Fixed Version: 2.3.1. Upgrade to version 2.3.1 or higher.", - "inventory": { - "component": {"name": "braces", "version": "1.8.5", "type": "npm"}, - "advisory_ids": [ - "GHSA-g95f-p29q-9xw4", - "https://github.com/advisories/GHSA-g95f-p29q-9xw4", - "https://github.com/micromatch/braces/commit/abdafb0cae1e0c00f184abbadc692f4eaa98f451", - ], - }, -} - -TEST_VULN_LOCK_FILE_DICT = { - "Target": "node/dependency_vulnerability_samples/package-lock.json", - "Type": "npm", - "Vulnerabilities": [ - { - "VulnerabilityID": "GHSA-g95f-p29q-9xw4", - "PkgName": "braces", - "InstalledVersion": "1.8.5", - "FixedVersion": "2.3.1", - "PrimaryURL": "https://github.com/advisories/GHSA-g95f-p29q-9xw4", - "Title": "Regular Expression Denial of Service in braces", - "Description": "Versions of `braces` prior to 2.3.1 are vulnerable to Regular Expression Denial of " - "Service (ReDoS). Untrusted input may cause catastrophic backtracking while matching " - "regular expressions. This can cause the application to be unresponsive leading to Denial " - "of Service.\n\n\n## Recommendation\n\nUpgrade to version 2.3.1 or higher.", - "Severity": "LOW", - "References": [ - "https://github.com/advisories/GHSA-g95f-p29q-9xw4", - "https://github.com/micromatch/braces/commit/abdafb0cae1e0c00f184abbadc692f4eaa98f451", - ], - } - ], -} TEST_IMAGE_VULN_DICT = { "Target": "test-docker-dcff6ea60ec3cbae155abaed414c033b-t-1000 (alpine 3.12.3)", @@ -164,15 +99,6 @@ def setUp(self) -> None: with open(TEST_OUTPUT) as output_file: self.demo_results_dict = json.load(output_file) - 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) - self.assertIn(TEST_CHECK_OUTPUT_GEMLOCK_FILE, check_output_list) - - def test_parse_output_one_file(self): - result = Trivy.parse_output([TEST_VULN_LOCK_FILE_DICT]) - self.assertEqual([TEST_CHECK_OUTPUT_PACKAGE_LOCK], result) - def test_parse_output_image_output(self): result = Trivy.parse_output([TEST_IMAGE_VULN_DICT]) self.assertEqual(TEST_IMAGE_VULN_RESULT, result) @@ -191,32 +117,6 @@ def tearDown(self) -> None: for image in self.images["results"]: remover.remove_docker_image(image) - @pytest.mark.integtest - def test_execute_trivy_no_files(self): - result = Trivy.execute_trivy_lock_scan(os.path.abspath(os.path.join(TRIVY_DATA, "no_files"))) - self.assertEqual(None, result) - - @pytest.mark.integtest - def test_execute_trivy_success(self): - result = Trivy.execute_trivy_lock_scan(TEST_ROOT) - self.assertIsInstance(result, str) - - @pytest.mark.integtest - def test_convert_output_success(self): - response = Trivy.execute_trivy_lock_scan(TEST_ROOT) - result = Trivy.convert_output(response) - self.assertNotIn(result, [[], None]) - self.assertIsInstance(result, list) - - @pytest.mark.integtest - def test_convert_output_output_correct(self): - self.maxDiff = None - response = Trivy.execute_trivy_lock_scan(TRIVY_DATA) - result = Trivy.convert_output(response) - self.assertIsInstance(result[0]["Vulnerabilities"], list) - result[0]["Vulnerabilities"] = None - self.assertEqual(ARTEMIS_VULN, result) - @pytest.mark.integtest def test_execute_trivy_image_no_image(self): response = Trivy.execute_trivy_image_scan("test-docker-doesnt-exist-t-1000") diff --git a/backend/engine/tests/test_trivy_sca.py b/backend/engine/tests/test_trivy_sca.py new file mode 100644 index 00000000..5c4e5d8c --- /dev/null +++ b/backend/engine/tests/test_trivy_sca.py @@ -0,0 +1,173 @@ +import json +import os.path +import unittest +import pytest + +from subprocess import CompletedProcess +from unittest.mock import patch +from docker import remover +from engine.plugins.trivy_sca import main as Trivy +from engine.plugins.lib.trivy_common.generate_locks import check_package_files + +TEST_DIR = os.path.dirname(os.path.abspath(__file__)) + +NODE_DIR = os.path.join(TEST_DIR, "data", "node") + +TEST_ROOT = os.path.abspath(os.path.join(TEST_DIR, "..", "..")) +GENERATE_LOCKS_PREFIX = "engine.plugins.lib.trivy_common.generate_locks." + +TEST_DATA = os.path.join(TEST_DIR, "data") + +TRIVY_DATA = os.path.join(TEST_DATA, "trivy") + +TEST_OUTPUT = os.path.join(TRIVY_DATA, "demo-results.json") + + +TEST_CHECK_OUTPUT_GEMLOCK_FILE = { + "component": "rest-client-1.7.3", + "source": "ruby/dependency_vulnerability_samples/Gemfile.lock", + "id": "CVE-2015-1820", + "description": "REST client for Ruby (aka rest-client) before 1.8.0 allows remote attackers to conduct session " + "fixation attacks or obtain sensitive cookie information by leveraging passage of cookies set in a " + "response to a redirect.", + "severity": "critical", + "remediation": "Fixed Version: 1.8.0.", + "inventory": { + "component": {"name": "rest-client", "version": "1.7.3", "type": "gem"}, + "advisory_ids": [ + "CVE-2015-1820", + "http://www.openwall.com/lists/oss-security/2015/03/24/3", + "http://www.securityfocus.com/bid/73295", + "https://avd.aquasec.com/nvd/cve-2015-1820", + "https://bugzilla.redhat.com/show_bug.cgi?id=1205291", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-1820", + "https://github.com/advisories/GHSA-3fhf-6939-qg8p", + "https://github.com/rest-client/rest-client/issues/369", + "https://nvd.nist.gov/vuln/detail/CVE-2015-1820", + ], + }, +} + +TEST_CHECK_OUTPUT_PACKAGE_LOCK = { + "component": "braces-1.8.5", + "source": "node/dependency_vulnerability_samples/package-lock.json", + "id": "GHSA-g95f-p29q-9xw4", + "description": "Versions of `braces` prior to 2.3.1 are vulnerable to Regular Expression Denial of Service (" + "ReDoS). Untrusted input may cause catastrophic backtracking while matching regular expressions. " + "This can cause the application to be unresponsive leading to Denial of Service.", + "severity": "low", + "remediation": "Fixed Version: 2.3.1. Upgrade to version 2.3.1 or higher.", + "inventory": { + "component": {"name": "braces", "version": "1.8.5", "type": "npm"}, + "advisory_ids": [ + "GHSA-g95f-p29q-9xw4", + "https://github.com/advisories/GHSA-g95f-p29q-9xw4", + "https://github.com/micromatch/braces/commit/abdafb0cae1e0c00f184abbadc692f4eaa98f451", + ], + }, +} + +TEST_VULN_LOCK_FILE_DICT = { + "Target": "node/dependency_vulnerability_samples/package-lock.json", + "Type": "npm", + "Vulnerabilities": [ + { + "VulnerabilityID": "GHSA-g95f-p29q-9xw4", + "PkgName": "braces", + "InstalledVersion": "1.8.5", + "FixedVersion": "2.3.1", + "PrimaryURL": "https://github.com/advisories/GHSA-g95f-p29q-9xw4", + "Title": "Regular Expression Denial of Service in braces", + "Description": "Versions of `braces` prior to 2.3.1 are vulnerable to Regular Expression Denial of " + "Service (ReDoS). Untrusted input may cause catastrophic backtracking while matching " + "regular expressions. This can cause the application to be unresponsive leading to Denial " + "of Service.\n\n\n## Recommendation\n\nUpgrade to version 2.3.1 or higher.", + "Severity": "LOW", + "References": [ + "https://github.com/advisories/GHSA-g95f-p29q-9xw4", + "https://github.com/micromatch/braces/commit/abdafb0cae1e0c00f184abbadc692f4eaa98f451", + ], + } + ], +} + +ARTEMIS_VULN = [{"Target": "Gemfile.lock", "Type": "bundler", "Vulnerabilities": None}] + +TEST_BUILD_SCAN_PARSE_RESULT_DICT = { + "component": "libcrypto1.1-1.1.1g-r0", + "source": "/image/test_file_for_docker", + "id": "CVE-2021-23839", + "description": "", + "severity": "low", + "remediation": "Fixed Version: 1.1.1j-r0.", +} + + +class TestTrivy(unittest.TestCase): + def setUp(self) -> None: + with open(TEST_OUTPUT) as output_file: + self.demo_results_dict = json.load(output_file) + + def test_lock_file_exists(self): + with patch(f"{GENERATE_LOCKS_PREFIX}glob") as mock_glob: + mock_glob.return_value = ["/mocked/path/package.json"] + with patch(f"{GENERATE_LOCKS_PREFIX}handle_npmrc_creation"): + 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_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"] + with patch(f"{GENERATE_LOCKS_PREFIX}handle_npmrc_creation"): + 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_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) + self.assertIn(TEST_CHECK_OUTPUT_GEMLOCK_FILE, check_output_list) + + def test_parse_output_one_file(self): + result = Trivy.parse_output([TEST_VULN_LOCK_FILE_DICT]) + self.assertEqual([TEST_CHECK_OUTPUT_PACKAGE_LOCK], result) + + +@pytest.mark.integtest +class TestTrivyIntegration(unittest.TestCase): + def tearDown(self) -> None: + for image in self.images["results"]: + remover.remove_docker_image(image) + + @pytest.mark.integtest + def test_execute_trivy_no_files(self): + result = Trivy.execute_trivy_lock_scan(os.path.abspath(os.path.join(TRIVY_DATA, "no_files"))) + self.assertEqual(None, result) + + @pytest.mark.integtest + def test_execute_trivy_success(self): + result = Trivy.execute_trivy_lock_scan(TEST_ROOT) + self.assertIsInstance(result, str) + + @pytest.mark.integtest + def test_convert_output_success(self): + response = Trivy.execute_trivy_lock_scan(TEST_ROOT) + result = Trivy.convert_output(response) + self.assertNotIn(result, [[], None]) + self.assertIsInstance(result, list) + + @pytest.mark.integtest + def test_convert_output_output_correct(self): + self.maxDiff = None + response = Trivy.execute_trivy_lock_scan(TRIVY_DATA) + result = Trivy.convert_output(response) + self.assertIsInstance(result[0]["Vulnerabilities"], list) + result[0]["Vulnerabilities"] = None + self.assertEqual(ARTEMIS_VULN, result) diff --git a/backend/lambdas/api/repo/repo/util/const.py b/backend/lambdas/api/repo/repo/util/const.py index 73f52bef..60963e7e 100644 --- a/backend/lambdas/api/repo/repo/util/const.py +++ b/backend/lambdas/api/repo/repo/util/const.py @@ -26,6 +26,7 @@ "php_sensio_security_checker": None, "bundler_audit": None, "trivy": None, + "trivy_sca": None, }, "secret": {"gitsecrets": None, "truffle_hog": None}, "static_analysis": { diff --git a/backend/lambdas/api/repo/tests/test_parse_event.py b/backend/lambdas/api/repo/tests/test_parse_event.py index 599162ca..28470723 100644 --- a/backend/lambdas/api/repo/tests/test_parse_event.py +++ b/backend/lambdas/api/repo/tests/test_parse_event.py @@ -220,6 +220,7 @@ "swiftlint", "checkov", "-trivy", + "-trivy_sca", "-technology_discovery", "-gitsecrets", "-owasp_dependency_check", diff --git a/backend/libs/artemisdb/artemisdb/artemisdb/migrations/0048_alter_component_version.py b/backend/libs/artemisdb/artemisdb/artemisdb/migrations/0048_alter_component_version.py new file mode 100644 index 00000000..dd85c988 --- /dev/null +++ b/backend/libs/artemisdb/artemisdb/artemisdb/migrations/0048_alter_component_version.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.23 on 2024-01-03 16:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("artemisdb", "0047_auto_20231115_1408"), + ] + + operations = [ + migrations.AlterField( + model_name="component", + name="version", + field=models.CharField(max_length=256), + ), + ] diff --git a/backend/libs/artemisdb/artemisdb/artemisdb/models.py b/backend/libs/artemisdb/artemisdb/artemisdb/models.py index f558db39..2428ddd1 100644 --- a/backend/libs/artemisdb/artemisdb/artemisdb/models.py +++ b/backend/libs/artemisdb/artemisdb/artemisdb/models.py @@ -964,7 +964,7 @@ class Component(models.Model): id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID") name = models.CharField(max_length=256) - version = models.CharField(max_length=32) + version = models.CharField(max_length=256) licenses = models.ManyToManyField(License) # This is an internal field to store the ltree-compatible label for this component. diff --git a/ui/package-lock.json b/ui/package-lock.json index b84062b4..ab9a188e 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -24,7 +24,7 @@ "@types/react-dom": "^18.2.4", "@types/react-router-dom": "^5.3.3", "autosuggest-highlight": "3.2.1", - "axios": "^1.6.1", + "axios": "^1.6.5", "formik": "^2.4.1", "formik-mui": "^5.0.0-alpha.0", "formik-mui-lab": "^1.0.0", @@ -6587,11 +6587,11 @@ } }, "node_modules/axios": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", - "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.4", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -10367,9 +10367,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", @@ -25348,11 +25348,11 @@ "dev": true }, "axios": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", - "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "requires": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.4", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -28210,9 +28210,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==" }, "fork-ts-checker-webpack-plugin": { "version": "6.5.2", diff --git a/ui/package.json b/ui/package.json index c6bf09fb..47a06e62 100644 --- a/ui/package.json +++ b/ui/package.json @@ -28,7 +28,7 @@ "@types/react-dom": "^18.2.4", "@types/react-router-dom": "^5.3.3", "autosuggest-highlight": "3.2.1", - "axios": "^1.6.1", + "axios": "^1.6.5", "formik": "^2.4.1", "formik-mui": "^5.0.0-alpha.0", "formik-mui-lab": "^1.0.0", diff --git a/ui/src/api/server.ts b/ui/src/api/server.ts index 02811d4c..8abc665f 100644 --- a/ui/src/api/server.ts +++ b/ui/src/api/server.ts @@ -326,7 +326,7 @@ export function makeServer() { description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse convallis tempor ligula vel tempus.", remediation: "", - source_plugins: ["Trivy"], + source_plugins: ["Trivy Container Image"], }, "CVE-2016-00000": { source: ["docker/Dockerfile"], @@ -334,7 +334,10 @@ export function makeServer() { description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras elementum fringilla elementum.", remediation: "", - source_plugins: ["Trivy", "Aqua Container Security"], + source_plugins: [ + "Trivy Container Image", + "Aqua Container Security", + ], }, }, component1: { diff --git a/ui/src/app/scanPlugins.ts b/ui/src/app/scanPlugins.ts index a8960f14..bb9694f6 100644 --- a/ui/src/app/scanPlugins.ts +++ b/ui/src/app/scanPlugins.ts @@ -179,10 +179,15 @@ export const vulnPluginsKeys: ScanPluginKeys = { }, snyk: { displayName: t`Snyk`, apiName: "snyk", group: GROUP_VULN }, trivy: { - displayName: t`Trivy (Container Images)`, + displayName: t`Trivy Container Image`, apiName: "trivy", group: GROUP_VULN, }, + trivy_sca: { + displayName: t`Trivy SCA`, + apiName: "trivy_sca", + group: GROUP_VULN, + }, veracode_sca: { displayName: t`Veracode SCA`, apiName: "veracode_sca", diff --git a/ui/src/locale/en/messages.po b/ui/src/locale/en/messages.po index 5890046f..0a79e0f1 100644 --- a/ui/src/locale/en/messages.po +++ b/ui/src/locale/en/messages.po @@ -188,10 +188,6 @@ msgstr "Priority" msgid "New Scan" msgstr "New Scan" -#: src/components/FormikPickers.tsx:71 -#~ msgid "Previous month" -#~ msgstr "Previous month" - #: src/pages/ResultsPage.tsx:925 msgid "Remove Hidden Finding" msgstr "Remove Hidden Finding" @@ -346,6 +342,10 @@ msgstr "No technology inventory detected" msgid "Component name must be less than {COMPONENT_NAME_LENGTH} characters" msgstr "Component name must be less than {COMPONENT_NAME_LENGTH} characters" +#: src/app/scanPlugins.ts:182 +#~ msgid "Trivy Container Images" +#~ msgstr "Trivy Container Images" + #: src/pages/UsersPage.tsx:410 msgid "Artemis - User Management" msgstr "Artemis - User Management" @@ -832,6 +832,10 @@ msgstr "Results may vary compared to scans run with other options. View scan res msgid "Remove this hidden finding? This finding will again appear in scan results for this repository." msgstr "Remove this hidden finding? This finding will again appear in scan results for this repository." +#: src/app/scanPlugins.ts:182 +msgid "Trivy Container Image" +msgstr "Trivy Container Image" + #: src/pages/ResultsPage.tsx:4789 msgid "Show line numbers" msgstr "Show line numbers" @@ -853,6 +857,10 @@ msgstr "Unable to get user keys" msgid "Scope {0}" msgstr "Scope {0}" +#: src/app/scanPlugins.ts:187 +msgid "Trivy SCA" +msgstr "Trivy SCA" + #: src/app/scanPlugins.ts:25 msgid "Vulnerability Detection" msgstr "Vulnerability Detection" @@ -1946,10 +1954,6 @@ msgstr "Clear options" msgid "Component:" msgstr "Component:" -#: src/components/FormikPickers.tsx:74 -#~ msgid "Next month" -#~ msgstr "Next month" - #: src/components/TableMenu.tsx:117 #: src/pages/ResultsPage.tsx:4718 msgid "I Acknowledge" @@ -2581,10 +2585,6 @@ msgstr "Remediation must be less than {REMEDIATION_LENGTH} characters" msgid "Found in source files ({0}):" msgstr "Found in source files ({0}):" -#: src/app/scanPlugins.ts:182 -msgid "Trivy (Container Images)" -msgstr "Trivy (Container Images)" - #: src/pages/SearchPage.tsx:2053 #: src/pages/SearchPage.tsx:3913 msgid "Licenses" @@ -2757,7 +2757,7 @@ msgstr "Remove API key named \"{0}\"?" msgid "Start a new scan using the same options used in this scan?<0/>Initiating a new scan will navigate to the main scan page to display scan progress." msgstr "Start a new scan using the same options used in this scan?<0/>Initiating a new scan will navigate to the main scan page to display scan progress." -#: src/app/scanPlugins.ts:187 +#: src/app/scanPlugins.ts:192 msgid "Veracode SCA" msgstr "Veracode SCA" diff --git a/ui/src/pages/__tests__/SearchPageVulns.test.tsx b/ui/src/pages/__tests__/SearchPageVulns.test.tsx index 2843e590..545daac8 100644 --- a/ui/src/pages/__tests__/SearchPageVulns.test.tsx +++ b/ui/src/pages/__tests__/SearchPageVulns.test.tsx @@ -1707,7 +1707,7 @@ describe("SearchPage component", () => { within(dialog).getByText("Aqua CLI Scanner (Docker)"); within(dialog).getByText("NPM Audit (NodeJS)"); within(dialog).getByText("Snyk"); - within(dialog).getByText("Trivy (Container Images)"); + within(dialog).getByText("Trivy Container Image"); within(dialog).getByText("Veracode SCA"); within(dialog).getByText("Youve_never_seen_this_plugin_before");