regscale-cli 6.27.1.0__py3-none-any.whl → 6.27.3.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/core/app/application.py +1 -0
- regscale/core/app/internal/control_editor.py +73 -21
- regscale/core/app/internal/login.py +4 -1
- regscale/core/app/internal/model_editor.py +219 -64
- regscale/core/app/utils/app_utils.py +41 -7
- regscale/core/login.py +21 -4
- regscale/core/utils/date.py +77 -1
- regscale/integrations/commercial/aws/scanner.py +7 -3
- regscale/integrations/commercial/microsoft_defender/defender_api.py +1 -1
- regscale/integrations/commercial/sicura/api.py +65 -29
- regscale/integrations/commercial/sicura/scanner.py +36 -7
- regscale/integrations/commercial/synqly/query_builder.py +4 -1
- regscale/integrations/commercial/tenablev2/commands.py +4 -4
- regscale/integrations/commercial/tenablev2/scanner.py +1 -2
- regscale/integrations/commercial/wizv2/scanner.py +40 -16
- regscale/integrations/control_matcher.py +78 -23
- regscale/integrations/public/cci_importer.py +400 -9
- regscale/integrations/public/csam/csam.py +572 -763
- regscale/integrations/public/csam/csam_agency_defined.py +179 -0
- regscale/integrations/public/csam/csam_common.py +154 -0
- regscale/integrations/public/csam/csam_controls.py +432 -0
- regscale/integrations/public/csam/csam_poam.py +124 -0
- regscale/integrations/public/fedramp/click.py +17 -4
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +271 -62
- regscale/integrations/public/fedramp/poam/scanner.py +74 -7
- regscale/integrations/scanner_integration.py +16 -1
- regscale/models/integration_models/aqua.py +2 -2
- regscale/models/integration_models/cisa_kev_data.json +121 -18
- regscale/models/integration_models/flat_file_importer/__init__.py +4 -6
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +35 -2
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +41 -12
- regscale/models/platform.py +3 -0
- regscale/models/regscale_models/__init__.py +5 -0
- regscale/models/regscale_models/component.py +1 -1
- regscale/models/regscale_models/control_implementation.py +55 -24
- regscale/models/regscale_models/organization.py +3 -0
- regscale/models/regscale_models/regscale_model.py +17 -5
- regscale/models/regscale_models/security_plan.py +1 -0
- regscale/regscale.py +11 -1
- {regscale_cli-6.27.1.0.dist-info → regscale_cli-6.27.3.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.1.0.dist-info → regscale_cli-6.27.3.0.dist-info}/RECORD +53 -49
- tests/regscale/core/test_login.py +171 -4
- tests/regscale/integrations/commercial/test_sicura.py +0 -1
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +86 -0
- tests/regscale/integrations/public/test_cci.py +596 -1
- tests/regscale/integrations/test_control_matcher.py +24 -0
- tests/regscale/models/test_control_implementation.py +118 -3
- {regscale_cli-6.27.1.0.dist-info → regscale_cli-6.27.3.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.1.0.dist-info → regscale_cli-6.27.3.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.1.0.dist-info → regscale_cli-6.27.3.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.1.0.dist-info → regscale_cli-6.27.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Integrates CSAM into RegScale"""
|
|
4
|
+
|
|
5
|
+
# standard python imports
|
|
6
|
+
import logging
|
|
7
|
+
from typing import List, Optional, Tuple, Any, Dict
|
|
8
|
+
from rich.progress import track
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from regscale.core.app.application import Application
|
|
11
|
+
from regscale.models.regscale_models import (
|
|
12
|
+
Catalog,
|
|
13
|
+
ControlImplementation,
|
|
14
|
+
InheritedControl,
|
|
15
|
+
Inheritance,
|
|
16
|
+
SecurityControl,
|
|
17
|
+
SecurityPlan,
|
|
18
|
+
)
|
|
19
|
+
from regscale.integrations.control_matcher import ControlMatcher
|
|
20
|
+
from regscale.models.regscale_models.form_field_value import FormFieldValue
|
|
21
|
+
from regscale.integrations.public.csam.csam_common import (
|
|
22
|
+
retrieve_from_csam,
|
|
23
|
+
retrieve_ssps_custom_form_map,
|
|
24
|
+
CSAM_FIELD_NAME,
|
|
25
|
+
SSP_BASIC_TAB,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
FULLY_IMPLEMENTED = "Fully Implemented"
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger("regscale")
|
|
31
|
+
console = Console()
|
|
32
|
+
|
|
33
|
+
####################################################################################################
|
|
34
|
+
#
|
|
35
|
+
# IMPORT SSP / POAM FROM DoJ's CSAM GRC
|
|
36
|
+
# CSAM API Docs: https://csam.dhs.gov/CSAM/api/docs/index.html (required PIV)
|
|
37
|
+
#
|
|
38
|
+
####################################################################################################
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def import_csam_controls(import_ids: Optional[List[int]] = None):
|
|
42
|
+
"""
|
|
43
|
+
Import Controls from CSAM
|
|
44
|
+
|
|
45
|
+
:param list import_ids: Filtered list of SSPs
|
|
46
|
+
:return: None
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# Get existing ssps by CSAM Id
|
|
50
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
51
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
52
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
plans = import_ids if import_ids else list(ssp_map.keys())
|
|
56
|
+
|
|
57
|
+
# Find the Catalogs
|
|
58
|
+
rev5_catalog_id, rev4_catalog_id = get_catalogs()
|
|
59
|
+
|
|
60
|
+
# Get the list of controls for each catalog
|
|
61
|
+
rev5_controls = SecurityControl.get_list_by_catalog(catalog_id=rev5_catalog_id)
|
|
62
|
+
rev4_controls = SecurityControl.get_list_by_catalog(catalog_id=rev4_catalog_id)
|
|
63
|
+
|
|
64
|
+
control_implementations = []
|
|
65
|
+
for regscale_ssp_id in plans:
|
|
66
|
+
results = []
|
|
67
|
+
system_id = ssp_map.get(regscale_ssp_id)
|
|
68
|
+
|
|
69
|
+
# Get the Implementation for AC-1
|
|
70
|
+
# Check the controlSet
|
|
71
|
+
# Match the catalog
|
|
72
|
+
imp = retrieve_from_csam(
|
|
73
|
+
csam_endpoint=f"/CSAM/api/v1/systems/{system_id}/controls/AC-1",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Get the controls
|
|
77
|
+
if imp[0].get("controlSet") in ["NIST 800-53 Rev4", "NIST 800-53 Rev5"]:
|
|
78
|
+
results = retrieve_controls(
|
|
79
|
+
csam_id=system_id,
|
|
80
|
+
controls=rev4_controls if imp[0].get("controlSet") == "NIST 800-53 Rev4" else rev5_controls,
|
|
81
|
+
regscale_id=regscale_ssp_id,
|
|
82
|
+
)
|
|
83
|
+
else:
|
|
84
|
+
logger.warning(
|
|
85
|
+
f"System framework {imp.get('controlSet')} \
|
|
86
|
+
for system {system_id} is not supported"
|
|
87
|
+
)
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
if not results:
|
|
91
|
+
logger.warning(f"No controls found for system id: {system_id}")
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
# Build the controls
|
|
95
|
+
control_implementations = build_implementations(results=results, regscale_id=regscale_ssp_id)
|
|
96
|
+
|
|
97
|
+
# Save the control implementations
|
|
98
|
+
for index in track(
|
|
99
|
+
range(len(control_implementations)),
|
|
100
|
+
description=f"Saving {len(control_implementations)} control implementations...",
|
|
101
|
+
):
|
|
102
|
+
control_implementation = control_implementations[index]
|
|
103
|
+
control_implementation.create() if control_implementation.id == 0 else control_implementation.save()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def build_implementations(results: list, regscale_id: int) -> list:
|
|
107
|
+
"""
|
|
108
|
+
Build out the control implementations
|
|
109
|
+
from the results returned from CSAM
|
|
110
|
+
|
|
111
|
+
:param list results: records from CSAM
|
|
112
|
+
:param int regscale_id: RegScale SSP Id
|
|
113
|
+
:return: list of ControlImplementation objects
|
|
114
|
+
:return_type: list
|
|
115
|
+
"""
|
|
116
|
+
matcher = ControlMatcher()
|
|
117
|
+
control_implementations = []
|
|
118
|
+
|
|
119
|
+
# Loop through the results and create or update the controls
|
|
120
|
+
for index in track(
|
|
121
|
+
range(len(results)),
|
|
122
|
+
description=f"Importing {len(results)} controls for system id: {regscale_id}...",
|
|
123
|
+
):
|
|
124
|
+
result = results[index]
|
|
125
|
+
|
|
126
|
+
# Skip if not implemented
|
|
127
|
+
if not result["statedImplementationStatus"]:
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
# Match existing controlImplementation
|
|
131
|
+
implementation = matcher.find_control_implementation(
|
|
132
|
+
control_id=result["controlId"], parent_id=regscale_id, parent_module="securityplans"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Set values
|
|
136
|
+
status_lst = [FULLY_IMPLEMENTED if result["statedImplementationStatus"] == "Implemented" else "Not Implemented"]
|
|
137
|
+
|
|
138
|
+
status = FULLY_IMPLEMENTED if result["statedImplementationStatus"] == "Implemented" else "Not Implemented"
|
|
139
|
+
|
|
140
|
+
responsibility = (
|
|
141
|
+
result["applicability"]
|
|
142
|
+
if result["applicability"] in ["Hybrid", "Inherited"]
|
|
143
|
+
else "Provider (System Specific)"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
impl = result["implementationStatement"]
|
|
147
|
+
|
|
148
|
+
if implementation:
|
|
149
|
+
# Update it
|
|
150
|
+
implementation.status_lst = status_lst
|
|
151
|
+
implementation.status = status
|
|
152
|
+
implementation.responsibility = responsibility
|
|
153
|
+
implementation.controlSource = "Baseline"
|
|
154
|
+
implementation.implementation = impl
|
|
155
|
+
else:
|
|
156
|
+
# Build it from the catalog
|
|
157
|
+
implementation = ControlImplementation(
|
|
158
|
+
status_lst=status_lst,
|
|
159
|
+
status=status,
|
|
160
|
+
parentId=regscale_id,
|
|
161
|
+
parentModule="securityplans",
|
|
162
|
+
controlID=result["controlID"],
|
|
163
|
+
responsibility=responsibility,
|
|
164
|
+
controlSource="Baseline",
|
|
165
|
+
implementation=impl,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
control_implementations.append(implementation)
|
|
169
|
+
|
|
170
|
+
return control_implementations
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def retrieve_controls(csam_id: int, controls: list, regscale_id: int) -> list:
|
|
174
|
+
"""
|
|
175
|
+
Takes a system id and list of controls
|
|
176
|
+
returns a list of implmentations for
|
|
177
|
+
that system id and framework
|
|
178
|
+
|
|
179
|
+
:param int system_id: CSAM system id
|
|
180
|
+
:param str framework: Framework name
|
|
181
|
+
:param list controls: list of possible controls
|
|
182
|
+
:param int regscale_id: RegScale SSP Id
|
|
183
|
+
:return: list of control implementations
|
|
184
|
+
:return_type: list
|
|
185
|
+
"""
|
|
186
|
+
imps = []
|
|
187
|
+
# Loop through the controls and get the implementations
|
|
188
|
+
for index in track(
|
|
189
|
+
range(len(controls)),
|
|
190
|
+
description=f"Retrieving implementations for system id: {csam_id}...",
|
|
191
|
+
):
|
|
192
|
+
control = controls[index]
|
|
193
|
+
implementations = retrieve_from_csam(
|
|
194
|
+
csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/controls/{control.controlId}",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if len(implementations) == 0:
|
|
198
|
+
logger.debug(f"No implementations found for control {control.controlId} in system id: {csam_id}")
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
# Add the RegScale SSP Id and controlID to the implementation
|
|
202
|
+
for impl in implementations:
|
|
203
|
+
if "NotApplicable" in impl["applicability"]:
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
impl["securityPlanId"] = regscale_id
|
|
207
|
+
impl["controlID"] = control.id
|
|
208
|
+
imps.append(impl)
|
|
209
|
+
return imps
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def set_inheritable(regscale_id: int):
|
|
213
|
+
"""
|
|
214
|
+
Given a RegScale SSP Id
|
|
215
|
+
Sets the inheritable flag on all control implementations
|
|
216
|
+
|
|
217
|
+
:param int regscale_id: id of Security Plan
|
|
218
|
+
:return: None
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
# Get list of existing controlimplementations
|
|
222
|
+
implementations = ControlImplementation.get_list_by_parent(regscale_id=regscale_id, regscale_module="securityplans")
|
|
223
|
+
|
|
224
|
+
for index in track(
|
|
225
|
+
range(len(implementations)),
|
|
226
|
+
description="Setting controls Inheritable...",
|
|
227
|
+
):
|
|
228
|
+
implementation = implementations[index]
|
|
229
|
+
imp = ControlImplementation.get_object(object_id=implementation["id"])
|
|
230
|
+
if imp.status in [FULLY_IMPLEMENTED, "Partially Implemented", "In Remediation"]:
|
|
231
|
+
imp.inheritable = True
|
|
232
|
+
imp.save()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def trim_inheritances(inheritances=List[Dict]) -> List[Dict]:
|
|
236
|
+
# Trim out the records that aren't inherited
|
|
237
|
+
# Trim down to one record per Control (vice "deterineifs")
|
|
238
|
+
|
|
239
|
+
unique_controls = []
|
|
240
|
+
new_list = []
|
|
241
|
+
|
|
242
|
+
for inheritance in inheritances:
|
|
243
|
+
if inheritance.get("isInherited") is False:
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
if inheritance.get("controlId") in unique_controls:
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
unique_controls.append(inheritance.get("controlId"))
|
|
250
|
+
new_list.append(inheritance)
|
|
251
|
+
|
|
252
|
+
return new_list
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def process_inheritances(
|
|
256
|
+
inheritances: List[Dict[str, Any]],
|
|
257
|
+
ssp: SecurityPlan,
|
|
258
|
+
ssp_map: Dict[str, int],
|
|
259
|
+
imp_map: Dict[str, int],
|
|
260
|
+
linked_ssps: List[SecurityPlan],
|
|
261
|
+
):
|
|
262
|
+
|
|
263
|
+
matcher = ControlMatcher()
|
|
264
|
+
for inheritance in inheritances:
|
|
265
|
+
# If not inherited, skip
|
|
266
|
+
if inheritance.get("isInherited") is False:
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
# Check if the control exists in plan
|
|
270
|
+
control_id = matcher.find_control_implementation(
|
|
271
|
+
control_id=inheritance.get("controlId"), parent_id=ssp.id, parent_module="securityplans"
|
|
272
|
+
)
|
|
273
|
+
if control_id not in imp_map:
|
|
274
|
+
logger.debug(f"Control {control_id} not found in RegScale for SSP {ssp.systemName} (ID: {ssp.id})")
|
|
275
|
+
continue
|
|
276
|
+
|
|
277
|
+
# Find the baseControl in RegScale
|
|
278
|
+
# Find the SSP
|
|
279
|
+
base_ssp = ssp_map.get(inheritance.get("offeringSystemName"))
|
|
280
|
+
if not base_ssp:
|
|
281
|
+
logger.debug(f"Base SSP {inheritance.get('offeringSystemName')} not found in RegScale, skipping")
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
# Find the source control
|
|
285
|
+
base_control_id = matcher.find_control_implementation(
|
|
286
|
+
control_id=inheritance.get("controlId"), parent_id=base_ssp, parent_module="securityplans"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if not base_control_id:
|
|
290
|
+
logger.debug(f"Control not found in Base SSP: {inheritance.get('offeringSystemName')}, skipping")
|
|
291
|
+
continue
|
|
292
|
+
|
|
293
|
+
# Create or update the inheritance record
|
|
294
|
+
# Add the parent if not already linked
|
|
295
|
+
if base_ssp not in linked_ssps:
|
|
296
|
+
linked_ssps.append(base_ssp)
|
|
297
|
+
|
|
298
|
+
# Create the records
|
|
299
|
+
create_inheritance(
|
|
300
|
+
parent_id=ssp.id,
|
|
301
|
+
parent_module="securityplans",
|
|
302
|
+
hybrid=inheritance.get("isHybrid", True),
|
|
303
|
+
base_id=base_ssp,
|
|
304
|
+
control_id=imp_map[control_id],
|
|
305
|
+
base_control_id=base_control_id,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Create the Inheritance Record(s)
|
|
309
|
+
for inheritance_ssp in linked_ssps:
|
|
310
|
+
create_inheritance_linage(
|
|
311
|
+
parent_id=ssp.id,
|
|
312
|
+
parent_module="securityplans",
|
|
313
|
+
base_id=inheritance_ssp,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def create_inheritance(
|
|
318
|
+
parent_id: int, parent_module: str, base_id: int, hybrid: bool, control_id: int, base_control_id: int
|
|
319
|
+
):
|
|
320
|
+
"""
|
|
321
|
+
Creates the records for inheritance
|
|
322
|
+
|
|
323
|
+
:param int parent_id: Id of inheriting record
|
|
324
|
+
:param str parent_module: Module of inheriting record
|
|
325
|
+
:param int base_id: Id of inherited record
|
|
326
|
+
:param bool hybrid: Is the control hybrid
|
|
327
|
+
:param int control_id: Id of inheriting control
|
|
328
|
+
:param int base_control_id: Id of inherited control
|
|
329
|
+
:return: None
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
# Update the control implementation
|
|
333
|
+
control_impl = ControlImplementation.get_object(object_id=control_id)
|
|
334
|
+
if control_impl:
|
|
335
|
+
control_impl.bInherited = True
|
|
336
|
+
control_impl.responsibility = "Hybrid" if hybrid else "Inherited"
|
|
337
|
+
control_impl.inheritedControlId = base_control_id
|
|
338
|
+
control_impl.inheritedSecurityPlanId = base_id
|
|
339
|
+
control_impl.save()
|
|
340
|
+
|
|
341
|
+
# Check if the Inherited Control already exists
|
|
342
|
+
existing = InheritedControl.get_all_by_control(control_id=control_id)
|
|
343
|
+
for exists in existing:
|
|
344
|
+
if exists["inheritedControlId"] == base_control_id:
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
InheritedControl(
|
|
348
|
+
parentId=parent_id, parentModule=parent_module, baseControlId=control_id, inheritedControlId=base_control_id
|
|
349
|
+
).create()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def create_inheritance_linage(parent_id: int, parent_module: str, base_id: int):
|
|
353
|
+
"""
|
|
354
|
+
Creates a RegScale Inheritance Record
|
|
355
|
+
|
|
356
|
+
:param int parent_id: Id of inheriting record
|
|
357
|
+
:param str parent_module: Module of inheriting record
|
|
358
|
+
:param int base_control_id: Id of inherited control
|
|
359
|
+
:return: None
|
|
360
|
+
"""
|
|
361
|
+
# Check if the Inheritance already exists
|
|
362
|
+
existing = Inheritance.get_all_by_parent(parent_id=parent_id, parent_module=parent_module)
|
|
363
|
+
for exists in existing:
|
|
364
|
+
if exists.planId == base_id:
|
|
365
|
+
return
|
|
366
|
+
|
|
367
|
+
# Update Lineage (no way to update.. only create)
|
|
368
|
+
Inheritance(recordId=parent_id, recordModule=parent_module, planId=base_id).create()
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def import_csam_inheritance(import_ids: Optional[List[int]] = None):
|
|
372
|
+
"""
|
|
373
|
+
Import control inheritance from CSAM
|
|
374
|
+
|
|
375
|
+
:param list import_ids: List of SSPs to import
|
|
376
|
+
:return: None
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
# Get list of existing SSPs in RegScale
|
|
380
|
+
existing_ssps = SecurityPlan.get_ssp_list()
|
|
381
|
+
ssp_map = {ssp["title"]: ssp["id"] for ssp in existing_ssps}
|
|
382
|
+
|
|
383
|
+
if not import_ids:
|
|
384
|
+
import_ids = [ssp["id"] for ssp in existing_ssps]
|
|
385
|
+
|
|
386
|
+
# Get Inheritance data from CSAM
|
|
387
|
+
for index in track(
|
|
388
|
+
range(len(import_ids)),
|
|
389
|
+
description=f"Importing inheritance for {len(import_ids)} Systems...",
|
|
390
|
+
):
|
|
391
|
+
ssp = SecurityPlan.get_object(object_id=import_ids[index])
|
|
392
|
+
linked_ssps = []
|
|
393
|
+
# Get the inheritance data from CSAM
|
|
394
|
+
|
|
395
|
+
inheritances = retrieve_from_csam(
|
|
396
|
+
csam_endpoint=f"/CSAM/api/v1/systems/{ssp.otherIdentifier}/inheritedcontrols",
|
|
397
|
+
)
|
|
398
|
+
if not inheritances:
|
|
399
|
+
logger.debug(f"No inheritance data found for SSP {ssp.systemName} (ID: {ssp.id})")
|
|
400
|
+
continue
|
|
401
|
+
# Process each inheritance record
|
|
402
|
+
imp_map = ControlImplementation.get_control_label_map_by_plan(plan_id=ssp.id)
|
|
403
|
+
|
|
404
|
+
inheritances = trim_inheritances(inheritances=inheritances)
|
|
405
|
+
|
|
406
|
+
process_inheritances(
|
|
407
|
+
inheritances=inheritances, ssp=ssp, ssp_map=ssp_map, imp_map=imp_map, linked_ssps=linked_ssps
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def get_catalogs() -> Tuple[Optional[int], Optional[int]]:
|
|
412
|
+
"""
|
|
413
|
+
Get the catalog ids for NIST SP 800-53 Rev 5 and Rev 4
|
|
414
|
+
|
|
415
|
+
:return: tuple of catalog ids
|
|
416
|
+
:return_type: Tuple[Optional[int], Optional[int]]
|
|
417
|
+
"""
|
|
418
|
+
# Find the Catalogs
|
|
419
|
+
# Use the init.yaml values, otherwise search for the catalogs by guid
|
|
420
|
+
app = Application()
|
|
421
|
+
catalogs = app.config.get("csamFrameworkCatalog")
|
|
422
|
+
rev5_catalog = Catalog.find_by_guid("b0c40faa-fda4-4ed3-83df-368908d9e9b2") # NIST SP 800-53 Rev 5
|
|
423
|
+
rev4_catalog = Catalog.find_by_guid("02158108-e491-49de-b9a8-3cb1cb8197dd") # NIST SP 800-53 Rev 4
|
|
424
|
+
|
|
425
|
+
if catalogs:
|
|
426
|
+
rev5_catalog_id = catalogs.get("800-53r5")
|
|
427
|
+
rev4_catalog_id = catalogs.get("800-53r4")
|
|
428
|
+
elif rev5_catalog and rev4_catalog:
|
|
429
|
+
rev5_catalog_id = rev5_catalog.id
|
|
430
|
+
rev4_catalog_id = rev4_catalog.id
|
|
431
|
+
|
|
432
|
+
return rev5_catalog_id, rev4_catalog_id
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Integrates CSAM into RegScale"""
|
|
4
|
+
|
|
5
|
+
# standard python imports
|
|
6
|
+
import logging
|
|
7
|
+
from typing import List
|
|
8
|
+
from rich.progress import track
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from regscale.core.app.api import Api
|
|
11
|
+
from regscale.core.app.application import Application
|
|
12
|
+
from regscale.core.utils.date import format_to_regscale_iso, date_obj
|
|
13
|
+
from regscale.core.app.utils.app_utils import error_and_exit, filter_list
|
|
14
|
+
from regscale.core.app.utils.parser_utils import safe_date_str
|
|
15
|
+
from regscale.models.regscale_models import (
|
|
16
|
+
Issue,
|
|
17
|
+
SecurityControl,
|
|
18
|
+
SecurityPlan,
|
|
19
|
+
User,
|
|
20
|
+
)
|
|
21
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
22
|
+
from regscale.integrations.control_matcher import ControlMatcher
|
|
23
|
+
from regscale.models.regscale_models.form_field_value import FormFieldValue
|
|
24
|
+
from regscale.integrations.public.csam.csam_common import (
|
|
25
|
+
retrieve_ssps_custom_form_map,
|
|
26
|
+
retrieve_from_csam,
|
|
27
|
+
FISMA_FIELD_NAME,
|
|
28
|
+
CSAM_FIELD_NAME,
|
|
29
|
+
SSP_BASIC_TAB,
|
|
30
|
+
SYSTEM_ID,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
POAM_ID = "POAM Id"
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger("regscale")
|
|
36
|
+
console = Console()
|
|
37
|
+
|
|
38
|
+
####################################################################################################
|
|
39
|
+
#
|
|
40
|
+
# IMPORT SSP / POAM FROM DoJ's CSAM GRC
|
|
41
|
+
# CSAM API Docs: https://csam.dhs.gov/CSAM/api/docs/index.html (required PIV)
|
|
42
|
+
#
|
|
43
|
+
####################################################################################################
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def import_csam_poams():
|
|
47
|
+
# Check Custom Fields
|
|
48
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields(
|
|
49
|
+
fields_list=[FISMA_FIELD_NAME, CSAM_FIELD_NAME], module_name="securityplans", tab_name=SSP_BASIC_TAB
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Get the SSPs
|
|
53
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
54
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Get a list of users and create a map to id
|
|
58
|
+
users = User.get_all()
|
|
59
|
+
user_map = {user.userName: user.id for user in users}
|
|
60
|
+
|
|
61
|
+
# Grab the data from CSAM
|
|
62
|
+
results = retrieve_from_csam(csam_endpoint="/CSAM/api/v1/reports/POAM_Details_Report_CBP")
|
|
63
|
+
|
|
64
|
+
# Parse the results
|
|
65
|
+
poam_list = []
|
|
66
|
+
for index in track(
|
|
67
|
+
range(len(results)),
|
|
68
|
+
description=f"Importing {len(results)} POA&Ms...",
|
|
69
|
+
):
|
|
70
|
+
result = results[index]
|
|
71
|
+
|
|
72
|
+
# Get the existing SSP:
|
|
73
|
+
ssp_id = ssp_map.get(str(result[SYSTEM_ID]))
|
|
74
|
+
if not ssp_id:
|
|
75
|
+
logger.error(
|
|
76
|
+
f"A RegScale Security Plan does not exist for CSAM id: {result[SYSTEM_ID]}\
|
|
77
|
+
create or import the Security Plan prior to importing POA&Ms"
|
|
78
|
+
)
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
# Check if the POAM exists:
|
|
82
|
+
existing_issue = Issue.find_by_other_identifier(result[POAM_ID])
|
|
83
|
+
if existing_issue:
|
|
84
|
+
new_issue = existing_issue
|
|
85
|
+
else:
|
|
86
|
+
new_issue = Issue()
|
|
87
|
+
|
|
88
|
+
# Update the issue
|
|
89
|
+
new_issue.isPoam = True
|
|
90
|
+
new_issue.parentId = ssp_id
|
|
91
|
+
new_issue.parentModule = "securityplans"
|
|
92
|
+
new_issue.otherIdentifier = result[POAM_ID]
|
|
93
|
+
new_issue.title = result["POAM Title"]
|
|
94
|
+
new_issue.affectedControls = result["Controls"]
|
|
95
|
+
new_issue.securityPlanId = ssp_id
|
|
96
|
+
new_issue.identification = "Vulnerability Assessment"
|
|
97
|
+
new_issue.description = result["Detailed Weakness Description"]
|
|
98
|
+
new_issue.poamComments = f"{result['Weakness Comments']}\n \
|
|
99
|
+
{result['POA&M Delayed Comments']}\n \
|
|
100
|
+
{result['POA&M Comments']}"
|
|
101
|
+
new_issue.dateFirstDetected = safe_date_str(result["Create Date"])
|
|
102
|
+
new_issue.dueDate = safe_date_str(result["Planned Finish Date"])
|
|
103
|
+
# Need to convert cost to a int
|
|
104
|
+
# new_issue.costEstimate = result['Cost']
|
|
105
|
+
new_issue.issueOwnerId = (
|
|
106
|
+
user_map.get(result["Email"]) if user_map.get(result["Email"]) else RegScaleModel.get_user_id()
|
|
107
|
+
)
|
|
108
|
+
# Update with IssueSeverity String
|
|
109
|
+
new_issue.severityLevel = result["Severity"]
|
|
110
|
+
# Update with IssueStatus String
|
|
111
|
+
new_issue.status = result["Status"]
|
|
112
|
+
|
|
113
|
+
poam_list.append(new_issue)
|
|
114
|
+
|
|
115
|
+
for index in track(
|
|
116
|
+
range(len(poam_list)),
|
|
117
|
+
description=f"Updating RegScale with {len(poam_list)} POA&Ms...",
|
|
118
|
+
):
|
|
119
|
+
poam = poam_list[index]
|
|
120
|
+
if poam.id == 0:
|
|
121
|
+
poam.create()
|
|
122
|
+
else:
|
|
123
|
+
poam.save()
|
|
124
|
+
logger.info(f"Added or updated {len(poam_list)} POA&Ms in RegScale")
|
|
@@ -9,8 +9,9 @@ from typing import Literal, Optional
|
|
|
9
9
|
import click
|
|
10
10
|
from dateutil.relativedelta import relativedelta
|
|
11
11
|
|
|
12
|
-
from regscale.core.app.utils.regscale_utils import check_module_id
|
|
12
|
+
from regscale.core.app.utils.regscale_utils import check_module_id, error_and_exit
|
|
13
13
|
from regscale.models import regscale_id, regscale_module
|
|
14
|
+
from regscale.models.regscale_models import Asset
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger("regscale")
|
|
16
17
|
|
|
@@ -368,7 +369,7 @@ def import_fedramp_inventory(
|
|
|
368
369
|
type=click.Path(exists=True, dir_okay=False, file_okay=True),
|
|
369
370
|
required=True,
|
|
370
371
|
prompt="Enter the file path containing FedRAMP (.xlsx) POAM workbook to ingest to RegScale.",
|
|
371
|
-
help="RegScale will process and load the FedRAMP POAMs as RegScale issues.",
|
|
372
|
+
help="RegScale will process and load the FedRAMP POAMs (including POA&M Items and Configuration Findings) as RegScale issues.",
|
|
372
373
|
)
|
|
373
374
|
@regscale_id()
|
|
374
375
|
@regscale_module()
|
|
@@ -392,6 +393,8 @@ def import_fedramp_poam_template(
|
|
|
392
393
|
) -> None:
|
|
393
394
|
"""
|
|
394
395
|
Import a FedRamp POA&M document to RegScale issues.
|
|
396
|
+
|
|
397
|
+
Supports both POA&M Items and Configuration Findings tabs.
|
|
395
398
|
"""
|
|
396
399
|
# suppress UserWarnings from openpyxl
|
|
397
400
|
import warnings
|
|
@@ -401,7 +404,17 @@ def import_fedramp_poam_template(
|
|
|
401
404
|
warnings.filterwarnings("ignore", category=UserWarning, module="openpyxl")
|
|
402
405
|
|
|
403
406
|
if not check_module_id(parent_id=regscale_id, parent_module=regscale_module):
|
|
404
|
-
|
|
407
|
+
error_and_exit(f"RegScale ID {regscale_id} is not a valid member of {regscale_module}.")
|
|
408
|
+
|
|
409
|
+
# Check if assets exist before importing POAMs
|
|
410
|
+
existing_assets = Asset.get_all_by_parent(parent_id=regscale_id, parent_module=regscale_module)
|
|
411
|
+
if not existing_assets:
|
|
412
|
+
error_msg = (
|
|
413
|
+
f"No assets found in {regscale_module} #{regscale_id}. "
|
|
414
|
+
"Please import inventory first using 'regscale fedramp import_fedramp_inventory' "
|
|
415
|
+
"before importing POAMs."
|
|
416
|
+
)
|
|
417
|
+
error_and_exit(error_msg)
|
|
405
418
|
|
|
406
419
|
# Initialize the FedRAMP integration
|
|
407
420
|
integration = FedrampPoamIntegration(plan_id=regscale_id, file_path=str(file_path))
|
|
@@ -456,7 +469,7 @@ def import_drf(file_path: click.Path, regscale_id: int, regscale_module: str) ->
|
|
|
456
469
|
warnings.filterwarnings("ignore", category=UserWarning, module="openpyxl")
|
|
457
470
|
|
|
458
471
|
if not check_module_id(parent_id=regscale_id, parent_module=regscale_module):
|
|
459
|
-
|
|
472
|
+
error_and_exit(f"RegScale ID {regscale_id} is not a valid member of {regscale_module}.")
|
|
460
473
|
DRF(file_path=file_path, module_id=regscale_id, module=regscale_module)
|
|
461
474
|
|
|
462
475
|
|