regscale-cli 6.20.4.1__py3-none-any.whl → 6.20.6.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 (49) hide show
  1. regscale/__init__.py +1 -1
  2. regscale/_version.py +39 -0
  3. regscale/core/app/internal/__init__.py +13 -0
  4. regscale/core/app/internal/model_editor.py +3 -3
  5. regscale/core/app/internal/set_permissions.py +173 -0
  6. regscale/core/app/utils/file_utils.py +11 -1
  7. regscale/core/app/utils/regscale_utils.py +34 -129
  8. regscale/core/utils/date.py +86 -30
  9. regscale/integrations/commercial/defender.py +3 -0
  10. regscale/integrations/commercial/qualys/__init__.py +40 -14
  11. regscale/integrations/commercial/qualys/containers.py +324 -0
  12. regscale/integrations/commercial/qualys/scanner.py +203 -8
  13. regscale/integrations/commercial/synqly/edr.py +10 -0
  14. regscale/integrations/commercial/wizv2/click.py +11 -7
  15. regscale/integrations/commercial/wizv2/constants.py +28 -0
  16. regscale/integrations/commercial/wizv2/issue.py +3 -2
  17. regscale/integrations/commercial/wizv2/parsers.py +23 -0
  18. regscale/integrations/commercial/wizv2/scanner.py +89 -30
  19. regscale/integrations/commercial/wizv2/utils.py +208 -75
  20. regscale/integrations/commercial/wizv2/variables.py +2 -1
  21. regscale/integrations/commercial/wizv2/wiz_auth.py +3 -3
  22. regscale/integrations/public/fedramp/fedramp_cis_crm.py +98 -20
  23. regscale/integrations/public/fedramp/fedramp_docx.py +2 -3
  24. regscale/integrations/scanner_integration.py +7 -2
  25. regscale/models/integration_models/cisa_kev_data.json +187 -5
  26. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  27. regscale/models/regscale_models/__init__.py +2 -0
  28. regscale/models/regscale_models/asset.py +1 -1
  29. regscale/models/regscale_models/catalog.py +16 -0
  30. regscale/models/regscale_models/file.py +2 -1
  31. regscale/models/regscale_models/form_field_value.py +59 -1
  32. regscale/models/regscale_models/issue.py +47 -0
  33. regscale/models/regscale_models/modules.py +88 -1
  34. regscale/models/regscale_models/organization.py +30 -0
  35. regscale/models/regscale_models/regscale_model.py +20 -6
  36. regscale/models/regscale_models/security_control.py +47 -0
  37. regscale/models/regscale_models/security_plan.py +32 -0
  38. regscale/models/regscale_models/vulnerability.py +3 -3
  39. regscale/models/regscale_models/vulnerability_mapping.py +2 -2
  40. regscale/regscale.py +2 -0
  41. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/METADATA +1 -1
  42. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/RECORD +49 -44
  43. tests/fixtures/test_fixture.py +33 -4
  44. tests/regscale/core/test_app.py +53 -32
  45. tests/regscale/test_init.py +94 -0
  46. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/LICENSE +0 -0
  47. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/WHEEL +0 -0
  48. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/entry_points.txt +0 -0
  49. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/top_level.txt +0 -0
@@ -5,10 +5,11 @@ import json
5
5
  import logging
6
6
  import os
7
7
  import re
8
- from typing import Any, Dict, Iterator, List, Optional, Union
8
+ from typing import Any, Dict, Iterator, List, Optional, Union, Tuple
9
9
 
10
10
  from regscale.core.app.utils.app_utils import check_file_path, get_current_datetime
11
11
  from regscale.core.utils import get_base_protocol_from_port
12
+ from regscale.core.utils.date import format_to_regscale_iso
12
13
  from regscale.integrations.commercial.wizv2.constants import (
13
14
  INVENTORY_FILE_PATH,
14
15
  INVENTORY_QUERY,
@@ -19,7 +20,6 @@ from regscale.integrations.commercial.wizv2.parsers import (
19
20
  collect_components_to_create,
20
21
  fetch_wiz_data,
21
22
  get_disk_storage,
22
- get_ip_address_from_props,
23
23
  get_latest_version,
24
24
  get_network_info,
25
25
  get_product_ids,
@@ -101,7 +101,7 @@ class WizVulnerabilityIntegration(ScannerIntegration):
101
101
  :yield: IntegrationFinding objects
102
102
  :rtype: Iterator[IntegrationFinding]
103
103
  """
104
- self.authenticate(kwargs.get("client_id"), kwargs.get("client_secret"))
104
+
105
105
  project_id = kwargs.get("wiz_project_id")
106
106
  if not project_id:
107
107
  raise ValueError("Wiz project ID is required")
@@ -137,7 +137,7 @@ class WizVulnerabilityIntegration(ScannerIntegration):
137
137
  """
138
138
  for node in nodes:
139
139
  if finding := self.parse_finding(node, vulnerability_type):
140
- self.num_findings_to_process += 1
140
+ self.num_findings_to_process = (self.num_findings_to_process or 0) + 1
141
141
  yield finding
142
142
 
143
143
  @classmethod
@@ -151,6 +151,25 @@ class WizVulnerabilityIntegration(ScannerIntegration):
151
151
  """
152
152
  return cls.finding_severity_map.get(severity.capitalize(), regscale_models.IssueSeverity.Low)
153
153
 
154
+ def process_comments(self, comments_dict: Dict) -> Optional[str]:
155
+ """
156
+ Processes comments from Wiz findings to match RegScale's comment format.
157
+
158
+ :param Dict comments_dict: The comments from the Wiz finding
159
+ :return: If available the Processed comments in RegScale format
160
+ :rtype: Optional[str]
161
+ """
162
+ result = None
163
+
164
+ if comments := comments_dict.get("comments", {}).get("edges", []):
165
+ formatted_comments = [
166
+ f"{edge.get('node', {}).get('author', {}).get('name', 'Unknown')}: {edge.get('node', {}).get('body', 'No comment')}"
167
+ for edge in comments
168
+ ]
169
+ # Join with newlines
170
+ result = "\n".join(formatted_comments)
171
+ return result
172
+
154
173
  def parse_finding(
155
174
  self, node: Dict[str, Any], vulnerability_type: WizVulnerabilityType
156
175
  ) -> Optional[IntegrationFinding]:
@@ -167,7 +186,10 @@ class WizVulnerabilityIntegration(ScannerIntegration):
167
186
  if not asset_id:
168
187
  return None
169
188
 
189
+ first_seen = node.get("firstDetectedAt") or node.get("firstSeenAt") or get_current_datetime()
190
+ first_seen = format_to_regscale_iso(first_seen)
170
191
  severity = self.get_issue_severity(node.get("severity", "Low"))
192
+ due_date = regscale_models.Issue.get_due_date(severity, self.app.config, "wiz", first_seen)
171
193
 
172
194
  status = self.map_status_to_issue_status(node.get("status", "Open"))
173
195
  name: str = node.get("name", "")
@@ -177,6 +199,9 @@ class WizVulnerabilityIntegration(ScannerIntegration):
177
199
  else node.get("cve", name)
178
200
  )
179
201
 
202
+ comments_dict = node.get("commentThread", {})
203
+ formatted_comments = self.process_comments(comments_dict)
204
+
180
205
  return IntegrationFinding(
181
206
  control_labels=[],
182
207
  category="Wiz Vulnerability",
@@ -186,8 +211,11 @@ class WizVulnerabilityIntegration(ScannerIntegration):
186
211
  status=status,
187
212
  asset_identifier=asset_id,
188
213
  external_id=f"{node.get('sourceRule', {'id': cve}).get('id')}",
189
- first_seen=node.get("firstDetectedAt") or node.get("firstSeenAt") or get_current_datetime(),
190
- last_seen=node.get("lastDetectedAt") or node.get("analyzedAt") or get_current_datetime(),
214
+ first_seen=first_seen,
215
+ date_created=first_seen,
216
+ last_seen=format_to_regscale_iso(
217
+ node.get("lastDetectedAt") or node.get("analyzedAt") or get_current_datetime()
218
+ ),
191
219
  remediation=node.get("description", ""),
192
220
  cvss_score=node.get("score"),
193
221
  cve=cve,
@@ -195,6 +223,11 @@ class WizVulnerabilityIntegration(ScannerIntegration):
195
223
  cvss_v3_base_score=node.get("score"),
196
224
  source_rule_id=node.get("sourceRule", {}).get("id"),
197
225
  vulnerability_type=vulnerability_type.value,
226
+ due_date=due_date,
227
+ date_last_updated=format_to_regscale_iso(get_current_datetime()),
228
+ identification="Vulnerability Assessment",
229
+ comments=formatted_comments,
230
+ poam_comments=formatted_comments,
198
231
  )
199
232
  except (KeyError, TypeError, ValueError) as e:
200
233
  logger.error("Error parsing Wiz finding: %s", str(e), exc_info=True)
@@ -222,9 +255,9 @@ class WizVulnerabilityIntegration(ScannerIntegration):
222
255
  :yields: Iterator[IntegrationAsset]
223
256
  """
224
257
  self.authenticate(kwargs.get("client_id"), kwargs.get("client_secret"))
225
- wiz_project_id = kwargs.get("wiz_project_id")
258
+ wiz_project_id: str = kwargs.get("wiz_project_id", "")
226
259
  logger.info("Fetching Wiz assets...")
227
- filter_by_override = kwargs.get("filter_by_override") or WizVariables.wizInventoryFilterBy
260
+ filter_by_override: Dict[str, Any] = kwargs.get("filter_by_override") or WizVariables.wizInventoryFilterBy or {}
228
261
  filter_by = self.get_filter_by(filter_by_override, wiz_project_id)
229
262
 
230
263
  variables = self.get_variables()
@@ -257,6 +290,32 @@ class WizVulnerabilityIntegration(ScannerIntegration):
257
290
  filter_by["updatedAt"] = {"after": WizVariables.wizLastInventoryPull} # type: ignore
258
291
  return filter_by
259
292
 
293
+ def get_software_details(
294
+ self, wiz_entity_properties: Dict, node: Dict[str, Any], software_name_dict: Dict[str, str], name: str
295
+ ) -> Tuple[Optional[str], Optional[str], Optional[str]]:
296
+ """
297
+ Gets the software version, vendor, and name from the Wiz entity properties and node.
298
+ Handles container images differently by extracting the version and name from the image tags.
299
+ :param Dict wiz_entity_properties: The properties of the Wiz entity
300
+ :param Dict node: The Wiz node containing the entity
301
+ :param Dict software_name_dict: Dictionary containing software name and vendor
302
+ :param str name: The name of the software or container image
303
+ :return: A tuple containing software_version, software_vendor, and software_name
304
+ :rtype: Tuple[Optional[str], Optional[str], Optional[str]]
305
+ """
306
+ if node.get("type", "") == "CONTAINER_IMAGE":
307
+ software_version = handle_container_image_version(
308
+ image_tags=wiz_entity_properties.get("imageTags", []), name=name
309
+ )
310
+ software_name = name.split(":")[0].split("/")[-1] if name else ""
311
+ software_vendor = name.split(":")[0].split("/")[1] if len(name.split(":")[0].split("/")) > 1 else None
312
+ else:
313
+ software_version = self.get_software_version(wiz_entity_properties, node)
314
+ software_name = self.get_software_name(software_name_dict, wiz_entity_properties, node)
315
+ software_vendor = self.get_software_vendor(software_name_dict, wiz_entity_properties, node)
316
+
317
+ return software_version, software_vendor, software_name
318
+
260
319
  def parse_asset(self, node: Dict[str, Any]) -> Optional[IntegrationAsset]:
261
320
  """
262
321
  Parses Wiz assets
@@ -272,23 +331,20 @@ class WizVulnerabilityIntegration(ScannerIntegration):
272
331
  return None
273
332
 
274
333
  wiz_entity_properties = wiz_entity.get("properties", {})
334
+ is_public = False
335
+ if public_exposures := wiz_entity.get("publicExposures"):
336
+ if exposure_count := public_exposures.get("totalCount"):
337
+ is_public = exposure_count > 0
338
+
275
339
  network_dict = get_network_info(wiz_entity_properties)
276
340
  handle_provider_dict = handle_provider(wiz_entity_properties)
277
341
  software_name_dict = get_software_name_from_cpe(wiz_entity_properties, name)
278
342
  software_list = self.create_name_version_dict(wiz_entity_properties.get("installedPackages", []))
279
-
280
343
  ports_and_protocols = self.get_ports_and_protocols(wiz_entity_properties)
281
344
 
282
- if node.get("type", "") == "CONTAINER_IMAGE":
283
- software_version = handle_container_image_version(
284
- image_tags=wiz_entity_properties.get("imageTags", []), name=name
285
- )
286
- software_name = name.split(":")[0].split("/")[-1] if name else ""
287
- software_vendor = name.split(":")[0].split("/")[1] if len(name.split(":")[0].split("/")) > 1 else None
288
- else:
289
- software_version = self.get_software_version(wiz_entity_properties, node)
290
- software_name = self.get_software_name(software_name_dict, wiz_entity_properties, node)
291
- software_vendor = self.get_software_vendor(software_name_dict, wiz_entity_properties, node)
345
+ software_version, software_vendor, software_name = self.get_software_details(
346
+ wiz_entity_properties, node, software_name_dict, name
347
+ )
292
348
 
293
349
  if WizVariables.useWizHardwareAssetTypes and node.get("graphEntity", {}).get("technologies", []):
294
350
  technologies = node.get("graphEntity", {}).get("technologies", [])
@@ -312,7 +368,8 @@ class WizVulnerabilityIntegration(ScannerIntegration):
312
368
  date_last_updated=wiz_entity.get("lastSeen", ""),
313
369
  management_type=handle_management_type(wiz_entity_properties),
314
370
  status=self.map_wiz_status(wiz_entity_properties.get("status")),
315
- ip_address=get_ip_address_from_props(wiz_entity_properties),
371
+ ip_address=network_dict.get("ip4_address"),
372
+ ipv6_address=network_dict.get("ip6_address"),
316
373
  software_vendor=software_vendor,
317
374
  software_version=software_version,
318
375
  software_name=software_name,
@@ -321,10 +378,10 @@ class WizVulnerabilityIntegration(ScannerIntegration):
321
378
  model=wiz_entity_properties.get("nativeType"),
322
379
  manufacturer=wiz_entity_properties.get("cloudPlatform"),
323
380
  serial_number=get_product_ids(wiz_entity_properties),
324
- is_public_facing=wiz_entity_properties.get("directlyInternetFacing", False),
325
- azure_identifier=handle_provider_dict.get("azureIdentifier"),
381
+ is_public_facing=is_public,
382
+ azure_identifier=handle_provider_dict.get("azureIdentifier", ""),
326
383
  mac_address=wiz_entity_properties.get("macAddress"),
327
- fqdn=wiz_entity_properties.get("dnsName") or network_dict.get("dns"),
384
+ fqdn=network_dict.get("dns") or wiz_entity_properties.get("dnsName"),
328
385
  disk_storage=get_disk_storage(wiz_entity_properties) or 0,
329
386
  cpu=pull_resource_info_from_props(wiz_entity_properties)[1] or 0,
330
387
  ram=pull_resource_info_from_props(wiz_entity_properties)[0] or 0,
@@ -333,9 +390,9 @@ class WizVulnerabilityIntegration(ScannerIntegration):
333
390
  end_of_life_date=wiz_entity_properties.get("versionEndOfLifeDate"),
334
391
  vlan_id=wiz_entity_properties.get("zone"),
335
392
  uri=network_dict.get("url"),
336
- aws_identifier=handle_provider_dict.get("awsIdentifier"),
337
- google_identifier=handle_provider_dict.get("googleIdentifier"),
338
- other_cloud_identifier=handle_provider_dict.get("otherCloudIdentifier"),
393
+ aws_identifier=handle_provider_dict.get("awsIdentifier", ""),
394
+ google_identifier=handle_provider_dict.get("googleIdentifier", ""),
395
+ other_cloud_identifier=handle_provider_dict.get("otherCloudIdentifier", ""),
339
396
  patch_level=get_latest_version(wiz_entity_properties),
340
397
  cpe=wiz_entity_properties.get("cpe"),
341
398
  component_names=collect_components_to_create([node], []),
@@ -374,7 +431,7 @@ class WizVulnerabilityIntegration(ScannerIntegration):
374
431
  :return: Software vendor
375
432
  :rtype: Optional[str]
376
433
  """
377
- if map_category(node.get("type")) == regscale_models.AssetCategory.Software:
434
+ if map_category(node.get("type", "")) == regscale_models.AssetCategory.Software:
378
435
  return software_name_dict.get("software_vendor") or wiz_entity_properties.get("cloudPlatform")
379
436
  return None
380
437
 
@@ -388,8 +445,8 @@ class WizVulnerabilityIntegration(ScannerIntegration):
388
445
  :return: Software version
389
446
  :rtype: Optional[str]
390
447
  """
391
- if map_category(node.get("type")) == regscale_models.AssetCategory.Software:
392
- return handle_software_version(wiz_entity_properties, map_category(node.get("type"))) or "1.0"
448
+ if map_category(node.get("type", "")) == regscale_models.AssetCategory.Software:
449
+ return handle_software_version(wiz_entity_properties, map_category(node.get("type", ""))) or "1.0"
393
450
  return None
394
451
 
395
452
  @staticmethod
@@ -403,7 +460,7 @@ class WizVulnerabilityIntegration(ScannerIntegration):
403
460
  :return: Software name
404
461
  :rtype: Optional[str]
405
462
  """
406
- if map_category(node.get("type")) == regscale_models.AssetCategory.Software:
463
+ if map_category(node.get("type", "")) == regscale_models.AssetCategory.Software:
407
464
  return software_name_dict.get("software_name") or wiz_entity_properties.get("nativeType")
408
465
  return None
409
466
 
@@ -454,6 +511,8 @@ class WizVulnerabilityIntegration(ScannerIntegration):
454
511
  else:
455
512
  logger.info("File %s does not exist. Fetching new data...", file_path)
456
513
 
514
+ self.authenticate(WizVariables.wizClientId, WizVariables.wizClientSecret)
515
+
457
516
  if not self.wiz_token:
458
517
  raise ValueError("Wiz token is not set. Please authenticate first.")
459
518