Skip to content

Commit

Permalink
Added support to handle accepted risks #264
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveMcGrath committed May 29, 2024
1 parent 900e41c commit 9750ab6
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 17 deletions.
26 changes: 19 additions & 7 deletions tenb2jira/tenable/generators.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import typing
from typing import TYPE_CHECKING, Generator, Optional, Any
import arrow
from restfly.utils import dict_flatten, dict_merge
import uuid

if typing.TYPE_CHECKING:
if TYPE_CHECKING:
from tenable.io.exports.iterator import ExportsIterator
from tenable.sc.analysis import AnalysisResultsIterator


def tvm_asset_cleanup(*assets_iters: 'ExportsIterator') -> dict:
def tvm_asset_cleanup(*assets_iters: 'ExportsIterator'
) -> Generator[Any, Any, Any]:
"""
A simple wrapper to coalesce the multiple terminated asset states within
TVM.
Expand All @@ -26,8 +27,9 @@ def tvm_asset_cleanup(*assets_iters: 'ExportsIterator') -> dict:

def tvm_merged_data(assets_iter: 'ExportsIterator',
vulns_iter: 'ExportsIterator',
asset_fields: list[str] = None
) -> dict:
asset_fields: Optional[list[str]] = None,
close_accepted: bool = True,
) -> Generator[Any, Any, Any]:
"""
Merges the asset and vulnerability finding data together into a single
object and adds in a computed finding id based on the following attributes:
Expand Down Expand Up @@ -86,11 +88,19 @@ def spf(value: str) -> str:
))
f['integration_pid_updated'] = pid

# If accepted risks shoudl be flagged as closed, then we will replace
# the state field with "fixed" if the risk was indeed accepted.
sevmod = f.get('severity_modification_type')
if close_accepted and sevmod == 'ACCEPTED':
f['state'] = 'FIXED'

# Return the augmented finding to the caller.
yield f


def tsc_merged_data(*vuln_iters: 'AnalysisResultsIterator') -> dict:
def tsc_merged_data(*vuln_iters: 'AnalysisResultsIterator',
close_accepted: bool = True,
) -> Generator[Any, Any, Any]:
"""
Flattens and extends the vulnerability results returned from the
Security Center analysis API. The following fields are added to the
Expand Down Expand Up @@ -124,7 +134,9 @@ def tsc_merged_data(*vuln_iters: 'AnalysisResultsIterator') -> dict:
# If the hasBeenMitigated flag was flipped, then the finding isn't
# open, but is reopened. We want to confer state accurately so we
# will check that here.
if f['hasBeenMitigated'] == '1' and state == 'open':
if close_accepted and f['acceptRisk'] == '1':
f['integration_state'] = 'fixed'
elif f['hasBeenMitigated'] == '1' and state == 'open':
f['integration_state'] = 'reopened'
else:
f['integration_state'] = state
Expand Down
26 changes: 17 additions & 9 deletions tenb2jira/tenable/tenable.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Generator
from typing import Generator, Any
import arrow
from tenable.io import TenableIO
from tenable.sc import TenableSC
Expand All @@ -14,6 +14,7 @@ class Tenable:
platform: str
timestamp: int
age: int
close_accepted: bool
severity: list[str]
chunk_size: int = 1000
page_size: int = 1000
Expand All @@ -29,6 +30,9 @@ def __init__(self, config: dict):
self.chunk_size = self.config['tenable']['tvm_chunk_size']
self.page_size = self.config['tenable']['tsc_page_size']
self.query_id = self.config['tenable'].get('tsc_query_id')
self.close_accepted = self.config['tenable'].get('fix_accepted_risks',
True
)

if not self.timestamp:
self.timestamp = int(arrow.now()
Expand All @@ -53,7 +57,7 @@ def __init__(self, config: dict):
)

def get_generator(self) -> Generator:
self.last_run = arrow.now().timestamp()
self.last_run = int(arrow.now().timestamp())
if self.platform == 'tvm':
assets = self.tvm.exports.assets(updated_at=self.timestamp,
chunk_size=self.chunk_size
Expand All @@ -62,9 +66,12 @@ def get_generator(self) -> Generator:
severity=self.severity,
state=['open', 'reopened', 'fixed'],
include_unlicensed=True,
num_assets=self.chunk_size
num_assets=self.chunk_size,
)
return tvm_merged_data(assets, vulns)
return tvm_merged_data(assets,
vulns,
close_accepted=self.close_accepted,
)
if self.platform == 'tsc':
sevmap = {
'info': '0',
Expand All @@ -87,9 +94,12 @@ def get_generator(self) -> Generator:
query_id=self.query_id,
limit=self.page_size
)
return tsc_merged_data(cumulative, patched)
return tsc_merged_data(cumulative,
patched,
close_accepted=self.close_accepted,
)

def get_asset_cleanup(self) -> Generator:
def get_asset_cleanup(self) -> (Generator[Any, Any, Any] | list):
if self.platform == 'tvm':
dassets = self.tvm.exports.assets(deleted_at=self.timestamp,
chunk_size=self.chunk_size
Expand All @@ -98,7 +108,5 @@ def get_asset_cleanup(self) -> Generator:
chunk_size=self.chunk_size
)
return tvm_asset_cleanup(dassets, tassets)
if self.platform == 'tsc':
else:
return []


2 changes: 1 addition & 1 deletion tenb2jira/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version = '2.0.5'
version = '2.0.6'
35 changes: 35 additions & 0 deletions tests/tenable/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,31 @@ def test_tvm_merged_data(tvm_assets, tvm_finding):
assert finding['asset.test'] == 'value'


@responses.activate
def test_tvm_merged_data_accepted(tvm_assets, tvm_finding):
pmoddate = arrow.get('2020-04-27T00:00:00Z')
test_uuid = UUID('dd13a88d-2fbf-3d2a-930f-38fdc850f86d')
responses.get('https://cloud.tenable.com/assets/export/0/status',
json={'status': 'FINISHED', 'available_chunks': []})
tvm = TenableIO(access_key='None', secret_key='None')
asset_iter = ExportsIterator(tvm)
asset_iter.uuid = 0
asset_iter.type = 'assets'
asset_iter.page = tvm_assets
tvm_finding['severity_modification_type'] = 'ACCEPTED'
finding_iter = ExportsIterator(tvm)
finding_iter.page = [tvm_finding for _ in range(100)]
tvm_generator = tvm_merged_data(assets_iter=asset_iter,
vulns_iter=finding_iter,
close_accepted=True,
)
finding = next(tvm_generator)
assert finding['state'] == 'FIXED'
assert finding['asset.uuid'] == '7f68f334-17ba-4ba0-b057-b77ddd783e60'
assert finding['integration_finding_id'] == test_uuid
assert finding['integration_pid_updated'] == pmoddate


def test_tsc_merged_data(tsc_finding):
test_uuid = UUID('d90cdab5-b745-3e7e-9268-aa0f445ed924')
fuuid = UUID('bd371510-001f-3c13-86f4-20883ef0cd09')
Expand All @@ -184,3 +209,13 @@ def test_tsc_merged_data(tsc_finding):
tsc_generator = tsc_merged_data(findings)
finding = next(tsc_generator)
assert finding['integration_state'] == 'fixed'

tsc_finding['acceptRisk'] = '1'
findings = AnalysisResultsIterator(None)
findings.page = [tsc_finding for _ in range(100)]
findings._query = {'sourceType': 'cumulative'}
tsc_generator = tsc_merged_data(findings)
finding = next(tsc_generator)
assert finding['integration_state'] == 'fixed'


3 changes: 3 additions & 0 deletions tmpl_v1_conversion_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ tsc_page_size = 1000
# Management. You may need to adjust this number based on memory restrictions.
tvm_chunk_size = 1000

# Should accepted risks be treated as closed findings?
fix_accepted_risks = true

# The names of the platforms to be relayed to Jira for the "Tenable Platform"
# field. Generally it's best to leave these values alone.
platforms.tvm = "Tenable Vulnerability Management"
Expand Down
3 changes: 3 additions & 0 deletions tmpl_v2_new_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ tsc_page_size = 1000
# Management. You may need to adjust this number based on memory restrictions.
tvm_chunk_size = 1000

# Should accepted risks be treated as closed findings?
fix_accepted_risks = true

# The names of the platforms to be relayed to Jira for the "Tenable Platform"
# field. Generally it's best to leave these values alone.
platforms.tvm = "Tenable Vulnerability Management"
Expand Down

0 comments on commit 9750ab6

Please sign in to comment.