regscale-cli 6.20.9.1__py3-none-any.whl → 6.21.0.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.

Files changed (56) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/application.py +12 -5
  3. regscale/core/app/internal/set_permissions.py +58 -27
  4. regscale/integrations/commercial/defender.py +9 -0
  5. regscale/integrations/commercial/nessus/scanner.py +2 -0
  6. regscale/integrations/commercial/sonarcloud.py +35 -36
  7. regscale/integrations/commercial/synqly/ticketing.py +51 -0
  8. regscale/integrations/commercial/wizv2/async_client.py +325 -0
  9. regscale/integrations/commercial/wizv2/constants.py +756 -0
  10. regscale/integrations/commercial/wizv2/scanner.py +1301 -89
  11. regscale/integrations/commercial/wizv2/utils.py +280 -36
  12. regscale/integrations/commercial/wizv2/variables.py +2 -10
  13. regscale/integrations/integration_override.py +15 -6
  14. regscale/integrations/scanner_integration.py +221 -37
  15. regscale/integrations/variables.py +1 -0
  16. regscale/models/integration_models/amazon_models/inspector_scan.py +32 -57
  17. regscale/models/integration_models/aqua.py +92 -78
  18. regscale/models/integration_models/cisa_kev_data.json +47 -4
  19. regscale/models/integration_models/defenderimport.py +64 -59
  20. regscale/models/integration_models/ecr_models/ecr.py +100 -147
  21. regscale/models/integration_models/flat_file_importer/__init__.py +52 -38
  22. regscale/models/integration_models/ibm.py +29 -47
  23. regscale/models/integration_models/nexpose.py +156 -68
  24. regscale/models/integration_models/prisma.py +46 -66
  25. regscale/models/integration_models/qualys.py +99 -93
  26. regscale/models/integration_models/snyk.py +229 -158
  27. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  28. regscale/models/integration_models/veracode.py +15 -20
  29. regscale/models/integration_models/xray.py +276 -82
  30. regscale/models/regscale_models/__init__.py +13 -0
  31. regscale/models/regscale_models/classification.py +23 -0
  32. regscale/models/regscale_models/control_implementation.py +14 -12
  33. regscale/models/regscale_models/cryptography.py +56 -0
  34. regscale/models/regscale_models/deviation.py +4 -4
  35. regscale/models/regscale_models/group.py +3 -2
  36. regscale/models/regscale_models/interconnection.py +1 -1
  37. regscale/models/regscale_models/issue.py +140 -41
  38. regscale/models/regscale_models/milestone.py +40 -0
  39. regscale/models/regscale_models/property.py +0 -1
  40. regscale/models/regscale_models/rbac.py +22 -0
  41. regscale/models/regscale_models/regscale_model.py +29 -18
  42. regscale/models/regscale_models/team.py +55 -0
  43. {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.21.0.0.dist-info}/METADATA +1 -1
  44. {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.21.0.0.dist-info}/RECORD +56 -49
  45. tests/fixtures/test_fixture.py +58 -2
  46. tests/regscale/core/test_app.py +5 -3
  47. tests/regscale/integrations/test_integration_mapping.py +522 -40
  48. tests/regscale/integrations/test_issue_due_date.py +1 -1
  49. tests/regscale/integrations/test_property_and_milestone_creation.py +684 -0
  50. tests/regscale/integrations/test_update_finding_dates.py +336 -0
  51. tests/regscale/models/test_asset.py +406 -50
  52. tests/regscale/models/test_report.py +105 -29
  53. {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.21.0.0.dist-info}/LICENSE +0 -0
  54. {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.21.0.0.dist-info}/WHEEL +0 -0
  55. {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.21.0.0.dist-info}/entry_points.txt +0 -0
  56. {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.21.0.0.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,7 @@ import logging
9
9
  import re
10
10
  from calendar import firstweekday
11
11
  from datetime import datetime
12
- from typing import Any, Iterator, Optional, TypeVar, TextIO, Union
12
+ from typing import Any, Iterator, List, Optional, TextIO, TypeVar, Union
13
13
 
14
14
  from openpyxl.reader.excel import load_workbook
15
15
  from pandas import Timestamp
@@ -17,20 +17,21 @@ from pandas import Timestamp
17
17
  from regscale.core.app import create_logger
18
18
  from regscale.core.app.application import Application
19
19
  from regscale.core.app.utils.app_utils import get_current_datetime
20
- from regscale.core.app.utils.parser_utils import safe_int, safe_float
20
+ from regscale.core.app.utils.parser_utils import safe_float, safe_int
21
21
  from regscale.core.utils.date import date_str, datetime_str
22
- from regscale.integrations.scanner_integration import IntegrationAsset
22
+ from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
23
23
  from regscale.models import (
24
24
  Asset,
25
25
  AssetCategory,
26
+ AssetStatus,
26
27
  AssetType,
27
28
  ImportValidater,
28
29
  IssueSeverity,
30
+ IssueStatus,
29
31
  SecurityPlan,
30
32
  Vulnerability,
31
- VulnerabilityStatus,
32
- AssetStatus,
33
33
  VulnerabilitySeverity,
34
+ VulnerabilityStatus,
34
35
  )
35
36
  from regscale.models.integration_models.flat_file_importer import FlatFileImporter
36
37
 
@@ -50,6 +51,18 @@ FQDN = "FQDN"
50
51
  IMAGE_ID_FIELD = "IMAGE ID"
51
52
  CVE_ID_FIELD = "CVE ID"
52
53
 
54
+ SEVERITY_MAP = {
55
+ "critical": IssueSeverity.Critical,
56
+ "high": IssueSeverity.High,
57
+ "medium": IssueSeverity.Moderate,
58
+ "moderate": IssueSeverity.Moderate,
59
+ "low": IssueSeverity.Low,
60
+ "informational": IssueSeverity.NotAssigned,
61
+ "none": IssueSeverity.NotAssigned,
62
+ "info": IssueSeverity.NotAssigned,
63
+ "unknown": IssueSeverity.NotAssigned,
64
+ }
65
+
53
66
 
54
67
  class Qualys(FlatFileImporter):
55
68
  """Qualys Scan information"""
@@ -94,36 +107,30 @@ class Qualys(FlatFileImporter):
94
107
  # header is line# 11
95
108
  # start self.file_data from line #12
96
109
 
97
- def create_asset(self, dat: Optional[dict] = None) -> Optional[Asset]:
110
+ def create_asset(self, dat: Optional[dict] = None) -> Optional[IntegrationAsset]:
98
111
  """
99
- Create an asset from a row in the Qualys file
112
+ Create an integration asset from a row in the Qualys file
100
113
 
101
114
  :param Optional[dict] dat: Data row from CSV file
102
- :return: RegScale Issue object or None
103
- :rtype: Optional[Asset]
115
+ :return: RegScale IntegrationAsset object or None
116
+ :rtype: Optional[IntegrationAsset]
104
117
  """
105
- return Asset(
106
- **{
107
- "id": 0,
108
- "name": self.mapping.get_value(dat, DNS),
109
- "ipAddress": self.mapping.get_value(dat, IP),
110
- "isPublic": True,
111
- "status": "Active (On Network)",
112
- "assetCategory": "Hardware",
113
- "qualysId": str(self.mapping.get_value(dat, QG_HOST_ID)), # UUID from Nessus HostProperties tag
114
- "bLatestScan": True,
115
- "bAuthenticatedScan": True,
116
- "scanningTool": "Qualys",
117
- "assetOwnerId": self.attributes.app.config["userId"],
118
- "netBIOS": self.mapping.get_value(dat, NETBIOS),
119
- "assetType": "Other",
120
- "fqdn": self.mapping.get_value(dat, FQDN),
121
- "operatingSystem": Asset.find_os(self.mapping.get_value(dat, OS)),
122
- "operatingSystemVersion": self.mapping.get_value(dat, OS),
123
- "systemAdministratorId": self.attributes.app.config["userId"],
124
- "parentId": self.attributes.parent_id,
125
- "parentModule": self.attributes.parent_module,
126
- }
118
+ qid = str(self.mapping.get_value(dat, QG_HOST_ID))
119
+ return IntegrationAsset(
120
+ name=self.mapping.get_value(dat, DNS),
121
+ ip_address=self.mapping.get_value(dat, IP),
122
+ status=AssetStatus.Active.value,
123
+ cpu=0,
124
+ ram=0,
125
+ asset_category="Hardware",
126
+ identifier=qid, # UUID from Qualys Host ID
127
+ other_tracking_number=qid,
128
+ scanning_tool="Qualys",
129
+ asset_owner_id=self.attributes.app.config["userId"],
130
+ asset_type="Other",
131
+ fqdn=self.mapping.get_value(dat, FQDN),
132
+ operating_system=Asset.find_os(self.mapping.get_value(dat, OS)),
133
+ os_version=self.mapping.get_value(dat, OS),
127
134
  )
128
135
 
129
136
  def _convert_datetime_to_str(self, input_date: Union[Timestamp, datetime]) -> str:
@@ -140,59 +147,43 @@ class Qualys(FlatFileImporter):
140
147
  return input_date
141
148
  return input_date.strftime(self.dt_format)
142
149
 
143
- def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[Vulnerability]:
150
+ def create_vuln(self, dat: Optional[dict] = None, **kwargs) -> Optional[IntegrationFinding]:
144
151
  """
145
- Create a vuln from a row in the Qualys file
152
+ Create a finding from a row in the Qualys file
146
153
 
147
154
  :param Optional[dict] dat: Data row from CSV file, defaults to None
148
- :return: RegScale Vulnerability object or None
149
- :rtype: Optional[Vulnerability]
155
+ :rtype: IntegrationFinding
150
156
  """
151
157
  from regscale.integrations.commercial.qualys import map_qualys_severity_to_regscale
152
158
 
153
- dns: str = self.mapping.get_value(dat, DNS)
154
- other_id: str = self.mapping.get_value(dat, QG_HOST_ID)
155
- distro: str = self.mapping.get_value(dat, OS)
159
+ finding: Optional[IntegrationFinding] = None
160
+ qid = str(self.mapping.get_value(dat, QG_HOST_ID))
156
161
  cve: str = self.mapping.get_value(dat, CVE_ID)
157
162
  description: str = self.mapping.get_value(dat, "Threat")
158
163
  title = self.mapping.get_value(dat, self.vuln_title)
159
- regscale_vuln = None
160
164
  severity = self.mapping.get_value(dat, SEVERITY)
161
165
  regscale_severity = map_qualys_severity_to_regscale(int(severity))[1]
162
- config = self.attributes.app.config
163
- asset_match = [asset for asset in self.data["assets"] if asset.name == dns]
164
- asset = asset_match[0] if asset_match else None
165
- last_seen = self._convert_datetime_to_str(self.mapping.get_value(dat, "Last Detected"))
166
- first_seen = self._convert_datetime_to_str(self.mapping.get_value(dat, "First Detected"))
167
-
168
- if dat and asset_match:
169
- regscale_vuln = Vulnerability(
170
- id=0,
171
- scanId=0, # set later
172
- parentId=asset.id,
173
- parentModule="assets",
174
- ipAddress=self.mapping.get_value(dat, IP),
175
- lastSeen=last_seen,
176
- firstSeen=first_seen,
177
- daysOpen=None,
178
- dns=self.mapping.get_value(dat, DNS, other_id),
179
- mitigated=None,
180
- operatingSystem=(Asset.find_os(distro) if Asset.find_os(distro) else None),
181
- severity=regscale_severity,
182
- plugInName=self.mapping.get_value(dat, self.vuln_title),
183
- plugInId=self.mapping.get_value(dat, "QID"),
184
- cve=cve,
185
- vprScore=None,
186
- cvsSv3BaseScore=self.extract_float(self.mapping.get_value(dat, "CVSS3.1 Base", 0.0)),
187
- tenantsId=0,
166
+ if dat:
167
+ finding = IntegrationFinding(
168
+ control_labels=[], # Add an empty list for control_labels
188
169
  title=title,
189
170
  description=description,
190
- plugInText=title,
191
- createdById=config["userId"],
192
- lastUpdatedById=config["userId"],
193
- dateCreated=get_current_datetime(),
171
+ ip_address="0.0.0.0",
172
+ cve=cve,
173
+ severity=regscale_severity,
174
+ asset_identifier=qid,
175
+ plugin_name=description,
176
+ plugin_id=str(self.mapping.get_value(dat, "QID")),
177
+ cvss_v3_score=self.extract_float(self.mapping.get_value(dat, "CVSS3.1 Base", 0.0)),
178
+ plugin_text=title,
179
+ category="Hardware",
180
+ status=IssueStatus.Open,
181
+ first_seen=self._convert_datetime_to_str(self.mapping.get_value(dat, "First Detected")),
182
+ last_seen=self._convert_datetime_to_str(self.mapping.get_value(dat, "Last Detected")),
183
+ vulnerability_type="Vulnerability Scan",
184
+ baseline=f"{self.name} Host",
194
185
  )
195
- return regscale_vuln
186
+ return finding
196
187
 
197
188
  @staticmethod
198
189
  def extract_float(s: Union[str, float, int]) -> Optional[float]:
@@ -230,6 +221,7 @@ class QualysContainerScansImporter(FlatFileImporter): # (ScannerIntegration):
230
221
  last_seen_field = "UPDATED"
231
222
  container_id = "IMAGE UUID"
232
223
  records = []
224
+ title = "Qualys Container Scanner Export Integration" # Add explicit title for source report identification
233
225
 
234
226
  def __init__(self, **kwargs: dict):
235
227
  self.asset_identifier_field = "otherTrackingNumber"
@@ -278,26 +270,25 @@ class QualysContainerScansImporter(FlatFileImporter): # (ScannerIntegration):
278
270
  **kwargs,
279
271
  )
280
272
 
281
- def create_asset(self, row: Optional[dict] = None, **kwargs) -> Optional[Asset]:
273
+ def create_asset(self, row: Optional[dict] = None, **kwargs) -> Optional[IntegrationAsset]:
282
274
  """
283
275
  Fetch assets from the Qualys CSV file
284
276
 
285
- :return: Iterator of IntegrationAsset objects
286
- :rtype: Iterator[IntegrationAsset]
277
+ :return: IntegrationAsset object
278
+ :rtype: Optional[IntegrationAsset]
287
279
  """
288
280
  bad_ids = ["", "0", "None", "Unknown"]
289
281
  if self.mapping.get_value(row, self.container_id) in bad_ids:
290
282
  return None
291
283
  max_length = 450 # max length of asset name
292
- asset = Asset(
284
+ container_id = self.mapping.get_value(row, self.container_id)
285
+ asset = IntegrationAsset(
293
286
  name=self.mapping.get_value(row, self.asset_notes_field)[:max_length] or "Unknown",
294
287
  notes=self.mapping.get_value(row, self.asset_notes_field),
295
- otherTrackingNumber=self.mapping.get_value(row, self.container_id),
296
- # identifier=self.mapping.get_value(row, IMAGE_ID_FIELD),
297
- assetType=AssetType.VM.value,
298
- assetCategory=AssetCategory.Software.value,
299
- parentId=safe_int(self.plan_id),
300
- parentModule=SecurityPlan.get_module_slug(),
288
+ identifier=container_id,
289
+ other_tracking_number=container_id,
290
+ asset_type=AssetType.VM.value,
291
+ asset_category=AssetCategory.Software.value,
301
292
  status=AssetStatus.Active,
302
293
  )
303
294
  return asset
@@ -316,34 +307,44 @@ class QualysContainerScansImporter(FlatFileImporter): # (ScannerIntegration):
316
307
  )
317
308
  return date_str(date_obj_value, self.dt_format)
318
309
 
319
- def create_vuln(self, row: Optional[dict] = None, **kwargs) -> Optional[Vulnerability]:
310
+ def create_vuln(self, row: Optional[dict] = None, **kwargs) -> Iterator[IntegrationFinding]:
320
311
  """
321
312
  Fetch vulnerabilities from the Qualys CSV file
322
313
 
323
314
  :return: Iterator of IntegrationFinding objects
324
- :rtype: Vulnerability
315
+ :rtype: Iterator[IntegrationFinding]
325
316
  """
326
-
317
+ findings: List[IntegrationFinding] = []
318
+ title = self.mapping.get_value(row, self.title_field)
319
+ description = self.mapping.get_value(row, self.threat_field)
320
+ qid = str(self.mapping.get_value(row, self.container_id))
327
321
  if self.mapping.get_value(row, CVE_ID_FIELD) and isinstance(self.mapping.get_value(row, CVE_ID_FIELD), str):
328
322
  for cve_id in self.mapping.get_value(row, CVE_ID_FIELD, "").split(","):
329
- finding = Vulnerability(
323
+ finding = IntegrationFinding(
324
+ control_labels=[], # Add an empty list for control_labels
330
325
  title=self.mapping.get_value(row, self.title_field),
331
326
  description=self.mapping.get_value(row, self.threat_field),
332
327
  cve=cve_id,
333
328
  severity=severity_to_regscale(self.mapping.get_value(row, self.severity_field)),
334
- status=VulnerabilityStatus.Open,
335
- cvsSv3BaseScore=safe_float(self.mapping.get_value(row, self.cve_v3_base, 0.0)),
336
- vprScore=safe_float(self.mapping.get_value(row, self.cve_base, 0.0)),
337
- firstSeen=self.handle_integration_date(
329
+ asset_identifier=qid,
330
+ plugin_name=description,
331
+ plugin_id=str(self.mapping.get_value(dat, "QID")),
332
+ cvss_v3_score=safe_float(self.mapping.get_value(row, self.cve_v3_base, 0.0)),
333
+ vpr_score=safe_float(self.mapping.get_value(row, self.cve_base, 0.0)),
334
+ plugin_text=title,
335
+ category="Hardware",
336
+ status=IssueStatus.Open,
337
+ first_seen=self.handle_integration_date(
338
338
  self.mapping.get_value(row, self.first_seen_field, get_current_datetime())
339
339
  ),
340
- lastSeen=self.handle_integration_date(
340
+ last_seen=self.handle_integration_date(
341
341
  self.mapping.get_value(row, self.last_seen_field, get_current_datetime())
342
342
  ),
343
- plugInName=cve_id,
344
- dns=self.mapping.get_value(row, self.container_id), # really asset_identifier
343
+ vulnerability_type="Vulnerability Scan",
344
+ baseline=f"{self.name} Host",
345
345
  )
346
- return finding
346
+ findings.append(finding)
347
+ yield from findings
347
348
 
348
349
 
349
350
  class QualysWasScansImporter(FlatFileImporter):
@@ -365,6 +366,7 @@ class QualysWasScansImporter(FlatFileImporter):
365
366
  container_id = "ID"
366
367
  row_key = "VULNERABILITY"
367
368
  records = []
369
+ title = "Qualys WAS Scanner Export Integration" # Add explicit title for source report identification
368
370
 
369
371
  def __init__(self, **kwargs: dict):
370
372
  self.asset_identifier_field = "otherTrackingNumber"
@@ -561,6 +563,8 @@ class QualysWasScansImporter(FlatFileImporter):
561
563
  :rtype: Vulnerability
562
564
  """
563
565
  # additional_fields = ["Title", "Severity Level", "CVSS Base", "CWE", "Solution"]
566
+ asset_identifier = self.mapping.get_value(row, self.container_id)
567
+
564
568
  finding = Vulnerability(
565
569
  title=row.get("Title", self.mapping.get_value(row, "Url")),
566
570
  description=row.get("Solution"),
@@ -576,7 +580,8 @@ class QualysWasScansImporter(FlatFileImporter):
576
580
  self.mapping.get_value(row, self.last_seen_field, get_current_datetime())
577
581
  ),
578
582
  plugInName=row.get("CWE"),
579
- dns=self.mapping.get_value(row, self.container_id), # really asset_identifier
583
+ dns=asset_identifier, # Use consistent asset identifier
584
+ assetIdentifier=asset_identifier, # Add explicit asset identifier
580
585
  )
581
586
  return finding
582
587
 
@@ -588,6 +593,7 @@ class QualysPolicyScansImporter(FlatFileImporter):
588
593
 
589
594
  plan_id = 0
590
595
  records = []
596
+ title = "Qualys Policy Scanner Export Integration" # Add explicit title for source report identification
591
597
 
592
598
  def __init__(self, **kwargs: dict):
593
599
  self.asset_identifier_field = "qualysId"