Skip to content

Commit

Permalink
severity and status filters
Browse files Browse the repository at this point in the history
  • Loading branch information
nleach999 committed Jun 7, 2024
1 parent 823b743 commit 0e32198
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 45 deletions.
16 changes: 15 additions & 1 deletion config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from cxoneflow_logging import SecretRegistry
from workflows.state_service import WorkflowStateService
from workflows.pull_request import PullRequestWorkflow
from workflows import ResultSeverity, ResultStates
from typing import Tuple
from multiprocessing import cpu_count

Expand Down Expand Up @@ -200,7 +201,20 @@ def __workflow_service_client_factory(config_path, moniker, **kwargs):
pr_workflow_dict = CxOneFlowConfig.__get_value_for_key_or_default("pull-request", kwargs, {})
scan_monitor_dict = CxOneFlowConfig.__get_value_for_key_or_default("scan-monitor", kwargs, {})

pr_workflow = PullRequestWorkflow(
exclusions_dict = CxOneFlowConfig.__get_value_for_key_or_default("exclusions", kwargs, {})
excluded_states = excluded_severities = []

try:
excluded_states = [ResultStates(state) for state in CxOneFlowConfig.__get_value_for_key_or_default("state", exclusions_dict, [])]
except ValueError as ve:
raise ConfigurationException(f"{config_path}/exclusions/state {ve}: must be one of {ResultStates.names()}")

try:
excluded_severities = [ResultSeverity(sev) for sev in CxOneFlowConfig.__get_value_for_key_or_default("severity", exclusions_dict, [])]
except ValueError as ve:
raise ConfigurationException(f"{config_path}/exclusions/severity {ve}: must be one of {ResultSeverity.names()}")

pr_workflow = PullRequestWorkflow(excluded_severities, excluded_states,
CxOneFlowConfig.__get_value_for_key_or_default("enabled", pr_workflow_dict, False), \
int(CxOneFlowConfig.__get_value_for_key_or_default("poll-interval-seconds", scan_monitor_dict, 60)), \
int(CxOneFlowConfig.__get_value_for_key_or_default("scan-timeout-hours", scan_monitor_dict, 48)) \
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ requests
aio-pika
dataclasses-json
markdown

aenum
2 changes: 1 addition & 1 deletion workflow_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ async def spawn_agents():
if __name__ == '__main__':
try:
CxOneFlowConfig.bootstrap(get_config_path())
asyncio.run(spawn_agents())
except ConfigurationException as ce:
__log.exception(ce)

asyncio.run(spawn_agents())


28 changes: 26 additions & 2 deletions workflows/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from enum import Enum
from aenum import MultiValueEnum

class __base_enum(Enum):
def __str__(self):
return str(self.value)

def __repr__(self):
return str(self.value)

class ScanWorkflow(__base_enum):
PR = "pr"
Expand All @@ -20,4 +19,29 @@ class ScanStates(__base_enum):
ANNOTATE = "annotate"


class GoofyEnum(MultiValueEnum):
def __repr__(self):
return str(self.value)

@classmethod
def names(clazz):
return list(clazz._member_map_.values())

class ResultStates(GoofyEnum):
TO_VERIFY = "To Verify"
NOT_EXPLOITABLE = "Not Exploitable"
PROP_NOT_EXPLOITABLE = "Proposed Not Exploitable"
CONFIRMED = "Confirmed"
URGENT = "Urgent"


class ResultSeverity(GoofyEnum):
CRITICAL = "Critical"
HIGH = "High"
MEDIUM = "Medium"
LOW = "Low"
INFO = "Info", "Information"




90 changes: 58 additions & 32 deletions workflows/pr.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from pathlib import Path
from jsonpath_ng import parse
from workflows.messaging import PRDetails
from typing import Callable
from typing import Callable, List, Type
from . import ResultSeverity, ResultStates


class PullRequestDecoration:
Expand Down Expand Up @@ -131,10 +132,20 @@ class PullRequestFeedback(PullRequestDecoration):

__scanner_stat_query = parse("$.scanInformation.scannerStatus[*]")

def __init__(self, display_url : str, project_id : str, scanid : str, enhanced_report : dict, code_permalink_func : Callable, pr_details : PRDetails):
@staticmethod
def __test_in_enum(clazz : Type, value : str, exclusions : List[Type]):
try:
return clazz(value) in exclusions
except ValueError:
return False

def __init__(self, excluded_severities : List[ResultSeverity], excluded_states : List[ResultStates], display_url : str,
project_id : str, scanid : str, enhanced_report : dict, code_permalink_func : Callable, pr_details : PRDetails):
super().__init__()
self.__enhanced_report = enhanced_report
self.__permalink = code_permalink_func
self.__excluded_severities = excluded_severities
self.__excluded_states = excluded_states

self.__add_annotation_section(display_url, project_id, scanid)
self.__add_sast_details(pr_details)
Expand All @@ -146,64 +157,79 @@ def __add_resolved_details(self):
title_added = False
for resolved in PullRequestFeedback.__resolved_results_query.find(self.__enhanced_report):
for vuln in resolved.value['resolvedVulnerabilities']:
if not title_added:
self.start_resolved_detail_section()
title_added = True

for result in vuln['resolvedResults']:
self.add_resolved_detail(PullRequestDecoration.severity_indicator(result['severity']),
vuln['vulnerabilityName'],
PullRequestDecoration.link(vuln['vulnerabilityLink'], "View"))
if not PullRequestFeedback.__test_in_enum(ResultSeverity, result['severity'], self.__excluded_severities):

if not title_added:
self.start_resolved_detail_section()
title_added = True

self.add_resolved_detail(PullRequestDecoration.severity_indicator(result['severity']),
vuln['vulnerabilityName'],
PullRequestDecoration.link(vuln['vulnerabilityLink'], "View"))

def __add_iac_details(self, pr_details):
title_added = False
for result in PullRequestFeedback.__iac_results_query.find(self.__enhanced_report):
x = result.value
if not title_added:
self.start_iac_detail_section()
title_added = True

for query in x['queries']:
for result in query['resultsList']:
self.add_iac_detail(PullRequestDecoration.severity_indicator(result['severity']), x['name'],
f"`{result['fileName']}`{PullRequestDecoration.link(self.__permalink(pr_details.organization,
pr_details.repo_project, pr_details.repo_slug, pr_details.source_branch,
result['fileName'], 1), "view")}", query['queryName'],
PullRequestDecoration.link(result['resultViewerLink'], "Risk Details"))
if not (PullRequestFeedback.__test_in_enum(ResultStates, result['state'], self.__excluded_states) or
PullRequestFeedback.__test_in_enum(ResultSeverity, result['severity'], self.__excluded_severities)):

if not title_added:
self.start_iac_detail_section()
title_added = True

self.add_iac_detail(PullRequestDecoration.severity_indicator(result['severity']), x['name'],
f"`{result['fileName']}`{PullRequestDecoration.link(self.__permalink(pr_details.organization,
pr_details.repo_project, pr_details.repo_slug, pr_details.source_branch,
result['fileName'], 1), "view")}", query['queryName'],
PullRequestDecoration.link(result['resultViewerLink'], "Risk Details"))

def __add_sca_details(self, display_url, project_id, scanid):
title_added = False
for result in PullRequestFeedback.__sca_results_query.find(self.__enhanced_report):
x = result.value

if not title_added:
self.start_sca_detail_section()
title_added = True

for category in x['packageCategory']:
for cat_result in category['categoryResults']:
self.add_sca_detail(PullRequestDecoration.severity_indicator(cat_result['severity']),
x['packageName'], x['packageVersion'],
PullRequestDecoration.sca_result_link(display_url, project_id, scanid, "Risk Details",
cat_result['cve'], x['packageId']))
if not (PullRequestFeedback.__test_in_enum(ResultStates, cat_result['state'], self.__excluded_states) or
PullRequestFeedback.__test_in_enum(ResultSeverity, cat_result['severity'], self.__excluded_severities)):

if not title_added:
self.start_sca_detail_section()
title_added = True

self.add_sca_detail(PullRequestDecoration.severity_indicator(cat_result['severity']),
x['packageName'], x['packageVersion'],
PullRequestDecoration.sca_result_link(display_url, project_id, scanid, "Risk Details",
cat_result['cve'], x['packageId']))


def __add_sast_details(self, pr_details):
title_added = False
for result in PullRequestFeedback.__sast_results_query.find(self.__enhanced_report):
if not title_added:
self.start_sast_detail_section()
title_added = True

x = result.value
describe_link = PullRequestDecoration.link(x['queryDescriptionLink'], x['queryName'])
for vuln in x['vulnerabilities']:
self.add_sast_detail(PullRequestDecoration.severity_indicator(vuln['severity']), describe_link,
f"`{vuln['sourceFileName']}`;{PullRequestDecoration.link(self.__permalink(pr_details.organization,
pr_details.repo_project, pr_details.repo_slug, pr_details.source_branch,
vuln['sourceFileName'], vuln['sourceLine']),
vuln['sourceLine'])}",
PullRequestDecoration.link(vuln['resultViewerLink'], "Attack Vector"))
if not (PullRequestFeedback.__test_in_enum(ResultStates, vuln['state'], self.__excluded_states) or
PullRequestFeedback.__test_in_enum(ResultSeverity, vuln['severity'], self.__excluded_severities)):

if not title_added:
self.start_sast_detail_section()
title_added = True

self.add_sast_detail(PullRequestDecoration.severity_indicator(vuln['severity']), describe_link,
f"`{vuln['sourceFileName']}`;{PullRequestDecoration.link(self.__permalink(pr_details.organization,
pr_details.repo_project, pr_details.repo_slug, pr_details.source_branch,
vuln['sourceFileName'], vuln['sourceLine']),
vuln['sourceLine'])}",
PullRequestDecoration.link(vuln['resultViewerLink'], "Attack Vector"))

def __add_annotation_section(self, display_url, project_id, scanid):
self.add_to_annotation(f"**Results for Scan ID {PullRequestDecoration.scan_link(display_url, project_id, scanid)}**")
Expand Down
17 changes: 15 additions & 2 deletions workflows/pull_request.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import aio_pika, logging, pamqp.commands, pamqp.base
from datetime import timedelta
from .state_service import WorkflowStateService
from . import ScanWorkflow, ScanStates
from . import ScanWorkflow, ScanStates, ResultSeverity, ResultStates
from .workflow_base import AbstractWorkflow
from .messaging import ScanAwaitMessage, ScanFeedbackMessage, ScanAnnotationMessage
from .messaging.util import compute_drop_by_timestamp
from typing import List

class PullRequestWorkflow(AbstractWorkflow):

Expand All @@ -14,11 +15,23 @@ def log():
return logging.getLogger("PullRequestWorkflow")


def __init__(self, enabled : bool = False, interval_seconds : int = 90, scan_timeout : int = 48):
def __init__(self, excluded_severities : List[ResultSeverity] = [], excluded_states : List[ResultStates] = [],
enabled : bool = False, interval_seconds : int = 90, scan_timeout : int = 48):
self.__enabled = enabled
self.__excluded_states = excluded_states
self.__excluded_severities = excluded_severities
self.__interval = timedelta(seconds=interval_seconds)
self.__scan_timeout = timedelta(hours=scan_timeout)

@property
def excluded_severities(self) -> List[ResultSeverity]:
return self.__excluded_severities

@property
def excluded_states(self) -> List[ResultStates]:
return self.__excluded_states


def __feedback_msg_factory(self, projectid : str, scanid : str, moniker : str, **kwargs) -> aio_pika.Message:
return aio_pika.Message(ScanFeedbackMessage(projectid=projectid, scanid=scanid, moniker=moniker, state=ScanStates.FEEDBACK,
workflow=ScanWorkflow.PR, workflow_details=kwargs).to_binary(),
Expand Down
15 changes: 9 additions & 6 deletions workflows/state_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,16 @@ async def execute_pr_feedback_workflow(self, msg : aio_pika.abc.AbstractIncoming
am = ScanFeedbackMessage.from_binary(msg.body)
pr_details = PRDetails.from_dict(am.workflow_details)
try:
report = await cxone_service.retrieve_report(am.projectid, am.scanid)
if report is None:
await msg.nack()
if await self.__workflow_map[ScanWorkflow.PR].is_enabled():
report = await cxone_service.retrieve_report(am.projectid, am.scanid)
if report is None:
await msg.nack()
else:
feedback = PullRequestFeedback(self.__workflow_map[ScanWorkflow.PR].excluded_severities, self.__workflow_map[ScanWorkflow.PR].excluded_states, cxone_service.display_link, am.projectid, am.scanid, report, scm_service.create_code_permalink, pr_details)
await scm_service.exec_pr_decorate(pr_details.organization, pr_details.repo_project, pr_details.repo_slug, pr_details.pr_id,
am.scanid, md.markdown(feedback.content, extensions=['tables']))
await msg.ack()
else:
feedback = PullRequestFeedback(cxone_service.display_link, am.projectid, am.scanid, report, scm_service.create_code_permalink, pr_details)
await scm_service.exec_pr_decorate(pr_details.organization, pr_details.repo_project, pr_details.repo_slug, pr_details.pr_id,
am.scanid, md.markdown(feedback.content, extensions=['tables']))
await msg.ack()
except CxOneException as ex:
WorkflowStateService.log().exception(ex)
Expand Down
10 changes: 10 additions & 0 deletions workflows/workflow_base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import aio_pika
from typing import List
from . import ResultSeverity, ResultStates


class AbstractWorkflow:
@property
def excluded_severities(self) -> List[ResultSeverity]:
raise NotImplementedError("excluded_severities")

@property
def excluded_states(self) -> List[ResultStates]:
raise NotImplementedError("excluded_states")

async def workflow_start(self, mq_client : aio_pika.abc.AbstractRobustConnection, moniker : str, projectid : str, scanid : str, **kwargs):
raise NotImplementedError("workflow_start")

Expand Down

0 comments on commit 0e32198

Please sign in to comment.