regscale-cli 6.19.0.1__py3-none-any.whl → 6.19.1.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/utils/app_utils.py +1 -1
- regscale/integrations/commercial/tenablev2/commands.py +34 -4
- regscale/integrations/commercial/tenablev2/sync_compliance.py +550 -0
- regscale/integrations/scanner_integration.py +2 -1
- regscale/models/regscale_models/assessment_plan.py +1 -1
- regscale/models/regscale_models/assessment_result.py +39 -0
- regscale/models/regscale_models/line_of_inquiry.py +2 -2
- regscale/models/regscale_models/software_inventory.py +1 -1
- regscale/models/regscale_models/supply_chain.py +4 -4
- regscale/models/regscale_models/user.py +11 -0
- {regscale_cli-6.19.0.1.dist-info → regscale_cli-6.19.1.0.dist-info}/METADATA +5 -5
- {regscale_cli-6.19.0.1.dist-info → regscale_cli-6.19.1.0.dist-info}/RECORD +17 -15
- {regscale_cli-6.19.0.1.dist-info → regscale_cli-6.19.1.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.19.0.1.dist-info → regscale_cli-6.19.1.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.19.0.1.dist-info → regscale_cli-6.19.1.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.19.0.1.dist-info → regscale_cli-6.19.1.0.dist-info}/top_level.txt +0 -0
regscale/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "6.19.0
|
|
1
|
+
__version__ = "6.19.1.0"
|
|
@@ -642,7 +642,7 @@ def save_to_json(file: Path, data: Any, output_log: bool) -> None:
|
|
|
642
642
|
with open(file, "w", encoding="utf-8") as outfile:
|
|
643
643
|
outfile.write(str(data))
|
|
644
644
|
if output_log:
|
|
645
|
-
logger.info("Data successfully saved to %s", file.
|
|
645
|
+
logger.info("Data successfully saved to %s", file.name)
|
|
646
646
|
|
|
647
647
|
|
|
648
648
|
def save_data_to(file: Path, data: Any, output_log: bool = True, transpose_data: bool = True) -> None:
|
|
@@ -30,12 +30,17 @@ from regscale.integrations.commercial.tenablev2.jsonl_scanner import TenableSCJs
|
|
|
30
30
|
from regscale.integrations.commercial.tenablev2.sc_scanner import SCIntegration
|
|
31
31
|
from regscale.integrations.commercial.tenablev2.variables import TenableVariables
|
|
32
32
|
from regscale.models import regscale_id, regscale_ssp_id
|
|
33
|
-
from regscale.models.app_models.click import
|
|
34
|
-
from regscale.models.regscale_models
|
|
33
|
+
from regscale.models.app_models.click import file_types, hidden_file_path, save_output_to
|
|
34
|
+
from regscale.models.regscale_models import SecurityPlan
|
|
35
35
|
|
|
36
36
|
logger = logging.getLogger("regscale")
|
|
37
37
|
console = Console()
|
|
38
38
|
artifacts_dir = "./artifacts"
|
|
39
|
+
REGSCALE_INC = "RegScale, Inc."
|
|
40
|
+
REGSCALE_CLI = "RegScale CLI"
|
|
41
|
+
FULLY_IMPLEMENTED = "Fully Implemented"
|
|
42
|
+
NOT_IMPLEMENTED = "Not Implemented"
|
|
43
|
+
IN_REMEDIATION = "In Remediation"
|
|
39
44
|
|
|
40
45
|
|
|
41
46
|
# Define a helper function for gen_client to replace the original one
|
|
@@ -146,7 +151,7 @@ def io_sync_assets(regscale_ssp_id: int, tags: List[Tuple[str, str]] = None):
|
|
|
146
151
|
from regscale.integrations.commercial.tenablev2.scanner import TenableIntegration
|
|
147
152
|
|
|
148
153
|
integration = TenableIntegration(plan_id=regscale_ssp_id, tags=tags)
|
|
149
|
-
integration.sync_assets()
|
|
154
|
+
integration.sync_assets(plan_id=regscale_ssp_id)
|
|
150
155
|
|
|
151
156
|
console.print("[bold green]Tenable.io asset synchronization complete.[/bold green]")
|
|
152
157
|
except Exception as e:
|
|
@@ -185,7 +190,7 @@ def io_sync_findings(
|
|
|
185
190
|
from regscale.integrations.commercial.tenablev2.scanner import TenableIntegration
|
|
186
191
|
|
|
187
192
|
integration = TenableIntegration(plan_id=regscale_ssp_id, tags=tags, scan_date=scan_date)
|
|
188
|
-
integration.sync_findings(severity=severity)
|
|
193
|
+
integration.sync_findings(plan_id=regscale_ssp_id, severity=severity)
|
|
189
194
|
|
|
190
195
|
console.print("[bold green]Tenable.io finding synchronization complete.[/bold green]")
|
|
191
196
|
except Exception as e:
|
|
@@ -768,6 +773,31 @@ def sync_jsonl(
|
|
|
768
773
|
logger.error(f"Error in Tenable SC JSONL sync: {str(e)}", exc_info=True)
|
|
769
774
|
|
|
770
775
|
|
|
776
|
+
@io.command(name="sync_compliance_controls")
|
|
777
|
+
@regscale_ssp_id()
|
|
778
|
+
@click.option(
|
|
779
|
+
"--catalog_id",
|
|
780
|
+
type=click.INT,
|
|
781
|
+
help="The ID number from RegScale Catalog that the System Security Plan's controls belong to",
|
|
782
|
+
prompt="Enter RegScale Catalog ID",
|
|
783
|
+
required=True,
|
|
784
|
+
)
|
|
785
|
+
@click.option(
|
|
786
|
+
"--framework",
|
|
787
|
+
required=True,
|
|
788
|
+
type=click.Choice(["800-53", "800-53r5", "CSF", "800-171"], case_sensitive=True),
|
|
789
|
+
help="The framework to use. from Tenable.io frameworks MUST be the same RegScale Catalog of controls",
|
|
790
|
+
)
|
|
791
|
+
@hidden_file_path(help="The file path to load control data instead of fetching from Tenable.io")
|
|
792
|
+
def sync_compliance_data(regscale_ssp_id: int, catalog_id: int, framework: str, offline: Optional[Path] = None):
|
|
793
|
+
"""
|
|
794
|
+
Sync the compliance data from Tenable.io to create control implementations for controls in frameworks.
|
|
795
|
+
"""
|
|
796
|
+
from regscale.integrations.commercial.tenablev2.sync_compliance import sync_compliance_data
|
|
797
|
+
|
|
798
|
+
sync_compliance_data(ssp_id=regscale_ssp_id, catalog_id=catalog_id, framework=framework, offline=offline)
|
|
799
|
+
|
|
800
|
+
|
|
771
801
|
# Add import_nessus to __all__ exports at the end of the file
|
|
772
802
|
__all__ = [
|
|
773
803
|
"tenable",
|
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sync the compliance data from Tenable.io to RegScale
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
|
+
from urllib.parse import urljoin
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
|
|
14
|
+
from regscale.core.app.api import Api
|
|
15
|
+
from regscale.core.app.application import Application
|
|
16
|
+
from regscale.core.app.utils.app_utils import (
|
|
17
|
+
format_dict_to_html,
|
|
18
|
+
get_current_datetime,
|
|
19
|
+
)
|
|
20
|
+
from regscale.models.integration_models.tenable_models.models import AssetCheck
|
|
21
|
+
from regscale.models.regscale_models import ControlImplementation
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger("regscale")
|
|
24
|
+
console = Console()
|
|
25
|
+
artifacts_dir = "./artifacts"
|
|
26
|
+
REGSCALE_INC = "RegScale, Inc."
|
|
27
|
+
REGSCALE_CLI = "RegScale CLI"
|
|
28
|
+
FULLY_IMPLEMENTED = "Fully Implemented"
|
|
29
|
+
NOT_IMPLEMENTED = "Not Implemented"
|
|
30
|
+
IN_REMEDIATION = "In Remediation"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def sync_compliance_data(ssp_id: int, catalog_id: int, framework: str, offline: Optional[Path] = None) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Sync the compliance data from Tenable.io to create control implementations for controls in frameworks
|
|
36
|
+
:param int ssp_id: The ID number from RegScale of the System Security Plan
|
|
37
|
+
:param int catalog_id: The ID number from RegScale Catalog that the System Security Plan's controls belong to
|
|
38
|
+
:param str framework: The framework to use. from Tenable.io frameworks MUST be the same RegScale Catalog of controls
|
|
39
|
+
:param Optional[Path] offline: The file path to load control data instead of fetching from Tenable.io, defaults to None
|
|
40
|
+
:rtype: None
|
|
41
|
+
"""
|
|
42
|
+
logger.info("Note: This command only available for Tenable.io")
|
|
43
|
+
logger.info("Note: This command Requires admin access.")
|
|
44
|
+
app = Application()
|
|
45
|
+
config = app.config
|
|
46
|
+
# we specifically don't gen client here, so we only get the client for Tenable.io as its only supported there
|
|
47
|
+
|
|
48
|
+
compliance_data = _get_compliance_data(config=config, offline=offline) # type: ignore
|
|
49
|
+
|
|
50
|
+
dict_of_frameworks_and_asset_checks: Dict = dict()
|
|
51
|
+
framework_controls: Dict[str, List[str]] = {}
|
|
52
|
+
asset_checks: Dict[str, List[AssetCheck]] = {}
|
|
53
|
+
passing_controls: Dict = dict()
|
|
54
|
+
# partial_passing_controls: Dict = dict()
|
|
55
|
+
failing_controls: Dict = dict()
|
|
56
|
+
for findings in compliance_data:
|
|
57
|
+
asset_check = AssetCheck(**findings)
|
|
58
|
+
for ref in asset_check.reference:
|
|
59
|
+
if ref.framework not in framework_controls:
|
|
60
|
+
framework_controls[ref.framework] = []
|
|
61
|
+
if ref.control not in framework_controls[ref.framework]: # Avoid duplicate controls
|
|
62
|
+
framework_controls[ref.framework].append(ref.control)
|
|
63
|
+
formatted_control_id = convert_control_id(ref.control)
|
|
64
|
+
# sort controls by status
|
|
65
|
+
add_control_to_status_dict(
|
|
66
|
+
control_id=formatted_control_id,
|
|
67
|
+
status=asset_check.status,
|
|
68
|
+
dict_obj=failing_controls,
|
|
69
|
+
desired_status="FAILED",
|
|
70
|
+
)
|
|
71
|
+
add_control_to_status_dict(
|
|
72
|
+
control_id=formatted_control_id,
|
|
73
|
+
status=asset_check.status,
|
|
74
|
+
dict_obj=passing_controls,
|
|
75
|
+
desired_status="PASSED",
|
|
76
|
+
)
|
|
77
|
+
remove_passing_controls_if_in_failed_status(passing=passing_controls, failing=failing_controls)
|
|
78
|
+
if formatted_control_id not in asset_checks:
|
|
79
|
+
asset_checks[formatted_control_id] = [asset_check]
|
|
80
|
+
else:
|
|
81
|
+
asset_checks[formatted_control_id].append(asset_check)
|
|
82
|
+
dict_of_frameworks_and_asset_checks = {
|
|
83
|
+
key: {"controls": framework_controls, "asset_checks": asset_checks} for key in framework_controls.keys()
|
|
84
|
+
}
|
|
85
|
+
logger.info(f"Found {len(dict_of_frameworks_and_asset_checks)} findings to process")
|
|
86
|
+
framework_data = dict_of_frameworks_and_asset_checks.get(framework, None)
|
|
87
|
+
process_compliance_data(
|
|
88
|
+
framework_data=framework_data,
|
|
89
|
+
catalog_id=catalog_id,
|
|
90
|
+
ssp_id=ssp_id,
|
|
91
|
+
framework=framework,
|
|
92
|
+
passing_controls=passing_controls,
|
|
93
|
+
failing_controls=failing_controls,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _get_compliance_data(config: dict, offline: Optional[Path] = None) -> Dict:
|
|
98
|
+
"""
|
|
99
|
+
Get compliance data from Tenable.io
|
|
100
|
+
:param dict config: Configuration dictionary
|
|
101
|
+
:param Optional[Path] offline: File path to load control data instead of fetching from Tenable.io
|
|
102
|
+
:return: Compliance data
|
|
103
|
+
:rtype: Dict
|
|
104
|
+
"""
|
|
105
|
+
from regscale import __version__
|
|
106
|
+
from tenable.io import TenableIO
|
|
107
|
+
|
|
108
|
+
if offline:
|
|
109
|
+
with open(offline.absolute(), "r") as f:
|
|
110
|
+
compliance_data = json.load(f)
|
|
111
|
+
else:
|
|
112
|
+
client = TenableIO(
|
|
113
|
+
url=config["tenableUrl"],
|
|
114
|
+
access_key=config["tenableAccessKey"],
|
|
115
|
+
secret_key=config["tenableSecretKey"],
|
|
116
|
+
vendor=REGSCALE_INC,
|
|
117
|
+
product=REGSCALE_CLI,
|
|
118
|
+
build=__version__,
|
|
119
|
+
)
|
|
120
|
+
compliance_data = client.exports.compliance()
|
|
121
|
+
return compliance_data
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def add_control_to_status_dict(control_id: str, status: str, dict_obj: Dict, desired_status: str) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Add a control to a status dictionary
|
|
127
|
+
:param str control_id: The control id to add to the dictionary
|
|
128
|
+
:param str status: The status of the control
|
|
129
|
+
:param Dict dict_obj: The dictionary to add the control to
|
|
130
|
+
:param str desired_status: The desired status of the control
|
|
131
|
+
:rtype: None
|
|
132
|
+
"""
|
|
133
|
+
friendly_control_id = control_id.lower()
|
|
134
|
+
if status == desired_status and friendly_control_id not in dict_obj:
|
|
135
|
+
dict_obj[friendly_control_id] = desired_status
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def remove_passing_controls_if_in_failed_status(passing: Dict, failing: Dict) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Remove passing controls if they are in failed status
|
|
141
|
+
:param Dict passing: Dictionary of passing controls
|
|
142
|
+
:param Dict failing: Dictionary of failing controls
|
|
143
|
+
:rtype: None
|
|
144
|
+
"""
|
|
145
|
+
to_remove = []
|
|
146
|
+
for k in passing.keys():
|
|
147
|
+
if k in failing.keys():
|
|
148
|
+
to_remove.append(k)
|
|
149
|
+
|
|
150
|
+
for k in to_remove:
|
|
151
|
+
del passing[k]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def process_compliance_data(
|
|
155
|
+
framework_data: Dict,
|
|
156
|
+
catalog_id: int,
|
|
157
|
+
ssp_id: int,
|
|
158
|
+
framework: str,
|
|
159
|
+
passing_controls: Dict,
|
|
160
|
+
failing_controls: Dict,
|
|
161
|
+
) -> None:
|
|
162
|
+
"""
|
|
163
|
+
Processes the compliance data from Tenable.io to create control implementations for controls in frameworks
|
|
164
|
+
:param Dict framework_data: List of tenable.io controls per framework
|
|
165
|
+
:param int catalog_id: The catalog id
|
|
166
|
+
:param int ssp_id: The ssp id
|
|
167
|
+
:param str framework: The framework name
|
|
168
|
+
:param Dict passing_controls: Dictionary of passing controls
|
|
169
|
+
:param Dict failing_controls: Dictionary of failing controls
|
|
170
|
+
:rtype: None
|
|
171
|
+
"""
|
|
172
|
+
if not framework_data:
|
|
173
|
+
return
|
|
174
|
+
framework_controls = framework_data.get("controls", {})
|
|
175
|
+
asset_checks = framework_data.get("asset_checks", {})
|
|
176
|
+
existing_implementation_dict = get_existing_control_implementations(ssp_id)
|
|
177
|
+
catalog_controls = get_controls(catalog_id)
|
|
178
|
+
matched_controls = []
|
|
179
|
+
for tenable_framework, tenable_controls in framework_controls.items():
|
|
180
|
+
logger.info(f"Found {len(tenable_controls)} controls that passed for framework: {tenable_framework}")
|
|
181
|
+
# logger.info(f"tenable_controls: {tenable_controls[0]}") if len(tenable_controls) >0 else None
|
|
182
|
+
if tenable_framework == framework:
|
|
183
|
+
matched_controls = get_matched_controls(tenable_controls, catalog_controls)
|
|
184
|
+
|
|
185
|
+
logger.info(f"Found {len(matched_controls)} controls that matched")
|
|
186
|
+
|
|
187
|
+
control_implementations = create_control_implementations(
|
|
188
|
+
controls=matched_controls,
|
|
189
|
+
parent_id=ssp_id,
|
|
190
|
+
parent_module="securityplans",
|
|
191
|
+
existing_implementation_dict=existing_implementation_dict,
|
|
192
|
+
passing_controls=passing_controls,
|
|
193
|
+
failing_controls=failing_controls,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
logger.info(f"SSP now has {len(control_implementations)} control implementations")
|
|
197
|
+
catalog_controls_dict = {c["id"]: c for c in catalog_controls}
|
|
198
|
+
create_assessments(control_implementations, catalog_controls_dict, asset_checks)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def create_assessments(
|
|
202
|
+
control_implementations: List[Dict],
|
|
203
|
+
catalog_controls_dict: Dict,
|
|
204
|
+
asset_checks: Dict,
|
|
205
|
+
) -> None:
|
|
206
|
+
"""
|
|
207
|
+
Create assessments from control implementations
|
|
208
|
+
:param List[Dict] control_implementations: List of control implementations
|
|
209
|
+
:param Dict catalog_controls_dict: Dictionary of catalog controls
|
|
210
|
+
:param Dict asset_checks: Dictionary of asset checks
|
|
211
|
+
:rtype: None
|
|
212
|
+
:return: None
|
|
213
|
+
"""
|
|
214
|
+
app = Application()
|
|
215
|
+
user_id = app.config.get("userId", "")
|
|
216
|
+
assessments_to_create = []
|
|
217
|
+
for cim in control_implementations:
|
|
218
|
+
control = catalog_controls_dict.get(cim["controlID"], {})
|
|
219
|
+
check = asset_checks.get(control["controlId"].lower())
|
|
220
|
+
assessment = create_assessment_from_cim(cim, user_id, control, check)
|
|
221
|
+
assessments_to_create.append(assessment)
|
|
222
|
+
update_control_implementations(control_implementations, assessments_to_create)
|
|
223
|
+
post_assessments_to_api(assessments_to_create)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def get_control_assessments(control: Dict, assessments_to_create: List[Dict]) -> List[Dict]:
|
|
227
|
+
"""
|
|
228
|
+
Get control assessments
|
|
229
|
+
:param Dict control: Control
|
|
230
|
+
:param List[Dict] assessments_to_create: List of assessments to create
|
|
231
|
+
:return: List of control assessments
|
|
232
|
+
:rtype: List[Dict]
|
|
233
|
+
"""
|
|
234
|
+
return [
|
|
235
|
+
assess
|
|
236
|
+
for assess in assessments_to_create
|
|
237
|
+
if assess["controlID"] == control["id"] and assess["status"] == "Complete"
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def post_assessments_to_api(assessments_to_create: List[Dict]) -> None:
|
|
242
|
+
"""
|
|
243
|
+
Post assessments to the API
|
|
244
|
+
:param List[Dict] assessments_to_create: List of assessments to create
|
|
245
|
+
:rtype: None
|
|
246
|
+
"""
|
|
247
|
+
app = Application()
|
|
248
|
+
api = Api()
|
|
249
|
+
assessment_url = urljoin(app.config.get("domain", ""), "/api/assessments/batchCreate")
|
|
250
|
+
assessment_response = api.post(url=assessment_url, json=assessments_to_create)
|
|
251
|
+
if assessment_response.ok:
|
|
252
|
+
logger.info(f"Created {len(assessment_response.json())} Assessments!")
|
|
253
|
+
else:
|
|
254
|
+
logger.debug(assessment_response.status_code)
|
|
255
|
+
logger.error(f"Failed to insert Assessment.\n{assessment_response.text}")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def update_control_implementations(control_implementations: List[Dict], assessments_to_create: List[Dict]) -> None:
|
|
259
|
+
"""
|
|
260
|
+
Update control implementations with assessments
|
|
261
|
+
:param List[Dict] control_implementations: List of control implementations
|
|
262
|
+
:param List[Dict] assessments_to_create: List of assessments to create
|
|
263
|
+
:rtype: None
|
|
264
|
+
"""
|
|
265
|
+
for control in control_implementations:
|
|
266
|
+
control_assessments = get_control_assessments(control, assessments_to_create)
|
|
267
|
+
if sorted_assessments := sort_assessments(control_assessments):
|
|
268
|
+
update_control_object(control, sorted_assessments)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def update_control_object(control: Dict, sorted_assessments: List[Dict]) -> None:
|
|
272
|
+
"""
|
|
273
|
+
Update control object
|
|
274
|
+
:param Dict control: Control
|
|
275
|
+
:param List[Dict] sorted_assessments: Sorted assessments
|
|
276
|
+
:rtype: None
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
dt_format = "%Y-%m-%d %H:%M:%S"
|
|
280
|
+
app = Application()
|
|
281
|
+
control["dateLastAssessed"] = sorted_assessments[0]["actualFinish"]
|
|
282
|
+
control["lastAssessmentResult"] = sorted_assessments[0]["assessmentResult"]
|
|
283
|
+
if control.get("lastAssessmentResult"):
|
|
284
|
+
control_obj = ControlImplementation(**control)
|
|
285
|
+
if control_obj.lastAssessmentResult == "Fail" and control_obj.status != IN_REMEDIATION:
|
|
286
|
+
control_obj.status = IN_REMEDIATION
|
|
287
|
+
control_obj.plannedImplementationDate = (datetime.now() + timedelta(30)).strftime(dt_format)
|
|
288
|
+
control_obj.stepsToImplement = "n/a"
|
|
289
|
+
elif control_obj.status == IN_REMEDIATION:
|
|
290
|
+
control_obj.plannedImplementationDate = (
|
|
291
|
+
(datetime.now() + timedelta(30)).strftime(dt_format)
|
|
292
|
+
if not control_obj.plannedImplementationDate
|
|
293
|
+
else control_obj.plannedImplementationDate
|
|
294
|
+
)
|
|
295
|
+
control_obj.stepsToImplement = "n/a" if not control_obj.stepsToImplement else control_obj.stepsToImplement
|
|
296
|
+
elif control_obj.lastAssessmentResult == "Pass" and control_obj.status != FULLY_IMPLEMENTED:
|
|
297
|
+
control_obj.status = FULLY_IMPLEMENTED
|
|
298
|
+
ControlImplementation.update(app=app, implementation=control_obj)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def sort_assessments(control_assessments: List[Dict]) -> List[Dict]:
|
|
302
|
+
"""
|
|
303
|
+
Sort assessments by actual finish date
|
|
304
|
+
:param List[Dict] control_assessments: List of control assessments
|
|
305
|
+
:return: Sorted assessments
|
|
306
|
+
:rtype: List[Dict]
|
|
307
|
+
"""
|
|
308
|
+
dt_format = "%Y-%m-%d %H:%M:%S"
|
|
309
|
+
return sorted(
|
|
310
|
+
control_assessments,
|
|
311
|
+
key=lambda x: datetime.strptime(x["actualFinish"], dt_format),
|
|
312
|
+
reverse=True,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def get_assessment_status_from_implementation_status(status: str) -> str:
|
|
317
|
+
"""
|
|
318
|
+
Get the assessment status from the implementation status
|
|
319
|
+
:param str status: Implementation status
|
|
320
|
+
:return: Assessment status
|
|
321
|
+
:rtype: str
|
|
322
|
+
"""
|
|
323
|
+
if status == FULLY_IMPLEMENTED:
|
|
324
|
+
return "Pass"
|
|
325
|
+
if status == IN_REMEDIATION:
|
|
326
|
+
return "Fail"
|
|
327
|
+
else:
|
|
328
|
+
return "N/A"
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def create_assessment_from_cim(cim: Dict, user_id: str, control: Dict, check: List[AssetCheck]) -> Dict:
|
|
332
|
+
"""
|
|
333
|
+
Create an assessment from a control implementation
|
|
334
|
+
:param Dict cim: Control Implementation
|
|
335
|
+
:param str user_id: User ID
|
|
336
|
+
:param Dict control: Control
|
|
337
|
+
:param List[AssetCheck] check: Asset Check
|
|
338
|
+
:return: Assessment
|
|
339
|
+
:rtype: Dict
|
|
340
|
+
"""
|
|
341
|
+
assessment_result = get_assessment_status_from_implementation_status(cim.get("status"))
|
|
342
|
+
summary_dict = check[0].dict() if check else dict()
|
|
343
|
+
summary_dict.pop("reference", None)
|
|
344
|
+
title = summary_dict.get("check_name") if summary_dict else control.get("title")
|
|
345
|
+
html_summary = format_dict_to_html(summary_dict)
|
|
346
|
+
document_reviewed = check[0].audit_file if check else None
|
|
347
|
+
check_name = check[0].check_name if check else None
|
|
348
|
+
methodology = check[0].check_info if check else None
|
|
349
|
+
summary_of_results = check[0].description if check else None
|
|
350
|
+
uuid = check[0].asset_uuid if check and check[0].asset_uuid is not None else None
|
|
351
|
+
title_part = f"{title} - {uuid}" if uuid else f"{title}"
|
|
352
|
+
uuid_title = f"{title_part} Automated Assessment test"
|
|
353
|
+
return {
|
|
354
|
+
"leadAssessorId": user_id,
|
|
355
|
+
"title": uuid_title,
|
|
356
|
+
"assessmentType": "Control Testing",
|
|
357
|
+
"plannedStart": get_current_datetime(),
|
|
358
|
+
"plannedFinish": get_current_datetime(),
|
|
359
|
+
"status": "Complete",
|
|
360
|
+
"assessmentResult": assessment_result if assessment_result else "N/A",
|
|
361
|
+
"controlID": cim["id"],
|
|
362
|
+
"actualFinish": get_current_datetime(),
|
|
363
|
+
"assessmentReport": html_summary if html_summary else "Passed",
|
|
364
|
+
"parentId": cim["id"],
|
|
365
|
+
"parentModule": "controls",
|
|
366
|
+
"assessmentPlan": check_name if check_name else None,
|
|
367
|
+
"documentsReviewed": document_reviewed if document_reviewed else None,
|
|
368
|
+
"methodology": methodology if methodology else None,
|
|
369
|
+
"summaryOfResults": summary_of_results if summary_of_results else None,
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def get_matched_controls(tenable_controls: List[Dict], catalog_controls: List[Dict]) -> List[Dict]:
|
|
374
|
+
"""
|
|
375
|
+
Get controls that match between Tenable and the catalog
|
|
376
|
+
:param List[Dict] tenable_controls: List of controls from Tenable
|
|
377
|
+
:param List[Dict] catalog_controls: List of controls from the catalog
|
|
378
|
+
:return: List of matched controls
|
|
379
|
+
:rtype: List[Dict]
|
|
380
|
+
"""
|
|
381
|
+
matched_controls = []
|
|
382
|
+
for control in tenable_controls:
|
|
383
|
+
formatted_control = convert_control_id(control)
|
|
384
|
+
logger.info(formatted_control)
|
|
385
|
+
for catalog_control in catalog_controls:
|
|
386
|
+
if catalog_control["controlId"].lower() == formatted_control.lower():
|
|
387
|
+
logger.info(f"Catalog Control {formatted_control} matched")
|
|
388
|
+
matched_controls.append(catalog_control)
|
|
389
|
+
break
|
|
390
|
+
return matched_controls
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def create_control_implementations(
|
|
394
|
+
controls: list,
|
|
395
|
+
parent_id: int,
|
|
396
|
+
parent_module: str,
|
|
397
|
+
existing_implementation_dict: Dict,
|
|
398
|
+
passing_controls: Dict,
|
|
399
|
+
failing_controls: Dict,
|
|
400
|
+
) -> List[Dict]:
|
|
401
|
+
"""
|
|
402
|
+
Creates a list of control implementations
|
|
403
|
+
:param list controls: list of controls
|
|
404
|
+
:param int parent_id: parent control id
|
|
405
|
+
:param str parent_module: parent module
|
|
406
|
+
:param Dict existing_implementation_dict: Dictionary of existing control implementations
|
|
407
|
+
:param Dict passing_controls: Dictionary of passing controls
|
|
408
|
+
:param Dict failing_controls: Dictionary of failing controls
|
|
409
|
+
:return: list of control implementations
|
|
410
|
+
:rtype: List[Dict]
|
|
411
|
+
"""
|
|
412
|
+
app = Application()
|
|
413
|
+
api = Api()
|
|
414
|
+
user_id = app.config.get("userId")
|
|
415
|
+
domain = app.config.get("domain")
|
|
416
|
+
control_implementations = []
|
|
417
|
+
to_create = []
|
|
418
|
+
to_update = []
|
|
419
|
+
for control in controls:
|
|
420
|
+
lower_case_control_id = control["controlId"].lower()
|
|
421
|
+
status = check_implementation(
|
|
422
|
+
passing_controls=passing_controls,
|
|
423
|
+
failing_controls=failing_controls,
|
|
424
|
+
control_id=lower_case_control_id,
|
|
425
|
+
)
|
|
426
|
+
if control["controlId"] not in existing_implementation_dict.keys():
|
|
427
|
+
cim = ControlImplementation(
|
|
428
|
+
controlOwnerId=user_id,
|
|
429
|
+
dateLastAssessed=get_current_datetime(),
|
|
430
|
+
status=status,
|
|
431
|
+
controlID=control["id"],
|
|
432
|
+
parentId=parent_id,
|
|
433
|
+
parentModule=parent_module,
|
|
434
|
+
createdById=user_id,
|
|
435
|
+
dateCreated=get_current_datetime(),
|
|
436
|
+
lastUpdatedById=user_id,
|
|
437
|
+
dateLastUpdated=get_current_datetime(),
|
|
438
|
+
).dict()
|
|
439
|
+
cim["controlSource"] = "Baseline"
|
|
440
|
+
to_create.append(cim)
|
|
441
|
+
|
|
442
|
+
else:
|
|
443
|
+
# update existing control implementation data
|
|
444
|
+
existing_imp = existing_implementation_dict.get(control["controlId"])
|
|
445
|
+
existing_imp["status"] = status
|
|
446
|
+
existing_imp["dateLastAssessed"] = get_current_datetime()
|
|
447
|
+
existing_imp["lastUpdatedById"] = user_id
|
|
448
|
+
existing_imp["dateLastUpdated"] = get_current_datetime()
|
|
449
|
+
del existing_imp["createdBy"]
|
|
450
|
+
del existing_imp["systemRole"]
|
|
451
|
+
del existing_imp["controlOwner"]
|
|
452
|
+
del existing_imp["lastUpdatedBy"]
|
|
453
|
+
to_update.append(existing_imp)
|
|
454
|
+
|
|
455
|
+
if len(to_create) > 0:
|
|
456
|
+
ci_url = urljoin(domain, "/api/controlImplementation/batchCreate")
|
|
457
|
+
resp = api.post(url=ci_url, json=to_create)
|
|
458
|
+
if resp.ok:
|
|
459
|
+
control_implementations.extend(resp.json())
|
|
460
|
+
logger.info(f"Created {len(to_create)} Control Implementation(s), Successfully!")
|
|
461
|
+
else:
|
|
462
|
+
resp.raise_for_status()
|
|
463
|
+
if len(to_update) > 0:
|
|
464
|
+
ci_url = urljoin(domain, "/api/controlImplementation/batchUpdate")
|
|
465
|
+
resp = api.post(url=ci_url, json=to_update)
|
|
466
|
+
if resp.ok:
|
|
467
|
+
control_implementations.extend(resp.json())
|
|
468
|
+
logger.info(f"Updated {len(to_update)} Control Implementation(s), Successfully!")
|
|
469
|
+
else:
|
|
470
|
+
resp.raise_for_status()
|
|
471
|
+
return control_implementations
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def check_implementation(passing_controls: Dict, failing_controls: Dict, control_id: str) -> str:
|
|
475
|
+
"""
|
|
476
|
+
Checks the status of a control implementation
|
|
477
|
+
:param Dict passing_controls: Dictionary of passing controls
|
|
478
|
+
:param Dict failing_controls: Dictionary of failing controls
|
|
479
|
+
:param str control_id: control id
|
|
480
|
+
:return: status of control implementation
|
|
481
|
+
:rtype: str
|
|
482
|
+
"""
|
|
483
|
+
if control_id in passing_controls.keys():
|
|
484
|
+
return FULLY_IMPLEMENTED
|
|
485
|
+
elif control_id in failing_controls.keys():
|
|
486
|
+
return IN_REMEDIATION
|
|
487
|
+
else:
|
|
488
|
+
return NOT_IMPLEMENTED
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def convert_control_id(control_id: str) -> str:
|
|
492
|
+
"""
|
|
493
|
+
Convert the control id to a format that can be used in Tenable.io
|
|
494
|
+
:param str control_id: The control id to convert
|
|
495
|
+
:return: The converted control id
|
|
496
|
+
:rtype: str
|
|
497
|
+
"""
|
|
498
|
+
# Convert to lowercase
|
|
499
|
+
control_id = control_id.lower()
|
|
500
|
+
|
|
501
|
+
# Check if there's a parenthesis and replace its content
|
|
502
|
+
if "(" in control_id and ")" in control_id:
|
|
503
|
+
inner_value = control_id.split("(")[1].split(")")[0]
|
|
504
|
+
control_id = control_id.replace(f"({inner_value})", f".{inner_value}")
|
|
505
|
+
|
|
506
|
+
return control_id
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def get_existing_control_implementations(parent_id: int) -> Dict:
|
|
510
|
+
"""
|
|
511
|
+
fetch existing control implementations
|
|
512
|
+
:param int parent_id: parent control id
|
|
513
|
+
:return: Dictionary of existing control implementations
|
|
514
|
+
:rtype: Dict
|
|
515
|
+
"""
|
|
516
|
+
app = Application()
|
|
517
|
+
api = Api()
|
|
518
|
+
domain = app.config.get("domain")
|
|
519
|
+
existing_implementation_dict = {}
|
|
520
|
+
get_url = urljoin(domain, f"/api/controlImplementation/getAllByPlan/{parent_id}")
|
|
521
|
+
response = api.get(get_url)
|
|
522
|
+
if response.ok:
|
|
523
|
+
existing_control_implementations_json = response.json()
|
|
524
|
+
for cim in existing_control_implementations_json:
|
|
525
|
+
existing_implementation_dict[cim["controlName"]] = cim
|
|
526
|
+
logger.info(f"Found {len(existing_implementation_dict)} existing control implementations")
|
|
527
|
+
elif response.status_code == 404:
|
|
528
|
+
logger.info(f"No existing control implementations found for {parent_id}")
|
|
529
|
+
else:
|
|
530
|
+
logger.warning(f"Unable to get existing control implementations. {response.text}")
|
|
531
|
+
|
|
532
|
+
return existing_implementation_dict
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def get_controls(catalog_id: int) -> List[Dict]:
|
|
536
|
+
"""
|
|
537
|
+
Gets all the controls
|
|
538
|
+
:param int catalog_id: catalog id
|
|
539
|
+
:return: list of controls
|
|
540
|
+
:rtype: List[Dict]
|
|
541
|
+
"""
|
|
542
|
+
app = Application()
|
|
543
|
+
api = Api()
|
|
544
|
+
url = urljoin(app.config.get("domain"), f"/api/SecurityControls/getList/{catalog_id}")
|
|
545
|
+
response = api.get(url)
|
|
546
|
+
if response.ok:
|
|
547
|
+
return response.json()
|
|
548
|
+
else:
|
|
549
|
+
response.raise_for_status()
|
|
550
|
+
return []
|
|
@@ -14,7 +14,7 @@ import time
|
|
|
14
14
|
from abc import ABC, abstractmethod
|
|
15
15
|
from collections import defaultdict
|
|
16
16
|
from concurrent.futures import ThreadPoolExecutor
|
|
17
|
-
from typing import Any, Dict, Generic, Iterator, List, Optional, Set, TypeVar, Union
|
|
17
|
+
from typing import Any, Callable, Dict, Generic, Iterator, List, Optional, Set, TypeVar, Union
|
|
18
18
|
|
|
19
19
|
from rich.progress import Progress, TaskID
|
|
20
20
|
|
|
@@ -616,6 +616,7 @@ class ScannerIntegration(ABC):
|
|
|
616
616
|
|
|
617
617
|
# Close Outdated Findings
|
|
618
618
|
close_outdated_findings = True
|
|
619
|
+
closed_count = 0
|
|
619
620
|
|
|
620
621
|
def __init__(self, plan_id: int, tenant_id: int = 1, is_component: bool = False, **kwargs):
|
|
621
622
|
"""
|
|
@@ -4,7 +4,7 @@ from typing import Optional, Union
|
|
|
4
4
|
from pydantic import Field, ConfigDict
|
|
5
5
|
|
|
6
6
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
7
|
-
from regscale.models.regscale_models import RegScaleModel
|
|
7
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class AssesmentPlanArea(str, Enum):
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from pydantic import Field, ConfigDict
|
|
6
|
+
|
|
7
|
+
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
8
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AssessmentResult(RegScaleModel):
|
|
12
|
+
"""Assessment Results Model"""
|
|
13
|
+
|
|
14
|
+
_module_slug = "assessmentresults"
|
|
15
|
+
_unique_fields = ["id"]
|
|
16
|
+
|
|
17
|
+
id: int = 0
|
|
18
|
+
isPublic: bool = True
|
|
19
|
+
uuid: Optional[str] = None
|
|
20
|
+
status: Optional[str] = None
|
|
21
|
+
loqId: Optional[int] = 0
|
|
22
|
+
lineOfInquiry: Optional[str] = None
|
|
23
|
+
requirement: Optional[str] = None
|
|
24
|
+
citation: Optional[str] = None
|
|
25
|
+
weight: Optional[int] = 0
|
|
26
|
+
responsibility: Optional[str] = None
|
|
27
|
+
guidance: Optional[str] = None
|
|
28
|
+
observations: Optional[str] = None
|
|
29
|
+
recommendations: Optional[str] = None
|
|
30
|
+
issuesIdentified: Optional[str] = None
|
|
31
|
+
riskAssessment: Optional[str] = None
|
|
32
|
+
samplingMethodology: Optional[str] = None
|
|
33
|
+
fixedDuringAssessment: bool = False
|
|
34
|
+
bComplete: bool = False
|
|
35
|
+
parentAssessmentId: Optional[int] = 0
|
|
36
|
+
dataDate: Optional[str] = Field(default_factory=get_current_datetime)
|
|
37
|
+
dataString: Optional[datetime] = None
|
|
38
|
+
dataDecimal: Optional[float] = 0.0
|
|
39
|
+
dataBoolean: Optional[bool] = False
|
|
@@ -4,7 +4,7 @@ from typing import Optional, Union
|
|
|
4
4
|
from pydantic import Field, ConfigDict
|
|
5
5
|
|
|
6
6
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
7
|
-
from regscale.models.regscale_models import RegScaleModel
|
|
7
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class LoIType(str, Enum):
|
|
@@ -48,5 +48,5 @@ class LinesOfInquiry(RegScaleModel):
|
|
|
48
48
|
:rtype: ConfigDict
|
|
49
49
|
"""
|
|
50
50
|
return ConfigDict( # type: ignore
|
|
51
|
-
get_all_by_parent="/api/{model_slug}/getAllByParent/{
|
|
51
|
+
get_all_by_parent="/api/{model_slug}/getAllByParent/{intParentID}",
|
|
52
52
|
)
|
|
@@ -9,7 +9,7 @@ from pydantic import ConfigDict, Field
|
|
|
9
9
|
from regscale.core.app.api import Api
|
|
10
10
|
from regscale.core.app.application import Application
|
|
11
11
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
12
|
-
from regscale.models.regscale_models import RegScaleModel
|
|
12
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class SoftwareInventory(RegScaleModel):
|
|
@@ -6,7 +6,7 @@ from typing import Optional, Union
|
|
|
6
6
|
|
|
7
7
|
from pydantic import ConfigDict, Field
|
|
8
8
|
|
|
9
|
-
from .regscale_model import RegScaleModel
|
|
9
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger("regscale")
|
|
12
12
|
|
|
@@ -103,9 +103,9 @@ class SupplyChain(RegScaleModel):
|
|
|
103
103
|
parentModule: Optional[str] = None
|
|
104
104
|
orgId: Optional[int] = None
|
|
105
105
|
facilityId: Optional[int] = None
|
|
106
|
-
contractValue: Optional[
|
|
107
|
-
fundedAmount: Optional[
|
|
108
|
-
actualCosts: Optional[
|
|
106
|
+
contractValue: Optional[float] = 0.0
|
|
107
|
+
fundedAmount: Optional[float] = 0.0
|
|
108
|
+
actualCosts: Optional[float] = 0.0
|
|
109
109
|
scope: Optional[str] = None
|
|
110
110
|
startDate: Optional[str] = None
|
|
111
111
|
endDate: Optional[str] = None
|
|
@@ -175,6 +175,17 @@ class User(RegScaleModel):
|
|
|
175
175
|
response = cls._get_api_handler().get(endpoint=cls.get_endpoint("get_all"))
|
|
176
176
|
return cast(List[T], cls._handle_list_response(response))
|
|
177
177
|
|
|
178
|
+
@classmethod
|
|
179
|
+
def get_roles(cls) -> List["User"]:
|
|
180
|
+
"""
|
|
181
|
+
Get all roles from RegScale
|
|
182
|
+
|
|
183
|
+
:return: List of RegScale roles
|
|
184
|
+
:rtype: dict
|
|
185
|
+
"""
|
|
186
|
+
response = cls._get_api_handler().get(endpoint=cls.get_endpoint("get_roles"))
|
|
187
|
+
return cast(List[T], cls._handle_list_response(response))
|
|
188
|
+
|
|
178
189
|
def assign_role(self, role_id: str) -> bool:
|
|
179
190
|
"""
|
|
180
191
|
Assign a role to a user
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: regscale-cli
|
|
3
|
-
Version: 6.19.0
|
|
3
|
+
Version: 6.19.1.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
|
|
@@ -78,7 +78,7 @@ Requires-Dist: PyMuPDF >=1.24.7 ; extra == 'airflow'
|
|
|
78
78
|
Requires-Dist: PyYAML ==6.0.1 ; extra == 'airflow'
|
|
79
79
|
Requires-Dist: apache-airflow-providers-common-sql ; extra == 'airflow'
|
|
80
80
|
Requires-Dist: apache-airflow-providers-postgres ; extra == 'airflow'
|
|
81
|
-
Requires-Dist: apache-airflow
|
|
81
|
+
Requires-Dist: apache-airflow ==2.10.3 ; extra == 'airflow'
|
|
82
82
|
Requires-Dist: beautifulsoup4 ; extra == 'airflow'
|
|
83
83
|
Requires-Dist: boto3 ==1.35.10 ; extra == 'airflow'
|
|
84
84
|
Requires-Dist: botocore ==1.35.10 ; extra == 'airflow'
|
|
@@ -146,7 +146,7 @@ Requires-Dist: PyYAML ==6.0.1 ; extra == 'airflow-azure'
|
|
|
146
146
|
Requires-Dist: apache-airflow-providers-common-sql ; extra == 'airflow-azure'
|
|
147
147
|
Requires-Dist: apache-airflow-providers-microsoft-azure ; extra == 'airflow-azure'
|
|
148
148
|
Requires-Dist: apache-airflow-providers-postgres ; extra == 'airflow-azure'
|
|
149
|
-
Requires-Dist: apache-airflow
|
|
149
|
+
Requires-Dist: apache-airflow ==2.10.3 ; extra == 'airflow-azure'
|
|
150
150
|
Requires-Dist: azure-storage-blob ; extra == 'airflow-azure'
|
|
151
151
|
Requires-Dist: beautifulsoup4 ; extra == 'airflow-azure'
|
|
152
152
|
Requires-Dist: boto3 ==1.35.10 ; extra == 'airflow-azure'
|
|
@@ -216,7 +216,7 @@ Requires-Dist: PyYAML ==6.0.1 ; extra == 'airflow-sqlserver'
|
|
|
216
216
|
Requires-Dist: apache-airflow-providers-common-sql ; extra == 'airflow-sqlserver'
|
|
217
217
|
Requires-Dist: apache-airflow-providers-microsoft-mssql ; extra == 'airflow-sqlserver'
|
|
218
218
|
Requires-Dist: apache-airflow-providers-postgres ; extra == 'airflow-sqlserver'
|
|
219
|
-
Requires-Dist: apache-airflow
|
|
219
|
+
Requires-Dist: apache-airflow ==2.10.3 ; extra == 'airflow-sqlserver'
|
|
220
220
|
Requires-Dist: beautifulsoup4 ; extra == 'airflow-sqlserver'
|
|
221
221
|
Requires-Dist: boto3 ==1.35.10 ; extra == 'airflow-sqlserver'
|
|
222
222
|
Requires-Dist: botocore ==1.35.10 ; extra == 'airflow-sqlserver'
|
|
@@ -286,7 +286,7 @@ Requires-Dist: PyYAML ==6.0.1 ; extra == 'all'
|
|
|
286
286
|
Requires-Dist: ansible ; extra == 'all'
|
|
287
287
|
Requires-Dist: apache-airflow-providers-common-sql ; extra == 'all'
|
|
288
288
|
Requires-Dist: apache-airflow-providers-postgres ; extra == 'all'
|
|
289
|
-
Requires-Dist: apache-airflow
|
|
289
|
+
Requires-Dist: apache-airflow ==2.10.3 ; extra == 'all'
|
|
290
290
|
Requires-Dist: beautifulsoup4 ; extra == 'all'
|
|
291
291
|
Requires-Dist: boto3 ==1.35.10 ; extra == 'all'
|
|
292
292
|
Requires-Dist: botocore ==1.35.10 ; extra == 'all'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
regscale/__init__.py,sha256=
|
|
1
|
+
regscale/__init__.py,sha256=nj8VCAjjre7hxmOERduZFW-KJyVxE8MitYAHu5V7Sic,25
|
|
2
2
|
regscale/regscale.py,sha256=t4uLMzy9P_KeKazI9ZTQupTkbgh28uMilLSpmzn7adE,30983
|
|
3
3
|
regscale/airflow/__init__.py,sha256=yMwN0Bz4JbM0nl5qY_hPegxo_O2ilhTOL9PY5Njhn-s,270
|
|
4
4
|
regscale/airflow/click_dags.py,sha256=H3SUR5jkvInNMv1gu-VG-Ja_H-kH145CpQYNalWNAbE,4520
|
|
@@ -55,7 +55,7 @@ regscale/core/app/internal/workflow.py,sha256=SpgYk1QyzdilVLOK1fFzaKhdLspumaugf5
|
|
|
55
55
|
regscale/core/app/utils/XMLIR.py,sha256=M_RrCsbjznihatkucCKw6dPgHTPQczXyqIdUXWhuCLI,8328
|
|
56
56
|
regscale/core/app/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
57
|
regscale/core/app/utils/api_handler.py,sha256=T1meKw6Yi3ZAgRbQ1xuKDVh9Q9B8mbMqqN_LrSwIlAM,11765
|
|
58
|
-
regscale/core/app/utils/app_utils.py,sha256=
|
|
58
|
+
regscale/core/app/utils/app_utils.py,sha256=zLEHHHWR4likFQYY_bZsWLTAoRajyyaWrESkotVrFlM,36240
|
|
59
59
|
regscale/core/app/utils/file_utils.py,sha256=URKWVEiR9aFnwoW3-Io7R22tBVeROTC3sX1wOZuhqXw,8912
|
|
60
60
|
regscale/core/app/utils/parser_utils.py,sha256=aBEgcFwbJMD-ARf3wzf-tyWwR6NHvzEcdYcPMm8hGqo,2533
|
|
61
61
|
regscale/core/app/utils/pickle_file_handler.py,sha256=iMdv4N8z00TB5LyPdxIcLKNRpDQVWQ8ZQWAqCKpqmF0,1695
|
|
@@ -111,7 +111,7 @@ regscale/integrations/api_paginator.py,sha256=73rjaNM9mGv8evHAeoObXEjZPg-bJuGPo6
|
|
|
111
111
|
regscale/integrations/api_paginator_example.py,sha256=lEuYI-xEGcjnXuIzbCobCP0YRuukLF0s8S3d382SAH4,12119
|
|
112
112
|
regscale/integrations/integration_override.py,sha256=PH7t_bf-RCe_it3FJ61tlKX5UghqHuSEQNJWDfCamAg,5480
|
|
113
113
|
regscale/integrations/jsonl_scanner_integration.py,sha256=fglDgJN9alUvbJzNYKrlIf_25oqQ8kIU0moE3f9tDMQ,56717
|
|
114
|
-
regscale/integrations/scanner_integration.py,sha256=
|
|
114
|
+
regscale/integrations/scanner_integration.py,sha256=fAHnFQ7twysa6ouo_gIVOR7WbbCHQq6-MQm7r-l9fPQ,134699
|
|
115
115
|
regscale/integrations/variables.py,sha256=A0R76VAeJFj48wHnNgUY-xaD7QKcSmGO9-9cQLlqDJ8,2071
|
|
116
116
|
regscale/integrations/commercial/__init__.py,sha256=BZcCIUdyW027NjOGkcBOT52RPSLrtUv69UEPIkAmq1M,13319
|
|
117
117
|
regscale/integrations/commercial/ad.py,sha256=YXSmK8vRf6yi2GnREGa5GrE6GelhFrLj44SY8AO1pK0,15509
|
|
@@ -210,11 +210,12 @@ regscale/integrations/commercial/synqly/ticketing.py,sha256=6UhmDa0l-GYTJAyc93Hi
|
|
|
210
210
|
regscale/integrations/commercial/synqly/vulnerabilities.py,sha256=B8GwfKVDdoOIikjZO-4KqDNf-ApXlcJNkwmno0FjDOQ,7842
|
|
211
211
|
regscale/integrations/commercial/tenablev2/__init__.py,sha256=UpSY_oww83kz9c7amdbptJKwDB1gAOBQDS-Q9WFp588,295
|
|
212
212
|
regscale/integrations/commercial/tenablev2/authenticate.py,sha256=VPTmxaVCaah2gJYNeU9P1KoQ734ohGQ-wcVy6JfqDTE,1247
|
|
213
|
-
regscale/integrations/commercial/tenablev2/commands.py,sha256=
|
|
213
|
+
regscale/integrations/commercial/tenablev2/commands.py,sha256=zFwCbGAMlvQXZq5Jw1Ii7NRD5xXH8wtFpX09bawXU6A,27473
|
|
214
214
|
regscale/integrations/commercial/tenablev2/jsonl_scanner.py,sha256=zNruS_Oi6ls6lJTUxwdD6jRiXCvVLuGI2nvpF29pvU4,83485
|
|
215
215
|
regscale/integrations/commercial/tenablev2/sc_scanner.py,sha256=Y70eZf_1DM3Np9tlrvZ_OHHjqzHjqrizFcHG8esqLQQ,23220
|
|
216
216
|
regscale/integrations/commercial/tenablev2/scanner.py,sha256=bKm7MmZ_xAjKwNT0x16WvuRbQcip3pXg8xqPNHejgKs,21584
|
|
217
217
|
regscale/integrations/commercial/tenablev2/stig_parsers.py,sha256=01h5ImYMUsjrVHaGgqj5JVBx6Jzlhg06ufu0SL_uBEs,5983
|
|
218
|
+
regscale/integrations/commercial/tenablev2/sync_compliance.py,sha256=xViAWWKI6wAN6OoRVocXWbynKCgAFVki6mbgsKLhM0o,21927
|
|
218
219
|
regscale/integrations/commercial/tenablev2/utils.py,sha256=_MmvcR71PmvH4dQ1k4M-q4PYAg0EE_aQ9w4cUdP7SwE,3359
|
|
219
220
|
regscale/integrations/commercial/tenablev2/variables.py,sha256=CHK8HpSFHMUF4HWj0xosfEQ-RRNDoWdeRvjJok_mKpU,991
|
|
220
221
|
regscale/integrations/commercial/trivy/__init__.py,sha256=fTTABeR1Z3Wei3l-A54SoTPn2mrMQrKLmr9SwTyYLTM,117
|
|
@@ -347,7 +348,8 @@ regscale/models/integration_models/tenable_models/integration.py,sha256=lplL8zmj
|
|
|
347
348
|
regscale/models/integration_models/tenable_models/models.py,sha256=dmG7btkN4YkDWwnfW5Ldc3tWEAGjPiaRgJjrqMOkPEU,15846
|
|
348
349
|
regscale/models/regscale_models/__init__.py,sha256=OmFpksQ7e1P52xkBfP5_CTew1A4JiB65cvNuwchGXWU,1900
|
|
349
350
|
regscale/models/regscale_models/assessment.py,sha256=ekzNlcsfDGBu97PMCi7hBRGbzVgxk7Ij0RfrdGh1Rfw,20440
|
|
350
|
-
regscale/models/regscale_models/assessment_plan.py,sha256=
|
|
351
|
+
regscale/models/regscale_models/assessment_plan.py,sha256=qo2YA5ckSbUKDHnC_2BUc2I9kMTje9Gq-qTCXqvEKCY,1716
|
|
352
|
+
regscale/models/regscale_models/assessment_result.py,sha256=K48yjYKwgY1-d_Y3aQUDcCvaqcTIVYdbKV5Wgicf4Ts,1283
|
|
351
353
|
regscale/models/regscale_models/asset.py,sha256=-ws6o6hRl2sMLNcT8CIw0SQvOwLtrVSeEp5kF80ydAQ,19840
|
|
352
354
|
regscale/models/regscale_models/asset_mapping.py,sha256=HFlkAoPZHy2xPYq28cXuzLpFoP36SI08HTL8mH_Q-uE,6851
|
|
353
355
|
regscale/models/regscale_models/case.py,sha256=hLVTwZXzusnXR9avqh7xSVLJwPJ1rPI_Nla_VAkpZcg,1192
|
|
@@ -387,7 +389,7 @@ regscale/models/regscale_models/inherited_control.py,sha256=RuQJgVyZgfoIrUG_vvwQ
|
|
|
387
389
|
regscale/models/regscale_models/interconnection.py,sha256=B8Y4I6KnN-T_gid08QM_o50OwGZcFfO8SJFY2uB68Ak,1256
|
|
388
390
|
regscale/models/regscale_models/issue.py,sha256=wFt_pWARHYG6YikIPePgmvEVlinDPveKx7VB4zbCFvQ,46791
|
|
389
391
|
regscale/models/regscale_models/leveraged_authorization.py,sha256=OUrL8JQV3r7T3ldHlL6Y_ZLv6KuQIC-3eZW5wZ7XFUk,4192
|
|
390
|
-
regscale/models/regscale_models/line_of_inquiry.py,sha256=
|
|
392
|
+
regscale/models/regscale_models/line_of_inquiry.py,sha256=Uu0lQEhif0W6yTSkJo27GyQGmExSngJvyqGBTr4Q8Fg,1713
|
|
391
393
|
regscale/models/regscale_models/link.py,sha256=lAY4Ig3Menm1EqfcAbVJ7jsCsRO5tWtJIf-9-G9FXT8,6593
|
|
392
394
|
regscale/models/regscale_models/master_assessment.py,sha256=thG3dDTuhKTXxjarLlHlLG-KkpfQOZ7UUVMQFhypIrQ,6758
|
|
393
395
|
regscale/models/regscale_models/meta_data.py,sha256=Fg8rrWSTx3K00QkF4glH9UdY9OFWJ4_UqxleLSSbx8I,2482
|
|
@@ -415,17 +417,17 @@ regscale/models/regscale_models/scan_history.py,sha256=o4e9P2rQlqlLj4mbgSPX44jut
|
|
|
415
417
|
regscale/models/regscale_models/search.py,sha256=rPbFDCnnBRHY5JJv9Ev3_6GjMlkdhUAsaUzC97eE2Ys,1015
|
|
416
418
|
regscale/models/regscale_models/security_control.py,sha256=tob8y9zaMGdITju4ffrCAa5s6qhdchdkuxJdZ6VdkOA,4614
|
|
417
419
|
regscale/models/regscale_models/security_plan.py,sha256=4hBnAqHpjVXd6i1dsO2r2HbPKjeyM-QnMKCmdk1sYGw,7549
|
|
418
|
-
regscale/models/regscale_models/software_inventory.py,sha256=
|
|
420
|
+
regscale/models/regscale_models/software_inventory.py,sha256=FRAIfoUlS0kaX1HQRDyV5q4yxwRHilXbS52NSj6exo0,5555
|
|
419
421
|
regscale/models/regscale_models/stake_holder.py,sha256=JIuDTIky_3acDl-NOMwylTHkppN38JgPDZ1A6wM-BGE,1956
|
|
420
422
|
regscale/models/regscale_models/stig.py,sha256=y-PQuGg3pwDTfsNZGW6anaNAjIZBQoNe7GOLMiT5zfw,26329
|
|
421
|
-
regscale/models/regscale_models/supply_chain.py,sha256=
|
|
423
|
+
regscale/models/regscale_models/supply_chain.py,sha256=tMP0IUt5mrJ1QaV5YtcdV7KW0wPQbKdPO3dX0se0gZo,5154
|
|
422
424
|
regscale/models/regscale_models/system_role.py,sha256=cGKhdekD0ZlT_cuUGDhzED9J46UzWdAVzdoq_TxG0KA,6406
|
|
423
425
|
regscale/models/regscale_models/system_role_external_assignment.py,sha256=vCI-paDP4gARkHyGareBUdkmK0hcp1cDSe7yFdYMSug,1333
|
|
424
426
|
regscale/models/regscale_models/tag.py,sha256=D4n5ABDzjI7u1ukjRyHgmgyVd8iNTNJlQrdi55AhYmM,1130
|
|
425
427
|
regscale/models/regscale_models/tag_mapping.py,sha256=QtafVsWjpBR8BAxRhabk3FL3E4WI5OdCv95fuvNOrZs,657
|
|
426
428
|
regscale/models/regscale_models/task.py,sha256=eKVdR89Gb6M9E2plhK4fru8teQI3zPJpgtJ0f43FOrY,4919
|
|
427
429
|
regscale/models/regscale_models/threat.py,sha256=4TNZcRnTgmlDwBsYu5Pbh9GRd8ZWAtqqr0Xph3uPNAA,7255
|
|
428
|
-
regscale/models/regscale_models/user.py,sha256=
|
|
430
|
+
regscale/models/regscale_models/user.py,sha256=wiU2qKwp3aYtmaHEmTtv8BbMEgFb913dHgc2VnmsAkg,7186
|
|
429
431
|
regscale/models/regscale_models/user_group.py,sha256=vzlXHvPNsgJd38H0R3osi46Oj19QO5oPx0qXntQBKWI,1891
|
|
430
432
|
regscale/models/regscale_models/vulnerability.py,sha256=VKgWMVvAQ74Sq46myYsivGfIeW63dHq3bv3cRCkDcbg,10960
|
|
431
433
|
regscale/models/regscale_models/vulnerability_mapping.py,sha256=cq44xkkCH7rfp0BJxavr4DLiEAHouyyPmi5EaizH6NI,6261
|
|
@@ -500,9 +502,9 @@ tests/regscale/models/test_regscale_model.py,sha256=ZsrEZkC4EtdIsoQuayn1xv2gEGcV
|
|
|
500
502
|
tests/regscale/models/test_report.py,sha256=eiSvS_zS0aVeL0HBvtmHVvEzcfF9ZFVn2twj5g8KttY,970
|
|
501
503
|
tests/regscale/models/test_tenable_integrations.py,sha256=PNJC2Zu6lv1xj7y6e1yOsz5FktSU3PRKb5x3n5YG3w0,4072
|
|
502
504
|
tests/regscale/models/test_user_model.py,sha256=e9olv28qBApgnvK6hFHOgXjUC-pkaV8aGDirEIWASL4,4427
|
|
503
|
-
regscale_cli-6.19.0.
|
|
504
|
-
regscale_cli-6.19.0.
|
|
505
|
-
regscale_cli-6.19.0.
|
|
506
|
-
regscale_cli-6.19.0.
|
|
507
|
-
regscale_cli-6.19.0.
|
|
508
|
-
regscale_cli-6.19.0.
|
|
505
|
+
regscale_cli-6.19.1.0.dist-info/LICENSE,sha256=ytNhYQ9Rmhj_m-EX2pPq9Ld6tH5wrqqDYg-fCf46WDU,1076
|
|
506
|
+
regscale_cli-6.19.1.0.dist-info/METADATA,sha256=eMCQ4kKNLhTBf2WOaVp35o_ZK0k7s13eNZ7OBEFNPy4,30913
|
|
507
|
+
regscale_cli-6.19.1.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
508
|
+
regscale_cli-6.19.1.0.dist-info/entry_points.txt,sha256=cLOaIP1eRv1yZ2u7BvpE3aB4x3kDrDwkpeisKOu33z8,269
|
|
509
|
+
regscale_cli-6.19.1.0.dist-info/top_level.txt,sha256=Uv8VUCAdxRm70bgrD4YNEJUmDhBThad_1aaEFGwRByc,15
|
|
510
|
+
regscale_cli-6.19.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|