regscale-cli 6.18.0.0__py3-none-any.whl → 6.19.0.1__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.

Files changed (47) hide show
  1. regscale/__init__.py +1 -1
  2. regscale/integrations/api_paginator.py +932 -0
  3. regscale/integrations/api_paginator_example.py +348 -0
  4. regscale/integrations/commercial/__init__.py +11 -10
  5. regscale/integrations/commercial/{qualys.py → qualys/__init__.py} +756 -105
  6. regscale/integrations/commercial/qualys/scanner.py +1051 -0
  7. regscale/integrations/commercial/qualys/variables.py +21 -0
  8. regscale/integrations/commercial/sicura/api.py +1 -0
  9. regscale/integrations/commercial/stigv2/click_commands.py +36 -8
  10. regscale/integrations/commercial/stigv2/stig_integration.py +63 -9
  11. regscale/integrations/commercial/tenablev2/__init__.py +9 -0
  12. regscale/integrations/commercial/tenablev2/authenticate.py +23 -2
  13. regscale/integrations/commercial/tenablev2/commands.py +779 -0
  14. regscale/integrations/commercial/tenablev2/jsonl_scanner.py +1999 -0
  15. regscale/integrations/commercial/tenablev2/sc_scanner.py +600 -0
  16. regscale/integrations/commercial/tenablev2/scanner.py +7 -5
  17. regscale/integrations/commercial/tenablev2/utils.py +21 -4
  18. regscale/integrations/commercial/tenablev2/variables.py +4 -0
  19. regscale/integrations/jsonl_scanner_integration.py +523 -142
  20. regscale/integrations/scanner_integration.py +102 -26
  21. regscale/integrations/transformer/__init__.py +17 -0
  22. regscale/integrations/transformer/data_transformer.py +445 -0
  23. regscale/integrations/transformer/mappings/__init__.py +8 -0
  24. regscale/integrations/variables.py +2 -0
  25. regscale/models/__init__.py +5 -2
  26. regscale/models/integration_models/cisa_kev_data.json +6 -6
  27. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  28. regscale/models/regscale_models/asset.py +5 -2
  29. regscale/models/regscale_models/file.py +5 -2
  30. regscale/models/regscale_models/group.py +2 -1
  31. regscale/models/regscale_models/user_group.py +1 -1
  32. regscale/regscale.py +3 -1
  33. {regscale_cli-6.18.0.0.dist-info → regscale_cli-6.19.0.1.dist-info}/METADATA +1 -1
  34. {regscale_cli-6.18.0.0.dist-info → regscale_cli-6.19.0.1.dist-info}/RECORD +46 -30
  35. tests/regscale/core/test_version.py +22 -0
  36. tests/regscale/integrations/__init__.py +0 -0
  37. tests/regscale/integrations/test_api_paginator.py +597 -0
  38. tests/regscale/integrations/test_integration_mapping.py +60 -0
  39. tests/regscale/integrations/test_issue_creation.py +317 -0
  40. tests/regscale/integrations/test_issue_due_date.py +46 -0
  41. tests/regscale/integrations/transformer/__init__.py +0 -0
  42. tests/regscale/integrations/transformer/test_data_transformer.py +850 -0
  43. regscale/integrations/commercial/tenablev2/click.py +0 -1641
  44. {regscale_cli-6.18.0.0.dist-info → regscale_cli-6.19.0.1.dist-info}/LICENSE +0 -0
  45. {regscale_cli-6.18.0.0.dist-info → regscale_cli-6.19.0.1.dist-info}/WHEEL +0 -0
  46. {regscale_cli-6.18.0.0.dist-info → regscale_cli-6.19.0.1.dist-info}/entry_points.txt +0 -0
  47. {regscale_cli-6.18.0.0.dist-info → regscale_cli-6.19.0.1.dist-info}/top_level.txt +0 -0
@@ -50,11 +50,11 @@ def get_thread_workers_max() -> int:
50
50
  def issue_due_date(
51
51
  severity: regscale_models.IssueSeverity,
52
52
  created_date: str,
53
- critical: int = ScannerVariables.issueDueDates.get("critical", 30),
54
- high: int = ScannerVariables.issueDueDates.get("high", 60),
55
- moderate: int = ScannerVariables.issueDueDates.get("moderate", 120),
56
- low: int = ScannerVariables.issueDueDates.get("low", 364),
57
- title: str = "",
53
+ critical: Optional[int] = None,
54
+ high: Optional[int] = None,
55
+ moderate: Optional[int] = None,
56
+ low: Optional[int] = None,
57
+ title: Optional[str] = "",
58
58
  config: Optional[Dict[str, Dict]] = None,
59
59
  ) -> str:
60
60
  """
@@ -62,15 +62,24 @@ def issue_due_date(
62
62
 
63
63
  :param regscale_models.IssueSeverity severity: The severity of the issue.
64
64
  :param str created_date: The creation date of the issue.
65
- :param int critical: Days until due for high severity issues. Default is 30.
66
- :param int high: Days until due for high severity issues. Default is 60.
67
- :param int moderate: Days until due for moderate severity issues. Default is 210.
68
- :param int low: Days until due for low severity issues. Default is 364.
69
- :param str title: The title of the Integration.
70
- :param Dict[str, Dict] config: Configuration options for the due date calculation.
65
+ :param Optional[int] critical: Days until due for high severity issues.
66
+ :param Optional[int] high: Days until due for high severity issues.
67
+ :param Optional[int] moderate: Days until due for moderate severity issues.
68
+ :param Optional[int] low: Days until due for low severity issues.
69
+ :param Optional[str] title: The title of the Integration.
70
+ :param Optional[Dict[str, Dict]] config: Configuration options for the due date calculation.
71
71
  :return: The due date for the issue.
72
72
  :rtype: str
73
73
  """
74
+ if critical is None:
75
+ critical = ScannerVariables.issueDueDates.get("critical", 30)
76
+ if high is None:
77
+ high = ScannerVariables.issueDueDates.get("high", 60)
78
+ if moderate is None:
79
+ moderate = ScannerVariables.issueDueDates.get("moderate", 120)
80
+ if low is None:
81
+ low = ScannerVariables.issueDueDates.get("low", 364)
82
+
74
83
  if config is None:
75
84
  config = {}
76
85
 
@@ -452,6 +461,21 @@ class IntegrationFinding:
452
461
  vpr_score: Optional[float] = None
453
462
 
454
463
  def __post_init__(self):
464
+ """Validate and adjust types after initialization."""
465
+ # Set default date values if empty
466
+ if not self.first_seen:
467
+ self.first_seen = get_current_datetime()
468
+ if not self.last_seen:
469
+ self.last_seen = get_current_datetime()
470
+ if not self.scan_date:
471
+ self.scan_date = get_current_datetime()
472
+
473
+ # Validate the values of the dataclass
474
+ if not self.title:
475
+ self.title = "Unknown Issue"
476
+ if not self.description:
477
+ self.description = "No description provided"
478
+
455
479
  if self.plugin_name is None:
456
480
  self.plugin_name = self.cve or self.title
457
481
  if self.plugin_id is None:
@@ -593,7 +617,7 @@ class ScannerIntegration(ABC):
593
617
  # Close Outdated Findings
594
618
  close_outdated_findings = True
595
619
 
596
- def __init__(self, plan_id: int, tenant_id: int = 1, **kwargs):
620
+ def __init__(self, plan_id: int, tenant_id: int = 1, is_component: bool = False, **kwargs):
597
621
  """
598
622
  Initialize the ScannerIntegration.
599
623
 
@@ -607,6 +631,7 @@ class ScannerIntegration(ABC):
607
631
  logger.debug(f"RegScale Version: {self.regscale_version}")
608
632
  self.plan_id: int = plan_id
609
633
  self.tenant_id: int = tenant_id
634
+ self.is_component: bool = is_component
610
635
  self.components: ThreadSafeList[Any] = ThreadSafeList()
611
636
  self.asset_map_by_identifier: ThreadSafeDict[str, regscale_models.Asset] = ThreadSafeDict()
612
637
  self.software_to_create: ThreadSafeList[regscale_models.SoftwareInventory] = ThreadSafeList()
@@ -759,7 +784,9 @@ class ScannerIntegration(ABC):
759
784
  """
760
785
  if self.options_map_assets_to_components:
761
786
  # Fetches the asset map directly using a specified key field.
762
- return regscale_models.Asset.get_map(plan_id=self.plan_id, key_field=self.asset_identifier_field)
787
+ return regscale_models.Asset.get_map(
788
+ plan_id=self.plan_id, key_field=self.asset_identifier_field, is_component=self.is_component
789
+ )
763
790
  else:
764
791
  # Constructs the asset map by fetching all assets under the plan and using the asset identifier field as
765
792
  # the key.
@@ -767,10 +794,27 @@ class ScannerIntegration(ABC):
767
794
  getattr(x, self.asset_identifier_field): x
768
795
  for x in regscale_models.Asset.get_all_by_parent(
769
796
  parent_id=self.plan_id,
770
- parent_module=regscale_models.SecurityPlan.get_module_string(),
797
+ parent_module=(
798
+ regscale_models.Component.get_module_string()
799
+ if self.is_component
800
+ else regscale_models.SecurityPlan.get_module_string()
801
+ ),
771
802
  )
772
803
  }
773
804
 
805
+ def get_issues_map(self) -> dict[int, regscale_models.Issue]:
806
+ """
807
+ Gets the issues map
808
+
809
+ :return: The issues map
810
+ :rtype: dict[int, regscale_models.Issue]
811
+ """
812
+ all_issues = regscale_models.Issue.get_all_by_parent(
813
+ parent_id=self.plan_id,
814
+ parent_module=regscale_models.SecurityPlan.get_module_string(),
815
+ )
816
+ return {issue.integrationFindingId: issue for issue in all_issues}
817
+
774
818
  @abstractmethod
775
819
  def fetch_findings(self, *args, **kwargs) -> Iterator[IntegrationFinding]:
776
820
  """
@@ -882,10 +926,17 @@ class ScannerIntegration(ABC):
882
926
  """
883
927
  if any(self.components):
884
928
  return self.components
885
- components: List[regscale_models.Component] = regscale_models.Component.get_all_by_parent(
886
- parent_id=self.plan_id,
887
- parent_module=regscale_models.SecurityPlan.get_module_string(),
888
- )
929
+ if self.is_component:
930
+ components: List[regscale_models.Component] = [
931
+ regscale_models.Component.get_object(
932
+ object_id=self.plan_id,
933
+ )
934
+ ]
935
+ else:
936
+ components: List[regscale_models.Component] = regscale_models.Component.get_all_by_parent(
937
+ parent_id=self.plan_id,
938
+ parent_module=regscale_models.SecurityPlan.get_module_string(),
939
+ )
889
940
  self.components = ThreadSafeList(components)
890
941
  return self.components
891
942
 
@@ -986,7 +1037,7 @@ class ScannerIntegration(ABC):
986
1037
  componentOwnerId=self.get_assessor_id(),
987
1038
  ).get_or_create()
988
1039
  self.components.append(component)
989
- if component.securityPlansId:
1040
+ if component.securityPlansId and not self.is_component:
990
1041
  component_mapping = regscale_models.ComponentMapping(
991
1042
  componentId=component.id,
992
1043
  securityPlanId=self.plan_id,
@@ -1038,7 +1089,7 @@ class ScannerIntegration(ABC):
1038
1089
  parentId=component.id if component else self.plan_id,
1039
1090
  parentModule=(
1040
1091
  regscale_models.Component.get_module_string()
1041
- if component
1092
+ if component or self.is_component
1042
1093
  else regscale_models.SecurityPlan.get_module_string()
1043
1094
  ),
1044
1095
  assetType=asset.asset_type,
@@ -1222,6 +1273,10 @@ class ScannerIntegration(ABC):
1222
1273
  assets_processed = self._process_assets(assets, loading_assets)
1223
1274
 
1224
1275
  self._perform_batch_operations(self.asset_progress)
1276
+ if self.num_assets_to_process and self.asset_progress.tasks[loading_assets].completed != float(
1277
+ self.num_assets_to_process
1278
+ ):
1279
+ self.asset_progress.update(loading_assets, completed=self.num_assets_to_process)
1225
1280
 
1226
1281
  return assets_processed
1227
1282
 
@@ -1537,7 +1592,11 @@ class ScannerIntegration(ABC):
1537
1592
 
1538
1593
  # Update all fields
1539
1594
  issue.parentId = self.plan_id
1540
- issue.parentModule = regscale_models.SecurityPlan.get_module_string()
1595
+ issue.parentModule = (
1596
+ regscale_models.Component.get_module_string()
1597
+ if self.is_component
1598
+ else regscale_models.SecurityPlan.get_module_string()
1599
+ )
1541
1600
  issue.vulnerabilityId = finding.vulnerability_id
1542
1601
  issue.title = issue_title
1543
1602
  issue.dateCreated = finding.date_created
@@ -1549,7 +1608,7 @@ class ScannerIntegration(ABC):
1549
1608
  )
1550
1609
  issue.severityLevel = finding.severity
1551
1610
  issue.issueOwnerId = self.assessor_id
1552
- issue.securityPlanId = self.plan_id
1611
+ issue.securityPlanId = self.plan_id if not self.is_component else None
1553
1612
  issue.identification = "Vulnerability Assessment"
1554
1613
  issue.dateFirstDetected = finding.first_seen
1555
1614
  issue.dueDate = finding.due_date
@@ -1907,6 +1966,15 @@ class ScannerIntegration(ABC):
1907
1966
  self.log_error("1. Asset not found for identifier %s", identifier)
1908
1967
  return asset
1909
1968
 
1969
+ def get_issue_by_integration_finding_id(self, integration_finding_id: str) -> Optional[regscale_models.Issue]:
1970
+ """
1971
+ Gets an issue by its integration finding ID
1972
+
1973
+ :param str integration_finding_id: The integration finding ID
1974
+ :return: The issue
1975
+ """
1976
+ return self.issues_map.get(integration_finding_id)
1977
+
1910
1978
  def process_checklist(self, finding: IntegrationFinding) -> int:
1911
1979
  """
1912
1980
  Processes a single checklist item based on the provided finding.
@@ -2103,7 +2171,11 @@ class ScannerIntegration(ABC):
2103
2171
  """
2104
2172
  scan_history = regscale_models.ScanHistory(
2105
2173
  parentId=self.plan_id,
2106
- parentModule=regscale_models.SecurityPlan.get_module_string(),
2174
+ parentModule=(
2175
+ regscale_models.Component.get_module_string()
2176
+ if self.is_component
2177
+ else regscale_models.SecurityPlan.get_module_string()
2178
+ ),
2107
2179
  scanningTool=self.title,
2108
2180
  scanDate=self.scan_date if self.scan_date else get_current_datetime(),
2109
2181
  createdById=self.assessor_id,
@@ -2224,7 +2296,11 @@ class ScannerIntegration(ABC):
2224
2296
  description=finding.description,
2225
2297
  dateLastUpdated=finding.date_last_updated,
2226
2298
  parentId=self.plan_id,
2227
- parentModule=regscale_models.SecurityPlan.get_module_string(),
2299
+ parentModule=(
2300
+ regscale_models.Component.get_module_string()
2301
+ if self.is_component
2302
+ else regscale_models.SecurityPlan.get_module_string()
2303
+ ),
2228
2304
  dns=asset.fqdn or "unknown",
2229
2305
  status=regscale_models.VulnerabilityStatus.Open,
2230
2306
  ipAddress=finding.ip_address or asset.ipAddress or "",
@@ -2253,7 +2329,7 @@ class ScannerIntegration(ABC):
2253
2329
  vulnerabilityId=vulnerability.id,
2254
2330
  assetId=asset.id,
2255
2331
  scanId=scan_history.id,
2256
- securityPlansId=self.plan_id,
2332
+ securityPlansId=self.plan_id if not self.is_component else None,
2257
2333
  createdById=self.assessor_id,
2258
2334
  tenantsId=self.tenant_id,
2259
2335
  isPublic=True,
@@ -2712,7 +2788,7 @@ class ScannerIntegration(ABC):
2712
2788
  APIHandler().log_api_summary()
2713
2789
  created_count = instance._results.get("assets", {}).get("created_count", 0)
2714
2790
  updated_count = instance._results.get("assets", {}).get("updated_count", 0)
2715
- dedupe_count = assets_processed - (created_count + updated_count)
2791
+ dedupe_count = (instance.num_assets_to_process or assets_processed) - (created_count + updated_count)
2716
2792
  # Ensure dedupe_count is always a positive value
2717
2793
  dedupe_count = dedupe_count if dedupe_count >= 0 else dedupe_count * -1
2718
2794
  logger.info(
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Transformer package for RegScale integrations.
5
+
6
+ This package provides data transformation capabilities for RegScale integrations,
7
+ enabling the conversion of external data formats into IntegrationAsset and
8
+ IntegrationFinding objects.
9
+ """
10
+
11
+ from regscale.integrations.transformer.data_transformer import DataTransformer, DataMapping, TENABLE_SC_MAPPING
12
+
13
+ __all__ = [
14
+ "DataTransformer",
15
+ "DataMapping",
16
+ "TENABLE_SC_MAPPING",
17
+ ]