regscale-cli 6.23.0.0__py3-none-any.whl → 6.24.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.
- regscale/_version.py +1 -1
- regscale/core/app/application.py +2 -0
- regscale/integrations/commercial/__init__.py +1 -0
- regscale/integrations/commercial/sarif/sarif_converter.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +109 -2
- regscale/integrations/commercial/wizv2/compliance_report.py +1485 -0
- regscale/integrations/commercial/wizv2/constants.py +72 -2
- regscale/integrations/commercial/wizv2/data_fetcher.py +61 -0
- regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
- regscale/integrations/commercial/wizv2/issue.py +775 -27
- regscale/integrations/commercial/wizv2/policy_compliance.py +599 -181
- regscale/integrations/commercial/wizv2/reports.py +243 -0
- regscale/integrations/commercial/wizv2/scanner.py +668 -245
- regscale/integrations/compliance_integration.py +304 -51
- regscale/integrations/due_date_handler.py +210 -0
- regscale/integrations/public/cci_importer.py +444 -0
- regscale/integrations/scanner_integration.py +718 -153
- regscale/models/integration_models/CCI_List.xml +1 -0
- regscale/models/integration_models/cisa_kev_data.json +61 -3
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +3 -3
- regscale/models/regscale_models/form_field_value.py +1 -1
- regscale/models/regscale_models/milestone.py +1 -0
- regscale/models/regscale_models/regscale_model.py +225 -60
- regscale/models/regscale_models/security_plan.py +3 -2
- regscale/regscale.py +7 -0
- {regscale_cli-6.23.0.0.dist-info → regscale_cli-6.24.0.0.dist-info}/METADATA +9 -9
- {regscale_cli-6.23.0.0.dist-info → regscale_cli-6.24.0.0.dist-info}/RECORD +44 -27
- tests/fixtures/test_fixture.py +13 -8
- tests/regscale/integrations/public/__init__.py +0 -0
- tests/regscale/integrations/public/test_alienvault.py +220 -0
- tests/regscale/integrations/public/test_cci.py +458 -0
- tests/regscale/integrations/public/test_cisa.py +1021 -0
- tests/regscale/integrations/public/test_emass.py +518 -0
- tests/regscale/integrations/public/test_fedramp.py +851 -0
- tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
- tests/regscale/integrations/public/test_file_uploads.py +506 -0
- tests/regscale/integrations/public/test_oscal.py +453 -0
- tests/regscale/models/test_form_field_value_integration.py +304 -0
- tests/regscale/models/test_module_integration.py +582 -0
- {regscale_cli-6.23.0.0.dist-info → regscale_cli-6.24.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.23.0.0.dist-info → regscale_cli-6.24.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.23.0.0.dist-info → regscale_cli-6.24.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.23.0.0.dist-info → regscale_cli-6.24.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Wiz Report Management for RegScale CLI."""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
from typing import Dict, Any, Optional
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from regscale.integrations.commercial.wizv2.constants import (
|
|
12
|
+
CREATE_REPORT_QUERY,
|
|
13
|
+
REPORTS_QUERY,
|
|
14
|
+
DOWNLOAD_QUERY,
|
|
15
|
+
RERUN_REPORT_QUERY,
|
|
16
|
+
get_compliance_report_variables,
|
|
17
|
+
CHECK_INTERVAL_FOR_DOWNLOAD_REPORT,
|
|
18
|
+
MAX_RETRIES,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("regscale")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WizReportManager:
|
|
25
|
+
"""Manages Wiz report operations including creation, monitoring, and download."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, api_url: str, access_token: str):
|
|
28
|
+
"""
|
|
29
|
+
Initialize the report manager.
|
|
30
|
+
|
|
31
|
+
:param str api_url: The Wiz GraphQL API URL
|
|
32
|
+
:param str access_token: The authentication token
|
|
33
|
+
"""
|
|
34
|
+
self.api_url = api_url
|
|
35
|
+
self.access_token = access_token
|
|
36
|
+
self.headers = {"Content-Type": "application/json", "Authorization": f"Bearer {access_token}"}
|
|
37
|
+
|
|
38
|
+
def create_compliance_report(self, project_id: str, run_starts_at: Optional[str] = None) -> Optional[str]:
|
|
39
|
+
"""
|
|
40
|
+
Create a compliance report for the specified project.
|
|
41
|
+
|
|
42
|
+
:param str project_id: The Wiz project ID
|
|
43
|
+
:param Optional[str] run_starts_at: ISO timestamp for when the report should start
|
|
44
|
+
:return: Report ID if successful, None otherwise
|
|
45
|
+
:rtype: Optional[str]
|
|
46
|
+
"""
|
|
47
|
+
variables = get_compliance_report_variables(project_id, run_starts_at)
|
|
48
|
+
|
|
49
|
+
payload = {"query": CREATE_REPORT_QUERY, "variables": variables}
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
response = requests.post(self.api_url, headers=self.headers, json=payload, timeout=30)
|
|
53
|
+
response.raise_for_status()
|
|
54
|
+
|
|
55
|
+
data = response.json()
|
|
56
|
+
if "errors" in data:
|
|
57
|
+
logger.error(f"GraphQL errors: {data['errors']}")
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
report_data = data.get("data", {}).get("createReport", {}).get("report", {})
|
|
61
|
+
report_id = report_data.get("id")
|
|
62
|
+
|
|
63
|
+
if report_id:
|
|
64
|
+
logger.info(f"Successfully created compliance report with ID: {report_id}")
|
|
65
|
+
return report_id
|
|
66
|
+
else:
|
|
67
|
+
logger.error("No report ID returned from create report mutation")
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
except requests.exceptions.RequestException as e:
|
|
71
|
+
logger.error(f"Error creating compliance report: {e}")
|
|
72
|
+
return None
|
|
73
|
+
except (KeyError, ValueError) as e:
|
|
74
|
+
logger.error(f"Error parsing create report response: {e}")
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def get_report_status(self, report_id: str) -> Dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
Get the status and details of a report.
|
|
80
|
+
|
|
81
|
+
:param str report_id: The report ID
|
|
82
|
+
:return: Report status information
|
|
83
|
+
:rtype: Dict[str, Any]
|
|
84
|
+
"""
|
|
85
|
+
variables = {"reportId": report_id}
|
|
86
|
+
|
|
87
|
+
payload = {"query": DOWNLOAD_QUERY, "variables": variables}
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
response = requests.post(self.api_url, headers=self.headers, json=payload, timeout=30)
|
|
91
|
+
response.raise_for_status()
|
|
92
|
+
|
|
93
|
+
data = response.json()
|
|
94
|
+
if "errors" in data:
|
|
95
|
+
logger.error(f"GraphQL errors: {data['errors']}")
|
|
96
|
+
return {}
|
|
97
|
+
|
|
98
|
+
report_data = data.get("data", {}).get("report", {})
|
|
99
|
+
last_run = report_data.get("lastRun", {})
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
"status": last_run.get("status", "UNKNOWN"),
|
|
103
|
+
"url": last_run.get("url", ""),
|
|
104
|
+
"report_data": report_data,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
except requests.exceptions.RequestException as e:
|
|
108
|
+
logger.error(f"Error getting report status: {e}")
|
|
109
|
+
return {}
|
|
110
|
+
except (KeyError, ValueError) as e:
|
|
111
|
+
logger.error(f"Error parsing report status response: {e}")
|
|
112
|
+
return {}
|
|
113
|
+
|
|
114
|
+
def wait_for_report_completion(self, report_id: str) -> Optional[str]:
|
|
115
|
+
"""
|
|
116
|
+
Wait for a report to complete and return the download URL.
|
|
117
|
+
|
|
118
|
+
:param str report_id: The report ID
|
|
119
|
+
:return: Download URL if successful, None otherwise
|
|
120
|
+
:rtype: Optional[str]
|
|
121
|
+
"""
|
|
122
|
+
logger.info(f"Waiting for report {report_id} to complete...")
|
|
123
|
+
|
|
124
|
+
for attempt in range(MAX_RETRIES):
|
|
125
|
+
status_info = self.get_report_status(report_id)
|
|
126
|
+
status = status_info.get("status", "UNKNOWN")
|
|
127
|
+
|
|
128
|
+
logger.info(f"Report status (attempt {attempt + 1}/{MAX_RETRIES}): {status}")
|
|
129
|
+
|
|
130
|
+
if status in ["SUCCESS", "COMPLETED"]:
|
|
131
|
+
download_url = status_info.get("url", "")
|
|
132
|
+
if download_url:
|
|
133
|
+
logger.info(f"Report completed successfully. Download URL: {download_url}")
|
|
134
|
+
return download_url
|
|
135
|
+
else:
|
|
136
|
+
logger.warning("Report completed but no download URL available")
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
elif status in ["FAILED", "CANCELLED", "TIMEOUT"]:
|
|
140
|
+
logger.error(f"Report failed with status: {status}")
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
elif status in ["PENDING", "RUNNING", "IN_PROGRESS", "UNKNOWN"]:
|
|
144
|
+
logger.info(
|
|
145
|
+
f"Report is still {status.lower()}, waiting {CHECK_INTERVAL_FOR_DOWNLOAD_REPORT} seconds..."
|
|
146
|
+
)
|
|
147
|
+
time.sleep(CHECK_INTERVAL_FOR_DOWNLOAD_REPORT)
|
|
148
|
+
else:
|
|
149
|
+
logger.warning(f"Unknown report status: {status}")
|
|
150
|
+
time.sleep(CHECK_INTERVAL_FOR_DOWNLOAD_REPORT)
|
|
151
|
+
|
|
152
|
+
logger.error(f"Report did not complete after {MAX_RETRIES} attempts")
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
def download_report(self, download_url: str, output_path: str) -> bool:
|
|
156
|
+
"""
|
|
157
|
+
Download a report from the given URL.
|
|
158
|
+
|
|
159
|
+
:param str download_url: The download URL
|
|
160
|
+
:param str output_path: Path where the report should be saved
|
|
161
|
+
:return: True if successful, False otherwise
|
|
162
|
+
:rtype: bool
|
|
163
|
+
"""
|
|
164
|
+
try:
|
|
165
|
+
logger.info(f"Downloading report to: {output_path}")
|
|
166
|
+
|
|
167
|
+
response = requests.get(download_url, timeout=300)
|
|
168
|
+
response.raise_for_status()
|
|
169
|
+
|
|
170
|
+
with open(output_path, "wb") as f:
|
|
171
|
+
f.write(response.content)
|
|
172
|
+
|
|
173
|
+
logger.info(f"Report downloaded successfully to: {output_path}")
|
|
174
|
+
return True
|
|
175
|
+
|
|
176
|
+
except requests.exceptions.RequestException as e:
|
|
177
|
+
logger.error(f"Error downloading report: {e}")
|
|
178
|
+
return False
|
|
179
|
+
except IOError as e:
|
|
180
|
+
logger.error(f"Error saving report to file: {e}")
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
def rerun_report(self, report_id: str) -> Optional[str]:
|
|
184
|
+
"""
|
|
185
|
+
Rerun an existing report.
|
|
186
|
+
|
|
187
|
+
:param str report_id: The report ID
|
|
188
|
+
:return: Download URL if successful, None otherwise
|
|
189
|
+
:rtype: Optional[str]
|
|
190
|
+
"""
|
|
191
|
+
variables = {"reportId": report_id}
|
|
192
|
+
|
|
193
|
+
payload = {"query": RERUN_REPORT_QUERY, "variables": variables}
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
response = requests.post(self.api_url, headers=self.headers, json=payload, timeout=30)
|
|
197
|
+
response.raise_for_status()
|
|
198
|
+
|
|
199
|
+
data = response.json()
|
|
200
|
+
if "errors" in data:
|
|
201
|
+
logger.error(f"GraphQL errors: {data['errors']}")
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
logger.info(f"Successfully triggered rerun for report {report_id}")
|
|
205
|
+
return self.wait_for_report_completion(report_id)
|
|
206
|
+
|
|
207
|
+
except requests.exceptions.RequestException as e:
|
|
208
|
+
logger.error(f"Error rerunning report: {e}")
|
|
209
|
+
return None
|
|
210
|
+
except (KeyError, ValueError) as e:
|
|
211
|
+
logger.error(f"Error parsing rerun report response: {e}")
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
def list_reports(self, filter_by: Optional[Dict[str, Any]] = None) -> list:
|
|
215
|
+
"""
|
|
216
|
+
List available reports.
|
|
217
|
+
|
|
218
|
+
:param Optional[Dict[str, Any]] filter_by: Optional filter criteria
|
|
219
|
+
:return: List of reports
|
|
220
|
+
:rtype: list
|
|
221
|
+
"""
|
|
222
|
+
variables = {"first": 50, "filterBy": filter_by or {}}
|
|
223
|
+
|
|
224
|
+
payload = {"query": REPORTS_QUERY, "variables": variables}
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
response = requests.post(self.api_url, headers=self.headers, json=payload, timeout=30)
|
|
228
|
+
response.raise_for_status()
|
|
229
|
+
|
|
230
|
+
data = response.json()
|
|
231
|
+
if "errors" in data:
|
|
232
|
+
logger.error(f"GraphQL errors: {data['errors']}")
|
|
233
|
+
return []
|
|
234
|
+
|
|
235
|
+
reports = data.get("data", {}).get("reports", {}).get("nodes", [])
|
|
236
|
+
return reports
|
|
237
|
+
|
|
238
|
+
except requests.exceptions.RequestException as e:
|
|
239
|
+
logger.error(f"Error listing reports: {e}")
|
|
240
|
+
return []
|
|
241
|
+
except (KeyError, ValueError) as e:
|
|
242
|
+
logger.error(f"Error parsing list reports response: {e}")
|
|
243
|
+
return []
|