regscale-cli 6.16.0.0__py3-none-any.whl → 6.16.2.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/__init__.py +1 -1
- regscale/core/app/application.py +1 -0
- regscale/core/app/internal/login.py +1 -1
- regscale/core/app/internal/poam_editor.py +1 -1
- regscale/core/app/utils/app_utils.py +1 -1
- regscale/core/app/utils/parser_utils.py +2 -2
- regscale/integrations/commercial/__init__.py +2 -2
- regscale/integrations/commercial/ad.py +1 -1
- regscale/integrations/commercial/azure/intune.py +1 -0
- regscale/integrations/commercial/grype/__init__.py +3 -0
- regscale/integrations/commercial/grype/commands.py +72 -0
- regscale/integrations/commercial/grype/scanner.py +390 -0
- regscale/integrations/commercial/import_all/import_all_cmd.py +2 -2
- regscale/integrations/commercial/nessus/scanner.py +3 -0
- regscale/integrations/commercial/opentext/__init__.py +6 -0
- regscale/integrations/commercial/opentext/commands.py +77 -0
- regscale/integrations/commercial/opentext/scanner.py +449 -85
- regscale/integrations/commercial/sap/sysdig/sysdig_scanner.py +4 -0
- regscale/integrations/commercial/sap/tenable/click.py +1 -1
- regscale/integrations/commercial/sap/tenable/scanner.py +8 -2
- regscale/integrations/commercial/tenablev2/click.py +39 -16
- regscale/integrations/commercial/trivy/__init__.py +5 -0
- regscale/integrations/commercial/trivy/commands.py +74 -0
- regscale/integrations/commercial/trivy/scanner.py +276 -0
- regscale/integrations/commercial/wizv2/click.py +9 -21
- regscale/integrations/commercial/wizv2/scanner.py +2 -1
- regscale/integrations/commercial/wizv2/utils.py +146 -70
- regscale/integrations/jsonl_scanner_integration.py +869 -0
- regscale/integrations/public/fedramp/fedramp_common.py +4 -4
- regscale/integrations/public/fedramp/import_workbook.py +1 -1
- regscale/integrations/public/fedramp/inventory_items.py +3 -3
- regscale/integrations/public/fedramp/poam/scanner.py +51 -44
- regscale/integrations/public/fedramp/ssp_logger.py +6 -6
- regscale/integrations/scanner_integration.py +268 -64
- regscale/models/app_models/mapping.py +3 -3
- regscale/models/integration_models/amazon_models/inspector.py +15 -17
- regscale/models/integration_models/aqua.py +1 -5
- regscale/models/integration_models/cisa_kev_data.json +100 -10
- regscale/models/integration_models/ecr_models/ecr.py +2 -6
- regscale/models/integration_models/{flat_file_importer.py → flat_file_importer/__init__.py} +7 -4
- regscale/models/integration_models/grype_import.py +3 -3
- regscale/models/integration_models/prisma.py +3 -3
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/assets.py +1 -0
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +2 -0
- regscale/models/integration_models/tenable_models/integration.py +46 -10
- regscale/models/integration_models/trivy_import.py +1 -1
- regscale/models/integration_models/xray.py +1 -1
- regscale/models/regscale_models/__init__.py +2 -0
- regscale/models/regscale_models/control_implementation.py +18 -44
- regscale/models/regscale_models/inherited_control.py +61 -0
- regscale/models/regscale_models/issue.py +3 -2
- regscale/models/regscale_models/mixins/parent_cache.py +1 -1
- regscale/models/regscale_models/regscale_model.py +73 -7
- regscale/models/regscale_models/vulnerability.py +61 -8
- {regscale_cli-6.16.0.0.dist-info → regscale_cli-6.16.2.0.dist-info}/METADATA +3 -3
- {regscale_cli-6.16.0.0.dist-info → regscale_cli-6.16.2.0.dist-info}/RECORD +62 -56
- tests/regscale/core/test_logz.py +8 -0
- regscale/integrations/commercial/grype.py +0 -165
- regscale/integrations/commercial/opentext/click.py +0 -99
- regscale/integrations/commercial/trivy.py +0 -162
- {regscale_cli-6.16.0.0.dist-info → regscale_cli-6.16.2.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.16.0.0.dist-info → regscale_cli-6.16.2.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.16.0.0.dist-info → regscale_cli-6.16.2.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.16.0.0.dist-info → regscale_cli-6.16.2.0.dist-info}/top_level.txt +0 -0
|
@@ -84,6 +84,7 @@ class Assets(SynqlyModel):
|
|
|
84
84
|
title=f"{self.integration_name} Assets",
|
|
85
85
|
plan_id=regscale_ssp_id,
|
|
86
86
|
integration_assets=integration_assets,
|
|
87
|
+
asset_count=len(integration_assets),
|
|
87
88
|
)
|
|
88
89
|
|
|
89
90
|
self.logger.info(f"[green]Sync from {self.integration_name} to RegScale completed.")
|
|
@@ -133,6 +133,7 @@ class Vulnerabilities(SynqlyModel):
|
|
|
133
133
|
title=f"{self.integration_name} Vulnerabilities",
|
|
134
134
|
plan_id=regscale_ssp_id,
|
|
135
135
|
integration_assets=integration_assets,
|
|
136
|
+
asset_count=len(integration_assets),
|
|
136
137
|
)
|
|
137
138
|
|
|
138
139
|
if findings:
|
|
@@ -157,6 +158,7 @@ class Vulnerabilities(SynqlyModel):
|
|
|
157
158
|
title=f"{self.integration_name} Vulnerabilities",
|
|
158
159
|
plan_id=regscale_ssp_id,
|
|
159
160
|
integration_findings=integration_findings,
|
|
161
|
+
finding_count=len(integration_findings),
|
|
160
162
|
)
|
|
161
163
|
self.logger.info(f"[green]Sync from {self.integration_name} to RegScale completed.")
|
|
162
164
|
|
|
@@ -3,9 +3,9 @@ This module contains the Tenable SC Integration class that is responsible for fe
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
+
import re
|
|
6
7
|
from typing import Any, Iterator, List, Optional, Tuple
|
|
7
8
|
|
|
8
|
-
from regscale.core.app.application import Application
|
|
9
9
|
from regscale.core.app.utils.app_utils import epoch_to_datetime
|
|
10
10
|
from regscale.integrations.commercial.tenablev2.utils import get_filtered_severities
|
|
11
11
|
from regscale.integrations.integration_override import IntegrationOverride
|
|
@@ -13,6 +13,8 @@ from regscale.integrations.scanner_integration import IntegrationAsset, Integrat
|
|
|
13
13
|
from regscale.models import regscale_models
|
|
14
14
|
from regscale.models.integration_models.tenable_models.models import TenableAsset
|
|
15
15
|
|
|
16
|
+
logger = logging.getLogger("regscale")
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
class SCIntegration(ScannerIntegration):
|
|
18
20
|
"""
|
|
@@ -38,7 +40,7 @@ class SCIntegration(ScannerIntegration):
|
|
|
38
40
|
:param dict kwargs: Additional keyword arguments
|
|
39
41
|
:yields: Iterator[IntegrationAsset]
|
|
40
42
|
"""
|
|
41
|
-
integration_assets = kwargs.get("integration_assets")
|
|
43
|
+
integration_assets = kwargs.get("integration_assets", [])
|
|
42
44
|
yield from integration_assets
|
|
43
45
|
|
|
44
46
|
def fetch_findings(self, *args: Tuple, **kwargs: dict) -> Iterator[IntegrationFinding]:
|
|
@@ -50,7 +52,7 @@ class SCIntegration(ScannerIntegration):
|
|
|
50
52
|
:yields: Iterator[IntegrationFinding]
|
|
51
53
|
|
|
52
54
|
"""
|
|
53
|
-
integration_findings = kwargs.get("integration_findings")
|
|
55
|
+
integration_findings = kwargs.get("integration_findings", [])
|
|
54
56
|
yield from integration_findings
|
|
55
57
|
|
|
56
58
|
def parse_findings(self, vuln: TenableAsset, integration_mapping: Any) -> List[IntegrationFinding]:
|
|
@@ -113,14 +115,34 @@ class SCIntegration(ScannerIntegration):
|
|
|
113
115
|
|
|
114
116
|
validated_match = integration_mapping.field_map_validation(obj=vuln, model_type="asset")
|
|
115
117
|
asset_identifier = validated_match or vuln.dnsName or vuln.dns or vuln.ip
|
|
116
|
-
|
|
118
|
+
cvss_scores = self.get_cvss_scores(vuln)
|
|
117
119
|
severity = self.finding_severity_map.get(vuln.severity.name, regscale_models.IssueSeverity.Low)
|
|
118
120
|
|
|
121
|
+
installed_versions_str = ""
|
|
122
|
+
fixed_versions_str = ""
|
|
123
|
+
package_path_str = ""
|
|
124
|
+
|
|
125
|
+
if "Installed package" in vuln.pluginText:
|
|
126
|
+
installed_versions = re.findall(r"Installed package\s*:\s*(\S+)", vuln.pluginText)
|
|
127
|
+
installed_versions_str = ", ".join(installed_versions)
|
|
128
|
+
if "Fixed package" in vuln.pluginText:
|
|
129
|
+
fixed_versions = re.findall(r"Fixed package\s*:\s*(\S+)", vuln.pluginText)
|
|
130
|
+
fixed_versions_str = ", ".join(fixed_versions)
|
|
131
|
+
if "Path" in vuln.pluginText:
|
|
132
|
+
package_path = re.findall(r"Path\s*:\s*(\S+)", vuln.pluginText)
|
|
133
|
+
package_path_str = ", ".join(package_path)
|
|
134
|
+
if "Installed version" in vuln.pluginText:
|
|
135
|
+
installed_versions = re.findall(r"Installed version\s*:\s*(.+)", vuln.pluginText)
|
|
136
|
+
installed_versions_str = ", ".join(installed_versions)
|
|
137
|
+
if "Fixed version" in vuln.pluginText:
|
|
138
|
+
fixed_versions = re.findall(r"Fixed version\s*:\s*(.+)", vuln.pluginText)
|
|
139
|
+
fixed_versions_str = ", ".join(fixed_versions)
|
|
140
|
+
|
|
119
141
|
return IntegrationFinding(
|
|
120
142
|
control_labels=[], # Add an empty list for control_labels
|
|
121
143
|
category="Tenable SC Vulnerability", # Add a default category
|
|
122
144
|
dns=vuln.dnsName,
|
|
123
|
-
title=getter("title") or (vuln.synopsis or vuln.pluginName),
|
|
145
|
+
title=getter("title") or f"{cve}: {vuln.synopsis}" if cve else (vuln.synopsis or vuln.pluginName),
|
|
124
146
|
description=getter("description") or (vuln.description or vuln.pluginInfo),
|
|
125
147
|
severity=severity,
|
|
126
148
|
status=regscale_models.IssueStatus.Open, # Findings of > Low are considered as FAIL
|
|
@@ -132,8 +154,11 @@ class SCIntegration(ScannerIntegration):
|
|
|
132
154
|
date_last_updated=epoch_to_datetime(vuln.lastSeen),
|
|
133
155
|
recommendation_for_mitigation=vuln.solution,
|
|
134
156
|
cve=cve,
|
|
135
|
-
cvss_v3_score=
|
|
136
|
-
cvss_score=
|
|
157
|
+
cvss_v3_score=cvss_scores.get("cvss_v3_base_score", 0.0),
|
|
158
|
+
cvss_score=cvss_scores.get("cvss_v3_base_score", 0.0),
|
|
159
|
+
cvss_v3_vector=vuln.cvssV3Vector,
|
|
160
|
+
cvss_v2_score=cvss_scores.get("cvss_v2_base_score", 0.0),
|
|
161
|
+
cvss_v2_vector=vuln.cvssVector,
|
|
137
162
|
vpr_score=float(vuln.vprScore) if vuln.vprScore else None,
|
|
138
163
|
comments=vuln.cvssV3Vector,
|
|
139
164
|
plugin_id=vuln.pluginID,
|
|
@@ -143,9 +168,16 @@ class SCIntegration(ScannerIntegration):
|
|
|
143
168
|
basis_for_adjustment="Tenable SC import",
|
|
144
169
|
vulnerability_type="Tenable SC Vulnerability",
|
|
145
170
|
vulnerable_asset=vuln.dnsName,
|
|
171
|
+
build_version="",
|
|
172
|
+
affected_os=vuln.operatingSystem,
|
|
173
|
+
affected_packages=vuln.pluginName,
|
|
174
|
+
package_path=package_path_str,
|
|
175
|
+
installed_versions=installed_versions_str,
|
|
176
|
+
fixed_versions=fixed_versions_str,
|
|
177
|
+
fix_status="",
|
|
146
178
|
)
|
|
147
179
|
|
|
148
|
-
def
|
|
180
|
+
def get_cvss_scores(self, vuln: TenableAsset) -> dict:
|
|
149
181
|
"""
|
|
150
182
|
Returns the CVSS score for the finding
|
|
151
183
|
|
|
@@ -153,10 +185,14 @@ class SCIntegration(ScannerIntegration):
|
|
|
153
185
|
:return: The CVSS score
|
|
154
186
|
:rtype: float
|
|
155
187
|
"""
|
|
188
|
+
res = {}
|
|
156
189
|
try:
|
|
157
|
-
res = float(vuln.cvssV3BaseScore) if vuln.cvssV3BaseScore else 0.0
|
|
190
|
+
res["cvss_v3_base_score"] = float(vuln.cvssV3BaseScore) if vuln.cvssV3BaseScore else 0.0
|
|
191
|
+
res["cvss_v2_base_score"] = float(vuln.baseScore) if vuln.baseScore else 0.0
|
|
158
192
|
except (ValueError, TypeError):
|
|
159
|
-
res = 0.0
|
|
193
|
+
res["cvss_v3_base_score"] = 0.0
|
|
194
|
+
res["cvss_v2_base_score"] = 0.0
|
|
195
|
+
|
|
160
196
|
return res
|
|
161
197
|
|
|
162
198
|
def to_integration_asset(self, asset: TenableAsset, **kwargs: dict) -> IntegrationAsset:
|
|
@@ -221,7 +221,7 @@ class TrivyImport(FlatFileImporter):
|
|
|
221
221
|
"""
|
|
222
222
|
data = self.validater.data
|
|
223
223
|
assets: List[IntegrationAsset] = []
|
|
224
|
-
os_data = self.mapping.get_value(data, "Metadata", {}
|
|
224
|
+
os_data = self.mapping.get_value(data, "Metadata", {}).get("OS", {})
|
|
225
225
|
try:
|
|
226
226
|
assets.append(self.parse_asset(asset=data, os_data=os_data))
|
|
227
227
|
return assets
|
|
@@ -123,7 +123,7 @@ class XRay(FlatFileImporter):
|
|
|
123
123
|
cve=cve,
|
|
124
124
|
vprScore=None,
|
|
125
125
|
tenantsId=0, # Need a way to figure this out programmatically
|
|
126
|
-
title=f"{self.mapping.get_value(dat, 'issue_id'
|
|
126
|
+
title=f"{self.mapping.get_value(dat, 'issue_id') or self.mapping.get_value(dat, 'summary', f'XRay Vulnerability from Import {get_current_datetime()}')} on asset {asset.name}",
|
|
127
127
|
description=self.mapping.get_value(dat, "summary"),
|
|
128
128
|
plugInText=vuln.get("cve"),
|
|
129
129
|
extra_data={
|
|
@@ -8,6 +8,7 @@ from .cci import *
|
|
|
8
8
|
from .change import *
|
|
9
9
|
from .checklist import *
|
|
10
10
|
from .comment import *
|
|
11
|
+
from .compliance_settings import *
|
|
11
12
|
from .component import *
|
|
12
13
|
from .component_mapping import *
|
|
13
14
|
from .control import *
|
|
@@ -26,6 +27,7 @@ from .file import *
|
|
|
26
27
|
from .implementation_objective import *
|
|
27
28
|
from .implementation_option import *
|
|
28
29
|
from .incident import *
|
|
30
|
+
from .inherited_control import *
|
|
29
31
|
from .interconnection import *
|
|
30
32
|
from .issue import *
|
|
31
33
|
from .leveraged_authorization import *
|
|
@@ -13,7 +13,7 @@ from pydantic import ConfigDict, Field
|
|
|
13
13
|
|
|
14
14
|
from regscale.core.app.api import Api
|
|
15
15
|
from regscale.core.app.application import Application
|
|
16
|
-
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
16
|
+
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
17
17
|
from regscale.core.app.utils.catalog_utils.common import parentheses_to_dot
|
|
18
18
|
from regscale.models.regscale_models.implementation_role import ImplementationRole
|
|
19
19
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
@@ -178,7 +178,7 @@ class ControlImplementation(RegScaleModel):
|
|
|
178
178
|
export="/api/{model_slug}/export/{int_id}",
|
|
179
179
|
wizard="/api/{model_slug}/wizard/{int_id}/{str_module}",
|
|
180
180
|
get_date_last_assessed_by_parent="/api/{model_slug}/getDateLastAssessedByParent/{int_record}",
|
|
181
|
-
get_date_last_assessed_by_parent_and_module="/api/{model_slug}/getDateLastAssessedByParentAndModule/{str_module}/{int_record}",
|
|
181
|
+
get_date_last_assessed_by_parent_and_module="/api/{model_slug}/getDateLastAssessedByParentAndModule/{str_module}/{int_record}", # noqa: E501
|
|
182
182
|
get_date_last_assessed_for_all_assets="/api/{model_slug}/getDateLastAssessedForAllAssets/{int_record}",
|
|
183
183
|
graph_controls_by_date="/api/{model_slug}/graphControlsByDate/{year}",
|
|
184
184
|
get_date_last_assessed_by_control="/api/{model_slug}/getDateLastAssessedByControl/{int_control}",
|
|
@@ -203,14 +203,14 @@ class ControlImplementation(RegScaleModel):
|
|
|
203
203
|
quick_update="/api/{model_slug}/quickUpdate/{id}/{str_status}/{int_weight}/{str_user}",
|
|
204
204
|
dashboard_by_parent="/api/{model_slug}/dashboardByParent/{str_group_by}/{int_id}/{str_module}",
|
|
205
205
|
security_control_dashboard="/api/{model_slug}/securityControlDashboard/{str_group_by}/{int_id}",
|
|
206
|
-
dashboard_by_parent_and_catalogue="/api/{model_slug}/dashboardByParentAndCatalogue/{str_group_by}/{int_id}/{int_cat_id}",
|
|
206
|
+
dashboard_by_parent_and_catalogue="/api/{model_slug}/dashboardByParentAndCatalogue/{str_group_by}/{int_id}/{int_cat_id}", # noqa: E501
|
|
207
207
|
group_by_family="/api/{model_slug}/groupByFamily/{int_security_plan}",
|
|
208
208
|
dashboard_by_sp="/api/{model_slug}/dashboardBySP/{str_group_by}/{int_security_plan}",
|
|
209
209
|
report="/api/{model_slug}/report/{str_report}",
|
|
210
210
|
get_by_parent="/api/{model_slug}/getByParent/{int_id}/{str_module}",
|
|
211
211
|
get_count_by_parent="/api/{model_slug}/getCountByParent/{int_id}/{str_module}",
|
|
212
212
|
get_all_asset_controls_by_component="/api/{model_slug}/getAllAssetControlsByComponent/{int_id}",
|
|
213
|
-
drilldown_asset_controls_by_component="/api/{model_slug}/drilldownAssetControlsByComponent/{component_id}/{str_field}/{str_value}",
|
|
213
|
+
drilldown_asset_controls_by_component="/api/{model_slug}/drilldownAssetControlsByComponent/{component_id}/{str_field}/{str_value}", # noqa: E501
|
|
214
214
|
get_control_context="/api/{model_slug}/getControlContext/{int_control_id}/{int_parent_id}/{str_module}",
|
|
215
215
|
)
|
|
216
216
|
|
|
@@ -662,7 +662,6 @@ class ControlImplementation(RegScaleModel):
|
|
|
662
662
|
parent_module: str,
|
|
663
663
|
existing_implementation_dict: dict,
|
|
664
664
|
full_controls: dict,
|
|
665
|
-
partial_controls: dict,
|
|
666
665
|
failing_controls: dict,
|
|
667
666
|
include_not_implemented: Optional[bool] = False,
|
|
668
667
|
) -> None:
|
|
@@ -674,7 +673,6 @@ class ControlImplementation(RegScaleModel):
|
|
|
674
673
|
:param str parent_module: Name of the parent module
|
|
675
674
|
:param dict existing_implementation_dict: Dictionary of existing implementations
|
|
676
675
|
:param dict full_controls: Dictionary of fully implemented controls
|
|
677
|
-
:param dict partial_controls: Dictionary of partially implemented controls
|
|
678
676
|
:param dict failing_controls: Dictionary of failing controls
|
|
679
677
|
:param Optional[bool] include_not_implemented: Whether to include not implemented controls, defaults to False
|
|
680
678
|
:rtype: None
|
|
@@ -688,7 +686,6 @@ class ControlImplementation(RegScaleModel):
|
|
|
688
686
|
parent_module,
|
|
689
687
|
existing_implementation_dict,
|
|
690
688
|
full_controls,
|
|
691
|
-
partial_controls,
|
|
692
689
|
failing_controls,
|
|
693
690
|
user_id,
|
|
694
691
|
include_not_implemented,
|
|
@@ -705,7 +702,6 @@ class ControlImplementation(RegScaleModel):
|
|
|
705
702
|
parent_module: str,
|
|
706
703
|
existing_implementation_dict: dict,
|
|
707
704
|
full_controls: dict,
|
|
708
|
-
partial_controls: dict,
|
|
709
705
|
failing_controls: dict,
|
|
710
706
|
user_id: Optional[str] = None,
|
|
711
707
|
include_not_implemented: Optional[bool] = False,
|
|
@@ -718,7 +714,6 @@ class ControlImplementation(RegScaleModel):
|
|
|
718
714
|
:param str parent_module: Name of the parent module
|
|
719
715
|
:param dict existing_implementation_dict: Dictionary of existing implementations
|
|
720
716
|
:param dict full_controls: Dictionary of fully implemented controls
|
|
721
|
-
:param dict partial_controls: Dictionary of partially implemented controls
|
|
722
717
|
:param dict failing_controls: Dictionary of failing controls
|
|
723
718
|
:param Optional[str] user_id: ID of the user performing the operation, defaults to None
|
|
724
719
|
:param Optional[bool] include_not_implemented: Whether to include not implemented controls, defaults to False
|
|
@@ -729,21 +724,12 @@ class ControlImplementation(RegScaleModel):
|
|
|
729
724
|
to_update = []
|
|
730
725
|
|
|
731
726
|
for control in controls:
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
if len(control.get("otherId", [])) > 0:
|
|
735
|
-
lower_case_control_id = control["otherId"].lower()
|
|
736
|
-
else:
|
|
737
|
-
lower_case_control_id = control["controlId"].lower()
|
|
738
|
-
|
|
739
|
-
status = cls.check_implementation(full_controls, partial_controls, failing_controls, lower_case_control_id)
|
|
727
|
+
lower_case_control_id = control["controlId"].lower()
|
|
728
|
+
status = cls.check_implementation(full_controls, failing_controls, lower_case_control_id)
|
|
740
729
|
if not include_not_implemented and status == ControlImplementationStatus.NotImplemented.value:
|
|
741
730
|
continue
|
|
742
731
|
|
|
743
|
-
|
|
744
|
-
controlid = control["otherId"]
|
|
745
|
-
else:
|
|
746
|
-
controlid = control["controlId"]
|
|
732
|
+
controlid = control.get("controlId")
|
|
747
733
|
|
|
748
734
|
if controlid not in existing_implementation_dict:
|
|
749
735
|
cim = cls.create_new_control_implementation(control, parent_id, parent_module, status, user_id)
|
|
@@ -808,18 +794,11 @@ class ControlImplementation(RegScaleModel):
|
|
|
808
794
|
:param list to_update: List of controls to update
|
|
809
795
|
:param Optional[str] user_id: ID of the user performing the operation, defaults to None
|
|
810
796
|
"""
|
|
811
|
-
existing_imp = existing_implementation_dict[control["controlId"]]
|
|
812
|
-
existing_imp.
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
"dateLastAssessed": get_current_datetime(),
|
|
817
|
-
"lastUpdatedById": user_id,
|
|
818
|
-
"dateLastUpdated": get_current_datetime(),
|
|
819
|
-
}
|
|
820
|
-
)
|
|
821
|
-
|
|
822
|
-
remove_keys(existing_imp, ["createdBy", "systemRole", "controlOwner", "lastUpdatedBy"])
|
|
797
|
+
existing_imp: ControlImplementation = existing_implementation_dict[control["controlId"]]
|
|
798
|
+
existing_imp.status = status
|
|
799
|
+
existing_imp.dateLastAssessed = get_current_datetime()
|
|
800
|
+
existing_imp.lastUpdatedById = user_id
|
|
801
|
+
existing_imp.dateLastUpdated = get_current_datetime()
|
|
823
802
|
|
|
824
803
|
if existing_imp not in to_update:
|
|
825
804
|
to_update.append(existing_imp)
|
|
@@ -860,7 +839,6 @@ class ControlImplementation(RegScaleModel):
|
|
|
860
839
|
@staticmethod
|
|
861
840
|
def check_implementation(
|
|
862
841
|
full_controls: dict,
|
|
863
|
-
partial_controls: dict,
|
|
864
842
|
failing_controls: dict,
|
|
865
843
|
control_id: str,
|
|
866
844
|
) -> str:
|
|
@@ -874,18 +852,14 @@ class ControlImplementation(RegScaleModel):
|
|
|
874
852
|
:return: status of control implementation
|
|
875
853
|
:rtype: str
|
|
876
854
|
"""
|
|
855
|
+
status = ControlImplementationStatus.NotImplemented.value
|
|
877
856
|
if control_id in full_controls.keys():
|
|
878
|
-
logger.debug(f"Found
|
|
879
|
-
|
|
880
|
-
elif control_id in partial_controls.keys():
|
|
881
|
-
logger.debug(f"Found partially implemented control: {control_id}")
|
|
882
|
-
return ControlImplementationStatus.PartiallyImplemented.value
|
|
857
|
+
logger.debug(f"Found control passing compliance check: {control_id}")
|
|
858
|
+
status = ControlImplementationStatus.PartiallyImplemented.value
|
|
883
859
|
elif control_id in failing_controls.keys():
|
|
884
|
-
logger.debug(f"Found failing
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
logger.debug(f"Found not implemented control: {control_id}")
|
|
888
|
-
return ControlImplementationStatus.NotImplemented.value
|
|
860
|
+
logger.debug(f"Found control failing compliance check: {control_id}")
|
|
861
|
+
status = ControlImplementationStatus.InRemediation.value
|
|
862
|
+
return status
|
|
889
863
|
|
|
890
864
|
@classmethod
|
|
891
865
|
def get_sort_position_dict(cls) -> dict:
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Model for Security Plan in the application"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Union
|
|
6
|
+
|
|
7
|
+
from pydantic import ConfigDict, Field, field_validator
|
|
8
|
+
|
|
9
|
+
from regscale.core.app.api import Api
|
|
10
|
+
from regscale.core.app.logz import create_logger
|
|
11
|
+
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
12
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class InheritedControl(RegScaleModel):
|
|
16
|
+
"""
|
|
17
|
+
Inherited Control model
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
_module_slug = "inheritedControls"
|
|
21
|
+
|
|
22
|
+
id: int = 0
|
|
23
|
+
createdBy: Optional[str] = None
|
|
24
|
+
createdById: Optional[str] = None
|
|
25
|
+
dateCreated: Optional[str] = Field(default_factory=get_current_datetime)
|
|
26
|
+
lastUpdatedById: Optional[str] = Field(default_factory=RegScaleModel.get_user_id)
|
|
27
|
+
dateLastUpdated: Optional[str] = Field(default_factory=get_current_datetime)
|
|
28
|
+
isPublic: bool = True
|
|
29
|
+
parentId: int = 0
|
|
30
|
+
parentModule: str = ""
|
|
31
|
+
baseControlId: int = 0
|
|
32
|
+
inheritedControlId: int = 0
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def _get_additional_endpoints() -> ConfigDict:
|
|
36
|
+
"""
|
|
37
|
+
Get additional endpoints for the Inherited Controls model.
|
|
38
|
+
|
|
39
|
+
:return: A dictionary of additional endpoints
|
|
40
|
+
:rtype: ConfigDict
|
|
41
|
+
"""
|
|
42
|
+
return ConfigDict( # type: ignore
|
|
43
|
+
get_all_by_parent="/api/{model_slug}/getAllByParent/{intParentID}/{strModule}",
|
|
44
|
+
get_all_by_control="/api/{model_slug}/getAllByBaseControl/{control_id}",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def get_all_by_control(cls, control_id: int) -> dict:
|
|
49
|
+
"""
|
|
50
|
+
Fetch the Mega API data for the given SSP ID
|
|
51
|
+
|
|
52
|
+
:param int ssp_id: RegScale SSP ID
|
|
53
|
+
:return: Mega API data
|
|
54
|
+
:rtype: dict
|
|
55
|
+
"""
|
|
56
|
+
response = cls._get_api_handler().get(
|
|
57
|
+
endpoint=cls.get_endpoint("get_all_by_control").format(module_slug=cls._module_slug, control_id=control_id)
|
|
58
|
+
)
|
|
59
|
+
if not response.raise_for_status():
|
|
60
|
+
return response.json()
|
|
61
|
+
return {}
|
|
@@ -789,6 +789,7 @@ class Issue(RegScaleModel):
|
|
|
789
789
|
skip = 0
|
|
790
790
|
control_issues: Dict[int, List[OpenIssueDict]] = defaultdict(list)
|
|
791
791
|
logger.info("Fetching open issues for controls and for security plan %i...", plan_id)
|
|
792
|
+
supports_multiple_controls: bool = cls.is_multiple_controls_supported()
|
|
792
793
|
|
|
793
794
|
# Define fields based on version
|
|
794
795
|
fields = """
|
|
@@ -797,7 +798,7 @@ class Issue(RegScaleModel):
|
|
|
797
798
|
otherIdentifier
|
|
798
799
|
{extra_fields}
|
|
799
800
|
""".format(
|
|
800
|
-
extra_fields="controlImplementations { id }" if
|
|
801
|
+
extra_fields="controlImplementations { id }" if supports_multiple_controls else ""
|
|
801
802
|
)
|
|
802
803
|
|
|
803
804
|
while True:
|
|
@@ -822,7 +823,7 @@ class Issue(RegScaleModel):
|
|
|
822
823
|
items = response.get(cls.get_module_string(), {}).get("items", [])
|
|
823
824
|
|
|
824
825
|
for item in items:
|
|
825
|
-
if
|
|
826
|
+
if supports_multiple_controls and item.get("controlImplementations"):
|
|
826
827
|
for control in item.get("controlImplementations", []):
|
|
827
828
|
control_issues[control["id"]].append(
|
|
828
829
|
OpenIssueDict(id=item["id"], otherIdentifier=item["otherIdentifier"])
|
|
@@ -51,7 +51,7 @@ class PlanCacheMixin(Generic[T]):
|
|
|
51
51
|
objects = cls.get_plan_objects(plan_id)
|
|
52
52
|
for obj in objects:
|
|
53
53
|
cls.cache_object(obj)
|
|
54
|
-
logger.
|
|
54
|
+
logger.debug("Cached %s %s objects.", len(objects), cls.__name__)
|
|
55
55
|
|
|
56
56
|
@classmethod
|
|
57
57
|
def get_plan_objects(cls: Type[T], plan_id: int) -> List[T]:
|
|
@@ -53,6 +53,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
53
53
|
_original_data: Optional[Dict[str, Any]] = None
|
|
54
54
|
|
|
55
55
|
# Caching
|
|
56
|
+
_disable_cache: bool = False # flag to disable caching
|
|
56
57
|
_object_cache: ClassVar[Cache] = Cache(maxsize=100000)
|
|
57
58
|
_parent_cache: ClassVar[Cache] = Cache(maxsize=50000)
|
|
58
59
|
_lock_registry: ClassVar[ThreadSafeDict] = ThreadSafeDict()
|
|
@@ -80,9 +81,46 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
80
81
|
super().__init__(*args, **data)
|
|
81
82
|
# Capture initial state after initialization
|
|
82
83
|
self._original_data = self.dict(exclude_unset=True)
|
|
84
|
+
self._disable_cache = self._fetch_disabled_cache_setting()
|
|
85
|
+
if self._disable_cache:
|
|
86
|
+
logger.debug("cache is disabled")
|
|
83
87
|
except Exception as e:
|
|
84
88
|
logger.error(f"Error creating {self.__class__.__name__}: {e} {data}", exc_info=True)
|
|
85
89
|
|
|
90
|
+
def _fetch_disabled_cache_setting(self) -> bool:
|
|
91
|
+
"""
|
|
92
|
+
Check if caching is disabled based on the application config.
|
|
93
|
+
|
|
94
|
+
:return: True if caching is disabled, False otherwise
|
|
95
|
+
:rtype: bool
|
|
96
|
+
"""
|
|
97
|
+
is_disabled = False
|
|
98
|
+
if config := self._get_api_handler().config:
|
|
99
|
+
is_disabled = config.get("disableCache", False)
|
|
100
|
+
return is_disabled
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def disable_cache(cls) -> bool:
|
|
104
|
+
"""
|
|
105
|
+
Disable caching for the model.
|
|
106
|
+
|
|
107
|
+
:return: True if caching is disabled, False otherwise
|
|
108
|
+
:rtype: bool
|
|
109
|
+
"""
|
|
110
|
+
cls._disable_cache = True
|
|
111
|
+
return cls._disable_cache
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def enable_cache(cls) -> bool:
|
|
115
|
+
"""
|
|
116
|
+
Enable caching for the model.
|
|
117
|
+
|
|
118
|
+
:return: True if caching is enabled, False otherwise
|
|
119
|
+
:rtype: bool
|
|
120
|
+
"""
|
|
121
|
+
cls._disable_cache = False
|
|
122
|
+
return cls._disable_cache
|
|
123
|
+
|
|
86
124
|
@classmethod
|
|
87
125
|
def _get_api_handler(cls) -> APIHandler:
|
|
88
126
|
"""
|
|
@@ -169,6 +207,8 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
169
207
|
:return: The cached object if found, None otherwise
|
|
170
208
|
:rtype: Optional[T]
|
|
171
209
|
"""
|
|
210
|
+
if cls._disable_cache:
|
|
211
|
+
return None
|
|
172
212
|
with cls._get_lock(cache_key):
|
|
173
213
|
return cls._object_cache.get(cache_key)
|
|
174
214
|
|
|
@@ -181,6 +221,8 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
181
221
|
:return: None
|
|
182
222
|
:rtype: None
|
|
183
223
|
"""
|
|
224
|
+
if cls._disable_cache:
|
|
225
|
+
return
|
|
184
226
|
try:
|
|
185
227
|
if not obj:
|
|
186
228
|
return
|
|
@@ -229,6 +271,8 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
229
271
|
:return: None
|
|
230
272
|
:rtype: None
|
|
231
273
|
"""
|
|
274
|
+
if cls._disable_cache:
|
|
275
|
+
return
|
|
232
276
|
parent_id = getattr(obj, cls._parent_id_field, None)
|
|
233
277
|
parent_module = getattr(obj, "parentModule", getattr(obj, "parent_module", ""))
|
|
234
278
|
if parent_id and parent_module:
|
|
@@ -252,6 +296,8 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
252
296
|
:return: None
|
|
253
297
|
:rtype: None
|
|
254
298
|
"""
|
|
299
|
+
if cls._disable_cache:
|
|
300
|
+
return
|
|
255
301
|
with cls._get_lock(cache_key):
|
|
256
302
|
for obj in objects:
|
|
257
303
|
cls.cache_object(obj)
|
|
@@ -265,6 +311,8 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
265
311
|
:return: None
|
|
266
312
|
:rtype: None
|
|
267
313
|
"""
|
|
314
|
+
if cls._disable_cache:
|
|
315
|
+
return
|
|
268
316
|
cls._object_cache.clear()
|
|
269
317
|
|
|
270
318
|
@classmethod
|
|
@@ -276,6 +324,8 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
276
324
|
:return: None
|
|
277
325
|
:rtype: None
|
|
278
326
|
"""
|
|
327
|
+
if cls._disable_cache:
|
|
328
|
+
return
|
|
279
329
|
cache_key = cls._get_cache_key(obj)
|
|
280
330
|
with cls._get_lock(cache_key):
|
|
281
331
|
cls._object_cache.delete(cache_key)
|
|
@@ -1058,12 +1108,12 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1058
1108
|
return endpoint
|
|
1059
1109
|
|
|
1060
1110
|
@classmethod
|
|
1061
|
-
def _get_pending_updates(cls) -> Set[int]:
|
|
1111
|
+
def _get_pending_updates(cls) -> Set[Union[int, str]]:
|
|
1062
1112
|
"""
|
|
1063
1113
|
Get the set of pending updates for the class.
|
|
1064
1114
|
|
|
1065
1115
|
:return: Set of pending update IDs
|
|
1066
|
-
:rtype: Set[int]
|
|
1116
|
+
:rtype: Set[Union[int, str]]
|
|
1067
1117
|
"""
|
|
1068
1118
|
class_name = cls.__name__
|
|
1069
1119
|
if class_name not in cls._pending_updates:
|
|
@@ -1138,18 +1188,34 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1138
1188
|
# Handle updates
|
|
1139
1189
|
pending_updates = cls._get_pending_updates()
|
|
1140
1190
|
if pending_updates:
|
|
1141
|
-
logger.
|
|
1142
|
-
objects_to_update = [
|
|
1191
|
+
logger.debug(f"Analyzing {len(pending_updates)} {cls.__name__} objects for bulk update...")
|
|
1192
|
+
objects_to_update = [
|
|
1193
|
+
cls.get_cached_object(cache_key=cache_key)
|
|
1194
|
+
for cache_key in pending_updates
|
|
1195
|
+
if cls.get_cached_object(cache_key=cache_key)
|
|
1196
|
+
]
|
|
1197
|
+
logger.debug(
|
|
1198
|
+
f"{len(objects_to_update)}/{len(pending_updates)} {cls.__name__} objects qualify for bulk update."
|
|
1199
|
+
)
|
|
1143
1200
|
if objects_to_update:
|
|
1201
|
+
logger.info(f"Performing bulk update for {len(objects_to_update)} {cls.__name__} objects...")
|
|
1144
1202
|
result["updated"] = cls.batch_update(items=objects_to_update, progress_context=progress_context)
|
|
1145
1203
|
pending_updates.clear()
|
|
1146
1204
|
|
|
1147
1205
|
# Handle creations
|
|
1148
1206
|
pending_creations = cls._get_pending_creations()
|
|
1149
1207
|
if pending_creations:
|
|
1150
|
-
logger.
|
|
1151
|
-
objects_to_create = [
|
|
1208
|
+
logger.debug(f"Analyzing {len(pending_creations)} {cls.__name__} objects for bulk creation...")
|
|
1209
|
+
objects_to_create = [
|
|
1210
|
+
cls.get_cached_object(cache_key=cache_key)
|
|
1211
|
+
for cache_key in pending_creations
|
|
1212
|
+
if cls.get_cached_object(cache_key=cache_key)
|
|
1213
|
+
]
|
|
1214
|
+
logger.debug(
|
|
1215
|
+
f"{len(objects_to_create)}/{len(pending_creations)} {cls.__name__} objects qualify for bulk creation."
|
|
1216
|
+
)
|
|
1152
1217
|
if objects_to_create:
|
|
1218
|
+
logger.info(f"Performing bulk creation for {len(pending_creations)} {cls.__name__} objects...")
|
|
1153
1219
|
result["created"] = cls.batch_create(items=objects_to_create, progress_context=progress_context)
|
|
1154
1220
|
pending_creations.clear()
|
|
1155
1221
|
|
|
@@ -1437,7 +1503,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1437
1503
|
:rtype: Optional[T]
|
|
1438
1504
|
"""
|
|
1439
1505
|
if response and response.ok:
|
|
1440
|
-
logger.
|
|
1506
|
+
logger.debug(json.dumps(response.json(), indent=4))
|
|
1441
1507
|
return cast(T, cls(**response.json()))
|
|
1442
1508
|
else:
|
|
1443
1509
|
cls.log_response_error(response=response, suppress_error=suppress_error)
|