regscale-cli 6.21.0.0__py3-none-any.whl → 6.21.1.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 (37) hide show
  1. regscale/_version.py +1 -1
  2. regscale/integrations/commercial/__init__.py +1 -2
  3. regscale/integrations/commercial/amazon/common.py +79 -2
  4. regscale/integrations/commercial/aws/cli.py +183 -9
  5. regscale/integrations/commercial/aws/scanner.py +544 -9
  6. regscale/integrations/commercial/cpe.py +18 -1
  7. regscale/integrations/commercial/tenablev2/jsonl_scanner.py +2 -1
  8. regscale/integrations/commercial/wizv2/async_client.py +10 -3
  9. regscale/integrations/commercial/wizv2/click.py +102 -26
  10. regscale/integrations/commercial/wizv2/constants.py +249 -1
  11. regscale/integrations/commercial/wizv2/issue.py +2 -2
  12. regscale/integrations/commercial/wizv2/parsers.py +3 -2
  13. regscale/integrations/commercial/wizv2/policy_compliance.py +1858 -0
  14. regscale/integrations/commercial/wizv2/scanner.py +15 -21
  15. regscale/integrations/commercial/wizv2/utils.py +258 -85
  16. regscale/integrations/commercial/wizv2/variables.py +4 -3
  17. regscale/integrations/compliance_integration.py +1455 -0
  18. regscale/integrations/public/fedramp/fedramp_five.py +1 -1
  19. regscale/integrations/public/fedramp/markdown_parser.py +7 -1
  20. regscale/integrations/scanner_integration.py +30 -2
  21. regscale/models/app_models/__init__.py +1 -0
  22. regscale/models/integration_models/cisa_kev_data.json +73 -4
  23. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  24. regscale/{integrations/commercial/wizv2/models.py → models/integration_models/wizv2.py} +4 -12
  25. regscale/models/regscale_models/file.py +4 -0
  26. regscale/models/regscale_models/issue.py +123 -0
  27. regscale/models/regscale_models/regscale_model.py +4 -2
  28. regscale/models/regscale_models/security_plan.py +1 -1
  29. regscale/utils/graphql_client.py +3 -1
  30. {regscale_cli-6.21.0.0.dist-info → regscale_cli-6.21.1.0.dist-info}/METADATA +9 -9
  31. {regscale_cli-6.21.0.0.dist-info → regscale_cli-6.21.1.0.dist-info}/RECORD +37 -34
  32. tests/regscale/core/test_version_regscale.py +5 -3
  33. tests/regscale/integrations/test_wiz_policy_compliance_affected_controls.py +154 -0
  34. {regscale_cli-6.21.0.0.dist-info → regscale_cli-6.21.1.0.dist-info}/LICENSE +0 -0
  35. {regscale_cli-6.21.0.0.dist-info → regscale_cli-6.21.1.0.dist-info}/WHEEL +0 -0
  36. {regscale_cli-6.21.0.0.dist-info → regscale_cli-6.21.1.0.dist-info}/entry_points.txt +0 -0
  37. {regscale_cli-6.21.0.0.dist-info → regscale_cli-6.21.1.0.dist-info}/top_level.txt +0 -0
@@ -109,6 +109,23 @@ def extract_product_name_and_version(cpe_string: str) -> Dict:
109
109
  :rtype: Dict
110
110
  """
111
111
  # convert to version 2.3 if 2.2
112
+ # TODO: Note this is an incomplete conversion as the additional properties
113
+ # in the URI format (which is still supported in 2.3) are separated by
114
+ # tildes (~) after the final colon. We should extend this to support them
115
+ # at some point to be safe. Example from NISTIR7697 the 2.3 dictionary
116
+ # specification:
117
+ #
118
+ # WFN:
119
+ # wfn:[part="o",vendor="microsoft",product="windows_vista",version="6\.0",
120
+ # update="sp1",edition=NA,language=NA,sw_edition="home_premium",
121
+ # target_sw=NA,target_hw="x64",other=NA]
122
+ #
123
+ # WFN bound to a URI:
124
+ # cpe:/o:microsoft:windows_vista:6.0:sp1:~-~home_premium~-~x64~-
125
+ #
126
+ # WFN bound to a formatted string:
127
+ # cpe:2.3:o:microsoft:windows_vista:6.0:sp1:-:-:home_premium:-:x64:-
128
+ #
112
129
  if cpe_string.startswith("cpe:/"):
113
130
  cpe_string = cpe_string.replace("cpe:/", "cpe:2.3:")
114
131
 
@@ -117,7 +134,7 @@ def extract_product_name_and_version(cpe_string: str) -> Dict:
117
134
 
118
135
  # Extract the product name and version
119
136
  # parts[3] is the product name, parts[4] is the version
120
- part = parts[2]
137
+ part = parts[2] if len(parts) > 2 else None
121
138
  logger.debug(f"part: {part}")
122
139
  vendor_name = parts[3] if len(parts) > 3 else None
123
140
  product_name = parts[4] if len(parts) > 4 else None
@@ -446,7 +446,7 @@ class TenableSCJsonlScanner(JSONLScannerIntegration):
446
446
  # If no findings were created, return a basic finding
447
447
  # Get the IP from the vulnerability directly rather than using passed asset_identifier
448
448
  finding_asset_id = vuln.ip or asset_identifier
449
-
449
+ logger.debug(item)
450
450
  return IntegrationFinding(
451
451
  title=item.get("pluginName", "Unknown Finding"),
452
452
  description=item.get("description", "No description available"),
@@ -456,6 +456,7 @@ class TenableSCJsonlScanner(JSONLScannerIntegration):
456
456
  category="Vulnerability",
457
457
  scan_date=self.scan_date,
458
458
  plugin_name=item.get("pluginName", UNKNOWN_PLUGIN),
459
+ control_labels=item.get("controlLabels", []),
459
460
  )
460
461
 
461
462
  except Exception as e:
@@ -10,6 +10,7 @@ import anyio
10
10
  import httpx
11
11
 
12
12
  from regscale.core.app.utils.app_utils import error_and_exit
13
+ from regscale.integrations.variables import ScannerVariables
13
14
 
14
15
  logger = logging.getLogger("regscale")
15
16
 
@@ -72,7 +73,9 @@ class AsyncWizGraphQLClient:
72
73
  logger.debug(f"Variables: {variables}")
73
74
 
74
75
  try:
75
- async with httpx.AsyncClient(timeout=self.timeout) as client:
76
+ # Get SSL verify setting from scanner variables config
77
+ ssl_verify = getattr(ScannerVariables, "sslVerify", True)
78
+ async with httpx.AsyncClient(timeout=self.timeout, verify=ssl_verify) as client:
76
79
  if progress_callback:
77
80
  progress_callback(task_name, "requesting")
78
81
 
@@ -81,7 +84,7 @@ class AsyncWizGraphQLClient:
81
84
  if progress_callback:
82
85
  progress_callback(task_name, "processing")
83
86
 
84
- if response.raise_for_status():
87
+ if not response.is_success:
85
88
  error_and_exit(
86
89
  f"Received non-200 response from GraphQL API: {response.status_code}: {response.text}"
87
90
  )
@@ -304,6 +307,7 @@ def run_async_queries(
304
307
  query_configs: List[Dict[str, Any]],
305
308
  progress_tracker: Optional[Any] = None,
306
309
  max_concurrent: int = 5,
310
+ timeout: int = 60,
307
311
  ) -> List[Tuple[str, List[Dict[str, Any]], Optional[Exception]]]:
308
312
  """
309
313
  Convenience function to run async queries from synchronous code.
@@ -313,12 +317,15 @@ def run_async_queries(
313
317
  :param List[Dict[str, Any]] query_configs: Query configurations
314
318
  :param Optional[Any] progress_tracker: Progress tracker
315
319
  :param int max_concurrent: Maximum concurrent requests
320
+ :param int timeout: Request timeout in seconds
316
321
  :return: Query results
317
322
  :rtype: List[Tuple[str, List[Dict[str, Any]], Optional[Exception]]]
318
323
  """
319
324
 
320
325
  async def _run():
321
- client = AsyncWizGraphQLClient(endpoint=endpoint, headers=headers, max_concurrent=max_concurrent)
326
+ client = AsyncWizGraphQLClient(
327
+ endpoint=endpoint, headers=headers, max_concurrent=max_concurrent, timeout=timeout
328
+ )
322
329
  return await client.execute_concurrent_queries(query_configs, progress_tracker)
323
330
 
324
331
  # Use anyio.run for better compatibility
@@ -9,7 +9,7 @@ from typing import Optional
9
9
  import click
10
10
 
11
11
  from regscale.integrations.commercial.wizv2.variables import WizVariables
12
- from regscale.models import regscale_id, regscale_module
12
+ from regscale.models import regscale_id
13
13
  from regscale.models.app_models.click import regscale_ssp_id
14
14
 
15
15
  logger = logging.getLogger("regscale")
@@ -333,12 +333,10 @@ def add_report_evidence(
333
333
  "--wiz_project_id",
334
334
  "-p",
335
335
  prompt="Enter the Wiz project ID",
336
- help="Enter the Wiz Project ID. Options include: projects, \
337
- policies, supplychain, securityplans, components.",
336
+ help="Enter the Wiz Project ID for policy compliance sync.",
338
337
  required=True,
339
338
  )
340
- @regscale_id(help="RegScale will create and update issues as children of this record.")
341
- @regscale_module()
339
+ @regscale_id(help="RegScale will create and update control assessments as children of this record.")
342
340
  @click.option( # type: ignore
343
341
  "--client_id",
344
342
  "-i",
@@ -356,44 +354,122 @@ def add_report_evidence(
356
354
  required=False,
357
355
  )
358
356
  @click.option( # type: ignore
359
- "--catalog_id",
360
- "-c",
361
- help="RegScale Catalog ID for the selected framework.",
362
- hide_input=False,
357
+ "--framework_id",
358
+ "-f",
359
+ help="Wiz framework ID or shorthand (e.g., 'nist', 'aws', 'wf-id-4'). Use --list-frameworks to see options. Default: wf-id-4 (NIST SP 800-53 Rev 5)",
360
+ default="wf-id-4",
363
361
  required=False,
364
- default=None,
365
362
  )
366
363
  @click.option( # type: ignore
367
- "--framework",
368
- "-f",
369
- type=click.Choice(["CSF", "NIST800-53R5", "NIST800-53R4"], case_sensitive=False), # type: ignore
370
- help="Choose either one of the Frameworks",
371
- default="NIST800-53R5",
372
- required=True,
364
+ "--list-frameworks",
365
+ "-lf",
366
+ is_flag=True,
367
+ help="List all available framework options and shortcuts",
368
+ default=False,
369
+ )
370
+ @click.option( # type: ignore
371
+ "--create-issues/--no-create-issues",
372
+ "-ci/-ni",
373
+ default=True,
374
+ help="Create issues for failed policy assessments (default: enabled)",
375
+ )
376
+ @click.option( # type: ignore
377
+ "--update-control-status/--no-update-control-status",
378
+ "-ucs/-nucs",
379
+ default=True,
380
+ help="Update control implementation status based on assessment results (default: enabled)",
381
+ )
382
+ @click.option( # type: ignore
383
+ "--create-poams/--no-create-poams",
384
+ "-cp/-ncp",
385
+ default=False,
386
+ help="Mark created issues as POAMs (default: disabled)",
387
+ )
388
+ @click.option( # type: ignore
389
+ "--refresh/--no-refresh",
390
+ "-r/-nr",
391
+ default=False,
392
+ help="Force refresh and ignore cached data (default: use cache if available)",
393
+ )
394
+ @click.option( # type: ignore
395
+ "--cache-duration",
396
+ "-cd",
397
+ type=click.INT,
398
+ default=1440,
399
+ help="Cache duration in minutes - reuse cached data if newer than this (default: 1440 minutes / 1 day)",
373
400
  )
374
401
  def sync_compliance(
375
402
  wiz_project_id,
376
403
  regscale_id,
377
- regscale_module,
378
404
  client_id,
379
405
  client_secret,
380
- catalog_id,
381
- framework,
406
+ framework_id,
407
+ list_frameworks,
408
+ create_issues,
409
+ update_control_status,
410
+ create_poams,
411
+ refresh,
412
+ cache_duration,
382
413
  ):
383
- """Sync compliance posture from Wiz to RegScale"""
384
- from regscale.integrations.commercial.wizv2.utils import _sync_compliance
414
+ """
415
+ Sync policy compliance assessments from Wiz to RegScale.
416
+
417
+ This command fetches policy assessment data from Wiz and creates:
418
+ - Control assessments based on policy compliance results
419
+ - Issues for failed policy assessments (if --create-issues enabled)
420
+ - Updates to control implementation status (if --update-control-status enabled)
421
+ - JSON output file with policy compliance data in artifacts/wiz/ directory
422
+ - Cached framework mapping for improved performance
423
+
424
+ CACHING:
425
+ By default, the command will reuse cached policy data if it's newer than the cache
426
+ duration (default: 60 minutes). Use --refresh to force fresh data from Wiz.
427
+ Use --cache-duration to adjust how long cached data is considered valid.
428
+ """
429
+ from regscale.integrations.commercial.wizv2.policy_compliance import (
430
+ WizPolicyComplianceIntegration,
431
+ list_available_frameworks,
432
+ resolve_framework_id,
433
+ )
434
+
435
+ # Handle --list-frameworks flag
436
+ if list_frameworks:
437
+ click.echo(list_available_frameworks())
438
+ return
385
439
 
440
+ # Use environment variables if not provided
386
441
  if not client_secret:
387
442
  client_secret = WizVariables.wizClientSecret
388
443
  if not client_id:
389
444
  client_id = WizVariables.wizClientId
390
445
 
391
- _sync_compliance(
446
+ # Resolve framework ID using the enhanced framework resolution
447
+ try:
448
+ resolved_framework_id = resolve_framework_id(framework_id.lower())
449
+ if resolved_framework_id != framework_id:
450
+ from regscale.integrations.commercial.wizv2.policy_compliance import FRAMEWORK_MAPPINGS
451
+
452
+ framework_name = FRAMEWORK_MAPPINGS.get(resolved_framework_id, resolved_framework_id)
453
+ click.echo(f"🔍 Resolved '{framework_id}' to '{framework_name}' ({resolved_framework_id})")
454
+ except ValueError as e:
455
+ click.echo(f"❌ {str(e)}")
456
+ click.echo("\nUse --list-frameworks to see all available options.")
457
+ return
458
+
459
+ # Create and run the policy compliance integration
460
+ policy_integration = WizPolicyComplianceIntegration(
461
+ plan_id=regscale_id,
392
462
  wiz_project_id=wiz_project_id,
393
- regscale_id=regscale_id,
394
- regscale_module=regscale_module,
395
463
  client_id=client_id,
396
464
  client_secret=client_secret,
397
- catalog_id=catalog_id,
398
- framework=framework,
465
+ framework_id=resolved_framework_id,
466
+ create_poams=create_poams,
467
+ cache_duration_minutes=cache_duration,
468
+ force_refresh=refresh,
469
+ )
470
+
471
+ # Run the policy compliance sync
472
+ policy_integration.sync_policy_compliance(
473
+ create_issues=create_issues,
474
+ update_control_status=update_control_status,
399
475
  )
@@ -3,7 +3,225 @@
3
3
  from enum import Enum
4
4
  from typing import List, Optional
5
5
 
6
- from regscale.models import IssueSeverity
6
+ from regscale.models import IssueSeverity, regscale_models
7
+
8
+ WIZ_POLICY_QUERY = """
9
+ query PolicyAssessmentsTable($filterBy: PolicyAssessmentFilters, $first: Int, $after: String) {
10
+ policyAssessments(filterBy: $filterBy, first: $first, after: $after) {
11
+ nodes {
12
+ id
13
+ policy {
14
+ ... on CloudConfigurationRule {
15
+ id
16
+ shortId
17
+ name
18
+ ruleDescription: description
19
+ severity
20
+ graphId
21
+ remediationInstructions
22
+ risks
23
+ threats
24
+ securitySubCategories {
25
+ ...SecuritySubCategoriesDetails
26
+ }
27
+ }
28
+ ... on Control {
29
+ id
30
+ name
31
+ description
32
+ lastRunAt
33
+ lastRunError
34
+ lastSuccessfulRunAt
35
+ severity
36
+ risks
37
+ threats
38
+ securitySubCategories {
39
+ ...SecuritySubCategoriesDetails
40
+ }
41
+ }
42
+ ... on HostConfigurationRule {
43
+ id
44
+ name
45
+ shortName
46
+ remediationInstructions
47
+ risks
48
+ threats
49
+ securitySubCategories {
50
+ ...SecuritySubCategoriesDetails
51
+ }
52
+ }
53
+ }
54
+ result
55
+ resource {
56
+ id
57
+ name
58
+ type
59
+ region
60
+ tags { key value }
61
+ subscription { id name externalId cloudProvider }
62
+ }
63
+ output {
64
+ ... on Issue { id issueStatus: status }
65
+ ... on ConfigurationFinding { id name cloudConfigurationFindingStatus: status remediation }
66
+ ... on HostConfigurationRuleAssessment { id hostConfigurationRule: rule { id name shortName description remediationInstructions } }
67
+ }
68
+ }
69
+ pageInfo { hasNextPage endCursor }
70
+ totalCount
71
+ }
72
+ }
73
+
74
+ fragment SecuritySubCategoriesDetails on SecuritySubCategory {
75
+ description
76
+ id
77
+ resolutionRecommendation
78
+ title
79
+ externalId
80
+ category { id name framework { id name enabled } }
81
+ }
82
+ """
83
+
84
+ WIZ_FRAMEWORK_QUERY = """
85
+ query SecurityFrameworksTable($first: Int, $after: String, $filterBy: SecurityFrameworkFilters) {
86
+ securityFrameworks(first: $first, after: $after, filterBy: $filterBy) {
87
+ nodes { policyTypes ...SecurityFrameworkFragment }
88
+ pageInfo { hasNextPage endCursor }
89
+ totalCount
90
+ }
91
+ }
92
+
93
+ fragment SecurityFrameworkFragment on SecurityFramework {
94
+ id
95
+ name
96
+ description
97
+ builtin
98
+ enabled
99
+ parentFramework { id name }
100
+ }
101
+ """
102
+
103
+ # Comprehensive framework mappings with shorthand names for easy CLI usage
104
+ FRAMEWORK_MAPPINGS = {
105
+ "wf-id-4": "NIST SP 800-53 Revision 5",
106
+ "wf-id-48": "NIST SP 800-53 Revision 4",
107
+ "wf-id-5": "FedRAMP (Moderate and Low levels)",
108
+ "wf-id-17": "CIS Controls v7.1",
109
+ "wf-id-24": "CIS Controls v8",
110
+ "wf-id-6": "CIS AWS v1.2.0",
111
+ "wf-id-7": "CIS AWS v1.3.0",
112
+ "wf-id-32": "CIS AWS v1.4.0",
113
+ "wf-id-45": "CIS AWS v1.5.0",
114
+ "wf-id-84": "CIS AWS v2.0.0",
115
+ "wf-id-98": "CIS AWS v3.0.0",
116
+ "wf-id-197": "CIS AWS v4.0.0",
117
+ "wf-id-50": "AWS Foundational Security Best Practices v1.0.0",
118
+ "wf-id-124": "AWS Well-Architected Framework (Section 2 - Security)",
119
+ "wf-id-8": "CIS Azure v1.3.0",
120
+ "wf-id-35": "CIS Azure v1.4.0",
121
+ "wf-id-52": "CIS Azure v1.5.0",
122
+ "wf-id-74": "CIS Azure v2.0.0",
123
+ "wf-id-100": "CIS Azure v2.1.0",
124
+ "wf-id-196": "CIS Azure v2.1.0 (Latest)",
125
+ "wf-id-40": "Azure Security Benchmark v3",
126
+ "wf-id-9": "CIS GCP v1.1.0",
127
+ "wf-id-36": "CIS GCP v1.2.0",
128
+ "wf-id-53": "CIS GCP v1.3.0",
129
+ "wf-id-85": "CIS GCP v2.0.0",
130
+ "wf-id-25": "CIS AKS v1.0.0",
131
+ "wf-id-68": "CIS AKS v1.2.0",
132
+ "wf-id-75": "CIS AKS v1.3.0",
133
+ "wf-id-93": "CIS AKS v1.4.0",
134
+ "wf-id-162": "CIS AKS v1.5.0",
135
+ "wf-id-218": "CIS AKS v1.6.0",
136
+ "wf-id-23": "CIS EKS v1.0.1",
137
+ "wf-id-67": "CIS EKS v1.1.0",
138
+ "wf-id-86": "CIS EKS v1.2.0",
139
+ "wf-id-18": "CIS Kubernetes v1.5.1",
140
+ "wf-id-66": "CIS Kubernetes v1.6.1",
141
+ "wf-id-87": "CIS Kubernetes v1.7.0",
142
+ "wf-id-76": "SOC 2 Type I",
143
+ "wf-id-16": "ISO/IEC 27001:2013",
144
+ "wf-id-19": "PCI DSS v3.2.1",
145
+ "wf-id-78": "PCI DSS v4.0",
146
+ "wf-id-79": "GDPR",
147
+ "wf-id-64": "CCPA/CPRA",
148
+ "wf-id-77": "CCF (The Adobe Common Controls Framework)",
149
+ "wf-id-70": "Canadian PBMM (ITSG-33)",
150
+ "wf-id-111": "C5 - Cloud Computing Compliance Criteria Catalogue",
151
+ "wf-id-161": "CAF (Cyber Assessment Framework by NCSC)",
152
+ "wf-id-90": "APRA CPG 234",
153
+ "wf-id-207": "CISA Security Requirements for EO 14117",
154
+ "wf-id-214": "5Rs - Wiz for Data Security",
155
+ "wf-id-225": "Wiz for Risk Assessment",
156
+ }
157
+
158
+ FRAMEWORK_SHORTCUTS = {
159
+ "nist": "wf-id-4",
160
+ "nist53r5": "wf-id-4",
161
+ "nist53r4": "wf-id-48",
162
+ "fedramp": "wf-id-5",
163
+ "cis": "wf-id-24",
164
+ "cisv8": "wf-id-24",
165
+ "cisv7": "wf-id-17",
166
+ "aws": "wf-id-197",
167
+ "azure": "wf-id-196",
168
+ "gcp": "wf-id-85",
169
+ "k8s": "wf-id-87",
170
+ "kubernetes": "wf-id-87",
171
+ "eks": "wf-id-86",
172
+ "aks": "wf-id-218",
173
+ "soc2": "wf-id-76",
174
+ "iso27001": "wf-id-16",
175
+ "pci": "wf-id-78",
176
+ "gdpr": "wf-id-79",
177
+ "ccpa": "wf-id-64",
178
+ "aws-foundational": "wf-id-50",
179
+ "aws-wellarchitected": "wf-id-124",
180
+ "azure-benchmark": "wf-id-40",
181
+ }
182
+
183
+ FRAMEWORK_CATEGORIES = {
184
+ "NIST Frameworks": ["wf-id-4", "wf-id-48", "wf-id-5"],
185
+ "CIS Controls": ["wf-id-17", "wf-id-24"],
186
+ "AWS Security": [
187
+ "wf-id-197",
188
+ "wf-id-50",
189
+ "wf-id-124",
190
+ "wf-id-6",
191
+ "wf-id-7",
192
+ "wf-id-32",
193
+ "wf-id-45",
194
+ "wf-id-84",
195
+ "wf-id-98",
196
+ ],
197
+ "Azure Security": [
198
+ "wf-id-196",
199
+ "wf-id-40",
200
+ "wf-id-8",
201
+ "wf-id-35",
202
+ "wf-id-52",
203
+ "wf-id-74",
204
+ "wf-id-100",
205
+ ],
206
+ "Google Cloud Security": ["wf-id-85", "wf-id-9", "wf-id-36", "wf-id-53"],
207
+ "Kubernetes Security": [
208
+ "wf-id-87",
209
+ "wf-id-86",
210
+ "wf-id-218",
211
+ "wf-id-18",
212
+ "wf-id-23",
213
+ "wf-id-25",
214
+ "wf-id-66",
215
+ "wf-id-67",
216
+ "wf-id-68",
217
+ "wf-id-75",
218
+ "wf-id-93",
219
+ "wf-id-162",
220
+ ],
221
+ "Industry Standards": ["wf-id-76", "wf-id-16", "wf-id-78", "wf-id-19"],
222
+ "Privacy & Data Protection": ["wf-id-79", "wf-id-64", "wf-id-214"],
223
+ "Government/Regulatory": ["wf-id-70", "wf-id-111", "wf-id-161", "wf-id-90", "wf-id-207"],
224
+ }
7
225
 
8
226
  SBOM_FILE_PATH = "artifacts/wiz_sbom.json"
9
227
  INVENTORY_FILE_PATH = "artifacts/wiz_inventory.json"
@@ -181,6 +399,36 @@ RECOMMENDED_WIZ_INVENTORY_TYPES = [
181
399
  "VIRTUAL_NETWORK",
182
400
  ]
183
401
 
402
+ # This is the set of technology deploymentModels and CloudResource types which we
403
+ # map to the asset category Hardware (instead of Software) when the useWizHardwareTypes
404
+ # feature is enabled.
405
+ # So either things which are hardware-like, or which use technologies that, in turn,
406
+ # imply they are hardware-like.
407
+ # Note that using technology deploymentModels can grab things such as virutal machine
408
+ # image files in addition to actual virtual machines. While this doesn't fit with
409
+ # general concepts of "hardware", for the purposes of attestation, it is the correct
410
+ # choice, as we may be certifying a source image that dynamic resources are created from,
411
+ # rather than attempt to document a variable pool of auto-scaled resources.
412
+ DEFAULT_WIZ_HARDWARE_TYPES = [
413
+ # CloudResource types
414
+ "VIRTUAL_MACHINE",
415
+ "VIRTUAL_MACHINE_IMAGE",
416
+ "CONTAINER",
417
+ "CONTAINER_IMAGE",
418
+ "DB_SERVER",
419
+ # technology deploymentModels
420
+ "SERVER_APPLICATION",
421
+ "CLIENT_APPLICATION",
422
+ "VIRTUAL_APPLIANCE",
423
+ ]
424
+
425
+ # This maps CPE part values to Asset categories.
426
+ CPE_PART_TO_CATEGORY_MAPPING = {
427
+ "h": regscale_models.AssetCategory.Hardware, # Hardware
428
+ "a": regscale_models.AssetCategory.Software, # Application
429
+ "o": regscale_models.AssetCategory.Software, # Other? Operating system? (includes OSs and firmware)
430
+ }
431
+
184
432
  INVENTORY_QUERY = """
185
433
  query CloudResourceSearch(
186
434
  $filterBy: CloudResourceFilters
@@ -262,12 +262,12 @@ class WizIssue(WizVulnerabilityIntegration):
262
262
  return "Wiz-Event"
263
263
  if not name:
264
264
  return f"Wiz-{service_type}-Event"
265
- event_match = re.match(r"^([A-Za-z\s]+?)\s+(?:detection|event|alert|activity)", name)
265
+ event_match = re.match(r"^([A-Za-z\s]+?)\s+(detection|event|alert|activity)", name)
266
266
  if not event_match:
267
267
  return f"Wiz-{service_type}-Event"
268
268
 
269
269
  event_type = event_match.group(1).strip()
270
- if event_type == "Suspicious activity":
270
+ if event_type == "Suspicious" and event_match.group(2).strip().lower() == "activity":
271
271
  return f"Wiz-{service_type}-SuspiciousActivity"
272
272
 
273
273
  event_type = "".join(word.capitalize() for word in event_type.split())
@@ -76,11 +76,12 @@ def get_software_name_from_cpe(wiz_entity_properties: Dict, name: str) -> Dict:
76
76
  """
77
77
  cpe_info_dict = {
78
78
  "name": name,
79
+ "part": None,
79
80
  "software_name": None,
80
81
  "software_version": None,
81
82
  "software_vendor": None,
82
83
  }
83
- if "cpe" in wiz_entity_properties.keys() and wiz_entity_properties.get("cpe"):
84
+ if "cpe" in wiz_entity_properties and wiz_entity_properties.get("cpe"):
84
85
  cpe_info_dict = extract_product_name_and_version(wiz_entity_properties.get("cpe", ""))
85
86
  cpe_info_dict["name"] = name
86
87
  return cpe_info_dict
@@ -349,7 +350,7 @@ def get_ip_address(
349
350
  ip6_address = None
350
351
  dns = None
351
352
  url = None
352
- if "address" in wiz_entity_properties.keys():
353
+ if "address" in wiz_entity_properties:
353
354
  if wiz_entity_properties.get("addressType") == "IPV4":
354
355
  ip4_address = wiz_entity_properties.get("address")
355
356
  elif wiz_entity_properties.get("addressType") == "IPV6":