Skip to content

Commit

Permalink
Implementation of a CSV parser for Sysdig Vulnerability reports based…
Browse files Browse the repository at this point in the history
… around the 'new' engine. (#8868)

Covers Pipeline, Registry and Runtime reports
  • Loading branch information
aaronm-sysdig authored Nov 1, 2023
1 parent ad65ca2 commit 4ed3fc1
Show file tree
Hide file tree
Showing 12 changed files with 426 additions and 0 deletions.
8 changes: 8 additions & 0 deletions docs/content/en/integrations/parsers/file/sysdig_reports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: "Sysdig Vulnerability Reports"
toc_hide: true
---
Import CSV report files from Sysdig.
Parser will accept Pipeline, Registry and Runtime reports created from the UI

More information available at [our reporting docs page](https://docs.sysdig.com/en/docs/sysdig-secure/vulnerabilities/reporting)
Empty file.
160 changes: 160 additions & 0 deletions dojo/tools/sysdig_reports/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
from dojo.models import Finding
from dojo.tools.sysdig_reports.sysdig_csv_parser import CSVParser

from cvss.cvss3 import CVSS3
import cvss.parser


class SysdigReportsParser(object):
"""
Sysdig Report Importer - Runtime CSV
"""

def get_scan_types(self):
return ["Sysdig Vulnerability Report - Pipeline, Registry and Runtime (CSV)"]

def get_label_for_scan_types(self, scan_type):
return "Sysdig Vulnerability Report Scan"

def get_description_for_scan_types(self, scan_type):
return "Import of Sysdig Pipeline, Registry and Runtime Vulnerability Report Scans in CSV format."

def get_findings(self, filename, test):

if filename is None:
return ()

if filename.name.lower().endswith('.csv'):
arr_data = CSVParser().parse(filename=filename)
else:
return ()

if len(arr_data) == 0:
return ()
sysdig_report_findings = []

for row in arr_data:
finding = Finding(test=test)

# Generate finding
if row.k8s_cluster_name != "":
finding.title = f"{row.k8s_cluster_name} - {row.k8s_namespace_name} - {row.package_name} - {row.vulnerability_id}"
else:
finding.title = f"{row.vulnerability_id} - {row.package_name}"

finding.vuln_id_from_tool = row.vulnerability_id
finding.cve = row.vulnerability_id
finding.severity = row.severity

# Set Component Version
finding.component_name = row.package_name
finding.component_version = row.package_version

# Set some finding tags
tags = []

if row.k8s_cluster_name != "":
tags.append("Cluster: " + row.k8s_cluster_name)
if row.k8s_namespace_name != "":
tags.append("Namespace: " + row.k8s_namespace_name)
if row.k8s_workload_name != "":
tags.append("WorkloadName: " + row.k8s_workload_name)
if row.package_name != "":
tags.append("PackageName: " + row.package_name)
if row.package_version != "":
tags.append("PackageVersion: " + row.package_version)
if row.k8s_cluster_name != "":
tags.append("InUse: " + str(row.in_use))
if row.vulnerability_id != "":
tags.append("VulnId: " + row.vulnerability_id)
finding.tags = tags

if row.k8s_cluster_name != "":
finding.dynamic_finding = True
finding.static_finding = False
finding.description += f"###Runtime Context {row.k8s_cluster_name}" f"\n - **Cluster:** {row.k8s_cluster_name}"
finding.description += f"\n - **Namespace:** {row.k8s_namespace_name}"
finding.description += f"\n - **Workload Name:** {row.k8s_workload_name} "
finding.description += f"\n - **Workload Type:** {row.k8s_workload_type} "
finding.description += f"\n - **Container Name:** {row.k8s_container_name}"
else:
finding.dynamic_finding = False
finding.static_finding = True

if row.cloud_provider_name != "" or row.cloud_provider_name != "" or row.cloud_provider_region != "":
finding.description += "\n\n###Cloud Details"
if row.cloud_provider_name != "":
finding.description += f"\n - **Cloud Provider Name:** {row.cloud_provider_name}"
if row.cloud_provider_account_id != "":
finding.description += f"\n - **Cloud Provider Account Id:** {row.cloud_provider_account_id}"
if row.cloud_provider_region != "":
finding.description += f"\n - **Cloud Provider Region:** {row.cloud_provider_region}"

if row.registry_name != "" or row.registry_image_repository != "" or row.registry_vendor != "":
finding.description += "\n\n###Registry Details"
if row.registry_name != "":
finding.description += f"\n - **Registry Name:** {row.registry_name}"
if row.registry_image_repository != "":
finding.description += f"\n - **Registry Image Repository:** {row.registry_image_repository}"
if row.registry_vendor != "":
finding.description += f"\n - **Registry Vendor:** {row.registry_vendor}"

finding.description += "\n\n###Vulnerability Details"
finding.description += f"\n - **Vulnerability ID:** {row.vulnerability_id}"
finding.description += f"\n - **Vulnerability Link:** {row.vuln_link}"
finding.description += f"\n - **Severity:** {row.severity}"
finding.description += f"\n - **Publish Date:** {row.vuln_publish_date}"
finding.description += f"\n - **CVSS Version:** {row.cvss_version}"
finding.description += f"\n - **CVSS Vector:** {row.cvss_vector}"
if row.public_exploit != '':
finding.description += f"\n - **Public Exploit:** {row.public_exploit}"

finding.description += "\n\n###Package Details"
if row.package_type == "os":
finding.description += f"\n - **Package Type: {row.package_type} \\* Consider upgrading your Base OS \\***"
else:
finding.description += f"\n - **Package Type:** {row.package_type}"
finding.description += f"\n - **Package Name:** {row.package_name}"
finding.description += f"\n - **Package Version:** {row.package_version}"
finding.description += f"\n - **In-Use:** {row.in_use}"

if row.package_path != '':
finding.description += f"\n - **Package Path:** {row.package_path}"
finding.file_path = row.package_path
if row.package_suggested_fix != '':
finding.mitigation = f"Package suggested fix version: {row.package_suggested_fix}"
finding.description += f"\n - **Package suggested fix version:** {row.package_suggested_fix}"
if row.package_type == "os":
finding.mitigation += "\n\\*** Consider upgrading your Base OS \\***"

finding.description += "\n\n###Image Details"
finding.description += f"\n - **Image Name:** {row.image}"
finding.description += f"\n - **Image OS:** {row.os_name}"
finding.description += f"\n - **Image ID:** {row.image_id}"

# If we have registry information
if row.registry_name != "":
finding.description += f"\n - **Registry Name:** {row.registry_name}"
finding.description += f"\n - **Registy Image Repository:** {row.registry_image_repository}"

try:
if float(row.cvss_version) >= 3:
finding.cvssv3_score = row.cvss_score
vectors = cvss.parser.parse_cvss_from_text(row.cvss_vector)
if len(vectors) > 0 and isinstance(vectors[0], CVSS3):
finding.cvss = vectors[0].clean_vector()

except ValueError:
continue

finding.risk_accepted = row.risk_accepted

# Set reference
if row.vuln_link != "":
finding.references = row.vuln_link
finding.url = row.vuln_link

# finally, Add finding to list
sysdig_report_findings.append(finding)

return sysdig_report_findings
78 changes: 78 additions & 0 deletions dojo/tools/sysdig_reports/sysdig_csv_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import csv
import io
from dojo.tools.sysdig_reports.sysdig_data import SysdigData


class CSVParser:
"""
Sysdig CSV Data Parser
"""

def parse(self, filename) -> SysdigData:

if filename is None:
return ()

content = filename.read()
if type(content) is bytes:
content = content.decode('utf-8')
reader = csv.DictReader(io.StringIO(content), delimiter=',', quotechar='"')

# normalise on lower case for consistency
reader.fieldnames = [name.lower() for name in reader.fieldnames]

csvarray = []

for row in reader:
# Compare headers to values.
if len(row) != len(reader.fieldnames):
raise ValueError(f"Number of fields in row ({len(row)}) does not match number of headers ({len(reader.fieldnames)})")

# Check for a CVE value to being with
if not row[reader.fieldnames[0]].startswith("CVE"):
raise ValueError(f"Expected 'CVE' at the start but got: {row[reader.fieldnames[0]]}")

csvarray.append(row)

arr_csv_data = []

for row in csvarray:

csv_data_record = SysdigData()

csv_data_record.vulnerability_id = row.get('vulnerability id', '')
csv_data_record.severity = csv_data_record._map_severity(row.get('severity').upper())
csv_data_record.package_name = row.get('package name', '')
csv_data_record.package_version = row.get('package version', '')
csv_data_record.package_type = row.get('package type', '')
csv_data_record.package_path = row.get('package path', '')
csv_data_record.image = row.get('image', '')
csv_data_record.os_name = row.get('os name', '')
csv_data_record.cvss_version = row.get('cvss version', '')
csv_data_record.cvss_score = row.get('cvss score', '')
csv_data_record.cvss_vector = row.get('cvss vector', '')
csv_data_record.vuln_link = row.get('vuln link', '')
csv_data_record.vuln_publish_date = row.get('vuln publish date', '')
csv_data_record.vuln_fix_date = row.get('vuln fix date', '')
csv_data_record.vuln_fix_version = row.get('fix version', '')
csv_data_record.public_exploit = row.get('public exploit', '')
csv_data_record.k8s_cluster_name = row.get('k8s cluster name', '')
csv_data_record.k8s_namespace_name = row.get('k8s namespace name', '')
csv_data_record.k8s_workload_type = row.get('k8s workload type', '')
csv_data_record.k8s_workload_name = row.get('k8s workload name', '')
csv_data_record.k8s_container_name = row.get('k8s container name', '')
csv_data_record.image_id = row.get('image id', '')
csv_data_record.k8s_pod_count = row.get('k8s pod count', '')
csv_data_record.package_suggested_fix = row.get('package suggested fix', '')
csv_data_record.in_use = row.get('in use', '') == 'TRUE'
csv_data_record.risk_accepted = row.get('risk accepted', '') == 'TRUE'
csv_data_record.registry_name = row.get('registry name', '')
csv_data_record.registry_image_repository = row.get('registry image repository', '')
csv_data_record.cloud_provider_name = row.get('cloud provider name', '')
csv_data_record.cloud_provider_account_id = row.get('cloud provider account ID', '')
csv_data_record.cloud_provider_region = row.get('cloud provider region', '')
csv_data_record.registry_vendor = row.get('registry vendor', '')

arr_csv_data.append(csv_data_record)

return arr_csv_data
56 changes: 56 additions & 0 deletions dojo/tools/sysdig_reports/sysdig_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import datetime


class SysdigData:

def _map_severity(self, severity):
severity_mapping = {
"CRITICAL": "Critical",
"HIGH": "High",
"MEDIUM": "Medium",
"LOW": "Low",
"NEGLIGIBLE": "Informational"
}

return severity_mapping.get(severity, "Informational")

"""
Data class to represent the Sysdig data extracted from sources like CSV or JSON.
"""
def __init__(self):
self.vulnerability_id: str = ""
self.url: str = ""
self.severity: str = ""
self.package_name: str = ""
self.package_version: str = ""
self.package_type: str = ""
self.package_path: str = ""
self.image: str = ""
self.os_name: str = ""
self.cvss_version: float = 0
self.cvss_score: float = 0
self.cvss_vector: str = ""
self.vuln_link: str = ""
self.vuln_publish_date: str = ""
self.vuln_fix_date: datetime.date = None
self.vuln_fix_version: str = ""
self.public_exploit: str = ""
self.k8s_cluster_name: str = ""
self.k8s_namespace_name: str = ""
self.k8s_workload_type: str = ""
self.k8s_workload_name: str = ""
self.k8s_container_name: str = ""
self.image_id: str = ""
self.k8s_pod_count: str = 0
self.in_use: bool = False
self.risk_accepted: bool = False
self.publish_date: datetime.date = None
self.component_version: str = ""
self.package_suggested_fix: str = ""
self.image_type: str = ""
self.registry_name: str = ""
self.registry_image_repository: str = ""
self.registry_vendor: str = ""
self.cloud_provider_name: str = ""
self.cloud_provider_account_id: str = ""
self.cloud_provider_region: str = ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Vulnerability ID,Severity,Package name,Package version,Package type,Package path,Image,OS Name,CVSS version,CVSS score,CVSS vector,Vuln link,Vuln Publish date,Vuln Fix date,Fix version,Public Exploit,Registry name,Registry image repository,Image ID,Package suggested fix,Risk accepted
High,github.com/opencontainers/runc,v1.1.0,golang,/usr/local/bin/gosu,mongo,ubuntu 22.04,3.1,7.8,CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H,https://nvd.nist.gov/vuln/detail/CVE-2022-29162,2022-05-05,2022-05-12,v1.1.2,false,kubernetes,sock-shop,deployment,carts-db,carts-db,sha256:ee3b4d1239f12b094c4936dd08a2fbc227300beaf784c46c509e2f1ac5e6d879,1,v1.1.5,false,false
Loading

0 comments on commit 4ed3fc1

Please sign in to comment.