regscale-cli 6.20.7.0__py3-none-any.whl → 6.20.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/api.py +8 -1
- regscale/core/app/application.py +49 -3
- regscale/core/utils/date.py +16 -16
- regscale/integrations/commercial/aqua/aqua.py +1 -1
- regscale/integrations/commercial/aws/cli.py +1 -1
- regscale/integrations/commercial/defender.py +1 -1
- regscale/integrations/commercial/ecr.py +1 -1
- regscale/integrations/commercial/ibm.py +1 -1
- regscale/integrations/commercial/nexpose.py +1 -1
- regscale/integrations/commercial/prisma.py +1 -1
- regscale/integrations/commercial/qualys/__init__.py +150 -77
- regscale/integrations/commercial/qualys/containers.py +2 -1
- regscale/integrations/commercial/qualys/scanner.py +5 -3
- regscale/integrations/commercial/snyk.py +14 -4
- regscale/integrations/commercial/synqly/ticketing.py +23 -11
- regscale/integrations/commercial/veracode.py +15 -4
- regscale/integrations/commercial/xray.py +1 -1
- regscale/integrations/public/cisa.py +7 -1
- regscale/integrations/public/nist_catalog.py +8 -2
- regscale/integrations/scanner_integration.py +18 -36
- regscale/models/integration_models/cisa_kev_data.json +51 -6
- regscale/models/integration_models/flat_file_importer/__init__.py +34 -19
- regscale/models/integration_models/send_reminders.py +8 -2
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/control_implementation.py +40 -0
- regscale/models/regscale_models/issue.py +7 -4
- regscale/models/regscale_models/parameter.py +3 -2
- regscale/models/regscale_models/ports_protocol.py +15 -5
- regscale/models/regscale_models/vulnerability.py +1 -1
- regscale/utils/graphql_client.py +3 -6
- regscale/utils/threading/threadhandler.py +12 -2
- {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.8.0.dist-info}/METADATA +13 -13
- {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.8.0.dist-info}/RECORD +40 -39
- tests/regscale/core/test_version_regscale.py +62 -0
- tests/regscale/test_init.py +2 -0
- {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.8.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.8.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.8.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.7.0.dist-info → regscale_cli-6.20.8.0.dist-info}/top_level.txt +0 -0
|
@@ -35,7 +35,7 @@ from regscale.integrations.commercial.qualys.variables import QualysVariables
|
|
|
35
35
|
from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
|
|
36
36
|
from regscale.integrations.variables import ScannerVariables
|
|
37
37
|
from regscale.models import Asset, Issue, Search, regscale_models
|
|
38
|
-
from regscale.models.app_models.click import NotRequiredIf, regscale_ssp_id, save_output_to
|
|
38
|
+
from regscale.models.app_models.click import NotRequiredIf, regscale_ssp_id, save_output_to, ssp_or_component_id
|
|
39
39
|
from regscale.models.integration_models.flat_file_importer import FlatFileImporter
|
|
40
40
|
from regscale.models.integration_models.qualys import (
|
|
41
41
|
Qualys,
|
|
@@ -303,7 +303,7 @@ class FindingProgressTracker:
|
|
|
303
303
|
|
|
304
304
|
|
|
305
305
|
@click.command(name="import_total_cloud")
|
|
306
|
-
@
|
|
306
|
+
@ssp_or_component_id()
|
|
307
307
|
@click.option(
|
|
308
308
|
"--include_tags",
|
|
309
309
|
"-t",
|
|
@@ -341,21 +341,37 @@ class FindingProgressTracker:
|
|
|
341
341
|
default=True,
|
|
342
342
|
)
|
|
343
343
|
def import_total_cloud(
|
|
344
|
-
regscale_ssp_id: int,
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
344
|
+
regscale_ssp_id: int = None,
|
|
345
|
+
component_id: int = None,
|
|
346
|
+
include_tags: str = None,
|
|
347
|
+
exclude_tags: str = None,
|
|
348
|
+
vulnerability_creation: str = None,
|
|
349
|
+
ssl_verify: bool = None,
|
|
350
|
+
containers: bool = True,
|
|
350
351
|
):
|
|
351
352
|
"""
|
|
352
353
|
Import Qualys Total Cloud Assets and Vulnerabilities using JSONL scanner implementation.
|
|
353
354
|
|
|
354
355
|
This command uses the JSONLScannerIntegration class for improved efficiency and memory management.
|
|
355
356
|
"""
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
357
|
+
# Determine which ID to use and whether it's a component
|
|
358
|
+
if component_id:
|
|
359
|
+
plan_id = component_id
|
|
360
|
+
is_component = True
|
|
361
|
+
if not validate_regscale_object(component_id, "components"):
|
|
362
|
+
logger.warning("Component #%i is not a valid RegScale Component.", component_id)
|
|
363
|
+
return
|
|
364
|
+
elif regscale_ssp_id:
|
|
365
|
+
plan_id = regscale_ssp_id
|
|
366
|
+
is_component = False
|
|
367
|
+
if not validate_regscale_object(regscale_ssp_id, "securityplans"):
|
|
368
|
+
logger.warning("SSP #%i is not a valid RegScale Security Plan.", regscale_ssp_id)
|
|
369
|
+
return
|
|
370
|
+
else:
|
|
371
|
+
raise click.UsageError(
|
|
372
|
+
"You must provide either a --regscale_ssp_id or a --component_id to import Qualys Total Cloud data."
|
|
373
|
+
)
|
|
374
|
+
|
|
359
375
|
containers_lst = []
|
|
360
376
|
try:
|
|
361
377
|
# Configure scanner variables and fetch data
|
|
@@ -370,7 +386,7 @@ def import_total_cloud(
|
|
|
370
386
|
|
|
371
387
|
# Initialize and run integration
|
|
372
388
|
integration = _initialize_integration(
|
|
373
|
-
|
|
389
|
+
plan_id, response_data, vulnerability_creation, ssl_verify, containers_lst, is_component
|
|
374
390
|
)
|
|
375
391
|
_run_integration_import(integration)
|
|
376
392
|
|
|
@@ -483,31 +499,18 @@ def _fetch_qualys_api_data(include_tags, exclude_tags):
|
|
|
483
499
|
return None
|
|
484
500
|
|
|
485
501
|
|
|
486
|
-
def _initialize_integration(
|
|
487
|
-
"""Initialize the scanner integration with appropriate settings.
|
|
488
|
-
|
|
489
|
-
:param int regscale_ssp_id: RegScale SSP ID
|
|
490
|
-
:param dict response_data: Parsed XML data from API
|
|
491
|
-
:param str vulnerability_creation: Vulnerability creation mode
|
|
492
|
-
:param bool ssl_verify: SSL verification setting
|
|
493
|
-
:param list containers: List of containers
|
|
494
|
-
:return: Initialized integration object
|
|
495
|
-
"""
|
|
496
|
-
# Build integration kwargs
|
|
502
|
+
def _initialize_integration(plan_id, response_data, vulnerability_creation, ssl_verify, containers, is_component=False):
|
|
497
503
|
integration_kwargs = {
|
|
498
|
-
"plan_id":
|
|
504
|
+
"plan_id": plan_id,
|
|
499
505
|
"xml_data": response_data,
|
|
500
506
|
"vulnerability_creation": vulnerability_creation or ScannerVariables.vulnerabilityCreation,
|
|
501
507
|
"ssl_verify": ssl_verify if ssl_verify is not None else ScannerVariables.sslVerify,
|
|
502
508
|
"containers": containers,
|
|
509
|
+
"is_component": is_component,
|
|
503
510
|
}
|
|
504
|
-
|
|
505
|
-
# Add thread workers if available
|
|
506
511
|
if hasattr(ScannerVariables, "threadMaxWorkers"):
|
|
507
512
|
integration_kwargs["max_workers"] = ScannerVariables.threadMaxWorkers
|
|
508
513
|
logger.debug(f"Using thread max workers: {ScannerVariables.threadMaxWorkers}")
|
|
509
|
-
|
|
510
|
-
# Initialize and return integration
|
|
511
514
|
integration = QualysTotalCloudJSONLIntegration(**integration_kwargs)
|
|
512
515
|
return integration
|
|
513
516
|
|
|
@@ -539,7 +542,7 @@ def _run_integration_import(integration):
|
|
|
539
542
|
|
|
540
543
|
|
|
541
544
|
@click.command(name="import_total_cloud_xml")
|
|
542
|
-
@
|
|
545
|
+
@ssp_or_component_id()
|
|
543
546
|
@click.option(
|
|
544
547
|
"--xml_file",
|
|
545
548
|
"-f",
|
|
@@ -547,7 +550,7 @@ def _run_integration_import(integration):
|
|
|
547
550
|
required=True,
|
|
548
551
|
help="Path to Qualys Total Cloud XML file to process.",
|
|
549
552
|
)
|
|
550
|
-
def import_total_cloud_from_xml(regscale_ssp_id: int,
|
|
553
|
+
def import_total_cloud_from_xml(xml_file: str, regscale_ssp_id: int = None, component_id: int = None):
|
|
551
554
|
"""
|
|
552
555
|
Import Qualys Total Cloud Assets and Vulnerabilities from an existing XML file using JSONL scanner.
|
|
553
556
|
|
|
@@ -582,9 +585,27 @@ def import_total_cloud_from_xml(regscale_ssp_id: int, xml_file: str):
|
|
|
582
585
|
QualysErrorHandler.log_error_details(error_details)
|
|
583
586
|
return
|
|
584
587
|
|
|
588
|
+
# Determine which ID to use and whether it's a component
|
|
589
|
+
if component_id:
|
|
590
|
+
plan_id = component_id
|
|
591
|
+
is_component = True
|
|
592
|
+
if not validate_regscale_object(component_id, "components"):
|
|
593
|
+
logger.warning("Component #%i is not a valid RegScale Component.", component_id)
|
|
594
|
+
return
|
|
595
|
+
elif regscale_ssp_id:
|
|
596
|
+
plan_id = regscale_ssp_id
|
|
597
|
+
is_component = False
|
|
598
|
+
if not validate_regscale_object(regscale_ssp_id, "securityplans"):
|
|
599
|
+
logger.warning("SSP #%i is not a valid RegScale Security Plan.", regscale_ssp_id)
|
|
600
|
+
return
|
|
601
|
+
else:
|
|
602
|
+
raise click.UsageError(
|
|
603
|
+
"You must provide either a --regscale_ssp_id or a --component_id to import Qualys Total Cloud data."
|
|
604
|
+
)
|
|
605
|
+
|
|
585
606
|
# Initialize the JSONLScannerIntegration implementation
|
|
586
607
|
integration = QualysTotalCloudJSONLIntegration(
|
|
587
|
-
plan_id=
|
|
608
|
+
plan_id=plan_id, xml_data=response_data, file_path=xml_file, is_component=is_component
|
|
588
609
|
)
|
|
589
610
|
|
|
590
611
|
# Process data and generate JSONL files
|
|
@@ -878,7 +899,7 @@ def import_qualys_scans(
|
|
|
878
899
|
import_name="Qualys",
|
|
879
900
|
file_types=[".csv", ".xlsx"],
|
|
880
901
|
folder_path=folder_path,
|
|
881
|
-
|
|
902
|
+
object_id=regscale_ssp_id,
|
|
882
903
|
scan_date=scan_date,
|
|
883
904
|
mappings_path=mappings_path,
|
|
884
905
|
disable_mapping=disable_mapping,
|
|
@@ -951,13 +972,22 @@ def save_results(save_output_to: Path, scan_id: str):
|
|
|
951
972
|
save_scan_results_by_id(save_path=save_output_to, scan_id=scan_id)
|
|
952
973
|
|
|
953
974
|
|
|
975
|
+
def _resolve_plan_and_component(regscale_ssp_id: int = None, component_id: int = None):
|
|
976
|
+
"""
|
|
977
|
+
Utility to resolve plan_id and is_component from regscale_ssp_id and component_id.
|
|
978
|
+
Returns (plan_id, is_component)
|
|
979
|
+
"""
|
|
980
|
+
if (regscale_ssp_id is None and component_id is None) or (regscale_ssp_id and component_id):
|
|
981
|
+
raise click.UsageError("You must provide either --regscale_ssp_id or --component_id, but not both.")
|
|
982
|
+
is_component = component_id is not None
|
|
983
|
+
plan_id = component_id if is_component else regscale_ssp_id
|
|
984
|
+
return plan_id, is_component
|
|
985
|
+
|
|
986
|
+
|
|
954
987
|
@qualys.command(name="sync_qualys")
|
|
955
|
-
@
|
|
956
|
-
"
|
|
957
|
-
|
|
958
|
-
required=True,
|
|
959
|
-
prompt="Enter RegScale System Security Plan ID",
|
|
960
|
-
help="The ID number from RegScale of the System Security Plan",
|
|
988
|
+
@ssp_or_component_id(
|
|
989
|
+
ssp_kwargs={"help": "The ID number from RegScale of the System Security Plan."},
|
|
990
|
+
component_kwargs={"help": "The ID number from RegScale of the Component record to sync to."},
|
|
961
991
|
)
|
|
962
992
|
@click.option(
|
|
963
993
|
"--create_issue",
|
|
@@ -983,20 +1013,22 @@ def save_results(save_output_to: Path, scan_id: str):
|
|
|
983
1013
|
not_required_if=["asset_group_id"],
|
|
984
1014
|
)
|
|
985
1015
|
def sync_qualys(
|
|
986
|
-
regscale_ssp_id: int,
|
|
1016
|
+
regscale_ssp_id: int = None,
|
|
1017
|
+
component_id: int = None,
|
|
987
1018
|
create_issue: bool = False,
|
|
988
1019
|
asset_group_id: int = None,
|
|
989
1020
|
asset_group_name: str = None,
|
|
990
1021
|
):
|
|
991
1022
|
"""
|
|
992
|
-
Query Qualys and sync assets & their associated
|
|
993
|
-
vulnerabilities to a Security Plan in RegScale.
|
|
1023
|
+
Query Qualys and sync assets & their associated vulnerabilities to a Security Plan or Component in RegScale.
|
|
994
1024
|
"""
|
|
1025
|
+
plan_id, is_component = _resolve_plan_and_component(regscale_ssp_id, component_id)
|
|
995
1026
|
sync_qualys_to_regscale(
|
|
996
|
-
|
|
1027
|
+
plan_id=plan_id,
|
|
997
1028
|
create_issue=create_issue,
|
|
998
1029
|
asset_group_id=asset_group_id,
|
|
999
1030
|
asset_group_name=asset_group_name,
|
|
1031
|
+
is_component=is_component,
|
|
1000
1032
|
)
|
|
1001
1033
|
|
|
1002
1034
|
|
|
@@ -1259,18 +1291,20 @@ def save_scan_results_by_id(save_path: Path, scan_id: str) -> None:
|
|
|
1259
1291
|
|
|
1260
1292
|
|
|
1261
1293
|
def sync_qualys_to_regscale(
|
|
1262
|
-
|
|
1294
|
+
plan_id: int,
|
|
1263
1295
|
create_issue: bool = False,
|
|
1264
1296
|
asset_group_id: int = None,
|
|
1265
1297
|
asset_group_name: str = None,
|
|
1298
|
+
is_component: bool = False,
|
|
1266
1299
|
) -> None:
|
|
1267
1300
|
"""
|
|
1268
|
-
Sync Qualys assets and vulnerabilities to a security plan in RegScale
|
|
1301
|
+
Sync Qualys assets and vulnerabilities to a security plan or component in RegScale
|
|
1269
1302
|
|
|
1270
|
-
:param int
|
|
1303
|
+
:param int plan_id: ID # of the SSP or Component in RegScale
|
|
1271
1304
|
:param bool create_issue: Flag whether to create an issue in RegScale from Qualys vulnerabilities, defaults to False
|
|
1272
1305
|
:param int asset_group_id: Optional filter for assets in Qualys with an asset group ID, defaults to None
|
|
1273
1306
|
:param str asset_group_name: Optional filter for assets in Qualys with an asset group name, defaults to None
|
|
1307
|
+
:param bool is_component: Whether the sync is for a component (True) or security plan (False)
|
|
1274
1308
|
:rtype: None
|
|
1275
1309
|
"""
|
|
1276
1310
|
# see if user has enterprise license
|
|
@@ -1278,21 +1312,21 @@ def sync_qualys_to_regscale(
|
|
|
1278
1312
|
|
|
1279
1313
|
# check if the user provided an asset group id or name
|
|
1280
1314
|
if asset_group_id:
|
|
1281
|
-
# get the assets from Qualys using the group name
|
|
1282
1315
|
sync_qualys_assets_and_vulns(
|
|
1283
|
-
ssp_id=
|
|
1316
|
+
ssp_id=plan_id,
|
|
1284
1317
|
create_issue=create_issue,
|
|
1285
1318
|
asset_group_filter=asset_group_name,
|
|
1319
|
+
is_component=is_component,
|
|
1286
1320
|
)
|
|
1287
1321
|
elif asset_group_name:
|
|
1288
|
-
# get the assets from Qualys using the group name
|
|
1289
1322
|
sync_qualys_assets_and_vulns(
|
|
1290
|
-
ssp_id=
|
|
1323
|
+
ssp_id=plan_id,
|
|
1291
1324
|
create_issue=create_issue,
|
|
1292
1325
|
asset_group_filter=asset_group_id,
|
|
1326
|
+
is_component=is_component,
|
|
1293
1327
|
)
|
|
1294
1328
|
else:
|
|
1295
|
-
sync_qualys_assets_and_vulns(ssp_id=
|
|
1329
|
+
sync_qualys_assets_and_vulns(ssp_id=plan_id, create_issue=create_issue, is_component=is_component)
|
|
1296
1330
|
|
|
1297
1331
|
|
|
1298
1332
|
def get_scan_results(scans: Any, task: TaskID) -> dict:
|
|
@@ -1594,28 +1628,32 @@ def sync_qualys_assets_and_vulns(
|
|
|
1594
1628
|
ssp_id: int,
|
|
1595
1629
|
create_issue: bool,
|
|
1596
1630
|
asset_group_filter: Optional[Union[int, str]] = None,
|
|
1631
|
+
is_component: bool = False,
|
|
1597
1632
|
) -> None:
|
|
1598
1633
|
"""
|
|
1599
|
-
Function to query Qualys and sync assets & associated vulnerabilities to RegScale
|
|
1634
|
+
Function to query Qualys and sync assets & associated vulnerabilities to RegScale (Security Plan or Component)
|
|
1600
1635
|
|
|
1601
|
-
:param int ssp_id: RegScale System Security Plan ID
|
|
1636
|
+
:param int ssp_id: RegScale System Security Plan or Component ID
|
|
1602
1637
|
:param bool create_issue: Flag to create an issue in RegScale for each vulnerability from Qualys
|
|
1603
1638
|
:param Optional[Union[int, str]] asset_group_filter: Filter the Qualys assets by an asset group ID or name, if any
|
|
1639
|
+
:param bool is_component: Whether the sync is for a component (True) or security plan (False)
|
|
1604
1640
|
:rtype: None
|
|
1605
1641
|
"""
|
|
1606
1642
|
config = _get_config()
|
|
1643
|
+
parent_module = "components" if is_component else "securityplans"
|
|
1607
1644
|
|
|
1608
|
-
# Get the assets from RegScale with the provided
|
|
1609
|
-
logger.info("Getting assets from RegScale for
|
|
1610
|
-
reg_assets = Asset.get_all_by_search(search=Search(parentID=ssp_id, module=
|
|
1645
|
+
# Get the assets from RegScale with the provided parent ID
|
|
1646
|
+
logger.info(f"Getting assets from RegScale for {parent_module} #{ssp_id}...")
|
|
1647
|
+
reg_assets = Asset.get_all_by_search(search=Search(parentID=ssp_id, module=parent_module))
|
|
1611
1648
|
logger.info(
|
|
1612
|
-
"Located %s asset(s) associated with
|
|
1649
|
+
"Located %s asset(s) associated with %s #%s in RegScale.",
|
|
1613
1650
|
len(reg_assets),
|
|
1651
|
+
parent_module,
|
|
1614
1652
|
ssp_id,
|
|
1615
1653
|
)
|
|
1616
1654
|
logger.debug(reg_assets)
|
|
1617
1655
|
|
|
1618
|
-
if qualys_assets := get_qualys_assets_and_scan_results(asset_group_filter):
|
|
1656
|
+
if qualys_assets := get_qualys_assets_and_scan_results(asset_group_filter=asset_group_filter):
|
|
1619
1657
|
logger.info("Received %s assets from Qualys.", len(qualys_assets))
|
|
1620
1658
|
logger.debug(qualys_assets)
|
|
1621
1659
|
else:
|
|
@@ -1625,6 +1663,7 @@ def sync_qualys_assets_and_vulns(
|
|
|
1625
1663
|
reg_assets=reg_assets,
|
|
1626
1664
|
ssp_id=ssp_id,
|
|
1627
1665
|
config=config,
|
|
1666
|
+
is_component=is_component,
|
|
1628
1667
|
)
|
|
1629
1668
|
if create_issue:
|
|
1630
1669
|
# Get vulnerabilities from Qualys for the Qualys assets
|
|
@@ -1635,26 +1674,35 @@ def sync_qualys_assets_and_vulns(
|
|
|
1635
1674
|
sync_issues(
|
|
1636
1675
|
ssp_id=ssp_id,
|
|
1637
1676
|
qualys_assets_and_issues=qualys_assets_and_issues,
|
|
1677
|
+
is_component=is_component,
|
|
1638
1678
|
)
|
|
1639
1679
|
|
|
1640
1680
|
|
|
1641
|
-
def sync_assets(
|
|
1681
|
+
def sync_assets(
|
|
1682
|
+
qualys_assets: list[dict], reg_assets: list[Asset], ssp_id: int, config: dict, is_component: bool = False
|
|
1683
|
+
) -> None:
|
|
1642
1684
|
"""
|
|
1643
|
-
Function to sync Qualys assets to RegScale
|
|
1685
|
+
Function to sync Qualys assets to RegScale (Security Plan or Component)
|
|
1644
1686
|
|
|
1645
1687
|
:param list[dict] qualys_assets: List of Qualys assets
|
|
1646
1688
|
:param list[Asset] reg_assets: List of RegScale assets
|
|
1647
|
-
:param int ssp_id: RegScale System Security Plan ID
|
|
1689
|
+
:param int ssp_id: RegScale System Security Plan or Component ID
|
|
1648
1690
|
:param dict config: Configuration dictionary
|
|
1691
|
+
:param bool is_component: Whether the sync is for a component (True) or security plan (False)
|
|
1649
1692
|
:rtype: None
|
|
1650
1693
|
"""
|
|
1694
|
+
parent_module = "components" if is_component else "securityplans"
|
|
1651
1695
|
update_assets = []
|
|
1652
1696
|
for qualys_asset in qualys_assets: # you can list as many input dicts as you want here
|
|
1653
|
-
|
|
1697
|
+
logger.debug("qualys_asset: %s", qualys_asset)
|
|
1698
|
+
if not isinstance(qualys_asset, dict):
|
|
1699
|
+
logger.error("Expected dict, got %s: %s", type(qualys_asset), qualys_asset)
|
|
1700
|
+
continue
|
|
1701
|
+
# Update parent id to SSP or Component on insert
|
|
1654
1702
|
if lookup_assets := lookup_asset(reg_assets, qualys_asset["ASSET_ID"]):
|
|
1655
1703
|
for asset in set(lookup_assets):
|
|
1656
1704
|
asset.parentId = ssp_id
|
|
1657
|
-
asset.parentModule =
|
|
1705
|
+
asset.parentModule = parent_module
|
|
1658
1706
|
asset.otherTrackingNumber = qualys_asset["ID"]
|
|
1659
1707
|
asset.ipAddress = qualys_asset["IP"]
|
|
1660
1708
|
asset.qualysId = qualys_asset["ASSET_ID"]
|
|
@@ -1666,23 +1714,35 @@ def sync_assets(qualys_assets: list[dict], reg_assets: list[Asset], ssp_id: int,
|
|
|
1666
1714
|
except AssertionError as aex:
|
|
1667
1715
|
logger.error("Asset does not have an id, unable to update!\n%s", aex)
|
|
1668
1716
|
update_and_insert_assets(
|
|
1669
|
-
qualys_assets=qualys_assets,
|
|
1717
|
+
qualys_assets=qualys_assets,
|
|
1718
|
+
reg_assets=reg_assets,
|
|
1719
|
+
ssp_id=ssp_id,
|
|
1720
|
+
config=config,
|
|
1721
|
+
update_assets=update_assets,
|
|
1722
|
+
is_component=is_component,
|
|
1670
1723
|
)
|
|
1671
1724
|
|
|
1672
1725
|
|
|
1673
1726
|
def update_and_insert_assets(
|
|
1674
|
-
qualys_assets: list[dict],
|
|
1727
|
+
qualys_assets: list[dict],
|
|
1728
|
+
reg_assets: list[Asset],
|
|
1729
|
+
ssp_id: int,
|
|
1730
|
+
config: dict,
|
|
1731
|
+
update_assets: list[Asset],
|
|
1732
|
+
is_component: bool = False,
|
|
1675
1733
|
) -> None:
|
|
1676
1734
|
"""
|
|
1677
|
-
Function to update and insert Qualys assets into RegScale
|
|
1735
|
+
Function to update and insert Qualys assets into RegScale (Security Plan or Component)
|
|
1678
1736
|
|
|
1679
1737
|
:param list[dict] qualys_assets: List of Qualys assets as dictionaries
|
|
1680
1738
|
:param list[Asset] reg_assets: List of RegScale assets
|
|
1681
|
-
:param int ssp_id: RegScale System Security Plan ID
|
|
1739
|
+
:param int ssp_id: RegScale System Security Plan or Component ID
|
|
1682
1740
|
:param dict config: RegScale CLI Configuration dictionary
|
|
1683
1741
|
:param list[Asset] update_assets: List of assets to update in RegScale
|
|
1742
|
+
:param bool is_component: Whether the sync is for a component (True) or security plan (False)
|
|
1684
1743
|
:rtype: None
|
|
1685
1744
|
"""
|
|
1745
|
+
parent_module = "components" if is_component else "securityplans"
|
|
1686
1746
|
insert_assets = []
|
|
1687
1747
|
if assets_to_be_inserted := [
|
|
1688
1748
|
qualys_asset
|
|
@@ -1695,7 +1755,7 @@ def update_and_insert_assets(
|
|
|
1695
1755
|
name=f'Qualys Asset #{qualys_asset["ASSET_ID"]} IP: {qualys_asset["IP"]}',
|
|
1696
1756
|
otherTrackingNumber=qualys_asset["ID"],
|
|
1697
1757
|
parentId=ssp_id,
|
|
1698
|
-
parentModule=
|
|
1758
|
+
parentModule=parent_module,
|
|
1699
1759
|
ipAddress=qualys_asset["IP"],
|
|
1700
1760
|
assetOwnerId=config["userId"],
|
|
1701
1761
|
assetType="Other",
|
|
@@ -1727,22 +1787,28 @@ def update_and_insert_assets(
|
|
|
1727
1787
|
logger.error("Unable to Update Qualys Assets to RegScale\n%s", rex)
|
|
1728
1788
|
|
|
1729
1789
|
|
|
1730
|
-
def sync_issues(ssp_id: int, qualys_assets_and_issues: list[dict]) -> None:
|
|
1790
|
+
def sync_issues(ssp_id: int, qualys_assets_and_issues: list[dict], is_component: bool = False) -> None:
|
|
1731
1791
|
"""
|
|
1732
|
-
Function to sync Qualys issues to RegScale
|
|
1792
|
+
Function to sync Qualys issues to RegScale (Security Plan or Component)
|
|
1733
1793
|
|
|
1734
|
-
:param int ssp_id: RegScale System Security Plan ID
|
|
1794
|
+
:param int ssp_id: RegScale System Security Plan or Component ID
|
|
1735
1795
|
:param list[dict] qualys_assets_and_issues: List of Qualys assets and their issues
|
|
1796
|
+
:param bool is_component: Whether the sync is for a component (True) or security plan (False)
|
|
1736
1797
|
:rtype: None
|
|
1737
1798
|
"""
|
|
1799
|
+
parent_module = "components" if is_component else "securityplans"
|
|
1738
1800
|
update_issues = []
|
|
1739
1801
|
insert_issues = []
|
|
1740
1802
|
vuln_count = 0
|
|
1741
|
-
ssp_assets = Asset.get_all_by_parent(parent_id=ssp_id, parent_module=
|
|
1803
|
+
ssp_assets = Asset.get_all_by_parent(parent_id=ssp_id, parent_module=parent_module)
|
|
1742
1804
|
for asset in qualys_assets_and_issues:
|
|
1743
1805
|
# Create issues in RegScale from Qualys vulnerabilities
|
|
1744
1806
|
regscale_issue_updates, regscale_new_issues = create_regscale_issue_from_vuln(
|
|
1745
|
-
regscale_ssp_id=ssp_id,
|
|
1807
|
+
regscale_ssp_id=ssp_id,
|
|
1808
|
+
qualys_asset=asset,
|
|
1809
|
+
regscale_assets=ssp_assets,
|
|
1810
|
+
vulns=asset["ISSUES"],
|
|
1811
|
+
is_component=is_component,
|
|
1746
1812
|
)
|
|
1747
1813
|
update_issues.extend(regscale_issue_updates)
|
|
1748
1814
|
insert_issues.extend(regscale_new_issues)
|
|
@@ -1840,6 +1906,11 @@ def get_qualys_assets_and_scan_results(
|
|
|
1840
1906
|
# parse the xml data from response.text and convert it to a dictionary
|
|
1841
1907
|
# and try to extract the data from the parsed XML dictionary
|
|
1842
1908
|
asset_data = res_data["HOST_LIST_VM_DETECTION_OUTPUT"]["RESPONSE"]["HOST_LIST"]["HOST"]
|
|
1909
|
+
# Always make asset_data a list
|
|
1910
|
+
if isinstance(asset_data, dict):
|
|
1911
|
+
asset_data = [asset_data]
|
|
1912
|
+
elif not isinstance(asset_data, list):
|
|
1913
|
+
asset_data = []
|
|
1843
1914
|
# check if we need to paginate the asset data
|
|
1844
1915
|
if "WARNING" in res_data["HOST_LIST_VM_DETECTION_OUTPUT"]["RESPONSE"]:
|
|
1845
1916
|
logger.warning("Not all assets were fetched, fetching more assets from Qualys...")
|
|
@@ -2025,22 +2096,24 @@ def map_qualys_severity_to_regscale(severity: int) -> tuple[str, str]:
|
|
|
2025
2096
|
|
|
2026
2097
|
|
|
2027
2098
|
def create_regscale_issue_from_vuln(
|
|
2028
|
-
regscale_ssp_id: int, qualys_asset: dict, regscale_assets: list[Asset], vulns: dict
|
|
2099
|
+
regscale_ssp_id: int, qualys_asset: dict, regscale_assets: list[Asset], vulns: dict, is_component: bool = False
|
|
2029
2100
|
) -> Tuple[list[Issue], list[Issue]]:
|
|
2030
2101
|
"""
|
|
2031
|
-
Sync Qualys vulnerabilities to RegScale issues.
|
|
2102
|
+
Sync Qualys vulnerabilities to RegScale issues (Security Plan or Component).
|
|
2032
2103
|
|
|
2033
|
-
:param int regscale_ssp_id: RegScale SSP ID
|
|
2104
|
+
:param int regscale_ssp_id: RegScale SSP or Component ID
|
|
2034
2105
|
:param dict qualys_asset: Qualys asset as a dictionary
|
|
2035
2106
|
:param list[Asset] regscale_assets: list of RegScale assets
|
|
2036
2107
|
:param dict vulns: dictionary of Qualys vulnerabilities associated with the provided asset
|
|
2108
|
+
:param bool is_component: Whether the sync is for a component (True) or security plan (False)
|
|
2037
2109
|
:return: list of RegScale issues to update, and a list of issues to be created
|
|
2038
2110
|
:rtype: Tuple[list[Issue], list[Issue]]
|
|
2039
2111
|
"""
|
|
2040
2112
|
config = _get_config()
|
|
2041
2113
|
default_status = config["issues"]["qualys"]["status"]
|
|
2042
2114
|
regscale_issues = []
|
|
2043
|
-
|
|
2115
|
+
parent_module = "components" if is_component else "securityplans"
|
|
2116
|
+
regscale_existing_issues = Issue.get_all_by_parent(parent_id=regscale_ssp_id, parent_module=parent_module)
|
|
2044
2117
|
for vuln in vulns.values():
|
|
2045
2118
|
asset_identifier = None
|
|
2046
2119
|
severity, key = map_qualys_severity_to_regscale(int(vuln["SEVERITY"]))
|
|
@@ -2071,7 +2144,7 @@ def create_regscale_issue_from_vuln(
|
|
|
2071
2144
|
dueDate=due_date.strftime(fmt),
|
|
2072
2145
|
identification="Vulnerability Assessment",
|
|
2073
2146
|
parentId=regscale_ssp_id,
|
|
2074
|
-
parentModule=
|
|
2147
|
+
parentModule=parent_module,
|
|
2075
2148
|
recommendedActions=vuln["ISSUE_DATA"]["SOLUTION"],
|
|
2076
2149
|
assetIdentifier=asset_identifier,
|
|
2077
2150
|
)
|
|
@@ -150,6 +150,7 @@ def _fetch_paginated_data(endpoint: str, filters: Optional[Dict] = None, limit:
|
|
|
150
150
|
"""
|
|
151
151
|
all_items = []
|
|
152
152
|
page: int = 1
|
|
153
|
+
current_url = None # Ensure current_url is always defined
|
|
153
154
|
|
|
154
155
|
try:
|
|
155
156
|
# Get authentication
|
|
@@ -214,7 +215,7 @@ def _fetch_paginated_data(endpoint: str, filters: Optional[Dict] = None, limit:
|
|
|
214
215
|
params = {}
|
|
215
216
|
|
|
216
217
|
except Exception as e:
|
|
217
|
-
logger.error("Error fetching data from %s: %s", current_url, e)
|
|
218
|
+
logger.error("Error fetching data from %s: %s", current_url if current_url else "N/A", e)
|
|
218
219
|
logger.debug(traceback.format_exc())
|
|
219
220
|
|
|
220
221
|
logger.info("Completed: Fetched %s total items from %s", len(all_items), endpoint)
|
|
@@ -67,10 +67,12 @@ class QualysTotalCloudJSONLIntegration(JSONLScannerIntegration):
|
|
|
67
67
|
|
|
68
68
|
:param Any *args: Variable positional arguments
|
|
69
69
|
:param Any **kwargs: Variable keyword arguments
|
|
70
|
+
:param bool is_component: Whether to upload to a component record (default: False)
|
|
70
71
|
"""
|
|
71
72
|
self.type = ScannerIntegrationType.VULNERABILITY
|
|
72
73
|
self.xml_data = kwargs.pop("xml_data", None)
|
|
73
74
|
self.containers = kwargs.pop("containers", None)
|
|
75
|
+
self.is_component = kwargs.get("is_component", False)
|
|
74
76
|
# Setting a dummy file path to avoid validation errors
|
|
75
77
|
if self.xml_data and "file_path" not in kwargs:
|
|
76
78
|
kwargs["file_path"] = None
|
|
@@ -210,7 +212,7 @@ class QualysTotalCloudJSONLIntegration(JSONLScannerIntegration):
|
|
|
210
212
|
asset_category="IT",
|
|
211
213
|
status=AssetStatus.Active,
|
|
212
214
|
parent_id=self.plan_id,
|
|
213
|
-
parent_module="securityplans",
|
|
215
|
+
parent_module="components" if self.is_component else "securityplans",
|
|
214
216
|
notes="Generated for missing Qualys data",
|
|
215
217
|
)
|
|
216
218
|
|
|
@@ -244,7 +246,7 @@ class QualysTotalCloudJSONLIntegration(JSONLScannerIntegration):
|
|
|
244
246
|
vlan_id=host_info["network_id"],
|
|
245
247
|
notes=f"Qualys Asset ID: {host_info['host_id']}",
|
|
246
248
|
parent_id=self.plan_id,
|
|
247
|
-
parent_module="securityplans",
|
|
249
|
+
parent_module="components" if self.is_component else "securityplans",
|
|
248
250
|
)
|
|
249
251
|
|
|
250
252
|
def _extract_host_from_structure(self, host_data):
|
|
@@ -1044,7 +1046,7 @@ class QualysTotalCloudJSONLIntegration(JSONLScannerIntegration):
|
|
|
1044
1046
|
mac_address=None,
|
|
1045
1047
|
notes=f"Qualys Container ID: {container_id}. Image ID: {image_id}. SHA: {sha}",
|
|
1046
1048
|
parent_id=self.plan_id,
|
|
1047
|
-
parent_module="securityplans",
|
|
1049
|
+
parent_module="components" if self.is_component else "securityplans",
|
|
1048
1050
|
is_virtual=True,
|
|
1049
1051
|
)
|
|
1050
1052
|
|
|
@@ -21,10 +21,12 @@ def snyk():
|
|
|
21
21
|
message="File path to the folder containing Snyk .xlsx or .json files to process to RegScale.",
|
|
22
22
|
prompt="File path for Snyk files",
|
|
23
23
|
import_name="snyk",
|
|
24
|
+
support_component=True,
|
|
24
25
|
)
|
|
25
26
|
def import_snyk(
|
|
26
27
|
folder_path: PathLike[str],
|
|
27
28
|
regscale_ssp_id: int,
|
|
29
|
+
component_id: int,
|
|
28
30
|
scan_date: datetime,
|
|
29
31
|
mappings_path: PathLike[str],
|
|
30
32
|
disable_mapping: bool,
|
|
@@ -36,9 +38,14 @@ def import_snyk(
|
|
|
36
38
|
"""
|
|
37
39
|
Import scans, vulnerabilities and assets to RegScale from Snyk export files
|
|
38
40
|
"""
|
|
41
|
+
|
|
42
|
+
if not regscale_ssp_id and not component_id:
|
|
43
|
+
raise click.UsageError("You must provide either a --regscale_ssp_id or a --component_id to import Snyk scans.")
|
|
44
|
+
|
|
39
45
|
import_synk_files(
|
|
40
46
|
folder_path=folder_path,
|
|
41
|
-
|
|
47
|
+
object_id=component_id if component_id else regscale_ssp_id,
|
|
48
|
+
is_component=bool(component_id),
|
|
42
49
|
scan_date=scan_date,
|
|
43
50
|
mappings_path=mappings_path,
|
|
44
51
|
disable_mapping=disable_mapping,
|
|
@@ -51,7 +58,8 @@ def import_snyk(
|
|
|
51
58
|
|
|
52
59
|
def import_synk_files(
|
|
53
60
|
folder_path: PathLike[str],
|
|
54
|
-
|
|
61
|
+
object_id: int,
|
|
62
|
+
is_component: bool,
|
|
55
63
|
scan_date: datetime,
|
|
56
64
|
mappings_path: PathLike[str],
|
|
57
65
|
disable_mapping: bool,
|
|
@@ -64,7 +72,7 @@ def import_synk_files(
|
|
|
64
72
|
Import Snyk scans, vulnerabilities and assets to RegScale from Snyk files
|
|
65
73
|
|
|
66
74
|
:param PathLike[str] folder_path: File path to the folder containing Snyk .xlsx files to process to RegScale
|
|
67
|
-
:param int
|
|
75
|
+
:param int object_id: The RegScale SSP ID or Component ID
|
|
68
76
|
:param datetime scan_date: The date of the scan
|
|
69
77
|
:param PathLike[str] mappings_path: The path to the mappings file
|
|
70
78
|
:param bool disable_mapping: Whether to disable custom mappings
|
|
@@ -72,6 +80,7 @@ def import_synk_files(
|
|
|
72
80
|
:param str s3_prefix: The S3 prefix to download the files from
|
|
73
81
|
:param str aws_profile: The AWS profile to use for S3 access
|
|
74
82
|
:param Optional[bool] upload_file: Whether to upload the file to RegScale after processing, defaults to True
|
|
83
|
+
:param bool is_component: Whether the object is a component
|
|
75
84
|
:rtype: None
|
|
76
85
|
"""
|
|
77
86
|
FlatFileImporter.import_files(
|
|
@@ -79,7 +88,7 @@ def import_synk_files(
|
|
|
79
88
|
import_name="Snyk",
|
|
80
89
|
file_types=[".xlsx", ".json"],
|
|
81
90
|
folder_path=folder_path,
|
|
82
|
-
|
|
91
|
+
object_id=object_id,
|
|
83
92
|
scan_date=scan_date,
|
|
84
93
|
mappings_path=mappings_path,
|
|
85
94
|
disable_mapping=disable_mapping,
|
|
@@ -87,4 +96,5 @@ def import_synk_files(
|
|
|
87
96
|
s3_prefix=s3_prefix,
|
|
88
97
|
aws_profile=aws_profile,
|
|
89
98
|
upload_file=upload_file,
|
|
99
|
+
is_component=is_component,
|
|
90
100
|
)
|