regscale-cli 6.25.1.0__py3-none-any.whl → 6.27.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/airflow/hierarchy.py +2 -2
- regscale/core/app/application.py +19 -4
- regscale/core/app/internal/evidence.py +419 -2
- regscale/core/app/internal/login.py +0 -1
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/dev/code_gen.py +24 -20
- 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/sicura/api.py +14 -13
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +49 -39
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- 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 +64 -79
- 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 +161 -165
- 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 +39 -99
- 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 +60 -41
- regscale/integrations/control_matcher.py +377 -0
- regscale/integrations/due_date_handler.py +14 -8
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +1 -0
- regscale/integrations/public/cci_importer.py +37 -38
- regscale/integrations/public/fedramp/click.py +60 -2
- 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/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +277 -153
- regscale/models/integration_models/cisa_kev_data.json +282 -9
- regscale/models/integration_models/nexpose.py +36 -10
- 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/locking.py +12 -8
- regscale/models/platform.py +1 -2
- regscale/models/regscale_models/control_implementation.py +47 -22
- regscale/models/regscale_models/issue.py +256 -95
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/regscale_model.py +6 -1
- regscale/templates/__init__.py +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/METADATA +1 -17
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/RECORD +145 -65
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3731 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +2204 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +350 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1365 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- 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_compliance_report_normalization.py +138 -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 +343 -0
- 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 +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- 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_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1132 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
- 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/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/public/test_fedramp.py +301 -0
- tests/regscale/integrations/test_control_matcher.py +1397 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_issue.py +378 -1
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3543
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/top_level.txt +0 -0
|
@@ -84,6 +84,35 @@ class IssueStatus(str, Enum):
|
|
|
84
84
|
return self.value
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
class IssueIdentification(str, Enum):
|
|
88
|
+
"""Issue Identification"""
|
|
89
|
+
|
|
90
|
+
A123Review = "A-123 Review"
|
|
91
|
+
AssessmentAuditInternal = "Assessment/Audit (Internal)"
|
|
92
|
+
AssessmentAuditExternal = "Assessment/Audit (External)"
|
|
93
|
+
CriticalControlReview = "Critical Control Review"
|
|
94
|
+
FDCCUSGCB = "FDCC/USGCB"
|
|
95
|
+
GAOAudit = "GAO Audit"
|
|
96
|
+
IGAudit = "IG Audit"
|
|
97
|
+
IncidentResponseLessonsLearned = "Incident Response Lessons Learned"
|
|
98
|
+
ITAR = "ITAR"
|
|
99
|
+
PenetrationTest = "Penetration Test"
|
|
100
|
+
RiskAssessment = "Risk Assessment"
|
|
101
|
+
SecurityAuthorization = "Security Authorization"
|
|
102
|
+
SecurityControlAssessment = "Security Control Assessment"
|
|
103
|
+
VulnerabilityAssessment = "Vulnerability Assessment"
|
|
104
|
+
Other = "Other"
|
|
105
|
+
|
|
106
|
+
def __str__(self) -> str:
|
|
107
|
+
"""
|
|
108
|
+
Return the value of the Enum as a string
|
|
109
|
+
|
|
110
|
+
:return: The value of the Enum as a string
|
|
111
|
+
:rtype: str
|
|
112
|
+
"""
|
|
113
|
+
return self.value
|
|
114
|
+
|
|
115
|
+
|
|
87
116
|
class Issue(RegScaleModel):
|
|
88
117
|
"""Issue Model"""
|
|
89
118
|
|
|
@@ -483,7 +512,6 @@ class Issue(RegScaleModel):
|
|
|
483
512
|
|
|
484
513
|
if severity == IssueSeverity.Critical.value:
|
|
485
514
|
days = cls._get_days_for_values(["critical"], config, key)
|
|
486
|
-
start_date = start_date + datetime.timedelta(days=days)
|
|
487
515
|
elif severity == IssueSeverity.High.value:
|
|
488
516
|
days = cls._get_days_for_values(["high"], config, key)
|
|
489
517
|
elif severity == IssueSeverity.Moderate.value:
|
|
@@ -917,103 +945,252 @@ class Issue(RegScaleModel):
|
|
|
917
945
|
"""
|
|
918
946
|
import logging
|
|
919
947
|
|
|
920
|
-
cache_disabled = cls._is_cache_disabled()
|
|
921
|
-
use_cache: bool = not cache_disabled
|
|
922
|
-
|
|
923
948
|
logger = logging.getLogger("regscale")
|
|
949
|
+
|
|
924
950
|
# Check cache first
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
logger.info(f"Using cached open issues data for security plan {plan_id}")
|
|
929
|
-
return cached_data
|
|
930
|
-
|
|
931
|
-
# Performance optimization: Use larger batch size and optimize query
|
|
932
|
-
take = 50 # Increased from 50 to reduce API roundtrips
|
|
933
|
-
skip = 0
|
|
934
|
-
control_issues: Dict[int, List[OpenIssueDict]] = defaultdict(list)
|
|
951
|
+
cached_data = cls._check_cache(plan_id, logger)
|
|
952
|
+
if cached_data is not None:
|
|
953
|
+
return cached_data
|
|
935
954
|
|
|
936
|
-
|
|
937
|
-
|
|
955
|
+
# Fetch open issues from API
|
|
956
|
+
control_issues = cls._fetch_open_issues_from_api(plan_id, is_component, logger)
|
|
957
|
+
|
|
958
|
+
# Cache the results if caching is enabled
|
|
959
|
+
if not cls._is_cache_disabled():
|
|
960
|
+
cls._cache_data(plan_id, control_issues)
|
|
961
|
+
|
|
962
|
+
return control_issues
|
|
963
|
+
|
|
964
|
+
@classmethod
|
|
965
|
+
def _check_cache(cls, plan_id: int, logger) -> Optional[Dict[int, List[OpenIssueDict]]]:
|
|
966
|
+
"""
|
|
967
|
+
Check cache for open issues data
|
|
968
|
+
|
|
969
|
+
:param int plan_id: The ID of the parent
|
|
970
|
+
:param logger: Logger instance
|
|
971
|
+
:return: Cached data if available and valid, None otherwise
|
|
972
|
+
:rtype: Optional[Dict[int, List[OpenIssueDict]]]
|
|
973
|
+
"""
|
|
974
|
+
if cls._is_cache_disabled():
|
|
975
|
+
return None
|
|
976
|
+
|
|
977
|
+
cached_data = cls._get_from_cache(plan_id)
|
|
978
|
+
if cached_data is not None:
|
|
979
|
+
logger.info(f"Using cached open issues data for security plan {plan_id}")
|
|
980
|
+
return cached_data
|
|
981
|
+
|
|
982
|
+
@classmethod
|
|
983
|
+
def _fetch_open_issues_from_api(cls, plan_id: int, is_component: bool, logger) -> Dict[int, List[OpenIssueDict]]:
|
|
984
|
+
"""
|
|
985
|
+
Fetch open issues from API with pagination
|
|
938
986
|
|
|
987
|
+
:param int plan_id: The ID of the parent
|
|
988
|
+
:param bool is_component: Whether parent is a component
|
|
989
|
+
:param logger: Logger instance
|
|
990
|
+
:return: Dictionary of control IDs to open issues
|
|
991
|
+
:rtype: Dict[int, List[OpenIssueDict]]
|
|
992
|
+
"""
|
|
993
|
+
start_time = time.time()
|
|
939
994
|
module_str = "component" if is_component else "security plan"
|
|
940
|
-
logger.info(
|
|
941
|
-
f"Fetching open issues for controls and for {module_str} {plan_id}...",
|
|
942
|
-
)
|
|
943
|
-
supports_multiple_controls: bool = cls.is_multiple_controls_supported()
|
|
995
|
+
logger.info(f"Fetching open issues for controls and for {module_str} {plan_id}...")
|
|
944
996
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
997
|
+
control_issues: Dict[int, List[OpenIssueDict]] = defaultdict(list)
|
|
998
|
+
|
|
999
|
+
try:
|
|
1000
|
+
total_fetched = cls._paginate_and_process_issues(plan_id, is_component, control_issues, logger)
|
|
1001
|
+
cls._log_completion(plan_id, total_fetched, len(control_issues), start_time, logger)
|
|
1002
|
+
except Exception as e:
|
|
1003
|
+
logger.error(f"Error fetching open issues for security plan {plan_id}: {e}")
|
|
1004
|
+
return defaultdict(list)
|
|
1005
|
+
|
|
1006
|
+
return control_issues
|
|
950
1007
|
|
|
1008
|
+
@classmethod
|
|
1009
|
+
def _paginate_and_process_issues(
|
|
1010
|
+
cls,
|
|
1011
|
+
plan_id: int,
|
|
1012
|
+
is_component: bool,
|
|
1013
|
+
control_issues: Dict[int, List[OpenIssueDict]],
|
|
1014
|
+
logger,
|
|
1015
|
+
) -> int:
|
|
1016
|
+
"""
|
|
1017
|
+
Paginate through API results and process issues
|
|
1018
|
+
|
|
1019
|
+
:param int plan_id: The ID of the parent
|
|
1020
|
+
:param bool is_component: Whether parent is a component
|
|
1021
|
+
:param Dict[int, List[OpenIssueDict]] control_issues: Dictionary to populate with results
|
|
1022
|
+
:param logger: Logger instance
|
|
1023
|
+
:return: Total number of items fetched
|
|
1024
|
+
:rtype: int
|
|
1025
|
+
"""
|
|
1026
|
+
take = 50
|
|
1027
|
+
skip = 0
|
|
951
1028
|
total_fetched = 0
|
|
1029
|
+
supports_multiple_controls = cls.is_multiple_controls_supported()
|
|
1030
|
+
fields = cls._get_query_fields(supports_multiple_controls)
|
|
952
1031
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1032
|
+
while True:
|
|
1033
|
+
query = cls._build_query(plan_id, is_component, skip, take, fields)
|
|
1034
|
+
response = cls._get_api_handler().graph(query)
|
|
1035
|
+
|
|
1036
|
+
items = response.get(cls.get_module_string(), {}).get("items", [])
|
|
1037
|
+
total_count = response.get(cls.get_module_string(), {}).get("totalCount", 0)
|
|
1038
|
+
|
|
1039
|
+
cls._log_progress(skip, take, len(items), total_count, logger)
|
|
1040
|
+
cls._process_issue_items(items, supports_multiple_controls, control_issues)
|
|
1041
|
+
|
|
1042
|
+
total_fetched += len(items)
|
|
1043
|
+
|
|
1044
|
+
if not response.get(cls.get_module_string(), {}).get("pageInfo", {}).get("hasNextPage", False):
|
|
1045
|
+
break
|
|
1046
|
+
|
|
1047
|
+
skip += take
|
|
1048
|
+
|
|
1049
|
+
return total_fetched
|
|
1050
|
+
|
|
1051
|
+
@classmethod
|
|
1052
|
+
def _get_query_fields(cls, supports_multiple_controls: bool) -> str:
|
|
1053
|
+
"""
|
|
1054
|
+
Get GraphQL query fields based on control support
|
|
1055
|
+
|
|
1056
|
+
:param bool supports_multiple_controls: Whether multiple controls are supported
|
|
1057
|
+
:return: GraphQL field selection string
|
|
1058
|
+
:rtype: str
|
|
1059
|
+
"""
|
|
1060
|
+
if supports_multiple_controls:
|
|
1061
|
+
return "id, otherIdentifier, integrationFindingId, controlImplementations { id }"
|
|
1062
|
+
return "id, controlId, otherIdentifier, integrationFindingId"
|
|
1063
|
+
|
|
1064
|
+
@classmethod
|
|
1065
|
+
def _build_query(cls, plan_id: int, is_component: bool, skip: int, take: int, fields: str) -> str:
|
|
1066
|
+
"""
|
|
1067
|
+
Build GraphQL query for fetching open issues
|
|
1068
|
+
|
|
1069
|
+
:param int plan_id: The ID of the parent
|
|
1070
|
+
:param bool is_component: Whether parent is a component
|
|
1071
|
+
:param int skip: Number of items to skip
|
|
1072
|
+
:param int take: Number of items to take
|
|
1073
|
+
:param str fields: GraphQL fields to select
|
|
1074
|
+
:return: GraphQL query string
|
|
1075
|
+
:rtype: str
|
|
1076
|
+
"""
|
|
1077
|
+
parent_field = "componentId" if is_component else "securityPlanId"
|
|
1078
|
+
return f"""
|
|
1079
|
+
query GetOpenIssuesByPlanOrComponent {{
|
|
1080
|
+
{cls.get_module_string()}(
|
|
1081
|
+
skip: {skip},
|
|
1082
|
+
take: {take},
|
|
1083
|
+
where: {{
|
|
1084
|
+
{parent_field}: {{eq: {plan_id}}},
|
|
1085
|
+
status: {{eq: "Open"}}
|
|
969
1086
|
}}
|
|
970
|
-
|
|
1087
|
+
) {{
|
|
1088
|
+
items {{ {fields} }}
|
|
1089
|
+
pageInfo {{ hasNextPage }}
|
|
1090
|
+
totalCount
|
|
1091
|
+
}}
|
|
1092
|
+
}}
|
|
1093
|
+
"""
|
|
971
1094
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1095
|
+
@classmethod
|
|
1096
|
+
def _log_progress(cls, skip: int, take: int, items_count: int, total_count: int, logger) -> None:
|
|
1097
|
+
"""
|
|
1098
|
+
Log progress for large datasets
|
|
975
1099
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1100
|
+
:param int skip: Number of items skipped
|
|
1101
|
+
:param int take: Batch size
|
|
1102
|
+
:param int items_count: Number of items in current batch
|
|
1103
|
+
:param int total_count: Total count of items
|
|
1104
|
+
:param logger: Logger instance
|
|
1105
|
+
:rtype: None
|
|
1106
|
+
"""
|
|
1107
|
+
if total_count > 1000:
|
|
1108
|
+
logger.info(
|
|
1109
|
+
f"Processing batch {skip // take + 1} - fetched {items_count} items ({skip + items_count}/{total_count})"
|
|
1110
|
+
)
|
|
981
1111
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1112
|
+
@classmethod
|
|
1113
|
+
def _process_issue_items(
|
|
1114
|
+
cls,
|
|
1115
|
+
items: List[Dict[str, Any]],
|
|
1116
|
+
supports_multiple_controls: bool,
|
|
1117
|
+
control_issues: Dict[int, List[OpenIssueDict]],
|
|
1118
|
+
) -> None:
|
|
1119
|
+
"""
|
|
1120
|
+
Process issue items and populate control_issues dictionary
|
|
988
1121
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1122
|
+
:param List[Dict[str, Any]] items: List of issue items from API
|
|
1123
|
+
:param bool supports_multiple_controls: Whether multiple controls are supported
|
|
1124
|
+
:param Dict[int, List[OpenIssueDict]] control_issues: Dictionary to populate
|
|
1125
|
+
:rtype: None
|
|
1126
|
+
"""
|
|
1127
|
+
for item in items:
|
|
1128
|
+
issue_dict = OpenIssueDict(
|
|
1129
|
+
id=item["id"],
|
|
1130
|
+
otherIdentifier=item.get("otherIdentifier", ""),
|
|
1131
|
+
integrationFindingId=item.get("integrationFindingId", ""),
|
|
1132
|
+
)
|
|
994
1133
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1134
|
+
if supports_multiple_controls:
|
|
1135
|
+
cls._add_issue_to_multiple_controls(item, issue_dict, control_issues)
|
|
1136
|
+
else:
|
|
1137
|
+
cls._add_issue_to_single_control(item, issue_dict, control_issues)
|
|
999
1138
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1139
|
+
@classmethod
|
|
1140
|
+
def _add_issue_to_multiple_controls(
|
|
1141
|
+
cls,
|
|
1142
|
+
item: Dict[str, Any],
|
|
1143
|
+
issue_dict: OpenIssueDict,
|
|
1144
|
+
control_issues: Dict[int, List[OpenIssueDict]],
|
|
1145
|
+
) -> None:
|
|
1146
|
+
"""
|
|
1147
|
+
Add issue to multiple control implementations
|
|
1148
|
+
|
|
1149
|
+
:param Dict[str, Any] item: Issue item from API
|
|
1150
|
+
:param OpenIssueDict issue_dict: Issue dictionary
|
|
1151
|
+
:param Dict[int, List[OpenIssueDict]] control_issues: Dictionary to populate
|
|
1152
|
+
:rtype: None
|
|
1153
|
+
"""
|
|
1154
|
+
if item.get("controlImplementations"):
|
|
1155
|
+
for control in item.get("controlImplementations", []):
|
|
1156
|
+
control_issues[control["id"]].append(issue_dict)
|
|
1004
1157
|
|
|
1158
|
+
@classmethod
|
|
1159
|
+
def _add_issue_to_single_control(
|
|
1160
|
+
cls,
|
|
1161
|
+
item: Dict[str, Any],
|
|
1162
|
+
issue_dict: OpenIssueDict,
|
|
1163
|
+
control_issues: Dict[int, List[OpenIssueDict]],
|
|
1164
|
+
) -> None:
|
|
1165
|
+
"""
|
|
1166
|
+
Add issue to single control
|
|
1167
|
+
|
|
1168
|
+
:param Dict[str, Any] item: Issue item from API
|
|
1169
|
+
:param OpenIssueDict issue_dict: Issue dictionary
|
|
1170
|
+
:param Dict[int, List[OpenIssueDict]] control_issues: Dictionary to populate
|
|
1171
|
+
:rtype: None
|
|
1172
|
+
"""
|
|
1173
|
+
if item.get("controlId"):
|
|
1174
|
+
control_issues[item["controlId"]].append(issue_dict)
|
|
1175
|
+
|
|
1176
|
+
@classmethod
|
|
1177
|
+
def _log_completion(cls, plan_id: int, total_fetched: int, control_count: int, start_time: float, logger) -> None:
|
|
1178
|
+
"""
|
|
1179
|
+
Log completion statistics
|
|
1180
|
+
|
|
1181
|
+
:param int plan_id: The ID of the parent
|
|
1182
|
+
:param int total_fetched: Total number of items fetched
|
|
1183
|
+
:param int control_count: Number of controls with issues
|
|
1184
|
+
:param float start_time: Start time of the operation
|
|
1185
|
+
:param logger: Logger instance
|
|
1186
|
+
:rtype: None
|
|
1187
|
+
"""
|
|
1005
1188
|
elapsed_time = time.time() - start_time
|
|
1006
1189
|
logger.info(
|
|
1007
|
-
f"Finished fetching {total_fetched} open issue(s) for {
|
|
1190
|
+
f"Finished fetching {total_fetched} open issue(s) for {control_count} control(s) "
|
|
1008
1191
|
f"in security plan {plan_id} - took {elapsed_time:.2f} seconds"
|
|
1009
1192
|
)
|
|
1010
1193
|
|
|
1011
|
-
# Cache the results
|
|
1012
|
-
if use_cache:
|
|
1013
|
-
cls._cache_data(plan_id, control_issues)
|
|
1014
|
-
|
|
1015
|
-
return control_issues
|
|
1016
|
-
|
|
1017
1194
|
@classmethod
|
|
1018
1195
|
def get_sort_position_dict(cls) -> Dict[str, int]:
|
|
1019
1196
|
"""
|
|
@@ -1103,36 +1280,20 @@ class Issue(RegScaleModel):
|
|
|
1103
1280
|
}
|
|
1104
1281
|
|
|
1105
1282
|
@classmethod
|
|
1106
|
-
def get_enum_values(cls, field_name: str) -> List[Union[IssueSeverity, IssueStatus, str]]:
|
|
1283
|
+
def get_enum_values(cls, field_name: str) -> List[Union[IssueSeverity, IssueStatus, IssueIdentification, str]]:
|
|
1107
1284
|
"""
|
|
1108
1285
|
Overrides the base method.
|
|
1109
1286
|
|
|
1110
1287
|
:param str field_name: The property name to provide enum values for
|
|
1111
1288
|
:return: List of enum values or strings
|
|
1112
|
-
:rtype: List[Union[IssueSeverity, IssueStatus, str]]
|
|
1289
|
+
:rtype: List[Union[IssueSeverity, IssueStatus, IssueIdentification, str]]
|
|
1113
1290
|
"""
|
|
1114
1291
|
if field_name == "severityLevel":
|
|
1115
1292
|
return [severity.__str__() for severity in IssueSeverity]
|
|
1116
1293
|
if field_name == "status":
|
|
1117
1294
|
return [status.__str__() for status in IssueStatus]
|
|
1118
1295
|
if field_name == "identification":
|
|
1119
|
-
return [
|
|
1120
|
-
"A-123 Review",
|
|
1121
|
-
"Assessment/Audit (External)",
|
|
1122
|
-
"Assessment/Audit (Internal)",
|
|
1123
|
-
"Critical Control Review",
|
|
1124
|
-
"FDCC/USGCB",
|
|
1125
|
-
"GAO Audit",
|
|
1126
|
-
"IG Audit",
|
|
1127
|
-
"Incidnet Response Lessons Learned",
|
|
1128
|
-
"ITAR",
|
|
1129
|
-
"Other",
|
|
1130
|
-
"Penetration Test",
|
|
1131
|
-
"Risk Assessment",
|
|
1132
|
-
"Security Authorization",
|
|
1133
|
-
"Security Control Assessment",
|
|
1134
|
-
"Vulnerability Assessment",
|
|
1135
|
-
]
|
|
1296
|
+
return [identification.__str__() for identification in IssueIdentification]
|
|
1136
1297
|
return cls.get_bool_enums(field_name)
|
|
1137
1298
|
|
|
1138
1299
|
@classmethod
|
|
@@ -60,6 +60,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
60
60
|
|
|
61
61
|
_pending_updates: ClassVar[Dict[str, Set[int]]] = {}
|
|
62
62
|
_pending_creations: ClassVar[Dict[str, Set[str]]] = {}
|
|
63
|
+
_ignore_has_changed: bool = False
|
|
63
64
|
|
|
64
65
|
id: int = 0
|
|
65
66
|
extra_data: Dict[str, Any] = Field(default={}, exclude=True)
|
|
@@ -1309,7 +1310,11 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1309
1310
|
"""
|
|
1310
1311
|
# Check if the model has change tracking and if there are changes
|
|
1311
1312
|
has_change_tracking = hasattr(self, "has_changed") and callable(getattr(self, "has_changed", None))
|
|
1312
|
-
|
|
1313
|
+
|
|
1314
|
+
if hasattr(self, "_ignore_has_changed") and self._ignore_has_changed:
|
|
1315
|
+
should_save = True
|
|
1316
|
+
else:
|
|
1317
|
+
should_save = not has_change_tracking or self.has_changed()
|
|
1313
1318
|
|
|
1314
1319
|
if should_save:
|
|
1315
1320
|
if bulk:
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: regscale-cli
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.27.0.0
|
|
4
4
|
Summary: Command Line Interface (CLI) for bulk processing/loading data into RegScale
|
|
5
5
|
Home-page: https://github.com/RegScale/regscale-cli
|
|
6
6
|
Author: Travis Howerton
|
|
@@ -30,7 +30,6 @@ Requires-Dist: distro ==1.8.0
|
|
|
30
30
|
Requires-Dist: docx ==0.2.4
|
|
31
31
|
Requires-Dist: filelock ~=3.13.1
|
|
32
32
|
Requires-Dist: frontend
|
|
33
|
-
Requires-Dist: future ~=0.18.3
|
|
34
33
|
Requires-Dist: google-api-python-client
|
|
35
34
|
Requires-Dist: google-cloud-asset ~=3.22
|
|
36
35
|
Requires-Dist: google-cloud-securitycenter ~=1.25
|
|
@@ -59,7 +58,6 @@ Requires-Dist: pytest
|
|
|
59
58
|
Requires-Dist: python-dateutil ~=2.9.0
|
|
60
59
|
Requires-Dist: python-docx
|
|
61
60
|
Requires-Dist: python-jwt ==4.1.0
|
|
62
|
-
Requires-Dist: pyxnat ==1.5.*
|
|
63
61
|
Requires-Dist: rapidfuzz ~=3.7
|
|
64
62
|
Requires-Dist: regscale-python-ssp
|
|
65
63
|
Requires-Dist: requests >=2.32.0
|
|
@@ -96,7 +94,6 @@ Requires-Dist: filelock ~=3.13.1 ; extra == 'airflow'
|
|
|
96
94
|
Requires-Dist: flask-appbuilder >=4.0.0 ; extra == 'airflow'
|
|
97
95
|
Requires-Dist: flask >=2.3.2 ; extra == 'airflow'
|
|
98
96
|
Requires-Dist: frontend ; extra == 'airflow'
|
|
99
|
-
Requires-Dist: future ~=0.18.3 ; extra == 'airflow'
|
|
100
97
|
Requires-Dist: google-api-python-client ; extra == 'airflow'
|
|
101
98
|
Requires-Dist: google-cloud-asset ~=3.22 ; extra == 'airflow'
|
|
102
99
|
Requires-Dist: google-cloud-securitycenter ~=1.25 ; extra == 'airflow'
|
|
@@ -140,7 +137,6 @@ Requires-Dist: pytest ; extra == 'airflow'
|
|
|
140
137
|
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'airflow'
|
|
141
138
|
Requires-Dist: python-docx ; extra == 'airflow'
|
|
142
139
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'airflow'
|
|
143
|
-
Requires-Dist: pyxnat ==1.5.* ; extra == 'airflow'
|
|
144
140
|
Requires-Dist: rapidfuzz ~=3.7 ; extra == 'airflow'
|
|
145
141
|
Requires-Dist: regscale-python-ssp ; extra == 'airflow'
|
|
146
142
|
Requires-Dist: requests >=2.32.0 ; extra == 'airflow'
|
|
@@ -180,7 +176,6 @@ Requires-Dist: filelock ~=3.13.1 ; extra == 'airflow-azure'
|
|
|
180
176
|
Requires-Dist: flask-appbuilder >=4.0.0 ; extra == 'airflow-azure'
|
|
181
177
|
Requires-Dist: flask >=2.3.2 ; extra == 'airflow-azure'
|
|
182
178
|
Requires-Dist: frontend ; extra == 'airflow-azure'
|
|
183
|
-
Requires-Dist: future ~=0.18.3 ; extra == 'airflow-azure'
|
|
184
179
|
Requires-Dist: google-api-python-client ; extra == 'airflow-azure'
|
|
185
180
|
Requires-Dist: google-cloud-asset ~=3.22 ; extra == 'airflow-azure'
|
|
186
181
|
Requires-Dist: google-cloud-securitycenter ~=1.25 ; extra == 'airflow-azure'
|
|
@@ -225,7 +220,6 @@ Requires-Dist: pytest ; extra == 'airflow-azure'
|
|
|
225
220
|
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'airflow-azure'
|
|
226
221
|
Requires-Dist: python-docx ; extra == 'airflow-azure'
|
|
227
222
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'airflow-azure'
|
|
228
|
-
Requires-Dist: pyxnat ==1.5.* ; extra == 'airflow-azure'
|
|
229
223
|
Requires-Dist: rapidfuzz ~=3.7 ; extra == 'airflow-azure'
|
|
230
224
|
Requires-Dist: regscale-python-ssp ; extra == 'airflow-azure'
|
|
231
225
|
Requires-Dist: requests >=2.32.0 ; extra == 'airflow-azure'
|
|
@@ -264,7 +258,6 @@ Requires-Dist: filelock ~=3.13.1 ; extra == 'airflow-sqlserver'
|
|
|
264
258
|
Requires-Dist: flask-appbuilder >=4.0.0 ; extra == 'airflow-sqlserver'
|
|
265
259
|
Requires-Dist: flask >=2.3.2 ; extra == 'airflow-sqlserver'
|
|
266
260
|
Requires-Dist: frontend ; extra == 'airflow-sqlserver'
|
|
267
|
-
Requires-Dist: future ~=0.18.3 ; extra == 'airflow-sqlserver'
|
|
268
261
|
Requires-Dist: google-api-python-client ; extra == 'airflow-sqlserver'
|
|
269
262
|
Requires-Dist: google-cloud-asset ~=3.22 ; extra == 'airflow-sqlserver'
|
|
270
263
|
Requires-Dist: google-cloud-securitycenter ~=1.25 ; extra == 'airflow-sqlserver'
|
|
@@ -310,7 +303,6 @@ Requires-Dist: pytest ; extra == 'airflow-sqlserver'
|
|
|
310
303
|
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'airflow-sqlserver'
|
|
311
304
|
Requires-Dist: python-docx ; extra == 'airflow-sqlserver'
|
|
312
305
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'airflow-sqlserver'
|
|
313
|
-
Requires-Dist: pyxnat ==1.5.* ; extra == 'airflow-sqlserver'
|
|
314
306
|
Requires-Dist: rapidfuzz ~=3.7 ; extra == 'airflow-sqlserver'
|
|
315
307
|
Requires-Dist: regscale-python-ssp ; extra == 'airflow-sqlserver'
|
|
316
308
|
Requires-Dist: requests >=2.32.0 ; extra == 'airflow-sqlserver'
|
|
@@ -354,7 +346,6 @@ Requires-Dist: flask-rebar ; extra == 'all'
|
|
|
354
346
|
Requires-Dist: flask-restx ; extra == 'all'
|
|
355
347
|
Requires-Dist: flask >=2.3.2 ; extra == 'all'
|
|
356
348
|
Requires-Dist: frontend ; extra == 'all'
|
|
357
|
-
Requires-Dist: future ~=0.18.3 ; extra == 'all'
|
|
358
349
|
Requires-Dist: google-api-python-client ; extra == 'all'
|
|
359
350
|
Requires-Dist: google-cloud-asset ~=3.22 ; extra == 'all'
|
|
360
351
|
Requires-Dist: google-cloud-securitycenter ~=1.25 ; extra == 'all'
|
|
@@ -400,7 +391,6 @@ Requires-Dist: pytest ; extra == 'all'
|
|
|
400
391
|
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'all'
|
|
401
392
|
Requires-Dist: python-docx ; extra == 'all'
|
|
402
393
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'all'
|
|
403
|
-
Requires-Dist: pyxnat ==1.5.* ; extra == 'all'
|
|
404
394
|
Requires-Dist: rapidfuzz ~=3.7 ; extra == 'all'
|
|
405
395
|
Requires-Dist: regscale-python-ssp ; extra == 'all'
|
|
406
396
|
Requires-Dist: requests >=2.32.0 ; extra == 'all'
|
|
@@ -433,7 +423,6 @@ Requires-Dist: distro ==1.8.0 ; extra == 'ansible'
|
|
|
433
423
|
Requires-Dist: docx ==0.2.4 ; extra == 'ansible'
|
|
434
424
|
Requires-Dist: filelock ~=3.13.1 ; extra == 'ansible'
|
|
435
425
|
Requires-Dist: frontend ; extra == 'ansible'
|
|
436
|
-
Requires-Dist: future ~=0.18.3 ; extra == 'ansible'
|
|
437
426
|
Requires-Dist: google-api-python-client ; extra == 'ansible'
|
|
438
427
|
Requires-Dist: google-cloud-asset ~=3.22 ; extra == 'ansible'
|
|
439
428
|
Requires-Dist: google-cloud-securitycenter ~=1.25 ; extra == 'ansible'
|
|
@@ -463,7 +452,6 @@ Requires-Dist: pytest ; extra == 'ansible'
|
|
|
463
452
|
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'ansible'
|
|
464
453
|
Requires-Dist: python-docx ; extra == 'ansible'
|
|
465
454
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'ansible'
|
|
466
|
-
Requires-Dist: pyxnat ==1.5.* ; extra == 'ansible'
|
|
467
455
|
Requires-Dist: rapidfuzz ~=3.7 ; extra == 'ansible'
|
|
468
456
|
Requires-Dist: regscale-python-ssp ; extra == 'ansible'
|
|
469
457
|
Requires-Dist: requests >=2.32.0 ; extra == 'ansible'
|
|
@@ -499,7 +487,6 @@ Requires-Dist: filelock ~=3.13.1 ; extra == 'dev'
|
|
|
499
487
|
Requires-Dist: flake8 ; extra == 'dev'
|
|
500
488
|
Requires-Dist: freezegun ; extra == 'dev'
|
|
501
489
|
Requires-Dist: frontend ; extra == 'dev'
|
|
502
|
-
Requires-Dist: future ~=0.18.3 ; extra == 'dev'
|
|
503
490
|
Requires-Dist: google-api-python-client ; extra == 'dev'
|
|
504
491
|
Requires-Dist: google-cloud-asset ~=3.22 ; extra == 'dev'
|
|
505
492
|
Requires-Dist: google-cloud-securitycenter ~=1.25 ; extra == 'dev'
|
|
@@ -542,7 +529,6 @@ Requires-Dist: pytest >=5 ; extra == 'dev'
|
|
|
542
529
|
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'dev'
|
|
543
530
|
Requires-Dist: python-docx ; extra == 'dev'
|
|
544
531
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'dev'
|
|
545
|
-
Requires-Dist: pyxnat ==1.5.* ; extra == 'dev'
|
|
546
532
|
Requires-Dist: radon ; extra == 'dev'
|
|
547
533
|
Requires-Dist: rapidfuzz ~=3.7 ; extra == 'dev'
|
|
548
534
|
Requires-Dist: regscale-python-ssp ; extra == 'dev'
|
|
@@ -585,7 +571,6 @@ Requires-Dist: flask-appbuilder ; extra == 'server'
|
|
|
585
571
|
Requires-Dist: flask-rebar ; extra == 'server'
|
|
586
572
|
Requires-Dist: flask-restx ; extra == 'server'
|
|
587
573
|
Requires-Dist: frontend ; extra == 'server'
|
|
588
|
-
Requires-Dist: future ~=0.18.3 ; extra == 'server'
|
|
589
574
|
Requires-Dist: google-api-python-client ; extra == 'server'
|
|
590
575
|
Requires-Dist: google-cloud-asset ~=3.22 ; extra == 'server'
|
|
591
576
|
Requires-Dist: google-cloud-securitycenter ~=1.25 ; extra == 'server'
|
|
@@ -615,7 +600,6 @@ Requires-Dist: pytest ; extra == 'server'
|
|
|
615
600
|
Requires-Dist: python-dateutil ~=2.9.0 ; extra == 'server'
|
|
616
601
|
Requires-Dist: python-docx ; extra == 'server'
|
|
617
602
|
Requires-Dist: python-jwt ==4.1.0 ; extra == 'server'
|
|
618
|
-
Requires-Dist: pyxnat ==1.5.* ; extra == 'server'
|
|
619
603
|
Requires-Dist: rapidfuzz ~=3.7 ; extra == 'server'
|
|
620
604
|
Requires-Dist: regscale-python-ssp ; extra == 'server'
|
|
621
605
|
Requires-Dist: requests >=2.32.0 ; extra == 'server'
|