From 209045b967c18d02cac555007e092f119fd34347 Mon Sep 17 00:00:00 2001 From: Jonathan Guihard <119664417+jonathan-guihard@users.noreply.github.com> Date: Fri, 13 Dec 2024 18:41:56 +0100 Subject: [PATCH] Added 'expires' parameters and the creation of a recast risks rule for all assets (#858) * Added 'expires' parameters and the creation of a recast risks rule for all assets * fix: cassette testing --- tenable/sc/accept_risks.py | 12 +-- tenable/sc/recast_risks.py | 24 ++++-- .../test_recast_risks_create_success.yaml | 85 ++++++++++--------- .../test_recast_risks_details_success.yaml | 78 +++++++++-------- .../test_recast_risks_list_success.yaml | 78 +++++++++-------- tests/sc/test_recast_risks.py | 13 +++ 6 files changed, 165 insertions(+), 125 deletions(-) diff --git a/tenable/sc/accept_risks.py b/tenable/sc/accept_risks.py index d85f44f6c..ae2784a63 100644 --- a/tenable/sc/accept_risks.py +++ b/tenable/sc/accept_risks.py @@ -54,7 +54,7 @@ def _constructor(self, **kw): if 'expires' in kw: # how many days until the accept risk rule expires? We should - # simply checkt o see if the value is an integer. + # simply check to see if the value is an integer. self._check('expires', kw['expires'], int) if 'ips' in kw: @@ -223,8 +223,8 @@ def apply(self, id, repo): def create(self, plugin_id, repos, **kw): """ - Creates a new accept risk rule. Either ips, uuids, or asset_list must - be specified. + Creates a new accept risk rule. Either ips, uuids, asset_list, or host_uuids must + be specified, otherwise it will apply to all. :sc-api:`accept-risk: create ` @@ -234,7 +234,7 @@ def create(self, plugin_id, repos, **kw): The list of repositories to apply this accept risk rule to. asset_list (int, optional): The asset list id to apply the accept risk rule to. Please note - that ``asset_list``, ``ips``, and ``uuids`` are mutually + that ``asset_list``, ``ips``, ``uuids``, and ``host_uuids`` are mutually exclusive. comments (str, optional): The comment associated to the accept risk rule. @@ -244,7 +244,7 @@ def create(self, plugin_id, repos, **kw): (-1 represents December 31st 1969 23:59:59 hours GMT) ips (list, optional): A list of IPs to apply the accept risk rule to. Please note - that ``asset_list``, ``ips``, and ``uuids`` are mutually + that ``asset_list``, ``ips``, ``uuids``, and ``host_uuids`` are mutually exclusive. port (int, optional): The port to restrict this accept risk rule to. The default is @@ -254,7 +254,7 @@ def create(self, plugin_id, repos, **kw): is unrestricted. uuids (list, optional): The agent uuids to apply the accept risk rule to. Please note - that ``asset_list``, ``ips``, and ``uuids`` are mutually + that ``asset_list``, ``ips``, ``uuids``, and ``host_uuids`` are mutually exclusive. host_uuids (list[str], optional): The hostUUIDs to apply the accept risk rule to. Please note diff --git a/tenable/sc/recast_risks.py b/tenable/sc/recast_risks.py index 8b4398bdd..12a407c1e 100644 --- a/tenable/sc/recast_risks.py +++ b/tenable/sc/recast_risks.py @@ -54,6 +54,11 @@ def _constructor(self, **kwargs): # that we actually have a string here before moving on. self._check('comments', kwargs['comments'], str) + if 'expires' in kwargs: + # how many days until the recast risk rule expires? We should + # simply check to see if the value is an integer. + self._check('expires', kwargs['expires'], int) + if 'severity_id' in kwargs: # What should be the new severity id for the vulnerabilities # matching the rule? Converts severity_id to a newSeverity document @@ -244,8 +249,8 @@ def apply(self, risk_id, repo): def create(self, plugin_id, repos, severity_id, **kwargs): """ - Creates a new recast risk rule. Either ips, uuids, or asset_list must - be specified. + Creates a new recast risk rule. Either ips, uuids, asset_list, or host_uuids must + be specified, otherwise it will apply to all. :sc-api:`recast-risk: create ` @@ -259,13 +264,17 @@ def create(self, plugin_id, repos, severity_id, **kwargs): ``3``: High, and ``4``: Critical. asset_list (int, optional): The asset list id to apply the recast risk rule to. Please note - that ``asset_list``, ``ips``, and ``uuids`` are mutually + that ``asset_list``, ``ips``, ``uuids``, and ``host_uuids`` are mutually exclusive. comments (str, optional): The comment associated to the recast risk rule. + expires (int, optional): + Timestamp. When should the rule expire? if no expiration is set, the rule + will never expire. If not mentioned, value is -1 + (-1 represents December 31st 1969 23:59:59 hours GMT) ips (list, optional): A list of IPs to apply the recast risk rule to. Please note - that ``asset_list``, ``ips``, and ``uuids`` are mutually + that ``asset_list``, ``ips``, ``uuids``, and ``host_uuids`` are mutually exclusive. port (int, optional): The port to restrict this recast risk rule to. The default is @@ -275,7 +284,7 @@ def create(self, plugin_id, repos, severity_id, **kwargs): is unrestricted. uuids (list, optional): The agent uuids to apply the recast risk rule to. Please note - that ``asset_list``, ``ips``, and ``uuids`` are mutually + that ``asset_list``, ``ips``, ``uuids``, and ``host_uuids`` are mutually exclusive. host_uuids (list[str], optional): The hostUUIDs to apply the accept risk rule to. Please note @@ -291,7 +300,12 @@ def create(self, plugin_id, repos, severity_id, **kwargs): >>> rule = sc.recast_risks.create(97737, [1], 0 ... ips=['192.168.0.101', '192.168.0.102']) + + Create a rule to recast 97737 on all IPs on repository 1: + + >>> rule = sc.recast_risks.create(97737, [1]) """ + kwargs['hostType'] = 'all' kwargs['plugin_id'] = plugin_id kwargs['repos'] = repos kwargs['severity_id'] = severity_id diff --git a/tests/sc/cassettes/test_recast_risks_create_success.yaml b/tests/sc/cassettes/test_recast_risks_create_success.yaml index 68885523c..0ae5f91f3 100644 --- a/tests/sc/cassettes/test_recast_risks_create_success.yaml +++ b/tests/sc/cassettes/test_recast_risks_create_success.yaml @@ -1,42 +1,47 @@ interactions: -- request: - body: !!python/unicode '{"hostValue": "127.0.0.1", "plugin": {"id": "19506"}, - "repositories": [{"id": 1}], "hostType": "ip", "newSeverity": {"id": "0"}}' - headers: - Accept: ['*/*'] - Accept-Encoding: ['gzip, deflate'] - Connection: [keep-alive] - Content-Length: ['128'] - Content-Type: [application/json] - Cookie: [TNS_SESSIONID=SESSIONID] - User-Agent: [pyTenable/0.3.16 (pyTenable/0.3.16; Python/2.7.15)] - X-SecurityCenter: ['0000000000'] - method: POST - uri: https://securitycenter.home.cugnet.net/rest/recastRiskRule - response: - body: {string: !!python/unicode '{"type":"regular","response":[{"id":"1","newSeverity":"0","hostType":"ip","hostValue":"127.0.0.1","port":"any","protocol":"any","comments":"","order":"1","status":"0","createdTime":"1554731789","modifiedTime":"1554731789","repository":{"id":"1","name":"Default","description":""},"organization":{"id":"1","name":"HomeNet","description":""},"user":{"id":"2","username":"api","firstname":"api","lastname":"user"},"plugin":{"id":"19506","name":"Nessus - Scan Information","description":"This plugin displays, for each tested host, - information about the scan itself :\n\n - The version of the plugin set.\n - - The type of scanner (Nessus or Nessus Home).\n - The version of the Nessus - Engine.\n - The port scanner(s) used.\n - The port range scanned.\n - Whether - credentialed or third-party patch management checks are possible.\n - - The date of the scan.\n - The duration of the scan.\n - The number of hosts - scanned in parallel.\n - The number of checks done in parallel."}}],"error_code":0,"error_msg":"","warnings":[],"timestamp":1554731789} + - request: + body: + !!python/unicode '{"hostValue": "127.0.0.1", "plugin": {"id": "19506"}, + "repositories": [{"id": 1}], "hostType": "ip", "newSeverity": {"id": "0"}}' + headers: + Accept: ["*/*"] + Accept-Encoding: ["gzip, deflate"] + Connection: [keep-alive] + Content-Length: ["128"] + Content-Type: [application/json] + Cookie: [TNS_SESSIONID=SESSIONID] + User-Agent: [pyTenable/0.3.16 (pyTenable/0.3.16; Python/2.7.15)] + X-SecurityCenter: ["0000000000"] + method: POST + uri: https://securitycenter.home.cugnet.net/rest/recastRiskRule + response: + body: + { + string: + !!python/unicode '{"type":"regular","response":[{"id":"1","newSeverity":"0","hostType":"ip","hostValue":"127.0.0.1","port":"any","protocol":"any","comments":"","expires":"-1","order":"1","status":"0","createdTime":"1554731789","modifiedTime":"1554731789","repository":{"id":"1","name":"Default","description":""},"organization":{"id":"1","name":"HomeNet","description":""},"user":{"id":"2","username":"api","firstname":"api","lastname":"user"},"plugin":{"id":"19506","name":"Nessus + Scan Information","description":"This plugin displays, for each tested host, + information about the scan itself :\n\n - The version of the plugin set.\n - + The type of scanner (Nessus or Nessus Home).\n - The version of the Nessus + Engine.\n - The port scanner(s) used.\n - The port range scanned.\n - Whether + credentialed or third-party patch management checks are possible.\n - + The date of the scan.\n - The duration of the scan.\n - The number of hosts + scanned in parallel.\n - The number of checks done in parallel."}}],"error_code":0,"error_msg":"","warnings":[],"timestamp":1554731789} -'} - headers: - cache-control: ['no-store, no-cache, must-revalidate'] - connection: [Keep-Alive] - content-length: ['1052'] - content-type: [application/json] - date: ['Mon, 08 Apr 2019 13:56:29 GMT'] - expires: ['Thu, 19 Nov 1981 08:52:00 GMT'] - keep-alive: ['timeout=15, max=100'] - pragma: [no-cache] - securitycenter: [5.8.0] - server: [Apache] - strict-transport-security: [max-age=31536000; includeSubDomains] - x-content-type-options: [nosniff] - x-frame-options: [DENY] - x-xss-protection: [1; mode=block] - status: {code: 200, message: OK} + ', + } + headers: + cache-control: ["no-store, no-cache, must-revalidate"] + connection: [Keep-Alive] + content-length: ["1052"] + content-type: [application/json] + date: ["Mon, 08 Apr 2019 13:56:29 GMT"] + expires: ["Thu, 19 Nov 1981 08:52:00 GMT"] + keep-alive: ["timeout=15, max=100"] + pragma: [no-cache] + securitycenter: [5.8.0] + server: [Apache] + strict-transport-security: [max-age=31536000; includeSubDomains] + x-content-type-options: [nosniff] + x-frame-options: [DENY] + x-xss-protection: [1; mode=block] + status: { code: 200, message: OK } diff --git a/tests/sc/cassettes/test_recast_risks_details_success.yaml b/tests/sc/cassettes/test_recast_risks_details_success.yaml index 52524c250..3f993b96f 100644 --- a/tests/sc/cassettes/test_recast_risks_details_success.yaml +++ b/tests/sc/cassettes/test_recast_risks_details_success.yaml @@ -1,40 +1,44 @@ interactions: -- request: - body: null - headers: - Accept: ['*/*'] - Accept-Encoding: ['gzip, deflate'] - Connection: [keep-alive] - Cookie: [TNS_SESSIONID=SESSIONID] - User-Agent: [pyTenable/0.3.16 (pyTenable/0.3.16; Python/2.7.15)] - X-SecurityCenter: ['0000000000'] - method: GET - uri: https://securitycenter.home.cugnet.net/rest/recastRiskRule/1 - response: - body: {string: !!python/unicode '{"type":"regular","response":{"id":"3","newSeverity":"0","hostType":"ip","hostValue":"127.0.0.1","port":"any","protocol":"any","comments":"","order":"1","status":"0","createdTime":"1554731791","modifiedTime":"1554731791","repository":{"id":"1","name":"Default","description":""},"organization":{"id":"1","name":"HomeNet","description":""},"user":{"id":"2","username":"api","firstname":"api","lastname":"user"},"plugin":{"id":"19506","name":"Nessus - Scan Information","description":"This plugin displays, for each tested host, - information about the scan itself :\n\n - The version of the plugin set.\n - - The type of scanner (Nessus or Nessus Home).\n - The version of the Nessus - Engine.\n - The port scanner(s) used.\n - The port range scanned.\n - Whether - credentialed or third-party patch management checks are possible.\n - - The date of the scan.\n - The duration of the scan.\n - The number of hosts - scanned in parallel.\n - The number of checks done in parallel."}},"error_code":0,"error_msg":"","warnings":[],"timestamp":1554731791} + - request: + body: null + headers: + Accept: ["*/*"] + Accept-Encoding: ["gzip, deflate"] + Connection: [keep-alive] + Cookie: [TNS_SESSIONID=SESSIONID] + User-Agent: [pyTenable/0.3.16 (pyTenable/0.3.16; Python/2.7.15)] + X-SecurityCenter: ["0000000000"] + method: GET + uri: https://securitycenter.home.cugnet.net/rest/recastRiskRule/1 + response: + body: + { + string: + !!python/unicode '{"type":"regular","response":{"id":"3","newSeverity":"0","hostType":"ip","hostValue":"127.0.0.1","port":"any","protocol":"any","comments":"","expires":"-1","order":"1","status":"0","createdTime":"1554731791","modifiedTime":"1554731791","repository":{"id":"1","name":"Default","description":""},"organization":{"id":"1","name":"HomeNet","description":""},"user":{"id":"2","username":"api","firstname":"api","lastname":"user"},"plugin":{"id":"19506","name":"Nessus + Scan Information","description":"This plugin displays, for each tested host, + information about the scan itself :\n\n - The version of the plugin set.\n - + The type of scanner (Nessus or Nessus Home).\n - The version of the Nessus + Engine.\n - The port scanner(s) used.\n - The port range scanned.\n - Whether + credentialed or third-party patch management checks are possible.\n - + The date of the scan.\n - The duration of the scan.\n - The number of hosts + scanned in parallel.\n - The number of checks done in parallel."}},"error_code":0,"error_msg":"","warnings":[],"timestamp":1554731791} -'} - headers: - cache-control: ['no-store, no-cache, must-revalidate'] - connection: [Keep-Alive] - content-length: ['1050'] - content-type: [application/json] - date: ['Mon, 08 Apr 2019 13:56:31 GMT'] - expires: ['Thu, 19 Nov 1981 08:52:00 GMT'] - keep-alive: ['timeout=15, max=100'] - pragma: [no-cache] - securitycenter: [5.8.0] - server: [Apache] - strict-transport-security: [max-age=31536000; includeSubDomains] - x-content-type-options: [nosniff] - x-frame-options: [DENY] - x-xss-protection: [1; mode=block] - status: {code: 200, message: OK} + ', + } + headers: + cache-control: ["no-store, no-cache, must-revalidate"] + connection: [Keep-Alive] + content-length: ["1050"] + content-type: [application/json] + date: ["Mon, 08 Apr 2019 13:56:31 GMT"] + expires: ["Thu, 19 Nov 1981 08:52:00 GMT"] + keep-alive: ["timeout=15, max=100"] + pragma: [no-cache] + securitycenter: [5.8.0] + server: [Apache] + strict-transport-security: [max-age=31536000; includeSubDomains] + x-content-type-options: [nosniff] + x-frame-options: [DENY] + x-xss-protection: [1; mode=block] + status: { code: 200, message: OK } version: 1 diff --git a/tests/sc/cassettes/test_recast_risks_list_success.yaml b/tests/sc/cassettes/test_recast_risks_list_success.yaml index e24e33ecd..f0e7cd858 100644 --- a/tests/sc/cassettes/test_recast_risks_list_success.yaml +++ b/tests/sc/cassettes/test_recast_risks_list_success.yaml @@ -1,40 +1,44 @@ interactions: -- request: - body: null - headers: - Accept: ['*/*'] - Accept-Encoding: ['gzip, deflate'] - Connection: [keep-alive] - Cookie: [TNS_SESSIONID=SESSIONID] - User-Agent: [pyTenable/0.3.16 (pyTenable/0.3.16; Python/2.7.15)] - X-SecurityCenter: ['0000000000'] - method: GET - uri: https://securitycenter.home.cugnet.net/rest/recastRiskRule - response: - body: {string: !!python/unicode '{"type":"regular","response":[{"id":"1","newSeverity":"0","hostType":"ip","hostValue":"127.0.0.1","port":"any","protocol":"any","order":"1","status":"0","repository":{"id":"1","name":"Default","description":""},"organization":{"id":"1","name":"HomeNet","description":""},"user":{"id":"2","username":"api","firstname":"api","lastname":"user"},"plugin":{"id":"19506","name":"Nessus - Scan Information","description":"This plugin displays, for each tested host, - information about the scan itself :\n\n - The version of the plugin set.\n - - The type of scanner (Nessus or Nessus Home).\n - The version of the Nessus - Engine.\n - The port scanner(s) used.\n - The port range scanned.\n - Whether - credentialed or third-party patch management checks are possible.\n - - The date of the scan.\n - The duration of the scan.\n - The number of hosts - scanned in parallel.\n - The number of checks done in parallel."}}],"error_code":0,"error_msg":"","warnings":[],"timestamp":1554731790} + - request: + body: null + headers: + Accept: ["*/*"] + Accept-Encoding: ["gzip, deflate"] + Connection: [keep-alive] + Cookie: [TNS_SESSIONID=SESSIONID] + User-Agent: [pyTenable/0.3.16 (pyTenable/0.3.16; Python/2.7.15)] + X-SecurityCenter: ["0000000000"] + method: GET + uri: https://securitycenter.home.cugnet.net/rest/recastRiskRule + response: + body: + { + string: + !!python/unicode '{"type":"regular","response":[{"id":"1","newSeverity":"0","hostType":"ip","hostValue":"127.0.0.1","port":"any","protocol":"any","expires":"-1","order":"1","status":"0","repository":{"id":"1","name":"Default","description":""},"organization":{"id":"1","name":"HomeNet","description":""},"user":{"id":"2","username":"api","firstname":"api","lastname":"user"},"plugin":{"id":"19506","name":"Nessus + Scan Information","description":"This plugin displays, for each tested host, + information about the scan itself :\n\n - The version of the plugin set.\n - + The type of scanner (Nessus or Nessus Home).\n - The version of the Nessus + Engine.\n - The port scanner(s) used.\n - The port range scanned.\n - Whether + credentialed or third-party patch management checks are possible.\n - + The date of the scan.\n - The duration of the scan.\n - The number of hosts + scanned in parallel.\n - The number of checks done in parallel."}}],"error_code":0,"error_msg":"","warnings":[],"timestamp":1554731790} -'} - headers: - cache-control: ['no-store, no-cache, must-revalidate'] - connection: [Keep-Alive] - content-length: ['983'] - content-type: [application/json] - date: ['Mon, 08 Apr 2019 13:56:30 GMT'] - expires: ['Thu, 19 Nov 1981 08:52:00 GMT'] - keep-alive: ['timeout=15, max=100'] - pragma: [no-cache] - securitycenter: [5.8.0] - server: [Apache] - strict-transport-security: [max-age=31536000; includeSubDomains] - x-content-type-options: [nosniff] - x-frame-options: [DENY] - x-xss-protection: [1; mode=block] - status: {code: 200, message: OK} + ', + } + headers: + cache-control: ["no-store, no-cache, must-revalidate"] + connection: [Keep-Alive] + content-length: ["983"] + content-type: [application/json] + date: ["Mon, 08 Apr 2019 13:56:30 GMT"] + expires: ["Thu, 19 Nov 1981 08:52:00 GMT"] + keep-alive: ["timeout=15, max=100"] + pragma: [no-cache] + securitycenter: [5.8.0] + server: [Apache] + strict-transport-security: [max-age=31536000; includeSubDomains] + x-content-type-options: [nosniff] + x-frame-options: [DENY] + x-xss-protection: [1; mode=block] + status: { code: 200, message: OK } version: 1 diff --git a/tests/sc/test_recast_risks.py b/tests/sc/test_recast_risks.py index db6697dd7..9e6349c3d 100644 --- a/tests/sc/test_recast_risks.py +++ b/tests/sc/test_recast_risks.py @@ -5,6 +5,7 @@ import pytest import uuid + from tenable.errors import APIError from tests.pytenable_log_handler import log_exception from ..checker import check @@ -58,6 +59,13 @@ def test_recast_risks_constructor_comments_typeerror(security_center): security_center.recast_risks._constructor(comments=1) +def test_recast_risks_constructor_expires_typeerror(security_center): + """ + test recast risks constructor for expires type error + """ + with pytest.raises(TypeError): + security_center.recast_risks._constructor(expires='nope') + def test_recast_risks_constructor_severity_id_typeerror(security_center): """ test recast risks constructor for 'severity id' type error @@ -133,6 +141,7 @@ def test_recast_risks_constructor_success(security_center): protocol=1, comments='comment', severity_id=0, + expires=9999999999, ips=['192.168.0.1'], ) assert resp == { @@ -141,6 +150,7 @@ def test_recast_risks_constructor_success(security_center): 'port': '0', 'protocol': '1', 'comments': 'comment', + 'expires': 9999999999, 'newSeverity': {'id': 0}, 'hostType': 'ip', 'hostValue': '192.168.0.1', @@ -183,6 +193,7 @@ def test_recast_risks_list_success(security_center): check(risk, 'hostValue', str) check(risk, 'port', str) check(risk, 'protocol', str) + check(risk, 'expires', str) check(risk, 'newSeverity', str) check(risk, 'status', str) check(risk, 'repository', dict) @@ -236,6 +247,7 @@ def test_recast_risks_create_success(arisk): check(arisk, 'port', str) check(arisk, 'protocol', str) check(arisk, 'comments', str) + check(arisk, 'expires', str) check(arisk, 'newSeverity', str) check(arisk, 'status', str) check(arisk, 'repository', dict) @@ -269,6 +281,7 @@ def test_recast_risks_details_success(security_center, arisk): check(risk, 'port', str) check(risk, 'protocol', str) check(risk, 'comments', str) + check(risk, 'expires', str) check(risk, 'newSeverity', str) check(risk, 'status', str) check(risk, 'repository', dict)