regscale-cli 6.26.0.0__py3-none-any.whl → 6.27.0.1__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 +1 -1
- regscale/core/app/internal/evidence.py +419 -2
- regscale/dev/code_gen.py +24 -20
- regscale/integrations/commercial/__init__.py +0 -1
- regscale/integrations/commercial/jira.py +367 -126
- regscale/integrations/commercial/qualys/__init__.py +7 -8
- regscale/integrations/commercial/qualys/scanner.py +8 -3
- regscale/integrations/commercial/synqly/assets.py +17 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +45 -28
- regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
- regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
- regscale/integrations/commercial/tenablev2/commands.py +142 -1
- regscale/integrations/commercial/tenablev2/scanner.py +0 -1
- regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
- regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +44 -59
- regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
- regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
- regscale/integrations/commercial/wizv2/compliance_report.py +10 -9
- regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
- regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +3 -3
- regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +1 -17
- regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
- regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +5 -9
- regscale/integrations/commercial/wizv2/issue.py +1 -1
- regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
- regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
- regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
- regscale/integrations/commercial/wizv2/reports.py +1 -1
- regscale/integrations/commercial/wizv2/sbom.py +1 -1
- regscale/integrations/commercial/wizv2/scanner.py +40 -100
- regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
- regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
- regscale/integrations/commercial/wizv2/variables.py +89 -3
- regscale/integrations/compliance_integration.py +0 -46
- regscale/integrations/control_matcher.py +22 -3
- regscale/integrations/due_date_handler.py +14 -8
- regscale/integrations/public/fedramp/docx_parser.py +10 -1
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +393 -340
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/scanner_integration.py +127 -57
- regscale/models/integration_models/cisa_kev_data.json +132 -9
- regscale/models/integration_models/qualys.py +3 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +24 -7
- regscale/models/integration_models/synqly_models/synqly_model.py +8 -1
- regscale/models/regscale_models/control_implementation.py +1 -1
- regscale/models/regscale_models/issue.py +0 -1
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/METADATA +1 -17
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/RECORD +94 -61
- tests/regscale/integrations/commercial/test_jira.py +481 -91
- tests/regscale/integrations/commercial/test_wiz.py +96 -200
- tests/regscale/integrations/commercial/wizv2/__init__.py +1 -1
- tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
- tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
- tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
- tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
- tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
- tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
- tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
- tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
- tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
- tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +1 -1
- tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
- tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
- tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
- tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +1 -1
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +72 -29
- tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +946 -78
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +97 -202
- tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
- tests/regscale/integrations/public/test_fedramp.py +301 -0
- tests/regscale/integrations/test_control_matcher.py +83 -0
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3543
- tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +0 -750
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/top_level.txt +0 -0
|
@@ -15,7 +15,7 @@ from regscale.core.app.utils.app_utils import get_current_datetime
|
|
|
15
15
|
from regscale.integrations.commercial.wizv2.file_cleanup import ReportFileCleanup
|
|
16
16
|
from regscale.integrations.commercial.wizv2.reports import WizReportManager
|
|
17
17
|
from regscale.integrations.commercial.wizv2.variables import WizVariables
|
|
18
|
-
from regscale.integrations.commercial.wizv2.
|
|
18
|
+
from regscale.integrations.commercial.wizv2.core.auth import wiz_authenticate
|
|
19
19
|
from regscale.integrations.compliance_integration import ComplianceIntegration, ComplianceItem
|
|
20
20
|
from regscale.integrations.control_matcher import ControlMatcher
|
|
21
21
|
from regscale.models import regscale_models
|
|
@@ -793,25 +793,26 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
793
793
|
:rtype: Optional[str]
|
|
794
794
|
"""
|
|
795
795
|
try:
|
|
796
|
-
# Filter for compliance reports
|
|
797
|
-
filter_by = {"
|
|
796
|
+
# Filter for compliance reports (projectId not supported in ReportFilters, using name-based lookup)
|
|
797
|
+
filter_by = {"type": ["COMPLIANCE_ASSESSMENTS"]}
|
|
798
798
|
|
|
799
799
|
logger.debug(f"Searching for existing compliance reports with filter: {filter_by}")
|
|
800
800
|
reports = self.report_manager.list_reports(filter_by=filter_by)
|
|
801
801
|
|
|
802
802
|
if not reports:
|
|
803
|
-
logger.info("No existing compliance reports found
|
|
803
|
+
logger.info("No existing compliance reports found")
|
|
804
804
|
return None
|
|
805
805
|
|
|
806
|
-
# Look for
|
|
807
|
-
|
|
806
|
+
# Look for report with project-specific name
|
|
807
|
+
expected_name = f"Compliance Report - {self.wiz_project_id}"
|
|
808
|
+
matching_reports = [report for report in reports if report.get("name", "").strip() == expected_name]
|
|
808
809
|
|
|
809
|
-
if not
|
|
810
|
-
logger.info("No compliance
|
|
810
|
+
if not matching_reports:
|
|
811
|
+
logger.info(f"No existing compliance report found with name: {expected_name}")
|
|
811
812
|
return None
|
|
812
813
|
|
|
813
814
|
# Return the first matching report (most recent will be used)
|
|
814
|
-
selected_report =
|
|
815
|
+
selected_report = matching_reports[0]
|
|
815
816
|
report_id = selected_report.get("id")
|
|
816
817
|
report_name = selected_report.get("name", "Unknown")
|
|
817
818
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Core Wiz integration modules including authentication, client, file operations, and constants."""
|
|
2
|
+
|
|
3
|
+
from regscale.integrations.commercial.wizv2.core.auth import (
|
|
4
|
+
AUTH0_URLS,
|
|
5
|
+
COGNITO_URLS,
|
|
6
|
+
generate_authentication_params,
|
|
7
|
+
get_token,
|
|
8
|
+
wiz_authenticate,
|
|
9
|
+
)
|
|
10
|
+
from regscale.integrations.commercial.wizv2.core.client import (
|
|
11
|
+
AsyncWizGraphQLClient,
|
|
12
|
+
run_async_queries,
|
|
13
|
+
)
|
|
14
|
+
from regscale.integrations.commercial.wizv2.core.file_operations import FileOperations
|
|
15
|
+
from regscale.integrations.commercial.wizv2.core.constants import (
|
|
16
|
+
ASSET_TYPE_MAPPING,
|
|
17
|
+
BEARER,
|
|
18
|
+
CHECK_INTERVAL_FOR_DOWNLOAD_REPORT,
|
|
19
|
+
CLOUD_CONFIG_FINDING_QUERY,
|
|
20
|
+
CLOUD_CONFIG_FINDINGS_FILE_PATH,
|
|
21
|
+
CONTENT_TYPE,
|
|
22
|
+
CPE_PART_TO_CATEGORY_MAPPING,
|
|
23
|
+
CREATE_REPORT_QUERY,
|
|
24
|
+
DATA_FINDING_QUERY,
|
|
25
|
+
DATA_FINDINGS_FILE_PATH,
|
|
26
|
+
DATASOURCE,
|
|
27
|
+
DEFAULT_WIZ_HARDWARE_TYPES,
|
|
28
|
+
DOWNLOAD_QUERY,
|
|
29
|
+
END_OF_LIFE_FILE_PATH,
|
|
30
|
+
END_OF_LIFE_QUERY,
|
|
31
|
+
EXCESSIVE_ACCESS_FILE_PATH,
|
|
32
|
+
EXCESSIVE_ACCESS_QUERY,
|
|
33
|
+
EXTERNAL_ATTACK_SURFACE_FILE_PATH,
|
|
34
|
+
EXTERNAL_ATTACK_SURFACE_QUERY,
|
|
35
|
+
FRAMEWORK_CATEGORIES,
|
|
36
|
+
FRAMEWORK_MAPPINGS,
|
|
37
|
+
FRAMEWORK_SHORTCUTS,
|
|
38
|
+
HOST_VULNERABILITY_FILE_PATH,
|
|
39
|
+
HOST_VULNERABILITY_QUERY,
|
|
40
|
+
INVENTORY_FILE_PATH,
|
|
41
|
+
INVENTORY_QUERY,
|
|
42
|
+
ISSUE_QUERY,
|
|
43
|
+
ISSUES_FILE_PATH,
|
|
44
|
+
MAX_RETRIES,
|
|
45
|
+
NETWORK_EXPOSURE_FILE_PATH,
|
|
46
|
+
NETWORK_EXPOSURE_QUERY,
|
|
47
|
+
PROVIDER,
|
|
48
|
+
RATE_LIMIT_MSG,
|
|
49
|
+
RECOMMENDED_WIZ_INVENTORY_TYPES,
|
|
50
|
+
REPORTS_QUERY,
|
|
51
|
+
RERUN_REPORT_QUERY,
|
|
52
|
+
RESOURCE,
|
|
53
|
+
SBOM_FILE_PATH,
|
|
54
|
+
SBOM_QUERY,
|
|
55
|
+
SECRET_FINDINGS_FILE_PATH,
|
|
56
|
+
SECRET_FINDINGS_QUERY,
|
|
57
|
+
SEVERITY_MAP,
|
|
58
|
+
TECHNOLOGIES_FILE_PATH,
|
|
59
|
+
VULNERABILITY_FILE_PATH,
|
|
60
|
+
VULNERABILITY_QUERY,
|
|
61
|
+
WIZ_FRAMEWORK_QUERY,
|
|
62
|
+
WIZ_POLICY_QUERY,
|
|
63
|
+
WizVulnerabilityType,
|
|
64
|
+
get_compliance_report_variables,
|
|
65
|
+
get_wiz_issue_queries,
|
|
66
|
+
get_wiz_vulnerability_queries,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
# Auth
|
|
71
|
+
"AUTH0_URLS",
|
|
72
|
+
"COGNITO_URLS",
|
|
73
|
+
"generate_authentication_params",
|
|
74
|
+
"get_token",
|
|
75
|
+
"wiz_authenticate",
|
|
76
|
+
# Client
|
|
77
|
+
"AsyncWizGraphQLClient",
|
|
78
|
+
"run_async_queries",
|
|
79
|
+
# File Operations
|
|
80
|
+
"FileOperations",
|
|
81
|
+
# Constants
|
|
82
|
+
"ASSET_TYPE_MAPPING",
|
|
83
|
+
"BEARER",
|
|
84
|
+
"CHECK_INTERVAL_FOR_DOWNLOAD_REPORT",
|
|
85
|
+
"CLOUD_CONFIG_FINDING_QUERY",
|
|
86
|
+
"CLOUD_CONFIG_FINDINGS_FILE_PATH",
|
|
87
|
+
"CONTENT_TYPE",
|
|
88
|
+
"CPE_PART_TO_CATEGORY_MAPPING",
|
|
89
|
+
"CREATE_REPORT_QUERY",
|
|
90
|
+
"DATA_FINDING_QUERY",
|
|
91
|
+
"DATA_FINDINGS_FILE_PATH",
|
|
92
|
+
"DATASOURCE",
|
|
93
|
+
"DEFAULT_WIZ_HARDWARE_TYPES",
|
|
94
|
+
"DOWNLOAD_QUERY",
|
|
95
|
+
"END_OF_LIFE_FILE_PATH",
|
|
96
|
+
"END_OF_LIFE_QUERY",
|
|
97
|
+
"EXCESSIVE_ACCESS_FILE_PATH",
|
|
98
|
+
"EXCESSIVE_ACCESS_QUERY",
|
|
99
|
+
"EXTERNAL_ATTACK_SURFACE_FILE_PATH",
|
|
100
|
+
"EXTERNAL_ATTACK_SURFACE_QUERY",
|
|
101
|
+
"FRAMEWORK_CATEGORIES",
|
|
102
|
+
"FRAMEWORK_MAPPINGS",
|
|
103
|
+
"FRAMEWORK_SHORTCUTS",
|
|
104
|
+
"HOST_VULNERABILITY_FILE_PATH",
|
|
105
|
+
"HOST_VULNERABILITY_QUERY",
|
|
106
|
+
"INVENTORY_FILE_PATH",
|
|
107
|
+
"INVENTORY_QUERY",
|
|
108
|
+
"ISSUE_QUERY",
|
|
109
|
+
"ISSUES_FILE_PATH",
|
|
110
|
+
"MAX_RETRIES",
|
|
111
|
+
"NETWORK_EXPOSURE_FILE_PATH",
|
|
112
|
+
"NETWORK_EXPOSURE_QUERY",
|
|
113
|
+
"PROVIDER",
|
|
114
|
+
"RATE_LIMIT_MSG",
|
|
115
|
+
"RECOMMENDED_WIZ_INVENTORY_TYPES",
|
|
116
|
+
"REPORTS_QUERY",
|
|
117
|
+
"RERUN_REPORT_QUERY",
|
|
118
|
+
"RESOURCE",
|
|
119
|
+
"SBOM_FILE_PATH",
|
|
120
|
+
"SBOM_QUERY",
|
|
121
|
+
"SECRET_FINDINGS_FILE_PATH",
|
|
122
|
+
"SECRET_FINDINGS_QUERY",
|
|
123
|
+
"SEVERITY_MAP",
|
|
124
|
+
"TECHNOLOGIES_FILE_PATH",
|
|
125
|
+
"VULNERABILITY_FILE_PATH",
|
|
126
|
+
"VULNERABILITY_QUERY",
|
|
127
|
+
"WIZ_FRAMEWORK_QUERY",
|
|
128
|
+
"WIZ_POLICY_QUERY",
|
|
129
|
+
"WizVulnerabilityType",
|
|
130
|
+
"get_compliance_report_variables",
|
|
131
|
+
"get_wiz_issue_queries",
|
|
132
|
+
"get_wiz_vulnerability_queries",
|
|
133
|
+
]
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import logging
|
|
7
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
8
8
|
|
|
9
9
|
import anyio
|
|
10
10
|
import httpx
|
|
@@ -48,7 +48,7 @@ class AsyncWizGraphQLClient:
|
|
|
48
48
|
self,
|
|
49
49
|
query: str,
|
|
50
50
|
variables: Optional[Dict[str, Any]] = None,
|
|
51
|
-
progress_callback: Optional[
|
|
51
|
+
progress_callback: Optional[Callable] = None,
|
|
52
52
|
task_name: str = "GraphQL Query",
|
|
53
53
|
) -> Dict[str, Any]:
|
|
54
54
|
"""
|
|
@@ -118,7 +118,7 @@ class AsyncWizGraphQLClient:
|
|
|
118
118
|
query: str,
|
|
119
119
|
variables: Dict[str, Any],
|
|
120
120
|
topic_key: str,
|
|
121
|
-
progress_callback: Optional[
|
|
121
|
+
progress_callback: Optional[Callable] = None,
|
|
122
122
|
task_name: str = "Paginated Query",
|
|
123
123
|
) -> List[Dict[str, Any]]:
|
|
124
124
|
"""
|
|
@@ -569,7 +569,7 @@ def get_compliance_report_variables(
|
|
|
569
569
|
|
|
570
570
|
return {
|
|
571
571
|
"input": {
|
|
572
|
-
"name": "Compliance Report",
|
|
572
|
+
"name": f"Compliance Report - {project_id}",
|
|
573
573
|
"type": "COMPLIANCE_ASSESSMENTS",
|
|
574
574
|
"compressionMethod": "GZIP",
|
|
575
575
|
"runIntervalHours": 168,
|
|
@@ -632,22 +632,6 @@ REPORTS_QUERY = """
|
|
|
632
632
|
emailTarget {
|
|
633
633
|
to
|
|
634
634
|
}
|
|
635
|
-
parameters {
|
|
636
|
-
query
|
|
637
|
-
framework {
|
|
638
|
-
name
|
|
639
|
-
}
|
|
640
|
-
subscriptions {
|
|
641
|
-
id
|
|
642
|
-
name
|
|
643
|
-
type
|
|
644
|
-
}
|
|
645
|
-
entities {
|
|
646
|
-
id
|
|
647
|
-
name
|
|
648
|
-
type
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
635
|
lastRun {
|
|
652
636
|
...LastRunDetails
|
|
653
637
|
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""File operations module for Wiz integration - handles caching and file I/O."""
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
from regscale.core.app.utils.app_utils import check_file_path
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("regscale")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FileOperations:
|
|
17
|
+
"""Handles file operations for Wiz integration including caching and data persistence."""
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def load_json_file(file_path: str) -> Optional[Any]:
|
|
21
|
+
"""
|
|
22
|
+
Load data from a JSON file.
|
|
23
|
+
|
|
24
|
+
:param str file_path: Path to JSON file
|
|
25
|
+
:return: Loaded data or None if file doesn't exist or is invalid
|
|
26
|
+
:rtype: Optional[Any]
|
|
27
|
+
"""
|
|
28
|
+
if not os.path.exists(file_path):
|
|
29
|
+
logger.debug(f"File does not exist: {file_path}")
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
with open(file_path, encoding="utf-8") as f:
|
|
34
|
+
return json.load(f)
|
|
35
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
36
|
+
logger.error(f"Error reading JSON file {file_path}: {e}")
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def save_json_file(data: Any, file_path: str, create_dir: bool = True) -> bool:
|
|
41
|
+
"""
|
|
42
|
+
Save data to a JSON file.
|
|
43
|
+
|
|
44
|
+
:param Any data: Data to save
|
|
45
|
+
:param str file_path: Path to save file
|
|
46
|
+
:param bool create_dir: Whether to create parent directory if needed
|
|
47
|
+
:return: True if successful, False otherwise
|
|
48
|
+
:rtype: bool
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
if create_dir:
|
|
52
|
+
check_file_path(os.path.dirname(file_path))
|
|
53
|
+
|
|
54
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
55
|
+
json.dump(data, f)
|
|
56
|
+
|
|
57
|
+
logger.debug(f"Saved data to {file_path}")
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.warning(f"Failed to save data to {file_path}: {e}")
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def get_file_age(file_path: str) -> Optional[datetime.timedelta]:
|
|
66
|
+
"""
|
|
67
|
+
Get the age of a file as a timedelta.
|
|
68
|
+
|
|
69
|
+
:param str file_path: Path to file
|
|
70
|
+
:return: File age or None if file doesn't exist
|
|
71
|
+
:rtype: Optional[datetime.timedelta]
|
|
72
|
+
"""
|
|
73
|
+
if not os.path.exists(file_path):
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
file_mod_time = datetime.datetime.fromtimestamp(os.path.getmtime(file_path))
|
|
78
|
+
current_time = datetime.datetime.now()
|
|
79
|
+
return current_time - file_mod_time
|
|
80
|
+
except OSError as e:
|
|
81
|
+
logger.warning(f"Error getting file age for {file_path}: {e}")
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def is_cache_valid(file_path: str, max_age_hours: float = 8) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Check if a cache file is valid (exists and not too old).
|
|
88
|
+
|
|
89
|
+
:param str file_path: Path to cache file
|
|
90
|
+
:param float max_age_hours: Maximum age in hours before cache is invalid
|
|
91
|
+
:return: True if cache is valid, False otherwise
|
|
92
|
+
:rtype: bool
|
|
93
|
+
"""
|
|
94
|
+
file_age = FileOperations.get_file_age(file_path)
|
|
95
|
+
if file_age is None:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
max_age = datetime.timedelta(hours=max_age_hours)
|
|
99
|
+
return file_age < max_age
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def load_cache_or_fetch(
|
|
103
|
+
file_path: str,
|
|
104
|
+
fetch_fn: Callable[[], Any],
|
|
105
|
+
max_age_hours: float = 8,
|
|
106
|
+
save_cache: bool = True,
|
|
107
|
+
) -> Any:
|
|
108
|
+
"""
|
|
109
|
+
Load data from cache if valid, otherwise fetch and optionally cache.
|
|
110
|
+
|
|
111
|
+
:param str file_path: Path to cache file
|
|
112
|
+
:param Callable fetch_fn: Function to call to fetch fresh data
|
|
113
|
+
:param float max_age_hours: Maximum cache age in hours
|
|
114
|
+
:param bool save_cache: Whether to save fetched data to cache
|
|
115
|
+
:return: Data from cache or freshly fetched
|
|
116
|
+
:rtype: Any
|
|
117
|
+
"""
|
|
118
|
+
# Try to load from cache if valid
|
|
119
|
+
if FileOperations.is_cache_valid(file_path, max_age_hours):
|
|
120
|
+
logger.info(f"Using cached data from {file_path} (newer than {max_age_hours} hours)")
|
|
121
|
+
cached_data = FileOperations.load_json_file(file_path)
|
|
122
|
+
if cached_data is not None:
|
|
123
|
+
return cached_data
|
|
124
|
+
|
|
125
|
+
# Cache invalid or doesn't exist - fetch fresh data
|
|
126
|
+
file_age = FileOperations.get_file_age(file_path)
|
|
127
|
+
if file_age:
|
|
128
|
+
logger.info(
|
|
129
|
+
f"Cache file {file_path} is {file_age.total_seconds() / 3600:.1f} hours old - fetching new data"
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
logger.info(f"Cache file {file_path} does not exist - fetching new data")
|
|
133
|
+
|
|
134
|
+
data = fetch_fn()
|
|
135
|
+
|
|
136
|
+
# Save to cache if requested
|
|
137
|
+
if save_cache:
|
|
138
|
+
FileOperations.save_json_file(data, file_path)
|
|
139
|
+
|
|
140
|
+
return data
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def search_json_files(
|
|
144
|
+
identifier: str,
|
|
145
|
+
file_paths: List[str],
|
|
146
|
+
match_fn: Callable[[Dict, str], bool],
|
|
147
|
+
) -> Tuple[Optional[Dict], Optional[str]]:
|
|
148
|
+
"""
|
|
149
|
+
Search for an item across multiple JSON files.
|
|
150
|
+
|
|
151
|
+
:param str identifier: Identifier to search for
|
|
152
|
+
:param List[str] file_paths: List of file paths to search
|
|
153
|
+
:param Callable match_fn: Function to determine if an item matches (takes item and identifier)
|
|
154
|
+
:return: Tuple of (found_item, source_file) or (None, None)
|
|
155
|
+
:rtype: Tuple[Optional[Dict], Optional[str]]
|
|
156
|
+
"""
|
|
157
|
+
for file_path in file_paths:
|
|
158
|
+
if not os.path.exists(file_path):
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
result = FileOperations.search_single_json_file(identifier, file_path, match_fn)
|
|
163
|
+
if result:
|
|
164
|
+
return result, file_path
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.debug(f"Error searching {file_path}: {e}")
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
return None, None
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def search_single_json_file(
|
|
173
|
+
identifier: str,
|
|
174
|
+
file_path: str,
|
|
175
|
+
match_fn: Callable[[Dict, str], bool],
|
|
176
|
+
) -> Optional[Dict]:
|
|
177
|
+
"""
|
|
178
|
+
Search for an item in a single JSON file.
|
|
179
|
+
|
|
180
|
+
:param str identifier: Identifier to search for
|
|
181
|
+
:param str file_path: Path to JSON file
|
|
182
|
+
:param Callable match_fn: Function to determine if an item matches
|
|
183
|
+
:return: Matched item or None
|
|
184
|
+
:rtype: Optional[Dict]
|
|
185
|
+
"""
|
|
186
|
+
logger.debug(f"Searching for {identifier} in {file_path}")
|
|
187
|
+
|
|
188
|
+
data = FileOperations.load_json_file(file_path)
|
|
189
|
+
if not isinstance(data, list):
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
# Use generator for memory efficiency
|
|
193
|
+
return next((item for item in data if match_fn(item, identifier)), None)
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def load_cached_findings(
|
|
197
|
+
query_configs: List[Dict[str, Any]],
|
|
198
|
+
progress_callback: Optional[Callable] = None,
|
|
199
|
+
) -> List[Tuple[str, List[Dict], Optional[Exception]]]:
|
|
200
|
+
"""
|
|
201
|
+
Load cached findings from multiple files.
|
|
202
|
+
|
|
203
|
+
:param List[Dict[str, Any]] query_configs: Query configurations with file paths
|
|
204
|
+
:param Optional[Callable] progress_callback: Optional progress callback
|
|
205
|
+
:return: List of (query_type, nodes, error) tuples
|
|
206
|
+
:rtype: List[Tuple[str, List[Dict], Optional[Exception]]]
|
|
207
|
+
"""
|
|
208
|
+
results = []
|
|
209
|
+
|
|
210
|
+
for config in query_configs:
|
|
211
|
+
query_type = config["type"].value
|
|
212
|
+
file_path = config.get("file_path")
|
|
213
|
+
|
|
214
|
+
if progress_callback:
|
|
215
|
+
progress_callback(query_type, "loading")
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
if file_path and os.path.exists(file_path):
|
|
219
|
+
nodes = FileOperations.load_json_file(file_path)
|
|
220
|
+
if nodes is not None:
|
|
221
|
+
logger.info(f"Loaded {len(nodes)} cached {query_type} findings from {file_path}")
|
|
222
|
+
results.append((query_type, nodes, None))
|
|
223
|
+
else:
|
|
224
|
+
logger.warning(f"Failed to load cached data for {query_type}")
|
|
225
|
+
results.append((query_type, [], Exception(f"Failed to load {file_path}")))
|
|
226
|
+
else:
|
|
227
|
+
logger.warning(f"No cached data found for {query_type} at {file_path}")
|
|
228
|
+
results.append((query_type, [], None))
|
|
229
|
+
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logger.error(f"Error loading cached data for {query_type}: {e}")
|
|
232
|
+
results.append((query_type, [], e))
|
|
233
|
+
|
|
234
|
+
if progress_callback:
|
|
235
|
+
progress_callback(query_type, "loaded")
|
|
236
|
+
|
|
237
|
+
return results
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Fetchers for Wiz integration - handles data retrieval and caching."""
|
|
2
|
+
|
|
3
|
+
from regscale.integrations.commercial.wizv2.fetchers.policy_assessment import (
|
|
4
|
+
PolicyAssessmentFetcher,
|
|
5
|
+
WizDataCache,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"PolicyAssessmentFetcher",
|
|
10
|
+
"WizDataCache",
|
|
11
|
+
]
|
|
@@ -8,8 +8,8 @@ import os
|
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
from typing import Dict, List, Optional, Any, Callable
|
|
10
10
|
|
|
11
|
-
from regscale.integrations.commercial.wizv2.
|
|
12
|
-
from regscale.integrations.commercial.wizv2.constants import WizVulnerabilityType, WIZ_POLICY_QUERY
|
|
11
|
+
from regscale.integrations.commercial.wizv2.core.client import run_async_queries
|
|
12
|
+
from regscale.integrations.commercial.wizv2.core.constants import WizVulnerabilityType, WIZ_POLICY_QUERY
|
|
13
13
|
|
|
14
14
|
logger = logging.getLogger("regscale")
|
|
15
15
|
|
|
@@ -127,14 +127,10 @@ class WizApiClient:
|
|
|
127
127
|
"Content-Type": "application/json",
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
def fetch_policy_assessments_async(
|
|
131
|
-
self, wiz_project_id: str, progress_callback: Optional[Callable] = None
|
|
132
|
-
) -> List[Dict[str, Any]]:
|
|
130
|
+
def fetch_policy_assessments_async(self) -> List[Dict[str, Any]]:
|
|
133
131
|
"""
|
|
134
132
|
Fetch policy assessments using async client.
|
|
135
133
|
|
|
136
|
-
:param wiz_project_id: Wiz project ID
|
|
137
|
-
:param progress_callback: Optional progress callback
|
|
138
134
|
:return: List of policy assessment nodes
|
|
139
135
|
"""
|
|
140
136
|
try:
|
|
@@ -204,7 +200,7 @@ class WizApiClient:
|
|
|
204
200
|
logger.debug(f"Filter variant {filter_variant} failed: {e}")
|
|
205
201
|
continue
|
|
206
202
|
|
|
207
|
-
raise
|
|
203
|
+
raise RuntimeError(f"All filter variants failed. Last error: {last_error}")
|
|
208
204
|
|
|
209
205
|
def _create_requests_session(self):
|
|
210
206
|
"""
|
|
@@ -350,7 +346,7 @@ class PolicyAssessmentFetcher:
|
|
|
350
346
|
|
|
351
347
|
:return: List of policy assessment nodes
|
|
352
348
|
"""
|
|
353
|
-
return self.api_client.fetch_policy_assessments_async(
|
|
349
|
+
return self.api_client.fetch_policy_assessments_async()
|
|
354
350
|
|
|
355
351
|
def _fetch_with_requests(self) -> List[Dict[str, Any]]:
|
|
356
352
|
"""
|
|
@@ -9,7 +9,7 @@ from regscale.core.app.utils.parser_utils import safe_datetime_str
|
|
|
9
9
|
from regscale.integrations.scanner_integration import IntegrationFinding
|
|
10
10
|
from regscale.models import Issue
|
|
11
11
|
from regscale.utils.dict_utils import get_value
|
|
12
|
-
from .constants import (
|
|
12
|
+
from .core.constants import (
|
|
13
13
|
get_wiz_issue_queries,
|
|
14
14
|
WizVulnerabilityType,
|
|
15
15
|
)
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Parsers module for Wiz integration - re-exports from main.py for clean imports."""
|
|
4
|
+
|
|
5
|
+
# Import all parser functions from the main parsers module
|
|
6
|
+
from regscale.integrations.commercial.wizv2.parsers.main import (
|
|
7
|
+
collect_components_to_create,
|
|
8
|
+
fetch_wiz_data,
|
|
9
|
+
get_disk_storage,
|
|
10
|
+
get_ip_address,
|
|
11
|
+
get_latest_version,
|
|
12
|
+
get_network_info,
|
|
13
|
+
get_product_ids,
|
|
14
|
+
get_software_name_from_cpe,
|
|
15
|
+
handle_container_image_version,
|
|
16
|
+
handle_provider,
|
|
17
|
+
handle_software_version,
|
|
18
|
+
pull_resource_info_from_props,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"collect_components_to_create",
|
|
23
|
+
"fetch_wiz_data",
|
|
24
|
+
"get_disk_storage",
|
|
25
|
+
"get_ip_address",
|
|
26
|
+
"get_latest_version",
|
|
27
|
+
"get_network_info",
|
|
28
|
+
"get_product_ids",
|
|
29
|
+
"get_software_name_from_cpe",
|
|
30
|
+
"handle_container_image_version",
|
|
31
|
+
"handle_provider",
|
|
32
|
+
"handle_software_version",
|
|
33
|
+
"pull_resource_info_from_props",
|
|
34
|
+
]
|
|
@@ -3,7 +3,7 @@ import logging
|
|
|
3
3
|
from typing import Dict, Optional, Tuple, Union, List, Any
|
|
4
4
|
|
|
5
5
|
from regscale.integrations.commercial.cpe import extract_product_name_and_version
|
|
6
|
-
from regscale.integrations.commercial.wizv2.constants import CONTENT_TYPE
|
|
6
|
+
from regscale.integrations.commercial.wizv2.core.constants import CONTENT_TYPE
|
|
7
7
|
from regscale.integrations.commercial.wizv2.variables import WizVariables
|
|
8
8
|
from regscale.models import regscale_models
|
|
9
9
|
from regscale.utils import PaginatedGraphQLClient
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Processors for Wiz integration - handles business logic and data transformation."""
|
|
2
|
+
|
|
3
|
+
from regscale.integrations.commercial.wizv2.processors.finding import (
|
|
4
|
+
FindingConsolidator,
|
|
5
|
+
FindingToIssueProcessor,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"FindingConsolidator",
|
|
10
|
+
"FindingToIssueProcessor",
|
|
11
|
+
]
|
|
@@ -7,7 +7,7 @@ from typing import Dict, List, Optional, Iterator, Any, Set, Union
|
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
|
|
9
9
|
from regscale.integrations.scanner_integration import IntegrationFinding
|
|
10
|
-
from regscale.integrations.commercial.wizv2.
|
|
10
|
+
from regscale.integrations.commercial.wizv2.compliance.helpers import AssetConsolidator
|
|
11
11
|
from regscale.models import regscale_models
|
|
12
12
|
|
|
13
13
|
logger = logging.getLogger("regscale")
|
|
@@ -6,7 +6,7 @@ from typing import List, Dict, Optional
|
|
|
6
6
|
from regscale.core.app.utils.app_utils import error_and_exit
|
|
7
7
|
from regscale.integrations.commercial.wizv2.WizDataMixin import WizMixin
|
|
8
8
|
from regscale.models.regscale_models.sbom import Sbom
|
|
9
|
-
from regscale.integrations.commercial.wizv2.constants import SBOM_QUERY, SBOM_FILE_PATH
|
|
9
|
+
from regscale.integrations.commercial.wizv2.core.constants import SBOM_QUERY, SBOM_FILE_PATH
|
|
10
10
|
from regscale.utils import get_value
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|