regscale-cli 6.24.0.1__py3-none-any.whl → 6.25.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 (30) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/api.py +1 -1
  3. regscale/core/app/application.py +5 -3
  4. regscale/core/app/internal/evidence.py +308 -202
  5. regscale/dev/code_gen.py +84 -3
  6. regscale/integrations/commercial/__init__.py +2 -0
  7. regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
  8. regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
  9. regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
  10. regscale/integrations/commercial/synqly/assets.py +99 -16
  11. regscale/integrations/commercial/synqly/query_builder.py +533 -0
  12. regscale/integrations/commercial/synqly/vulnerabilities.py +134 -14
  13. regscale/integrations/commercial/wizv2/compliance_report.py +22 -0
  14. regscale/integrations/compliance_integration.py +17 -0
  15. regscale/integrations/scanner_integration.py +16 -0
  16. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  17. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +12 -2
  18. regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
  19. regscale/models/integration_models/synqly_models/synqly_model.py +47 -3
  20. regscale/models/regscale_models/compliance_settings.py +28 -0
  21. regscale/models/regscale_models/component.py +1 -0
  22. regscale/models/regscale_models/control_implementation.py +130 -1
  23. regscale/regscale.py +1 -1
  24. regscale/validation/record.py +23 -1
  25. {regscale_cli-6.24.0.1.dist-info → regscale_cli-6.25.0.0.dist-info}/METADATA +1 -1
  26. {regscale_cli-6.24.0.1.dist-info → regscale_cli-6.25.0.0.dist-info}/RECORD +30 -28
  27. {regscale_cli-6.24.0.1.dist-info → regscale_cli-6.25.0.0.dist-info}/LICENSE +0 -0
  28. {regscale_cli-6.24.0.1.dist-info → regscale_cli-6.25.0.0.dist-info}/WHEEL +0 -0
  29. {regscale_cli-6.24.0.1.dist-info → regscale_cli-6.25.0.0.dist-info}/entry_points.txt +0 -0
  30. {regscale_cli-6.24.0.1.dist-info → regscale_cli-6.25.0.0.dist-info}/top_level.txt +0 -0
@@ -14,6 +14,33 @@ def vulnerabilities() -> None:
14
14
  pass
15
15
 
16
16
 
17
+ @vulnerabilities.command(name="build-query")
18
+ @click.option(
19
+ "--provider",
20
+ required=False,
21
+ help="Provider ID (e.g., vulnerabilities_armis_centrix). If not specified, starts interactive mode.",
22
+ )
23
+ @click.option("--validate", help="Validate a filter string against provider capabilities")
24
+ @click.option("--list-fields", is_flag=True, default=False, help="List all available fields for the provider")
25
+ def build_query(provider, validate, list_fields):
26
+ """
27
+ Build and validate filter queries for Vulnerabilities connectors.
28
+
29
+ Examples:
30
+ # Build a filter query
31
+ regscale vulnerabilities build-query
32
+
33
+ # List all fields for a specific provider
34
+ regscale vulnerabilities build-query --provider vulnerabilities_armis_centrix --list-fields
35
+
36
+ # Validate a filter string
37
+ regscale vulnerabilities build-query --provider vulnerabilities_armis_centrix --validate "device.ip[eq]192.168.1.1"
38
+ """
39
+ from regscale.integrations.commercial.synqly.query_builder import handle_build_query
40
+
41
+ handle_build_query("vulnerabilities", provider, validate, list_fields)
42
+
43
+
17
44
  @vulnerabilities.command(name="sync_crowdstrike")
18
45
  @regscale_ssp_id()
19
46
  @click.option(
@@ -37,19 +64,33 @@ def vulnerabilities() -> None:
37
64
  is_flag=True,
38
65
  default=False,
39
66
  )
67
+ @click.option(
68
+ "--asset_filter",
69
+ help='STRING: Apply filters to asset queries. Can be a single filter "field[operator]value" or semicolon-separated filters "field1[op]value1;field2[op]value2"',
70
+ required=False,
71
+ type=str,
72
+ default=None,
73
+ )
40
74
  @click.option(
41
75
  "--url",
42
76
  type=click.STRING,
43
77
  help="Base URL for the CrowdStrike Falcon® Spotlight API.",
44
78
  required=False,
45
79
  )
46
- def sync_crowdstrike(regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool, url: str) -> None:
80
+ def sync_crowdstrike(
81
+ regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str, url: str
82
+ ) -> None:
47
83
  """Sync Vulnerabilities from Crowdstrike to RegScale."""
48
84
  from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
49
85
 
50
86
  vulnerabilities_crowdstrike = Vulnerabilities("crowdstrike")
51
87
  vulnerabilities_crowdstrike.run_sync(
52
- regscale_ssp_id=regscale_ssp_id, vuln_filter=vuln_filter, scan_date=scan_date, all_scans=all_scans, url=url
88
+ regscale_ssp_id=regscale_ssp_id,
89
+ vuln_filter=vuln_filter,
90
+ scan_date=scan_date,
91
+ all_scans=all_scans,
92
+ filter=asset_filter.split(";") if asset_filter else [],
93
+ url=url,
53
94
  )
54
95
 
55
96
 
@@ -72,13 +113,26 @@ def sync_crowdstrike(regscale_ssp_id: int, vuln_filter: str, scan_date: datetime
72
113
  @click.option(
73
114
  "--all_scans", help="Whether to sync all vulnerabilities from Nucleus", required=False, is_flag=True, default=False
74
115
  )
75
- def sync_nucleus(regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool) -> None:
116
+ @click.option(
117
+ "--asset_filter",
118
+ help='STRING: Apply filters to asset queries. Can be a single filter "field[operator]value" or semicolon-separated filters "field1[op]value1;field2[op]value2"',
119
+ required=False,
120
+ type=str,
121
+ default=None,
122
+ )
123
+ def sync_nucleus(
124
+ regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str
125
+ ) -> None:
76
126
  """Sync Vulnerabilities from Nucleus to RegScale."""
77
127
  from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
78
128
 
79
129
  vulnerabilities_nucleus = Vulnerabilities("nucleus")
80
130
  vulnerabilities_nucleus.run_sync(
81
- regscale_ssp_id=regscale_ssp_id, vuln_filter=vuln_filter, scan_date=scan_date, all_scans=all_scans
131
+ regscale_ssp_id=regscale_ssp_id,
132
+ vuln_filter=vuln_filter,
133
+ scan_date=scan_date,
134
+ all_scans=all_scans,
135
+ filter=asset_filter.split(";") if asset_filter else [],
82
136
  )
83
137
 
84
138
 
@@ -105,13 +159,26 @@ def sync_nucleus(regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, al
105
159
  is_flag=True,
106
160
  default=False,
107
161
  )
108
- def sync_qualys_cloud(regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool) -> None:
162
+ @click.option(
163
+ "--asset_filter",
164
+ help='STRING: Apply filters to asset queries. Can be a single filter "field[operator]value" or semicolon-separated filters "field1[op]value1;field2[op]value2"',
165
+ required=False,
166
+ type=str,
167
+ default=None,
168
+ )
169
+ def sync_qualys_cloud(
170
+ regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str
171
+ ) -> None:
109
172
  """Sync Vulnerabilities from Qualys Cloud to RegScale."""
110
173
  from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
111
174
 
112
175
  vulnerabilities_qualys_cloud = Vulnerabilities("qualys_cloud")
113
176
  vulnerabilities_qualys_cloud.run_sync(
114
- regscale_ssp_id=regscale_ssp_id, vuln_filter=vuln_filter, scan_date=scan_date, all_scans=all_scans
177
+ regscale_ssp_id=regscale_ssp_id,
178
+ vuln_filter=vuln_filter,
179
+ scan_date=scan_date,
180
+ all_scans=all_scans,
181
+ filter=asset_filter.split(";") if asset_filter else [],
115
182
  )
116
183
 
117
184
 
@@ -138,13 +205,26 @@ def sync_qualys_cloud(regscale_ssp_id: int, vuln_filter: str, scan_date: datetim
138
205
  is_flag=True,
139
206
  default=False,
140
207
  )
141
- def sync_rapid7_insight_cloud(regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool) -> None:
208
+ @click.option(
209
+ "--asset_filter",
210
+ help='STRING: Apply filters to asset queries. Can be a single filter "field[operator]value" or semicolon-separated filters "field1[op]value1;field2[op]value2"',
211
+ required=False,
212
+ type=str,
213
+ default=None,
214
+ )
215
+ def sync_rapid7_insight_cloud(
216
+ regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str
217
+ ) -> None:
142
218
  """Sync Vulnerabilities from Rapid7 Insight Cloud to RegScale."""
143
219
  from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
144
220
 
145
221
  vulnerabilities_rapid7_insight_cloud = Vulnerabilities("rapid7_insight_cloud")
146
222
  vulnerabilities_rapid7_insight_cloud.run_sync(
147
- regscale_ssp_id=regscale_ssp_id, vuln_filter=vuln_filter, scan_date=scan_date, all_scans=all_scans
223
+ regscale_ssp_id=regscale_ssp_id,
224
+ vuln_filter=vuln_filter,
225
+ scan_date=scan_date,
226
+ all_scans=all_scans,
227
+ filter=asset_filter.split(";") if asset_filter else [],
148
228
  )
149
229
 
150
230
 
@@ -171,13 +251,26 @@ def sync_rapid7_insight_cloud(regscale_ssp_id: int, vuln_filter: str, scan_date:
171
251
  is_flag=True,
172
252
  default=False,
173
253
  )
174
- def sync_servicenow_vr(regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool) -> None:
254
+ @click.option(
255
+ "--asset_filter",
256
+ help='STRING: Apply filters to asset queries. Can be a single filter "field[operator]value" or semicolon-separated filters "field1[op]value1;field2[op]value2"',
257
+ required=False,
258
+ type=str,
259
+ default=None,
260
+ )
261
+ def sync_servicenow_vr(
262
+ regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str
263
+ ) -> None:
175
264
  """Sync Vulnerabilities from Servicenow Vr to RegScale."""
176
265
  from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
177
266
 
178
267
  vulnerabilities_servicenow_vr = Vulnerabilities("servicenow_vr")
179
268
  vulnerabilities_servicenow_vr.run_sync(
180
- regscale_ssp_id=regscale_ssp_id, vuln_filter=vuln_filter, scan_date=scan_date, all_scans=all_scans
269
+ regscale_ssp_id=regscale_ssp_id,
270
+ vuln_filter=vuln_filter,
271
+ scan_date=scan_date,
272
+ all_scans=all_scans,
273
+ filter=asset_filter.split(";") if asset_filter else [],
181
274
  )
182
275
 
183
276
 
@@ -204,13 +297,26 @@ def sync_servicenow_vr(regscale_ssp_id: int, vuln_filter: str, scan_date: dateti
204
297
  is_flag=True,
205
298
  default=False,
206
299
  )
207
- def sync_tanium_cloud(regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool) -> None:
300
+ @click.option(
301
+ "--asset_filter",
302
+ help='STRING: Apply filters to asset queries. Can be a single filter "field[operator]value" or semicolon-separated filters "field1[op]value1;field2[op]value2"',
303
+ required=False,
304
+ type=str,
305
+ default=None,
306
+ )
307
+ def sync_tanium_cloud(
308
+ regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str
309
+ ) -> None:
208
310
  """Sync Vulnerabilities from Tanium Cloud to RegScale."""
209
311
  from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
210
312
 
211
313
  vulnerabilities_tanium_cloud = Vulnerabilities("tanium_cloud")
212
314
  vulnerabilities_tanium_cloud.run_sync(
213
- regscale_ssp_id=regscale_ssp_id, vuln_filter=vuln_filter, scan_date=scan_date, all_scans=all_scans
315
+ regscale_ssp_id=regscale_ssp_id,
316
+ vuln_filter=vuln_filter,
317
+ scan_date=scan_date,
318
+ all_scans=all_scans,
319
+ filter=asset_filter.split(";") if asset_filter else [],
214
320
  )
215
321
 
216
322
 
@@ -237,19 +343,33 @@ def sync_tanium_cloud(regscale_ssp_id: int, vuln_filter: str, scan_date: datetim
237
343
  is_flag=True,
238
344
  default=False,
239
345
  )
346
+ @click.option(
347
+ "--asset_filter",
348
+ help='STRING: Apply filters to asset queries. Can be a single filter "field[operator]value" or semicolon-separated filters "field1[op]value1;field2[op]value2"',
349
+ required=False,
350
+ type=str,
351
+ default=None,
352
+ )
240
353
  @click.option(
241
354
  "--url",
242
355
  type=click.STRING,
243
356
  help="Base URL for the Tenable Cloud API.",
244
357
  required=False,
245
358
  )
246
- def sync_tenable_cloud(regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool, url: str) -> None:
359
+ def sync_tenable_cloud(
360
+ regscale_ssp_id: int, vuln_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str, url: str
361
+ ) -> None:
247
362
  """Sync Vulnerabilities from Tenable Cloud to RegScale."""
248
363
  from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
249
364
 
250
365
  vulnerabilities_tenable_cloud = Vulnerabilities("tenable_cloud")
251
366
  vulnerabilities_tenable_cloud.run_sync(
252
- regscale_ssp_id=regscale_ssp_id, vuln_filter=vuln_filter, scan_date=scan_date, all_scans=all_scans, url=url
367
+ regscale_ssp_id=regscale_ssp_id,
368
+ vuln_filter=vuln_filter,
369
+ scan_date=scan_date,
370
+ all_scans=all_scans,
371
+ filter=asset_filter.split(";") if asset_filter else [],
372
+ url=url,
253
373
  )
254
374
 
255
375
 
@@ -925,6 +925,19 @@ class WizComplianceReportProcessor(ComplianceIntegration):
925
925
  impl.lastAssessmentResult = "Pass"
926
926
  impl.bStatusImplemented = True
927
927
 
928
+ # Ensure required fields are set if empty
929
+ if not impl.responsibility:
930
+ impl.responsibility = ControlImplementation.get_default_responsibility(
931
+ parent_id=impl.parentId
932
+ )
933
+ logger.debug(
934
+ f"Setting default responsibility for control {control_id}: {impl.responsibility}"
935
+ )
936
+
937
+ if not impl.implementation:
938
+ impl.implementation = f"Implementation details for {control_id} will be documented."
939
+ logger.debug(f"Setting default implementation statement for control {control_id}")
940
+
928
941
  # Set audit fields if available
929
942
  user_id = self.app.config.get("userId")
930
943
  if user_id:
@@ -1041,6 +1054,15 @@ class WizComplianceReportProcessor(ComplianceIntegration):
1041
1054
  impl.lastAssessmentResult = "Fail"
1042
1055
  impl.bStatusImplemented = False
1043
1056
 
1057
+ # Ensure required fields are set if empty
1058
+ if not impl.responsibility:
1059
+ impl.responsibility = ControlImplementation.get_default_responsibility(parent_id=impl.parentId)
1060
+ logger.debug(f"Setting default responsibility for control {control_id}: {impl.responsibility}")
1061
+
1062
+ if not impl.implementation:
1063
+ impl.implementation = f"Implementation details for {control_id} will be documented."
1064
+ logger.debug(f"Setting default implementation statement for control {control_id}")
1065
+
1044
1066
  # Set audit fields if available
1045
1067
  user_id = self.app.config.get("userId")
1046
1068
  if user_id:
@@ -1864,6 +1864,23 @@ class ComplianceIntegration(ScannerIntegration, ABC):
1864
1864
  implementation.status = new_status
1865
1865
  implementation.dateLastAssessed = get_current_datetime()
1866
1866
  implementation.lastAssessmentResult = result
1867
+
1868
+ # Ensure required fields are set if empty
1869
+ if not implementation.responsibility:
1870
+ implementation.responsibility = ControlImplementation.get_default_responsibility(
1871
+ parent_id=implementation.parentId
1872
+ )
1873
+ logger.debug(
1874
+ f"Setting default responsibility for implementation {implementation.id}: {implementation.responsibility}"
1875
+ )
1876
+
1877
+ if not implementation.implementation:
1878
+ control_id = (
1879
+ getattr(implementation.control, "controlId", "control") if implementation.control else "control"
1880
+ )
1881
+ implementation.implementation = f"Implementation details for {control_id} will be documented."
1882
+ logger.debug(f"Setting default implementation statement for implementation {implementation.id}")
1883
+
1867
1884
  implementation.save()
1868
1885
 
1869
1886
  # Update objectives if they exist
@@ -1162,6 +1162,21 @@ class ScannerIntegration(ABC):
1162
1162
  self.components_by_title[component_name] = component
1163
1163
  return component
1164
1164
 
1165
+ def _get_compliance_settings_id(self) -> Optional[int]:
1166
+ """
1167
+ Get the compliance settings ID from the security plan.
1168
+
1169
+ :return: The compliance settings ID if available
1170
+ :rtype: Optional[int]
1171
+ """
1172
+ try:
1173
+ security_plan = regscale_models.SecurityPlan.get_object(object_id=self.plan_id)
1174
+ if security_plan and hasattr(security_plan, "complianceSettingsId"):
1175
+ return security_plan.complianceSettingsId
1176
+ except Exception as e:
1177
+ logger.debug(f"Failed to get compliance settings ID from security plan {self.plan_id}: {e}")
1178
+ return None
1179
+
1165
1180
  def _create_new_component(self, asset: IntegrationAsset, component_name: str) -> regscale_models.Component:
1166
1181
  """
1167
1182
  Create a new component for the asset.
@@ -1178,6 +1193,7 @@ class ScannerIntegration(ABC):
1178
1193
  securityPlansId=self.plan_id,
1179
1194
  description=component_name,
1180
1195
  componentOwnerId=self.get_assessor_id(),
1196
+ complianceSettingsId=self._get_compliance_settings_id(),
1181
1197
  ).get_or_create()
1182
1198
  self.components.append(component)
1183
1199
  return component