regscale-cli 6.20.9.1__py3-none-any.whl → 6.20.10.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/integrations/commercial/defender.py +9 -0
- regscale/integrations/commercial/wizv2/async_client.py +325 -0
- regscale/integrations/commercial/wizv2/constants.py +756 -0
- regscale/integrations/commercial/wizv2/scanner.py +1301 -89
- regscale/integrations/commercial/wizv2/utils.py +280 -36
- regscale/integrations/commercial/wizv2/variables.py +2 -10
- regscale/integrations/scanner_integration.py +58 -2
- regscale/integrations/variables.py +1 -0
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/__init__.py +13 -0
- regscale/models/regscale_models/classification.py +23 -0
- regscale/models/regscale_models/cryptography.py +56 -0
- regscale/models/regscale_models/deviation.py +4 -4
- regscale/models/regscale_models/group.py +3 -2
- regscale/models/regscale_models/interconnection.py +1 -1
- regscale/models/regscale_models/issue.py +140 -41
- regscale/models/regscale_models/milestone.py +40 -0
- regscale/models/regscale_models/property.py +0 -1
- regscale/models/regscale_models/regscale_model.py +29 -18
- regscale/models/regscale_models/team.py +55 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/RECORD +29 -23
- tests/regscale/integrations/test_property_and_milestone_creation.py +684 -0
- tests/regscale/models/test_report.py +105 -29
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/top_level.txt +0 -0
|
@@ -54,6 +54,7 @@ from regscale.models import (
|
|
|
54
54
|
ControlImplementationStatus,
|
|
55
55
|
ImplementationObjective,
|
|
56
56
|
)
|
|
57
|
+
from regscale.models.regscale_models.compliance_settings import ComplianceSettings
|
|
57
58
|
from regscale.utils import PaginatedGraphQLClient
|
|
58
59
|
from regscale.utils.decorators import deprecated
|
|
59
60
|
|
|
@@ -781,8 +782,6 @@ def _sync_compliance(
|
|
|
781
782
|
fetch_regscale_data_job = compliance_job_progress.add_task(
|
|
782
783
|
"[#f68d1f]Fetching RegScale Catalog info for framework...", total=1
|
|
783
784
|
)
|
|
784
|
-
compliance_job_progress.update(report_job, completed=True, advance=1)
|
|
785
|
-
|
|
786
785
|
framework_mapping = {
|
|
787
786
|
"CSF": "NIST CSF v1.1",
|
|
788
787
|
"NIST800-53R5": "NIST SP 800-53 Revision 5",
|
|
@@ -836,8 +835,14 @@ def _sync_compliance(
|
|
|
836
835
|
finally:
|
|
837
836
|
compliance_job_progress.update(running_compliance_job, advance=1)
|
|
838
837
|
try:
|
|
838
|
+
controls_with_data = len(controls_to_reports)
|
|
839
|
+
logger.info(f"Creating assessments for {controls_with_data} controls with compliance data")
|
|
840
|
+
if controls_with_data == 0:
|
|
841
|
+
logger.warning("No controls have compliance data from Wiz")
|
|
842
|
+
return report_models
|
|
843
|
+
|
|
839
844
|
saving_regscale_data_job = compliance_job_progress.add_task(
|
|
840
|
-
"[#f68d1f]Saving RegScale data...", total=
|
|
845
|
+
"[#f68d1f]Saving RegScale data...", total=controls_with_data
|
|
841
846
|
)
|
|
842
847
|
create_assessment_from_compliance_report(
|
|
843
848
|
controls_to_reports=controls_to_reports,
|
|
@@ -851,8 +856,8 @@ def _sync_compliance(
|
|
|
851
856
|
except Exception:
|
|
852
857
|
error_message = traceback.format_exc()
|
|
853
858
|
logger.error(f"Error creating ControlImplementations from compliance report: {error_message}")
|
|
854
|
-
|
|
855
|
-
|
|
859
|
+
# Re-raise the exception so it's not silently swallowed
|
|
860
|
+
raise
|
|
856
861
|
return report_models
|
|
857
862
|
|
|
858
863
|
|
|
@@ -932,50 +937,199 @@ def create_assessment_from_compliance_report(
|
|
|
932
937
|
:rtype: None
|
|
933
938
|
"""
|
|
934
939
|
implementations = ControlImplementation.get_all_by_parent(parent_module=regscale_module, parent_id=regscale_id)
|
|
940
|
+
total_controls = len(controls_to_reports)
|
|
941
|
+
processed_count = 0
|
|
942
|
+
|
|
935
943
|
for control_id, reports in controls_to_reports.items():
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
+
try:
|
|
945
|
+
processed_count += 1
|
|
946
|
+
logger.debug(f"Processing control {control_id} ({processed_count}/{total_controls})")
|
|
947
|
+
|
|
948
|
+
control_record_id = None
|
|
949
|
+
for control in controls:
|
|
950
|
+
if control.get("controlId").lower() == control_id:
|
|
951
|
+
control_record_id = control.get("id")
|
|
952
|
+
break
|
|
953
|
+
|
|
954
|
+
filtered_results = [x for x in implementations if x.controlID == control_record_id]
|
|
955
|
+
|
|
956
|
+
start_time = time.time()
|
|
957
|
+
create_report_assessment(filtered_results, reports, control_id)
|
|
958
|
+
end_time = time.time()
|
|
959
|
+
logger.debug(f"Assessment creation for {control_id} took {end_time - start_time:.2f} seconds")
|
|
960
|
+
|
|
961
|
+
progress.update(task, advance=1)
|
|
962
|
+
logger.debug(f"Updated progress: {processed_count}/{total_controls}")
|
|
963
|
+
|
|
964
|
+
except Exception as e:
|
|
965
|
+
logger.error(f"Error processing control {control_id}: {e}")
|
|
966
|
+
# Still update progress even if there's an error
|
|
967
|
+
progress.update(task, advance=1)
|
|
944
968
|
|
|
945
969
|
|
|
946
970
|
def create_report_assessment(filtered_results: List, reports: List, control_id: str) -> None:
|
|
947
971
|
"""
|
|
948
|
-
Create report assessment
|
|
972
|
+
Create a single aggregated report assessment per control
|
|
949
973
|
|
|
950
974
|
:param List filtered_results: Filtered results
|
|
951
|
-
:param List reports:
|
|
975
|
+
:param List reports: List of ComplianceReport objects for this control
|
|
952
976
|
:param str control_id: Control ID
|
|
953
977
|
:return: None
|
|
954
978
|
:rtype: None
|
|
955
979
|
"""
|
|
980
|
+
logger.debug(f"Creating assessment for control {control_id} with {len(reports)} reports")
|
|
981
|
+
|
|
956
982
|
implementation = filtered_results[0] if len(filtered_results) > 0 else None
|
|
983
|
+
if not implementation or not reports:
|
|
984
|
+
logger.debug(
|
|
985
|
+
f"Skipping control {control_id}: implementation={bool(implementation)}, reports={len(reports) if reports else 0}"
|
|
986
|
+
)
|
|
987
|
+
return
|
|
988
|
+
|
|
989
|
+
# Aggregate results: Fail if ANY asset fails, Pass only if ALL pass
|
|
990
|
+
overall_result = "Pass"
|
|
991
|
+
pass_count = 0
|
|
992
|
+
fail_count = 0
|
|
993
|
+
|
|
994
|
+
# Collect detailed results for comprehensive reporting
|
|
995
|
+
asset_details = []
|
|
996
|
+
|
|
957
997
|
for report in reports:
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
998
|
+
if report.result == ComplianceCheckStatus.FAIL.value:
|
|
999
|
+
overall_result = "Fail"
|
|
1000
|
+
fail_count += 1
|
|
1001
|
+
else:
|
|
1002
|
+
pass_count += 1
|
|
1003
|
+
|
|
1004
|
+
# Collect asset details for the report
|
|
1005
|
+
asset_details.append(
|
|
1006
|
+
{
|
|
1007
|
+
"resource_name": report.resource_name,
|
|
1008
|
+
"resource_id": report.resource_id,
|
|
1009
|
+
"cloud_provider": report.cloud_provider,
|
|
1010
|
+
"subscription": report.subscription,
|
|
1011
|
+
"result": report.result,
|
|
1012
|
+
"policy_short_name": report.policy_short_name,
|
|
1013
|
+
"compliance_check": report.compliance_check,
|
|
1014
|
+
"severity": report.severity,
|
|
1015
|
+
"assessed_at": report.assessed_at,
|
|
1016
|
+
}
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
# Create comprehensive HTML summary
|
|
1020
|
+
html_summary = _create_aggregated_assessment_report(
|
|
1021
|
+
control_id=control_id,
|
|
1022
|
+
overall_result=overall_result,
|
|
1023
|
+
pass_count=pass_count,
|
|
1024
|
+
fail_count=fail_count,
|
|
1025
|
+
asset_details=asset_details,
|
|
1026
|
+
total_assets=len(reports),
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
# Create single assessment for this control
|
|
1030
|
+
assessment = Assessment(
|
|
1031
|
+
leadAssessorId=implementation.createdById,
|
|
1032
|
+
title=f"Wiz compliance assessment for {control_id}",
|
|
1033
|
+
assessmentType="Control Testing",
|
|
1034
|
+
plannedStart=get_current_datetime(),
|
|
1035
|
+
plannedFinish=get_current_datetime(),
|
|
1036
|
+
actualFinish=get_current_datetime(),
|
|
1037
|
+
assessmentResult=overall_result,
|
|
1038
|
+
assessmentReport=html_summary,
|
|
1039
|
+
status="Complete",
|
|
1040
|
+
parentId=implementation.id,
|
|
1041
|
+
parentModule="controls",
|
|
1042
|
+
isPublic=True,
|
|
1043
|
+
).create()
|
|
1044
|
+
|
|
1045
|
+
# Update implementation status once with aggregated result
|
|
1046
|
+
update_implementation_status(
|
|
1047
|
+
implementation=implementation,
|
|
1048
|
+
result=overall_result,
|
|
1049
|
+
)
|
|
1050
|
+
|
|
1051
|
+
logger.info(
|
|
1052
|
+
f"Created aggregated assessment for {control_id}: {assessment.id} "
|
|
1053
|
+
f"(Result: {overall_result}, Assets: {len(reports)}, Pass: {pass_count}, Fail: {fail_count})"
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
def _create_aggregated_assessment_report(
|
|
1058
|
+
control_id: str, overall_result: str, pass_count: int, fail_count: int, asset_details: List[Dict], total_assets: int
|
|
1059
|
+
) -> str:
|
|
1060
|
+
"""
|
|
1061
|
+
Create a comprehensive HTML assessment report for aggregated compliance results
|
|
1062
|
+
|
|
1063
|
+
:param str control_id: Control identifier
|
|
1064
|
+
:param str overall_result: Overall Pass/Fail result
|
|
1065
|
+
:param int pass_count: Number of passing assets
|
|
1066
|
+
:param int fail_count: Number of failing assets
|
|
1067
|
+
:param List[Dict] asset_details: Detailed information about each asset
|
|
1068
|
+
:param int total_assets: Total number of assets assessed
|
|
1069
|
+
:return: HTML formatted assessment report
|
|
1070
|
+
:rtype: str
|
|
1071
|
+
"""
|
|
1072
|
+
# Create summary section
|
|
1073
|
+
summary_html = f"""
|
|
1074
|
+
<div style="margin-bottom: 20px; padding: 15px; border: 2px solid {'#d32f2f' if overall_result == 'Fail' else '#2e7d32'}; border-radius: 5px; background-color: {'#ffebee' if overall_result == 'Fail' else '#e8f5e8'};">
|
|
1075
|
+
<h3 style="margin: 0 0 10px 0; color: {'#d32f2f' if overall_result == 'Fail' else '#2e7d32'};">
|
|
1076
|
+
Assessment Summary for Control {control_id}
|
|
1077
|
+
</h3>
|
|
1078
|
+
<p><strong>Overall Result:</strong> <span style="color: {'#d32f2f' if overall_result == 'Fail' else '#2e7d32'}; font-weight: bold;">{overall_result}</span></p>
|
|
1079
|
+
<p><strong>Total Assets Assessed:</strong> {total_assets}</p>
|
|
1080
|
+
<p><strong>Passing Assets:</strong> <span style="color: #2e7d32;">{pass_count}</span></p>
|
|
1081
|
+
<p><strong>Failing Assets:</strong> <span style="color: #d32f2f;">{fail_count}</span></p>
|
|
1082
|
+
<p><strong>Assessment Date:</strong> {get_current_datetime()}</p>
|
|
1083
|
+
</div>
|
|
1084
|
+
"""
|
|
1085
|
+
|
|
1086
|
+
# Create detailed asset results table
|
|
1087
|
+
if asset_details:
|
|
1088
|
+
table_rows = []
|
|
1089
|
+
for asset in asset_details:
|
|
1090
|
+
result_color = "#d32f2f" if asset["result"] == "Fail" else "#2e7d32"
|
|
1091
|
+
table_rows.append(
|
|
1092
|
+
f"""
|
|
1093
|
+
<tr>
|
|
1094
|
+
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{asset.get('resource_name', 'N/A')}</td>
|
|
1095
|
+
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{asset.get('resource_id', 'N/A')}</td>
|
|
1096
|
+
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{asset.get('cloud_provider', 'N/A')}</td>
|
|
1097
|
+
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{asset.get('subscription', 'N/A')}</td>
|
|
1098
|
+
<td style="padding: 8px; border-bottom: 1px solid #ddd; color: {result_color}; font-weight: bold;">{asset.get('result', 'N/A')}</td>
|
|
1099
|
+
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{asset.get('policy_short_name', 'N/A')}</td>
|
|
1100
|
+
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{asset.get('compliance_check', 'N/A')}</td>
|
|
1101
|
+
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{asset.get('severity', 'N/A')}</td>
|
|
1102
|
+
</tr>
|
|
1103
|
+
"""
|
|
977
1104
|
)
|
|
978
|
-
|
|
1105
|
+
|
|
1106
|
+
asset_table_html = f"""
|
|
1107
|
+
<div style="margin-top: 20px;">
|
|
1108
|
+
<h4>Detailed Asset Results</h4>
|
|
1109
|
+
<table style="width: 100%; border-collapse: collapse; border: 1px solid #ddd;">
|
|
1110
|
+
<thead>
|
|
1111
|
+
<tr style="background-color: #f5f5f5;">
|
|
1112
|
+
<th style="padding: 10px; border-bottom: 2px solid #ddd; text-align: left;">Resource Name</th>
|
|
1113
|
+
<th style="padding: 10px; border-bottom: 2px solid #ddd; text-align: left;">Resource ID</th>
|
|
1114
|
+
<th style="padding: 10px; border-bottom: 2px solid #ddd; text-align: left;">Cloud Provider</th>
|
|
1115
|
+
<th style="padding: 10px; border-bottom: 2px solid #ddd; text-align: left;">Subscription</th>
|
|
1116
|
+
<th style="padding: 10px; border-bottom: 2px solid #ddd; text-align: left;">Result</th>
|
|
1117
|
+
<th style="padding: 10px; border-bottom: 2px solid #ddd; text-align: left;">Policy</th>
|
|
1118
|
+
<th style="padding: 10px; border-bottom: 2px solid #ddd; text-align: left;">Compliance Check</th>
|
|
1119
|
+
<th style="padding: 10px; border-bottom: 2px solid #ddd; text-align: left;">Severity</th>
|
|
1120
|
+
</tr>
|
|
1121
|
+
</thead>
|
|
1122
|
+
<tbody>
|
|
1123
|
+
{''.join(table_rows)}
|
|
1124
|
+
</tbody>
|
|
1125
|
+
</table>
|
|
1126
|
+
</div>
|
|
1127
|
+
"""
|
|
1128
|
+
else:
|
|
1129
|
+
asset_table_html = "<p><em>No asset details available.</em></p>"
|
|
1130
|
+
|
|
1131
|
+
# Combine summary and details
|
|
1132
|
+
return summary_html + asset_table_html
|
|
979
1133
|
|
|
980
1134
|
|
|
981
1135
|
def update_implementation_status(implementation: ControlImplementation, result: str) -> ControlImplementation:
|
|
@@ -1003,14 +1157,104 @@ def update_implementation_status(implementation: ControlImplementation, result:
|
|
|
1003
1157
|
logger.info(f"Updated implementation status for {implementation.id}: {implementation.status}")
|
|
1004
1158
|
|
|
1005
1159
|
|
|
1160
|
+
def get_wiz_compliance_settings():
|
|
1161
|
+
"""
|
|
1162
|
+
Get Wiz compliance settings for status mapping
|
|
1163
|
+
|
|
1164
|
+
:return: Compliance settings instance or None
|
|
1165
|
+
:rtype: Optional[ComplianceSettings]
|
|
1166
|
+
"""
|
|
1167
|
+
try:
|
|
1168
|
+
settings = ComplianceSettings.get_by_current_tenant()
|
|
1169
|
+
wiz_compliance_setting = next((comp for comp in settings if comp.title == "Wiz Compliance Setting"), None)
|
|
1170
|
+
if not wiz_compliance_setting:
|
|
1171
|
+
logger.debug("No Wiz Compliance Setting found, using default implementation status mapping")
|
|
1172
|
+
else:
|
|
1173
|
+
logger.debug("Using Wiz Compliance Setting for implementation status mapping")
|
|
1174
|
+
return wiz_compliance_setting
|
|
1175
|
+
except Exception as e:
|
|
1176
|
+
logger.debug(f"Error getting Wiz Compliance Setting: {e}")
|
|
1177
|
+
return None
|
|
1178
|
+
|
|
1179
|
+
|
|
1006
1180
|
def report_result_to_implementation_status(result: str) -> str:
|
|
1007
1181
|
"""
|
|
1008
|
-
Convert report result to implementation status
|
|
1182
|
+
Convert report result to implementation status using compliance settings if available
|
|
1009
1183
|
|
|
1010
1184
|
:param str result: Report result
|
|
1011
1185
|
:return: Implementation status
|
|
1012
1186
|
:rtype: str
|
|
1013
1187
|
"""
|
|
1188
|
+
compliance_settings = get_wiz_compliance_settings()
|
|
1189
|
+
|
|
1190
|
+
if compliance_settings:
|
|
1191
|
+
status = _get_status_from_compliance_settings(result, compliance_settings)
|
|
1192
|
+
if status:
|
|
1193
|
+
return status
|
|
1194
|
+
|
|
1195
|
+
# Fallback to default mapping
|
|
1196
|
+
return _get_default_status_mapping(result)
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
def _get_status_from_compliance_settings(result: str, compliance_settings) -> Optional[str]:
|
|
1200
|
+
"""
|
|
1201
|
+
Get implementation status from compliance settings
|
|
1202
|
+
|
|
1203
|
+
:param str result: Report result
|
|
1204
|
+
:param compliance_settings: Compliance settings object
|
|
1205
|
+
:return: Implementation status or None if not found
|
|
1206
|
+
:rtype: Optional[str]
|
|
1207
|
+
"""
|
|
1208
|
+
try:
|
|
1209
|
+
status_labels = compliance_settings.get_field_labels("implementationStatus")
|
|
1210
|
+
result_lower = result.lower()
|
|
1211
|
+
|
|
1212
|
+
for label in status_labels:
|
|
1213
|
+
status = _match_result_to_label(result_lower, label)
|
|
1214
|
+
if status:
|
|
1215
|
+
return status
|
|
1216
|
+
|
|
1217
|
+
logger.debug(f"No matching compliance setting found for result: {result}")
|
|
1218
|
+
return None
|
|
1219
|
+
|
|
1220
|
+
except Exception as e:
|
|
1221
|
+
logger.debug(f"Error using compliance settings for implementation status mapping: {e}")
|
|
1222
|
+
return None
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
def _match_result_to_label(result_lower: str, label: str) -> Optional[str]:
|
|
1226
|
+
"""
|
|
1227
|
+
Match a result to a status label based on predefined mappings
|
|
1228
|
+
|
|
1229
|
+
:param str result_lower: Lowercase result string
|
|
1230
|
+
:param str label: Status label to check
|
|
1231
|
+
:return: Matched label or None
|
|
1232
|
+
:rtype: Optional[str]
|
|
1233
|
+
"""
|
|
1234
|
+
label_lower = label.lower()
|
|
1235
|
+
|
|
1236
|
+
if result_lower == ComplianceCheckStatus.PASS.value.lower():
|
|
1237
|
+
return label if label_lower in ["implemented", "complete", "compliant"] else None
|
|
1238
|
+
|
|
1239
|
+
if result_lower == ComplianceCheckStatus.FAIL.value.lower():
|
|
1240
|
+
return (
|
|
1241
|
+
label
|
|
1242
|
+
if label_lower in ["inremediation", "in remediation", "remediation", "failed", "non-compliant"]
|
|
1243
|
+
else None
|
|
1244
|
+
)
|
|
1245
|
+
|
|
1246
|
+
# Not implemented or other status
|
|
1247
|
+
return label if label_lower in ["notimplemented", "not implemented", "pending", "planned"] else None
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
def _get_default_status_mapping(result: str) -> str:
|
|
1251
|
+
"""
|
|
1252
|
+
Get default status mapping for a result
|
|
1253
|
+
|
|
1254
|
+
:param str result: Report result
|
|
1255
|
+
:return: Default implementation status
|
|
1256
|
+
:rtype: str
|
|
1257
|
+
"""
|
|
1014
1258
|
if result == ComplianceCheckStatus.PASS.value:
|
|
1015
1259
|
return ControlImplementationStatus.Implemented.value
|
|
1016
1260
|
elif result == ComplianceCheckStatus.FAIL.value:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"""Wiz Variables"""
|
|
4
4
|
|
|
5
5
|
from regscale.core.app.utils.variables import RsVariableType, RsVariablesMeta
|
|
6
|
+
from regscale.integrations.commercial.wizv2.constants import RECOMMENDED_WIZ_INVENTORY_TYPES
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class WizVariables(metaclass=RsVariablesMeta):
|
|
@@ -22,16 +23,7 @@ class WizVariables(metaclass=RsVariablesMeta):
|
|
|
22
23
|
wizInventoryFilterBy: RsVariableType(
|
|
23
24
|
str,
|
|
24
25
|
'{"projectId": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"], "type": ["API_GATEWAY"]}',
|
|
25
|
-
default="""{"type":
|
|
26
|
-
[ "API_GATEWAY", "BACKUP_SERVICE", "CDN", "CICD_SERVICE", "CLOUD_LOG_CONFIGURATION",
|
|
27
|
-
"CLOUD_ORGANIZATION", "CONTAINER", "CONTAINER_IMAGE", "CONTAINER_REGISTRY", "CONTAINER_SERVICE",
|
|
28
|
-
"CONTROLLER_REVISION", "DATABASE", "DATA_WORKLOAD", "DB_SERVER", "DOMAIN", "EMAIL_SERVICE", "ENCRYPTION_KEY",
|
|
29
|
-
"FILE_SYSTEM_SERVICE", "FIREWALL", "GATEWAY", "KUBERNETES_CLUSTER", "LOAD_BALANCER",
|
|
30
|
-
"MANAGED_CERTIFICATE", "MESSAGING_SERVICE", "NAMESPACE", "NETWORK_INTERFACE", "PRIVATE_ENDPOINT",
|
|
31
|
-
"PRIVATE_LINK", "RAW_ACCESS_POLICY", "REGISTERED_DOMAIN", "RESOURCE_GROUP", "SECRET",
|
|
32
|
-
"SECRET_CONTAINER", "SERVERLESS", "SERVERLESS_PACKAGE", "SERVICE_ACCOUNT", "SERVICE_CONFIGURATION",
|
|
33
|
-
"STORAGE_ACCOUNT", "SUBNET", "SUBSCRIPTION", "VIRTUAL_DESKTOP", "VIRTUAL_MACHINE",
|
|
34
|
-
"VIRTUAL_MACHINE_IMAGE", "VIRTUAL_NETWORK", "VOLUME", "WEB_SERVICE", "NETWORK_ADDRESS"] }""",
|
|
26
|
+
default="""{"type": ["%s"] }""" % '","'.join(RECOMMENDED_WIZ_INVENTORY_TYPES), # type: ignore
|
|
35
27
|
) # type: ignore
|
|
36
28
|
wizAccessToken: RsVariableType(str, "", sensitive=True, required=False) # type: ignore
|
|
37
29
|
wizClientId: RsVariableType(str, "", sensitive=True) # type: ignore
|
|
@@ -1656,15 +1656,21 @@ class ScannerIntegration(ABC):
|
|
|
1656
1656
|
bulk_update=True, defaults={"otherIdentifier": self._get_other_identifier(finding, is_poam)}
|
|
1657
1657
|
)
|
|
1658
1658
|
|
|
1659
|
-
self.
|
|
1659
|
+
self._handle_property_and_milestone_creation(issue, finding, existing_issue)
|
|
1660
1660
|
return issue
|
|
1661
1661
|
|
|
1662
|
-
def
|
|
1662
|
+
def _handle_property_and_milestone_creation(
|
|
1663
|
+
self,
|
|
1664
|
+
issue: regscale_models.Issue,
|
|
1665
|
+
finding: IntegrationFinding,
|
|
1666
|
+
existing_issue: Optional[regscale_models.Issue] = None,
|
|
1667
|
+
) -> None:
|
|
1663
1668
|
"""
|
|
1664
1669
|
Handles property creation for an issue based on the finding data
|
|
1665
1670
|
|
|
1666
1671
|
:param regscale_models.Issue issue: The issue to handle properties for
|
|
1667
1672
|
:param IntegrationFinding finding: The finding data
|
|
1673
|
+
:param bool new_issue: Whether this is a new issue
|
|
1668
1674
|
:rtype: None
|
|
1669
1675
|
"""
|
|
1670
1676
|
if poc := finding.point_of_contact:
|
|
@@ -1685,6 +1691,45 @@ class ScannerIntegration(ABC):
|
|
|
1685
1691
|
).create_or_update()
|
|
1686
1692
|
logger.debug("Added CWE property %s to issue %s", finding.plugin_id, issue.id)
|
|
1687
1693
|
|
|
1694
|
+
if ScannerVariables.useMilestones:
|
|
1695
|
+
if (
|
|
1696
|
+
existing_issue
|
|
1697
|
+
and existing_issue.status == regscale_models.IssueStatus.Closed
|
|
1698
|
+
and issue.status == regscale_models.IssueStatus.Open
|
|
1699
|
+
):
|
|
1700
|
+
regscale_models.Milestone(
|
|
1701
|
+
title=f"Issue reopened from {self.title} scan",
|
|
1702
|
+
milestoneDate=get_current_datetime(),
|
|
1703
|
+
responsiblePersonId=self.assessor_id,
|
|
1704
|
+
parentID=issue.id,
|
|
1705
|
+
parentModule="issues",
|
|
1706
|
+
).create_or_update()
|
|
1707
|
+
logger.debug("Added milestone for issue %s from finding %s", issue.id, finding.external_id)
|
|
1708
|
+
elif (
|
|
1709
|
+
existing_issue
|
|
1710
|
+
and existing_issue.status == regscale_models.IssueStatus.Open
|
|
1711
|
+
and issue.status == regscale_models.IssueStatus.Closed
|
|
1712
|
+
):
|
|
1713
|
+
regscale_models.Milestone(
|
|
1714
|
+
title=f"Issue closed from {self.title} scan",
|
|
1715
|
+
milestoneDate=issue.dateCompleted,
|
|
1716
|
+
responsiblePersonId=self.assessor_id,
|
|
1717
|
+
parentID=issue.id,
|
|
1718
|
+
parentModule="issues",
|
|
1719
|
+
).create_or_update()
|
|
1720
|
+
logger.debug("Added milestone for issue %s from finding %s", issue.id, finding.external_id)
|
|
1721
|
+
elif not existing_issue:
|
|
1722
|
+
regscale_models.Milestone(
|
|
1723
|
+
title=f"Issue created from {self.title} scan",
|
|
1724
|
+
milestoneDate=self.scan_date,
|
|
1725
|
+
responsiblePersonId=self.assessor_id,
|
|
1726
|
+
parentID=issue.id,
|
|
1727
|
+
parentModule="issues",
|
|
1728
|
+
).create_or_update()
|
|
1729
|
+
logger.debug("Created milestone for issue %s from finding %s", issue.id, finding.external_id)
|
|
1730
|
+
else:
|
|
1731
|
+
logger.debug("No milestone created for issue %s from finding %s", issue.id, finding.external_id)
|
|
1732
|
+
|
|
1688
1733
|
@staticmethod
|
|
1689
1734
|
def get_consolidated_asset_identifier(
|
|
1690
1735
|
finding: IntegrationFinding,
|
|
@@ -2523,6 +2568,17 @@ class ScannerIntegration(ABC):
|
|
|
2523
2568
|
issue.dateLastUpdated = get_current_datetime()
|
|
2524
2569
|
issue.save()
|
|
2525
2570
|
|
|
2571
|
+
if ScannerVariables.useMilestones:
|
|
2572
|
+
regscale_models.Milestone(
|
|
2573
|
+
title=f"Issue closed from {self.title} scan",
|
|
2574
|
+
milestoneDate=issue.dateCompleted,
|
|
2575
|
+
responsiblePersonId=self.assessor_id,
|
|
2576
|
+
completed=True,
|
|
2577
|
+
parentID=issue.id,
|
|
2578
|
+
parentModule="issues",
|
|
2579
|
+
).create_or_update()
|
|
2580
|
+
logger.debug("Created milestone for issue %s from %s tool", issue.id, self.title)
|
|
2581
|
+
|
|
2526
2582
|
with count_lock:
|
|
2527
2583
|
self.closed_count += 1
|
|
2528
2584
|
if issue.controlImplementationIds:
|
|
@@ -26,3 +26,4 @@ class ScannerVariables(metaclass=RsVariablesMeta):
|
|
|
26
26
|
maxRetries: RsVariableType(int, "3", default=3, required=False) # type: ignore
|
|
27
27
|
timeout: RsVariableType(int, "60", default=60, required=False) # type: ignore
|
|
28
28
|
complianceCreation: RsVariableType(str, "Assessment|Issue|POAM", default="Assessment", required=False) # type: ignore # noqa: F722,F821
|
|
29
|
+
useMilestones: RsVariableType(bool, "true|false", default=False, required=False) # type: ignore # noqa: F722,F722,F821
|