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

platform(sca): Fix bom report #3867

Merged
merged 21 commits into from
Nov 15, 2022
29 changes: 29 additions & 0 deletions checkov/common/output/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,32 @@ class ImageDetails(SCADetails):
image_id: str = ''
name: str | None = ''
related_resource_id: str | None = ''


def is_raw_formatted(licenses: str) -> bool:
return licenses.__contains__('","')
AdamDev marked this conversation as resolved.
Show resolved Hide resolved


def format_licenses_to_string(licenses_lst: list[str]) -> str:
print('format_licenses_to_string - start', licenses_lst)
AdamDev marked this conversation as resolved.
Show resolved Hide resolved
AdamDev marked this conversation as resolved.
Show resolved Hide resolved
if licenses_lst and len(licenses_lst) > 1:
joined_str = '","'.join(licenses_lst)
return f'"{joined_str}"'
elif licenses_lst and len(licenses_lst) == 1:
return licenses_lst[0]
else:
return 'Unknown'
AdamDev marked this conversation as resolved.
Show resolved Hide resolved


def format_string_to_licenses(licenses_str: str) -> list[str]:
print('format_string_to_licenses - start', licenses_str)
AdamDev marked this conversation as resolved.
Show resolved Hide resolved
AdamDev marked this conversation as resolved.
Show resolved Hide resolved
if licenses_str == 'Unknown':
AdamDev marked this conversation as resolved.
Show resolved Hide resolved
return [licenses_str]
elif licenses_str and len(licenses_str) > 0:
AdamDev marked this conversation as resolved.
Show resolved Hide resolved
# remove first and last quotes
licenses_str = licenses_str[1:-1] if licenses_str.startswith('"') and licenses_str.endswith('"') else licenses_str
license_lst = licenses_str.split('","')
AdamDev marked this conversation as resolved.
Show resolved Hide resolved

return license_lst
else:
return []
19 changes: 19 additions & 0 deletions checkov/common/output/csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import Any, TYPE_CHECKING

from checkov.common.models.enums import CheckResult
from checkov.common.output.common import format_string_to_licenses, is_raw_formatted
from checkov.common.output.record import Record, SCA_PACKAGE_SCAN_CHECK_NAME
from checkov.common.output.report import Report, CheckType

Expand Down Expand Up @@ -75,6 +76,7 @@ def add_sca_package_resources(self, resource: Record | ExtraResource, git_org: s
CheckType.SCA_PACKAGE: self.package_rows,
CheckType.SCA_IMAGE: self.container_rows
}

csv_table[check_type].append(
{
"Package": resource.vulnerability_details["package_name"],
Expand Down Expand Up @@ -157,8 +159,23 @@ def persist_report_oss_packages(self, file_name: str, is_api_key: bool, output_p
is_api_key=is_api_key,
)

@staticmethod
def arrange_rows(rows: list[dict[str, Any]]) -> None:
AdamDev marked this conversation as resolved.
Show resolved Hide resolved
# we search for formatted rows and covert them back into csv formatted file.
AdamDev marked this conversation as resolved.
Show resolved Hide resolved
for row in rows:
for key in row.keys():
val = str(row[key])

if is_raw_formatted(val):
row[key] = ', '.join(format_string_to_licenses(val))
val = str(row[key])
row[key] = val[1:-1] if val.startswith('"') and val.endswith('"') else row[key]
row[key] = '' if val == 'None' else row[key]
AdamDev marked this conversation as resolved.
Show resolved Hide resolved

@staticmethod
def write_section(file: str, header: list[str], rows: list[dict[str, Any]], is_api_key: bool) -> None:
CSVSBOM.arrange_rows(rows)
AdamDev marked this conversation as resolved.
Show resolved Hide resolved

with open(file, "w", newline="") as f:
print(f"Persisting SBOM to {os.path.abspath(file)}")
if is_api_key:
Expand All @@ -184,6 +201,8 @@ def get_csv_output_packages(self, check_type: str) -> str:
if header == 'Package':
csv_output += f'\"{field}\"'
elif header == 'Licenses':
field = str(field).replace('","', ", ")
field = field[1:-1] if field.startswith('"') and field.endswith('"') else field
csv_output += f',\"{field}\"'
else:
csv_output += f',{field}'
Expand Down
3 changes: 2 additions & 1 deletion checkov/common/output/cyclonedx.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING, cast, Any
from checkov.common.output.common import format_string_to_licenses

from cyclonedx.model import (
XsUri,
Expand Down Expand Up @@ -221,7 +222,7 @@ def create_library_component(self, resource: Record | ExtraResource, check_type:
licenses = resource.vulnerability_details.get("licenses")
if licenses:
license_choices = [
LicenseChoice(license_=License(license_name=license)) for license in licenses.split(", ")
LicenseChoice(license_=License(license_name=license)) for license in format_string_to_licenses(licenses)
]

purl = PackageURL(
Expand Down
8 changes: 3 additions & 5 deletions checkov/common/sca/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
)
from checkov.common.util.http_utils import request_wrapper
from checkov.runner_filter import RunnerFilter
from checkov.common.output.common import format_licenses_to_string

if TYPE_CHECKING:
from checkov.common.output.common import SCADetails
Expand Down Expand Up @@ -258,7 +259,7 @@ def add_to_reports_cves_and_packages(
file_abs_path=scanned_file_path,
check_class=check_class or "",
vulnerability_details=vulnerability,
licenses=", ".join(licenses_per_package_map[get_package_alias(package_name, package_version)]) or "Unknown",
licenses=format_licenses_to_string(licenses_per_package_map[get_package_alias(package_name, package_version)]),
runner_filter=runner_filter,
sca_details=sca_details,
scan_data_format=scan_data_format,
Expand Down Expand Up @@ -292,10 +293,7 @@ def add_to_reports_cves_and_packages(
vulnerability_details={
"package_name": package["name"],
"package_version": package["version"],
"licenses": ", ".join(
licenses_per_package_map[get_package_alias(package["name"], package["version"])]
)
or "Unknown",
"licenses": format_licenses_to_string(licenses_per_package_map[get_package_alias(package["name"], package["version"])]),
"package_type": get_package_type(package["name"], package["version"], sca_details),
},
)
Expand Down
2 changes: 1 addition & 1 deletion tests/sca_package/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def test_run(sca_package_report):
cve_record_with_2_license = next((c for c in report.failed_checks if c.resource == "path/to/requirements.txt.flask" and c.check_name == "SCA package scan"), None)
assert cve_record_with_2_license is not None
assert "licenses" in cve_record_with_2_license.vulnerability_details
assert cve_record_with_2_license.vulnerability_details["licenses"] == "OSI_APACHE, DUMMY_OTHER_LICENSE"
assert cve_record_with_2_license.vulnerability_details["licenses"] == '"OSI_APACHE","DUMMY_OTHER_LICENSE"'
AdamDev marked this conversation as resolved.
Show resolved Hide resolved
AdamDev marked this conversation as resolved.
Show resolved Hide resolved

# making sure extra-resources (a scanned packages without cves) also have licenses - this data will be printed
# as part of the BON report.
Expand Down