regscale-cli 6.27.2.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/login.py +21 -4
- regscale/core/utils/date.py +77 -1
- regscale/integrations/commercial/aws/scanner.py +4 -1
- regscale/integrations/commercial/synqly/query_builder.py +4 -1
- regscale/integrations/control_matcher.py +78 -23
- 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/cisa_kev_data.json +49 -19
- 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.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/RECORD +40 -36
- tests/regscale/core/test_login.py +171 -4
- tests/regscale/integrations/test_control_matcher.py +24 -0
- tests/regscale/models/test_control_implementation.py +118 -3
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/top_level.txt +0 -0
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# standard python imports
|
|
6
6
|
import logging
|
|
7
|
-
from typing import List, Optional
|
|
8
|
-
from urllib.parse import urljoin
|
|
7
|
+
from typing import List, Optional
|
|
9
8
|
from rich.progress import track
|
|
10
9
|
import click
|
|
11
10
|
from rich.console import Console
|
|
@@ -13,22 +12,31 @@ from regscale.core.app.api import Api
|
|
|
13
12
|
from regscale.core.app.application import Application
|
|
14
13
|
from regscale.core.utils.date import format_to_regscale_iso, date_obj
|
|
15
14
|
from regscale.core.app.utils.app_utils import error_and_exit, filter_list
|
|
16
|
-
from regscale.core.app.utils.parser_utils import safe_date_str
|
|
17
15
|
from regscale.models.regscale_models import (
|
|
18
|
-
Catalog,
|
|
19
|
-
ControlImplementation,
|
|
20
|
-
InheritedControl,
|
|
21
|
-
Inheritance,
|
|
22
|
-
Issue,
|
|
23
16
|
Organization,
|
|
24
|
-
SecurityControl,
|
|
25
17
|
SecurityPlan,
|
|
26
18
|
User,
|
|
27
19
|
)
|
|
28
|
-
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
29
20
|
from regscale.models.regscale_models.module import Module
|
|
30
21
|
from regscale.models.regscale_models.form_field_value import FormFieldValue
|
|
31
|
-
from regscale.
|
|
22
|
+
from regscale.integrations.public.csam.csam_poam import import_csam_poams
|
|
23
|
+
from regscale.integrations.public.csam.csam_controls import (
|
|
24
|
+
import_csam_controls,
|
|
25
|
+
set_inheritable,
|
|
26
|
+
import_csam_inheritance,
|
|
27
|
+
)
|
|
28
|
+
from regscale.integrations.public.csam.csam_agency_defined import update_ssp_agency_details
|
|
29
|
+
from regscale.integrations.public.csam.csam_common import (
|
|
30
|
+
retrieve_ssps_custom_form_map,
|
|
31
|
+
retrieve_custom_form_ssps_map,
|
|
32
|
+
retrieve_from_csam,
|
|
33
|
+
set_custom_fields,
|
|
34
|
+
fix_form_field_value,
|
|
35
|
+
CSAM_FIELD_NAME,
|
|
36
|
+
FISMA_FIELD_NAME,
|
|
37
|
+
SSP_BASIC_TAB,
|
|
38
|
+
)
|
|
39
|
+
|
|
32
40
|
|
|
33
41
|
logger = logging.getLogger("regscale")
|
|
34
42
|
console = Console()
|
|
@@ -40,14 +48,30 @@ console = Console()
|
|
|
40
48
|
#
|
|
41
49
|
####################################################################################################
|
|
42
50
|
|
|
43
|
-
|
|
51
|
+
|
|
44
52
|
SSP_SYSTEM_TAB = "System Information"
|
|
45
53
|
SSP_FINANCIAL_TAB = "Financial Info"
|
|
46
54
|
SSP_PRIVACY_TAB = "Privacy-Details"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
SSP_CONTINGENCY_TAB = "Continuity and Incident Response"
|
|
56
|
+
|
|
57
|
+
CUSTOM_FIELDS_BASIC_LIST = [
|
|
58
|
+
"acronym",
|
|
59
|
+
"Classification",
|
|
60
|
+
"FISMA Reportable",
|
|
61
|
+
"Contractor System",
|
|
62
|
+
"Authorization Process",
|
|
63
|
+
"ATO Date",
|
|
64
|
+
"ATO Status",
|
|
65
|
+
"Critical Infrastructure",
|
|
66
|
+
"Mission Essential",
|
|
67
|
+
"uiiCode",
|
|
68
|
+
"HVA Identifier",
|
|
69
|
+
"External Web Interface",
|
|
70
|
+
"CFO Designation",
|
|
71
|
+
"Law Enforcement Sensitive",
|
|
72
|
+
CSAM_FIELD_NAME,
|
|
73
|
+
FISMA_FIELD_NAME,
|
|
74
|
+
]
|
|
51
75
|
|
|
52
76
|
|
|
53
77
|
@click.group()
|
|
@@ -77,48 +101,19 @@ def import_poam():
|
|
|
77
101
|
|
|
78
102
|
def import_csam_ssp():
|
|
79
103
|
"""
|
|
80
|
-
Import
|
|
104
|
+
Import SSPs from CSAM
|
|
81
105
|
Into RegScale
|
|
106
|
+
According to a filter in init.yaml
|
|
82
107
|
"""
|
|
83
|
-
custom_fields_basic_list = [
|
|
84
|
-
"acronym",
|
|
85
|
-
"Financial System",
|
|
86
|
-
"Classification",
|
|
87
|
-
"FISMA Reportable",
|
|
88
|
-
"Contractor System",
|
|
89
|
-
"Authorization Process",
|
|
90
|
-
"ATO Date",
|
|
91
|
-
"Critical Infrastructure",
|
|
92
|
-
"Mission Essential",
|
|
93
|
-
"uui Code",
|
|
94
|
-
"HVA Identifier",
|
|
95
|
-
"External Web Interface",
|
|
96
|
-
"CFO Designation",
|
|
97
|
-
"AI/ML Components",
|
|
98
|
-
"Law Enforcement Sensitive",
|
|
99
|
-
CSAM_FIELD_NAME,
|
|
100
|
-
FISMA_FIELD_NAME,
|
|
101
|
-
]
|
|
102
|
-
custom_fields_financial_list = [
|
|
103
|
-
"omb Exhibit",
|
|
104
|
-
"Investment Name",
|
|
105
|
-
"Portfolio",
|
|
106
|
-
"Prior Fy Funding",
|
|
107
|
-
"Current Fy Funding",
|
|
108
|
-
"Next Fy Funding",
|
|
109
|
-
"Funding Import Status",
|
|
110
|
-
]
|
|
111
108
|
|
|
109
|
+
logger.info("Gathering reference info...")
|
|
112
110
|
# Check Custom Fields exist
|
|
113
111
|
custom_fields_basic_map = FormFieldValue.check_custom_fields(
|
|
114
|
-
|
|
115
|
-
)
|
|
116
|
-
custom_fields_financial_map = FormFieldValue.check_custom_fields(
|
|
117
|
-
custom_fields_financial_list, "securityplans", SSP_FINANCIAL_TAB
|
|
112
|
+
CUSTOM_FIELDS_BASIC_LIST, "securityplans", SSP_BASIC_TAB
|
|
118
113
|
)
|
|
119
114
|
|
|
120
115
|
# Get a map of existing custom forms
|
|
121
|
-
ssp_map =
|
|
116
|
+
ssp_map = retrieve_custom_form_ssps_map(
|
|
122
117
|
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[FISMA_FIELD_NAME]
|
|
123
118
|
)
|
|
124
119
|
|
|
@@ -128,16 +123,18 @@ def import_csam_ssp():
|
|
|
128
123
|
|
|
129
124
|
# Grab the data from CSAM
|
|
130
125
|
app = Application()
|
|
131
|
-
csam_token = app.config.get("csamToken")
|
|
132
|
-
csam_url = app.config.get("csamURL")
|
|
133
126
|
csam_filter = app.config.get("csamFilter", None)
|
|
134
127
|
|
|
128
|
+
logger.info("Retrieving systems from CSAM...")
|
|
135
129
|
results = retrieve_from_csam(
|
|
136
|
-
csam_token=csam_token,
|
|
137
|
-
csam_url=csam_url,
|
|
138
130
|
csam_endpoint="/CSAM/api/v1/systems",
|
|
139
131
|
)
|
|
140
132
|
|
|
133
|
+
if not results:
|
|
134
|
+
error_and_exit("Failure to retrieve plans from CSAM")
|
|
135
|
+
else:
|
|
136
|
+
logger.info("Retrieved plans from CSAM, parsing results...")
|
|
137
|
+
|
|
141
138
|
results = filter_list(results, csam_filter)
|
|
142
139
|
if not results:
|
|
143
140
|
error_and_exit(
|
|
@@ -145,22 +142,28 @@ def import_csam_ssp():
|
|
|
145
142
|
Please check your CSAM configuration."
|
|
146
143
|
)
|
|
147
144
|
|
|
145
|
+
logger.info("Importing systems... ")
|
|
148
146
|
# Parse the results
|
|
149
147
|
updated_ssps = []
|
|
150
148
|
updated_ssps = save_ssp_front_matter(
|
|
151
149
|
results=results,
|
|
152
150
|
ssp_map=ssp_map,
|
|
153
151
|
custom_fields_basic_map=custom_fields_basic_map,
|
|
154
|
-
custom_fields_financial_map=custom_fields_financial_map,
|
|
155
152
|
org_map=org_map,
|
|
156
153
|
)
|
|
157
154
|
|
|
158
155
|
# Now have to get the system details for each system
|
|
159
|
-
update_ssp_agency_details(updated_ssps,
|
|
156
|
+
update_ssp_agency_details(updated_ssps, custom_fields_basic_map)
|
|
157
|
+
|
|
158
|
+
# Import the authorization process and status
|
|
159
|
+
import_csam_authorization(import_ids=[ssp.id for ssp in updated_ssps])
|
|
160
160
|
|
|
161
161
|
# Import the Privacy date
|
|
162
162
|
import_csam_privacy_info(import_ids=[ssp.id for ssp in updated_ssps])
|
|
163
163
|
|
|
164
|
+
# Import the Contingency & IR data
|
|
165
|
+
import_csam_contingency(import_ids=[ssp.id for ssp in updated_ssps])
|
|
166
|
+
|
|
164
167
|
# Import the controls
|
|
165
168
|
import_csam_controls(import_ids=[ssp.id for ssp in updated_ssps])
|
|
166
169
|
|
|
@@ -177,7 +180,7 @@ def import_csam_ssp():
|
|
|
177
180
|
continue
|
|
178
181
|
|
|
179
182
|
# Set the inheritable flag
|
|
180
|
-
set_inheritable(regscale_id=
|
|
183
|
+
set_inheritable(regscale_id=program_id)
|
|
181
184
|
|
|
182
185
|
# Import the Inheritance
|
|
183
186
|
import_csam_inheritance(import_ids=[ssp.id for ssp in updated_ssps])
|
|
@@ -186,164 +189,59 @@ def import_csam_ssp():
|
|
|
186
189
|
import_csam_pocs(import_ids=[ssp.id for ssp in updated_ssps])
|
|
187
190
|
|
|
188
191
|
|
|
189
|
-
def
|
|
190
|
-
"""
|
|
191
|
-
Import Controls from CSAM
|
|
192
|
+
def sync_csam_ssps():
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"""
|
|
196
|
-
|
|
197
|
-
# Grab the data from CSAM
|
|
198
|
-
app = Application()
|
|
199
|
-
csam_token = app.config.get("csamToken")
|
|
200
|
-
csam_url = app.config.get("csamURL")
|
|
201
|
-
|
|
202
|
-
# Get existing ssps by CSAM Id
|
|
203
|
-
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
204
|
-
ssp_map = retrieve_custom_form_ssp_map(
|
|
205
|
-
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
plans = import_ids if import_ids else list(ssp_map.keys())
|
|
209
|
-
|
|
210
|
-
# Find the Catalogs
|
|
211
|
-
rev5_catalog_id, rev4_catalog_id = get_catalogs()
|
|
212
|
-
|
|
213
|
-
# Get the list of controls for each catalog
|
|
214
|
-
rev5_controls = SecurityControl.get_list_by_catalog(catalog_id=rev5_catalog_id)
|
|
215
|
-
rev4_controls = SecurityControl.get_list_by_catalog(catalog_id=rev4_catalog_id)
|
|
216
|
-
|
|
217
|
-
control_implementations = []
|
|
218
|
-
for regscale_ssp_id in plans:
|
|
219
|
-
results = []
|
|
220
|
-
system_id = ssp_map.get(regscale_ssp_id)
|
|
221
|
-
|
|
222
|
-
# Get the Implementation for AC-1
|
|
223
|
-
# Check the controlSet
|
|
224
|
-
# Match the catalog
|
|
225
|
-
imp = retrieve_from_csam(
|
|
226
|
-
csam_token=csam_token,
|
|
227
|
-
csam_url=csam_url,
|
|
228
|
-
csam_endpoint=f"/CSAM/api/v1/systems/{system_id}/controls/AC-1",
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
# Get the controls
|
|
232
|
-
if imp[0].get("controlSet") in ["NIST 800-53 Rev4", "NIST 800-53 Rev5"]:
|
|
233
|
-
results = retrieve_controls(
|
|
234
|
-
csam_token=csam_token,
|
|
235
|
-
csam_url=csam_url,
|
|
236
|
-
csam_id=system_id,
|
|
237
|
-
controls=rev4_controls if imp[0].get("controlSet") == "NIST 800-53 Rev4" else rev5_controls,
|
|
238
|
-
regscale_id=regscale_ssp_id,
|
|
239
|
-
)
|
|
240
|
-
else:
|
|
241
|
-
logger.warning(
|
|
242
|
-
f"System framework {imp.get('controlSet')} \
|
|
243
|
-
for system {system_id} is not supported"
|
|
244
|
-
)
|
|
245
|
-
continue
|
|
246
|
-
|
|
247
|
-
if not results:
|
|
248
|
-
logger.warning(f"No controls found for system id: {system_id}")
|
|
249
|
-
continue
|
|
250
|
-
|
|
251
|
-
# Build the controls
|
|
252
|
-
control_implementations = build_implementations(results=results, csam_id=system_id, regscale_id=regscale_ssp_id)
|
|
253
|
-
|
|
254
|
-
# Save the control implementations
|
|
255
|
-
for index in track(
|
|
256
|
-
range(len(control_implementations)),
|
|
257
|
-
description=f"Saving {len(control_implementations)} control implementations...",
|
|
258
|
-
):
|
|
259
|
-
control_implementation = control_implementations[index]
|
|
260
|
-
control_implementation.create() if control_implementation.id == 0 else control_implementation.save()
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def import_csam_poams():
|
|
264
|
-
# Check Custom Fields
|
|
194
|
+
logger.info("Gathering reference info...")
|
|
195
|
+
# Check Custom Fields exist
|
|
265
196
|
custom_fields_basic_map = FormFieldValue.check_custom_fields(
|
|
266
|
-
|
|
197
|
+
CUSTOM_FIELDS_BASIC_LIST, "securityplans", SSP_BASIC_TAB
|
|
267
198
|
)
|
|
268
199
|
|
|
269
|
-
# Get
|
|
270
|
-
ssp_map =
|
|
200
|
+
# Get a map of existing custom forms
|
|
201
|
+
ssp_map = retrieve_custom_form_ssps_map(
|
|
202
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[FISMA_FIELD_NAME]
|
|
203
|
+
)
|
|
204
|
+
csam_map = retrieve_custom_form_ssps_map(
|
|
271
205
|
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
272
206
|
)
|
|
273
207
|
|
|
274
|
-
# Get a list of
|
|
275
|
-
|
|
276
|
-
|
|
208
|
+
# Get a list of orgs and create a map to id
|
|
209
|
+
orgs = Organization.get_list()
|
|
210
|
+
org_map = {org.name: org.id for org in orgs}
|
|
277
211
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
212
|
+
csam_list = []
|
|
213
|
+
for id in csam_map.keys():
|
|
214
|
+
csam_list.append(int(id))
|
|
215
|
+
csam_filter = {"id": csam_list}
|
|
216
|
+
|
|
217
|
+
logger.info("Retrieving systems from CSAM...")
|
|
282
218
|
results = retrieve_from_csam(
|
|
283
|
-
|
|
219
|
+
csam_endpoint="/CSAM/api/v1/systems",
|
|
284
220
|
)
|
|
285
221
|
|
|
222
|
+
results = filter_list(results, csam_filter)
|
|
223
|
+
|
|
224
|
+
logger.info("Syncing systems... ")
|
|
286
225
|
# Parse the results
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
226
|
+
updated_ssps = []
|
|
227
|
+
updated_ssps = save_ssp_front_matter(
|
|
228
|
+
results=results,
|
|
229
|
+
ssp_map=ssp_map,
|
|
230
|
+
custom_fields_basic_map=custom_fields_basic_map,
|
|
231
|
+
org_map=org_map,
|
|
232
|
+
)
|
|
293
233
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if not ssp_id:
|
|
297
|
-
logger.error(
|
|
298
|
-
f"A RegScale Security Plan does not exist for CSAM id: {result[SYSTEM_ID]}\
|
|
299
|
-
create or import the Security Plan prior to importing POA&Ms"
|
|
300
|
-
)
|
|
301
|
-
continue
|
|
234
|
+
# Now have to get the system details for each system
|
|
235
|
+
update_ssp_agency_details(updated_ssps, custom_fields_basic_map)
|
|
302
236
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if existing_issue:
|
|
306
|
-
new_issue = existing_issue
|
|
307
|
-
else:
|
|
308
|
-
new_issue = Issue()
|
|
309
|
-
|
|
310
|
-
# Update the issue
|
|
311
|
-
new_issue.isPoam = True
|
|
312
|
-
new_issue.parentId = ssp_id
|
|
313
|
-
new_issue.parentModule = "securityplans"
|
|
314
|
-
new_issue.otherIdentifier = result[POAM_ID]
|
|
315
|
-
new_issue.title = result["POAM Title"]
|
|
316
|
-
new_issue.affectedControls = result["Controls"]
|
|
317
|
-
new_issue.securityPlanId = ssp_id
|
|
318
|
-
new_issue.identification = "Vulnerability Assessment"
|
|
319
|
-
new_issue.description = result["Detailed Weakness Description"]
|
|
320
|
-
new_issue.poamComments = f"{result['Weakness Comments']}\n \
|
|
321
|
-
{result['POA&M Delayed Comments']}\n \
|
|
322
|
-
{result['POA&M Comments']}"
|
|
323
|
-
new_issue.dateFirstDetected = safe_date_str(result["Create Date"])
|
|
324
|
-
new_issue.dueDate = safe_date_str(result["Planned Finish Date"])
|
|
325
|
-
# Need to convert cost to a int
|
|
326
|
-
# new_issue.costEstimate = result['Cost']
|
|
327
|
-
new_issue.issueOwnerId = (
|
|
328
|
-
user_map.get(result["Email"]) if user_map.get(result["Email"]) else RegScaleModel.get_user_id()
|
|
329
|
-
)
|
|
330
|
-
# Update with IssueSeverity String
|
|
331
|
-
new_issue.severityLevel = result["Severity"]
|
|
332
|
-
# Update with IssueStatus String
|
|
333
|
-
new_issue.status = result["Status"]
|
|
237
|
+
# Import the authorization process and status
|
|
238
|
+
import_csam_authorization(import_ids=[ssp.id for ssp in updated_ssps])
|
|
334
239
|
|
|
335
|
-
|
|
240
|
+
# Import the Privacy date
|
|
241
|
+
import_csam_privacy_info(import_ids=[ssp.id for ssp in updated_ssps])
|
|
336
242
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
description=f"Updating RegScale with {len(poam_list)} POA&Ms...",
|
|
340
|
-
):
|
|
341
|
-
poam = poam_list[index]
|
|
342
|
-
if poam.id == 0:
|
|
343
|
-
poam.create()
|
|
344
|
-
else:
|
|
345
|
-
poam.save()
|
|
346
|
-
logger.info(f"Added or updated {len(poam_list)} POA&Ms in RegScale")
|
|
243
|
+
# Import the Contingency & IR data
|
|
244
|
+
import_csam_contingency(import_ids=[ssp.id for ssp in updated_ssps])
|
|
347
245
|
|
|
348
246
|
|
|
349
247
|
def import_csam_pocs(import_ids: Optional[List[int]] = None):
|
|
@@ -351,19 +249,19 @@ def import_csam_pocs(import_ids: Optional[List[int]] = None):
|
|
|
351
249
|
Import the Points of Contact from CSAM
|
|
352
250
|
Into RegScale
|
|
353
251
|
"""
|
|
354
|
-
|
|
252
|
+
custom_fields_pocs_list = [
|
|
355
253
|
"Certifying Official",
|
|
356
254
|
"Alternate Information System Security Manager",
|
|
357
255
|
"Alternate Information System Security Officer",
|
|
358
256
|
]
|
|
359
257
|
# Check Custom Fields exist
|
|
360
|
-
|
|
361
|
-
|
|
258
|
+
custom_fields_pocs_map = FormFieldValue.check_custom_fields(
|
|
259
|
+
custom_fields_pocs_list, "securityplans", "Points of Contact"
|
|
362
260
|
)
|
|
363
261
|
|
|
364
262
|
# Get existing ssps by CSAM Id
|
|
365
263
|
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
366
|
-
ssp_map =
|
|
264
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
367
265
|
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
368
266
|
)
|
|
369
267
|
|
|
@@ -375,7 +273,7 @@ def import_csam_pocs(import_ids: Optional[List[int]] = None):
|
|
|
375
273
|
|
|
376
274
|
# TO DO... Add the rest of the logic
|
|
377
275
|
# Delete these lines: Added to shut up sonarqube
|
|
378
|
-
logger.debug(f"Custom Fields Map: {
|
|
276
|
+
logger.debug(f"Custom Fields Map: {custom_fields_pocs_map}, User Map: {user_map}")
|
|
379
277
|
logger.debug(f"SSP Map: {ssp_map}, Plans: {plans}")
|
|
380
278
|
|
|
381
279
|
|
|
@@ -384,62 +282,72 @@ def import_csam_privacy_info(import_ids: Optional[List[int]] = None):
|
|
|
384
282
|
Import the Privacy Info from CSAM
|
|
385
283
|
Into RegScale
|
|
386
284
|
"""
|
|
387
|
-
custom_fields_privacy_list = ["PIA Date", "PTA Date", "SORN Date", "SORN Id"]
|
|
388
|
-
|
|
389
|
-
# Check for custom fields
|
|
390
|
-
custom_fields_privacy_map = FormFieldValue.check_custom_fields(
|
|
391
|
-
custom_fields_privacy_list, "securityplans", SSP_PRIVACY_TAB
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
# Grab the data from CSAM
|
|
395
|
-
app = Application()
|
|
396
|
-
csam_token = app.config.get("csamToken")
|
|
397
|
-
csam_url = app.config.get("csamURL")
|
|
398
285
|
|
|
399
286
|
# Get existing ssps by CSAM Id
|
|
400
287
|
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
401
|
-
ssp_map =
|
|
288
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
402
289
|
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
403
290
|
)
|
|
404
291
|
|
|
405
|
-
|
|
292
|
+
ssps = import_ids if import_ids else list(ssp_map.keys())
|
|
406
293
|
|
|
407
|
-
|
|
408
|
-
|
|
294
|
+
updated_ssps = []
|
|
295
|
+
if len(ssps) == 0:
|
|
296
|
+
return
|
|
297
|
+
for index in track(
|
|
298
|
+
range(len(ssps)),
|
|
299
|
+
description=f"Importing {len(ssps)} SSP privacy...",
|
|
300
|
+
):
|
|
301
|
+
ssp = ssps[index]
|
|
302
|
+
system_id = ssp_map.get(ssp)
|
|
303
|
+
if not system_id:
|
|
304
|
+
logger.error(f"Could not find CSAM ID for SSP id: {ssp}")
|
|
305
|
+
continue
|
|
306
|
+
else:
|
|
307
|
+
updated_ssps.append(ssp)
|
|
409
308
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
pta_date = privacy_status.get("privacyThresholdAnalysisDateCompleted")
|
|
309
|
+
result = retrieve_from_csam(csam_endpoint=f"/CSAM/api/v1/systems/{system_id}/privacy")
|
|
310
|
+
if len(result) == 0:
|
|
311
|
+
logger.error(f"Could not retrieve privacy for CSAM ID {system_id}. RegScale SSP id: {ssp}")
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
pia_date = result.get("privacyImpactAssessmentDateCompleted")
|
|
315
|
+
pta_date = result.get("privacyThresholdAnalysisDateCompleted")
|
|
418
316
|
|
|
419
317
|
# Get SORN Status
|
|
420
|
-
|
|
421
|
-
csam_token=csam_token,
|
|
422
|
-
csam_url=csam_url,
|
|
318
|
+
result = retrieve_from_csam(
|
|
423
319
|
csam_endpoint=f"/CSAM/api/v1/systems/{system_id}/sorn",
|
|
424
320
|
)
|
|
321
|
+
if len(result) == 0:
|
|
322
|
+
logger.debug(f"Could not retrieve SORN for CSAM ID {system_id}. RegScale SSP id: {ssp}")
|
|
323
|
+
continue
|
|
425
324
|
sorn_date = 0
|
|
426
325
|
sorn_id = ""
|
|
427
|
-
for sorn_status in
|
|
326
|
+
for sorn_status in result:
|
|
428
327
|
if date_obj(sorn_status.get("publishedDate")) > date_obj(sorn_date):
|
|
429
328
|
sorn_date = sorn_status.get("publishedDate")
|
|
430
329
|
sorn_id = sorn_status.get("systemOfRecordsNoticeId").strip()
|
|
431
330
|
|
|
432
331
|
# Set the records
|
|
433
332
|
record = {"pia_date": pia_date, "pta_date": pta_date, "sorn_date": sorn_date, "sorn_id": sorn_id}
|
|
434
|
-
save_privacy_records(regscale_id=
|
|
333
|
+
save_privacy_records(regscale_id=ssp, record=record)
|
|
334
|
+
|
|
335
|
+
logger.info(f"Updated {len(updated_ssps)} Security Plans with privacy data")
|
|
435
336
|
|
|
436
337
|
|
|
437
|
-
def save_privacy_records(regscale_id: int,
|
|
338
|
+
def save_privacy_records(regscale_id: int, record: dict):
|
|
339
|
+
|
|
340
|
+
custom_fields_privacy_list = ["PIA Date", "PTA Date", "SORN Date", "SORN Id"]
|
|
341
|
+
|
|
342
|
+
# Check for custom fields
|
|
343
|
+
custom_fields_map = FormFieldValue.check_custom_fields(custom_fields_privacy_list, "securityplans", SSP_PRIVACY_TAB)
|
|
344
|
+
|
|
438
345
|
privacy_fields = []
|
|
439
346
|
if record.get("pia_date"):
|
|
440
347
|
privacy_fields.append(
|
|
441
348
|
{
|
|
442
349
|
"record_id": regscale_id,
|
|
350
|
+
"record_module": "securityplans",
|
|
443
351
|
"form_field_id": custom_fields_map["PIA Date"],
|
|
444
352
|
"field_value": format_to_regscale_iso(record.get("pia_date")),
|
|
445
353
|
}
|
|
@@ -448,6 +356,7 @@ def save_privacy_records(regscale_id: int, custom_fields_map: dict, record: dict
|
|
|
448
356
|
privacy_fields.append(
|
|
449
357
|
{
|
|
450
358
|
"record_id": regscale_id,
|
|
359
|
+
"record_module": "securityplans",
|
|
451
360
|
"form_field_id": custom_fields_map["PTA Date"],
|
|
452
361
|
"field_value": format_to_regscale_iso(record.get("pta_date")),
|
|
453
362
|
}
|
|
@@ -456,6 +365,7 @@ def save_privacy_records(regscale_id: int, custom_fields_map: dict, record: dict
|
|
|
456
365
|
privacy_fields.append(
|
|
457
366
|
{
|
|
458
367
|
"record_id": regscale_id,
|
|
368
|
+
"record_module": "securityplans",
|
|
459
369
|
"form_field_id": custom_fields_map["SORN Date"],
|
|
460
370
|
"field_value": format_to_regscale_iso(record.get("sorn_date")),
|
|
461
371
|
}
|
|
@@ -464,149 +374,368 @@ def save_privacy_records(regscale_id: int, custom_fields_map: dict, record: dict
|
|
|
464
374
|
privacy_fields.append(
|
|
465
375
|
{
|
|
466
376
|
"record_id": regscale_id,
|
|
377
|
+
"record_module": "securityplans",
|
|
467
378
|
"form_field_id": custom_fields_map["SORN Id"],
|
|
468
|
-
"field_value": record.get("sorn_id"),
|
|
379
|
+
"field_value": str(record.get("sorn_id")),
|
|
469
380
|
}
|
|
470
381
|
)
|
|
471
382
|
if len(privacy_fields) > 0:
|
|
383
|
+
privacy_fields = fix_form_field_value(privacy_fields)
|
|
472
384
|
FormFieldValue.save_custom_fields(privacy_fields)
|
|
473
385
|
|
|
474
386
|
|
|
475
|
-
def
|
|
387
|
+
def import_csam_authorization(import_ids: Optional[List[int]] = None):
|
|
476
388
|
"""
|
|
477
|
-
|
|
478
|
-
|
|
389
|
+
Update the Authorization of the SSPs
|
|
390
|
+
This requires a call to the /system/{id}/securityauthorization
|
|
391
|
+
endpoint
|
|
392
|
+
|
|
393
|
+
:param list import_ids: Filtered list of SSPs
|
|
394
|
+
:return: None
|
|
479
395
|
"""
|
|
480
|
-
#
|
|
481
|
-
|
|
396
|
+
# Get existing ssps by CSAM Id
|
|
397
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
398
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
399
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
400
|
+
)
|
|
482
401
|
|
|
402
|
+
ssps = import_ids if import_ids else list(ssp_map.keys())
|
|
483
403
|
|
|
484
|
-
|
|
404
|
+
updated_ssps = []
|
|
405
|
+
field_values = []
|
|
406
|
+
if len(ssps) == 0:
|
|
407
|
+
return
|
|
408
|
+
for index in track(
|
|
409
|
+
range(len(ssps)),
|
|
410
|
+
description=f"Importing {len(ssps)} SSP authorization...",
|
|
411
|
+
):
|
|
412
|
+
ssp = ssps[index]
|
|
413
|
+
csam_id = ssp_map.get(ssp)
|
|
414
|
+
if not csam_id:
|
|
415
|
+
logger.error(f"Could not find CSAM ID for SSP id: {ssp}")
|
|
416
|
+
continue
|
|
417
|
+
else:
|
|
418
|
+
updated_ssps.append(ssp)
|
|
419
|
+
|
|
420
|
+
result = retrieve_from_csam(
|
|
421
|
+
csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/securityauthorization",
|
|
422
|
+
)
|
|
423
|
+
if len(result) == 0:
|
|
424
|
+
logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
|
|
425
|
+
continue
|
|
426
|
+
# Set the authorization expiration date
|
|
427
|
+
ssp_obj = SecurityPlan.get_object(object_id=ssp)
|
|
428
|
+
if ssp_obj:
|
|
429
|
+
ssp_obj.authorizationTerminationDate = result.get("authorizationExpirationDate")
|
|
430
|
+
ssp_obj.save()
|
|
431
|
+
else:
|
|
432
|
+
logger.debug(f"Failed to retrieve Security Plan id: {ssp}")
|
|
433
|
+
# Get the custom fields
|
|
434
|
+
field_values.append(
|
|
435
|
+
{
|
|
436
|
+
"record_id": ssp,
|
|
437
|
+
"record_module": "securityplans",
|
|
438
|
+
"form_field_id": custom_fields_basic_map["Authorization Process"],
|
|
439
|
+
"field_value": str(result.get("authorizationProcess")),
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
field_values.append(
|
|
443
|
+
{
|
|
444
|
+
"record_id": ssp,
|
|
445
|
+
"record_module": "securityplans",
|
|
446
|
+
"form_field_id": custom_fields_basic_map["ATO Date"],
|
|
447
|
+
"field_value": str(result.get("lastAuthorizationDate")),
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
field_values.append(
|
|
451
|
+
{
|
|
452
|
+
"record_id": ssp,
|
|
453
|
+
"record_module": "securityplans",
|
|
454
|
+
"form_field_id": custom_fields_basic_map["ATO Status"],
|
|
455
|
+
"field_value": str(result.get("authorizationStatus")),
|
|
456
|
+
}
|
|
457
|
+
)
|
|
458
|
+
# Save the Custom Fields
|
|
459
|
+
if len(field_values) > 0:
|
|
460
|
+
field_values = fix_form_field_value(field_values)
|
|
461
|
+
FormFieldValue.save_custom_fields(field_values)
|
|
462
|
+
|
|
463
|
+
logger.info(f"Updated {len(updated_ssps)} Security Plans with authorization data")
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def import_csam_contingency(import_ids: Optional[List[int]] = None):
|
|
485
467
|
"""
|
|
486
|
-
|
|
468
|
+
Update the Contingency & IR of the SSPs
|
|
469
|
+
This requires a call to the /systems/<system_id>/continuityresponse
|
|
470
|
+
endpoint
|
|
487
471
|
|
|
488
|
-
:param list import_ids:
|
|
472
|
+
:param list import_ids: Filtered list of SSPs
|
|
489
473
|
:return: None
|
|
490
474
|
"""
|
|
475
|
+
# Continuity & IR Fields
|
|
476
|
+
# Goes to "Continuity and Incident Response" Tab
|
|
477
|
+
# /CSAM/api/v1/systems/<system_id>/continuityresponse
|
|
478
|
+
continuity_map = {
|
|
479
|
+
"maximumTolerableDowntime": "MTD",
|
|
480
|
+
"recoveryTimeObjective": "RTO",
|
|
481
|
+
"recoveryPointObjective": "RPO",
|
|
482
|
+
"businessImpactAnalysisDateCompleted": "BIA Completed",
|
|
483
|
+
"businessImpactAnalysisNextDueDate": "BIA Next Due Date",
|
|
484
|
+
"contingencyPlanDateCompleted": "CP Completed",
|
|
485
|
+
"contingencyPlanNextDueDate": "CP Next Due Date",
|
|
486
|
+
"contingencyPlanTrainingDateCompleted": "CP Training Completed",
|
|
487
|
+
"contingencyPlanTrainingNextDueDate": "CP Training Next Due Date",
|
|
488
|
+
"contingencyPlanTestNextDueDate": "CP Test Next Due Date",
|
|
489
|
+
"incidentResponsePlanDateCompleted": "IRP Completed",
|
|
490
|
+
"incidentResponsePlanNextDueDate": "IRP Next Due Date",
|
|
491
|
+
"incidentResponsePlanTrainingDateCompleted": "IRP Training Completed",
|
|
492
|
+
"incidentResponsePlanTrainingNextDueDate": "IRP Training Next Due Date",
|
|
493
|
+
"incidentResponsePlanTestNextDueDate": "IRP Test Next Due Date",
|
|
494
|
+
}
|
|
491
495
|
|
|
492
|
-
#
|
|
493
|
-
|
|
494
|
-
|
|
496
|
+
# /CSAM/api/v1/systems/<system_id>/continuitytest
|
|
497
|
+
# testItem == "Contingency Plan (CP)""
|
|
498
|
+
continuity_test_map = {"testType": "CP Test Type", "dateTested": "CP Date Tested", "outcome": "CP Test Outcome"}
|
|
499
|
+
# testItem == "Incident Response Plan (IRP)"
|
|
500
|
+
irp_test_map = {"testType": "IRP Test Type", "dateTested": "IRP Date Tested", "outcome": "IRP Test Outcome"}
|
|
501
|
+
|
|
502
|
+
# /CSAM/api/v1/systems/{system_id}/additionalstatus
|
|
503
|
+
# name == "Contingency Plan Review"
|
|
504
|
+
contingency_plan_map = {
|
|
505
|
+
"dateCompleted": "CPR Completed",
|
|
506
|
+
"nextDueDate": "CPR Next Due Date",
|
|
507
|
+
"expirationDate": "CPR Expiration Date",
|
|
508
|
+
}
|
|
509
|
+
continuity_ir_fields = []
|
|
510
|
+
continuity_ir_fields = continuity_ir_fields + list(continuity_map.values())
|
|
511
|
+
continuity_ir_fields = continuity_ir_fields + list(continuity_test_map.values())
|
|
512
|
+
continuity_ir_fields = continuity_ir_fields + list(irp_test_map.values())
|
|
513
|
+
continuity_ir_fields = continuity_ir_fields + list(contingency_plan_map.values())
|
|
514
|
+
continuity_ir_fields_map = FormFieldValue.check_custom_fields(
|
|
515
|
+
continuity_ir_fields, "securityplans", "Continuity and Incident Response"
|
|
516
|
+
)
|
|
495
517
|
|
|
496
|
-
|
|
497
|
-
|
|
518
|
+
# name == "Document Review Approval"
|
|
519
|
+
# goes to "Document Review" Tab
|
|
520
|
+
doc_approv_map = {
|
|
521
|
+
"dateCompleted": "Doc Review Completed",
|
|
522
|
+
"nextDueDate": "Doc Review Next Due Date",
|
|
523
|
+
"expirationDate": "Doc Review Expiration Date",
|
|
524
|
+
}
|
|
525
|
+
doc_approv_fields = list(doc_approv_map.values())
|
|
526
|
+
doc_approv_fields_map = FormFieldValue.check_custom_fields(doc_approv_fields, "securityplans", "Document Review")
|
|
498
527
|
|
|
499
|
-
# Get
|
|
500
|
-
|
|
528
|
+
# Get existing ssps by CSAM Id
|
|
529
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
530
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
531
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
ssps = import_ids if import_ids else list(ssp_map.keys())
|
|
535
|
+
|
|
536
|
+
updated_ssps = []
|
|
537
|
+
field_values = []
|
|
538
|
+
if len(ssps) == 0:
|
|
539
|
+
return
|
|
501
540
|
for index in track(
|
|
502
|
-
range(len(
|
|
503
|
-
description=f"Importing
|
|
541
|
+
range(len(ssps)),
|
|
542
|
+
description=f"Importing {len(ssps)} SSP contingency data...",
|
|
504
543
|
):
|
|
505
|
-
ssp =
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
inheritances = retrieve_from_csam(
|
|
510
|
-
csam_url=app.config.get("csamURL"),
|
|
511
|
-
csam_token=app.config.get("csamToken"),
|
|
512
|
-
csam_endpoint=f"/CSAM/api/v1/systems/{ssp.otherIdentifier}/inheritedcontrols",
|
|
513
|
-
)
|
|
514
|
-
if not inheritances:
|
|
515
|
-
logger.debug(f"No inheritance data found for SSP {ssp.systemName} (ID: {ssp.id})")
|
|
544
|
+
ssp = ssps[index]
|
|
545
|
+
csam_id = ssp_map.get(ssp)
|
|
546
|
+
if not csam_id:
|
|
547
|
+
logger.error(f"Could not find CSAM ID for SSP id: {ssp}")
|
|
516
548
|
continue
|
|
517
|
-
|
|
518
|
-
|
|
549
|
+
else:
|
|
550
|
+
updated_ssps.append(ssp)
|
|
519
551
|
|
|
520
|
-
|
|
521
|
-
|
|
552
|
+
continuity_response = get_continuity_response_fields(
|
|
553
|
+
ssp=ssp, csam_id=csam_id, continuity_ir_fields_map=continuity_ir_fields_map, continuity_map=continuity_map
|
|
522
554
|
)
|
|
555
|
+
field_values = field_values + continuity_response
|
|
523
556
|
|
|
557
|
+
continuity_test = get_continuity_test_fields(
|
|
558
|
+
ssp=ssp,
|
|
559
|
+
csam_id=csam_id,
|
|
560
|
+
continuity_ir_fields_map=continuity_ir_fields_map,
|
|
561
|
+
continuity_test_map=continuity_test_map,
|
|
562
|
+
irp_test_map=irp_test_map,
|
|
563
|
+
)
|
|
564
|
+
field_values = field_values + continuity_test
|
|
524
565
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
566
|
+
additional_status = get_additional_status_fields(
|
|
567
|
+
ssp=ssp,
|
|
568
|
+
csam_id=csam_id,
|
|
569
|
+
continuity_ir_fields_map=continuity_ir_fields_map,
|
|
570
|
+
contingency_plan_map=contingency_plan_map,
|
|
571
|
+
doc_approv_fields_map=doc_approv_fields_map,
|
|
572
|
+
doc_approv_map=doc_approv_map,
|
|
573
|
+
)
|
|
574
|
+
field_values = field_values + additional_status
|
|
528
575
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
""
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
"
|
|
576
|
+
# Save the Custom Fields
|
|
577
|
+
if len(field_values) > 0:
|
|
578
|
+
field_values = fix_form_field_value(field_values)
|
|
579
|
+
FormFieldValue.save_custom_fields(field_values)
|
|
580
|
+
|
|
581
|
+
logger.info(f"Updated {len(updated_ssps)} Security Plans with contingency data")
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def get_continuity_response_fields(ssp: int, csam_id: int, continuity_map: dict, continuity_ir_fields_map: dict):
|
|
585
|
+
# Get the data from /continuityresponse
|
|
586
|
+
result = retrieve_from_csam(
|
|
587
|
+
csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/continuityresponse",
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
if not result:
|
|
591
|
+
logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
|
|
592
|
+
return []
|
|
593
|
+
|
|
594
|
+
response_data = result[0]
|
|
595
|
+
|
|
596
|
+
# Pre-compute the field mapping to avoid nested lookups
|
|
597
|
+
field_mapping = {
|
|
598
|
+
field: continuity_ir_fields_map[mapped_name]
|
|
599
|
+
for field, mapped_name in continuity_map.items()
|
|
600
|
+
if mapped_name in continuity_ir_fields_map
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
# Build field values with single-level lookup
|
|
604
|
+
return [
|
|
605
|
+
{
|
|
606
|
+
"record_id": ssp,
|
|
607
|
+
"record_module": "securityplans",
|
|
608
|
+
"form_field_id": field_id,
|
|
609
|
+
"field_value": str(response_data[field]),
|
|
610
|
+
}
|
|
611
|
+
for field, field_id in field_mapping.items()
|
|
612
|
+
if field in response_data
|
|
613
|
+
]
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def get_continuity_test_fields(
|
|
617
|
+
ssp: int, csam_id: int, continuity_ir_fields_map: dict, continuity_test_map: dict, irp_test_map: dict
|
|
618
|
+
):
|
|
619
|
+
# Get the data from continuitytest
|
|
620
|
+
results = retrieve_from_csam(csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/continuitytest")
|
|
621
|
+
|
|
622
|
+
if not results:
|
|
623
|
+
logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
|
|
624
|
+
return []
|
|
625
|
+
|
|
626
|
+
# Pre-compute field mappings to avoid nested lookups
|
|
627
|
+
cp_field_mapping = {
|
|
628
|
+
field: continuity_ir_fields_map[mapped_name]
|
|
629
|
+
for field, mapped_name in continuity_test_map.items()
|
|
630
|
+
if mapped_name in continuity_ir_fields_map
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
irp_field_mapping = {
|
|
634
|
+
field: continuity_ir_fields_map[mapped_name]
|
|
635
|
+
for field, mapped_name in irp_test_map.items()
|
|
636
|
+
if mapped_name in continuity_ir_fields_map
|
|
545
637
|
}
|
|
546
638
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
639
|
+
field_values = []
|
|
640
|
+
for result in results:
|
|
641
|
+
test_item = result.get("testItem")
|
|
642
|
+
|
|
643
|
+
# Process Contingency Plan (CP) fields
|
|
644
|
+
if test_item == "Contingency Plan (CP)":
|
|
645
|
+
field_values.extend(
|
|
646
|
+
[
|
|
647
|
+
{
|
|
648
|
+
"record_id": ssp,
|
|
649
|
+
"record_module": "securityplans",
|
|
650
|
+
"form_field_id": field_id,
|
|
651
|
+
"field_value": str(result[field]),
|
|
652
|
+
}
|
|
653
|
+
for field, field_id in cp_field_mapping.items()
|
|
654
|
+
if field in result
|
|
655
|
+
]
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
# Process Incident Response Plan (IRP) fields
|
|
659
|
+
elif test_item == "Incident Response Plan (IRP)":
|
|
660
|
+
field_values.extend(
|
|
661
|
+
[
|
|
662
|
+
{
|
|
663
|
+
"record_id": ssp,
|
|
664
|
+
"record_module": "securityplans",
|
|
665
|
+
"form_field_id": field_id,
|
|
666
|
+
"field_value": str(result[field]),
|
|
667
|
+
}
|
|
668
|
+
for field, field_id in irp_field_mapping.items()
|
|
669
|
+
if field in result
|
|
670
|
+
]
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
return field_values
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def get_additional_status_fields(
|
|
677
|
+
ssp: int,
|
|
678
|
+
csam_id: int,
|
|
679
|
+
continuity_ir_fields_map: dict,
|
|
680
|
+
contingency_plan_map: dict,
|
|
681
|
+
doc_approv_fields_map: dict,
|
|
682
|
+
doc_approv_map: dict,
|
|
683
|
+
):
|
|
684
|
+
# Get the data from additional status
|
|
685
|
+
results = retrieve_from_csam(csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/additionalstatus")
|
|
686
|
+
|
|
687
|
+
if not results:
|
|
688
|
+
logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
|
|
550
689
|
return []
|
|
551
|
-
if issue_response and issue_response.ok:
|
|
552
|
-
return issue_response.json()
|
|
553
690
|
|
|
554
|
-
|
|
691
|
+
# Pre-compute field mappings to avoid nested lookups
|
|
692
|
+
cp_field_mapping = {
|
|
693
|
+
field: continuity_ir_fields_map[mapped_name]
|
|
694
|
+
for field, mapped_name in contingency_plan_map.items()
|
|
695
|
+
if mapped_name in continuity_ir_fields_map
|
|
696
|
+
}
|
|
555
697
|
|
|
698
|
+
doc_field_mapping = {
|
|
699
|
+
field: doc_approv_fields_map[mapped_name]
|
|
700
|
+
for field, mapped_name in doc_approv_map.items()
|
|
701
|
+
if mapped_name in doc_approv_fields_map
|
|
702
|
+
}
|
|
556
703
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
Returns a map of Custom Field Value: RegScale Id
|
|
561
|
-
|
|
562
|
-
:param str tab_name: The RegScale tab name where the custom field is located
|
|
563
|
-
:param int field_form_id: The RegScale Form Id of custom field
|
|
564
|
-
:param int tab_id: The RegScale tab id
|
|
565
|
-
:return: dictionary of FieldForm Id: regscale_ssp_id
|
|
566
|
-
:return_type: dict
|
|
567
|
-
"""
|
|
568
|
-
tab = Module.get_tab_by_name(regscale_module_name="securityplans", regscale_tab_name=tab_name)
|
|
569
|
-
|
|
570
|
-
field_form_map = {}
|
|
571
|
-
ssps = SecurityPlan.get_ssp_list()
|
|
572
|
-
form_values = []
|
|
573
|
-
for ssp in ssps:
|
|
574
|
-
form_values = FormFieldValue.get_field_values(
|
|
575
|
-
record_id=ssp["id"], module_name=SecurityPlan.get_module_slug(), form_id=tab.id
|
|
576
|
-
)
|
|
704
|
+
field_values = []
|
|
705
|
+
for result in results:
|
|
706
|
+
result_name = result.get("name")
|
|
577
707
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
708
|
+
# Process Contingency Plan Review fields
|
|
709
|
+
if result_name == "Contingency Plan Review":
|
|
710
|
+
field_values.extend(
|
|
711
|
+
[
|
|
712
|
+
{
|
|
713
|
+
"record_id": ssp,
|
|
714
|
+
"record_module": "securityplans",
|
|
715
|
+
"form_field_id": field_id,
|
|
716
|
+
"field_value": str(result[field]),
|
|
717
|
+
}
|
|
718
|
+
for field, field_id in cp_field_mapping.items()
|
|
719
|
+
if field in result
|
|
720
|
+
]
|
|
721
|
+
)
|
|
583
722
|
|
|
723
|
+
# Process Document Review Approval fields
|
|
724
|
+
elif result_name == "Document Review Approval":
|
|
725
|
+
field_values.extend(
|
|
726
|
+
[
|
|
727
|
+
{
|
|
728
|
+
"record_id": ssp,
|
|
729
|
+
"record_module": "securityplans",
|
|
730
|
+
"form_field_id": field_id,
|
|
731
|
+
"field_value": str(result[field]),
|
|
732
|
+
}
|
|
733
|
+
for field, field_id in doc_field_mapping.items()
|
|
734
|
+
if field in result
|
|
735
|
+
]
|
|
736
|
+
)
|
|
584
737
|
|
|
585
|
-
|
|
586
|
-
"""
|
|
587
|
-
Retreives a list of the SSPs in RegScale
|
|
588
|
-
Returns a map of RegScale ID: Custom Field Value
|
|
589
|
-
|
|
590
|
-
:param str tab_name: The RegScale tab name where the custom field is located
|
|
591
|
-
:param int field_form_id: The RegScale Form Id of custom field
|
|
592
|
-
:param int tab_id: The RegScale tab id
|
|
593
|
-
:return: dictionary of FieldForm Id: regscale_ssp_id
|
|
594
|
-
:return_type: dict
|
|
595
|
-
"""
|
|
596
|
-
tab = Module.get_tab_by_name(regscale_module_name="securityplans", regscale_tab_name=tab_name)
|
|
597
|
-
|
|
598
|
-
field_form_map = {}
|
|
599
|
-
ssps = SecurityPlan.get_ssp_list()
|
|
600
|
-
form_values = []
|
|
601
|
-
for ssp in ssps:
|
|
602
|
-
form_values = FormFieldValue.get_field_values(
|
|
603
|
-
record_id=ssp["id"], module_name=SecurityPlan.get_module_slug(), form_id=tab.id
|
|
604
|
-
)
|
|
605
|
-
for form in form_values:
|
|
606
|
-
if form.formFieldId == field_form_id and form.data:
|
|
607
|
-
field_form_map[ssp["id"]] = form.data
|
|
608
|
-
form_values = []
|
|
609
|
-
return field_form_map
|
|
738
|
+
return field_values
|
|
610
739
|
|
|
611
740
|
|
|
612
741
|
def update_ssp_general(ssp: SecurityPlan, record: dict, org_map: dict) -> SecurityPlan:
|
|
@@ -629,6 +758,7 @@ def update_ssp_general(ssp: SecurityPlan, record: dict, org_map: dict) -> Securi
|
|
|
629
758
|
ssp.status = record["operationalStatus"]
|
|
630
759
|
ssp.systemType = record["systemType"]
|
|
631
760
|
ssp.description = record["purpose"]
|
|
761
|
+
ssp.defaultAssessmentDays = 0
|
|
632
762
|
if record["organization"] and org_map.get(record["organization"]):
|
|
633
763
|
ssp.orgId = org_map.get(record["organization"])
|
|
634
764
|
|
|
@@ -640,20 +770,18 @@ def update_ssp_general(ssp: SecurityPlan, record: dict, org_map: dict) -> Securi
|
|
|
640
770
|
return new_ssp
|
|
641
771
|
|
|
642
772
|
|
|
643
|
-
def save_ssp_front_matter(
|
|
644
|
-
results: list, ssp_map: dict, custom_fields_basic_map: dict, custom_fields_financial_map: dict, org_map: dict
|
|
645
|
-
) -> list:
|
|
773
|
+
def save_ssp_front_matter(results: list, ssp_map: dict, custom_fields_basic_map: dict, org_map: dict) -> list:
|
|
646
774
|
"""
|
|
647
775
|
Save the SSP data from the /systems endpoint
|
|
648
776
|
|
|
649
777
|
:param list results: list of results from CSAM
|
|
650
778
|
:param dict ssp_map: map of existing SSPs in RegScale
|
|
651
779
|
:param dict custom_fields_basic_map: map of custom fields in RegScale
|
|
652
|
-
:param dict custom_fields_financial_map: map of custom fields in RegScale
|
|
653
780
|
:param dict org_map: map of existing orgs in RegScale
|
|
654
781
|
:return: list of updated SSPs
|
|
655
782
|
:return_type: List[SecurityPlan]
|
|
656
783
|
"""
|
|
784
|
+
|
|
657
785
|
updated_ssps = []
|
|
658
786
|
for index in track(
|
|
659
787
|
range(len(results)),
|
|
@@ -671,107 +799,17 @@ def save_ssp_front_matter(
|
|
|
671
799
|
ssp = update_ssp_general(ssp, result, org_map)
|
|
672
800
|
|
|
673
801
|
# Grab the Custom Fields
|
|
674
|
-
field_values = set_front_matter_fields(
|
|
675
|
-
ssp=ssp,
|
|
676
|
-
result=result,
|
|
677
|
-
custom_fields_basic_map=custom_fields_basic_map,
|
|
678
|
-
custom_fields_financial_map=custom_fields_financial_map,
|
|
679
|
-
)
|
|
802
|
+
field_values = set_front_matter_fields(ssp=ssp, result=result, custom_fields_basic_map=custom_fields_basic_map)
|
|
680
803
|
|
|
681
804
|
# System Custom Fields
|
|
805
|
+
field_values = fix_form_field_value(field_values)
|
|
682
806
|
FormFieldValue.save_custom_fields(field_values)
|
|
683
807
|
updated_ssps.append(ssp)
|
|
684
808
|
logger.info(f"Updated {len(results)} Security Plans Front Matter")
|
|
685
809
|
return updated_ssps
|
|
686
810
|
|
|
687
811
|
|
|
688
|
-
def
|
|
689
|
-
"""
|
|
690
|
-
Update the Agency Details of the SSPs
|
|
691
|
-
This requires a call to the /system/{id}/agencydefineddataitems
|
|
692
|
-
endpoint
|
|
693
|
-
|
|
694
|
-
:param list ssps: list of RegScale SSPs
|
|
695
|
-
:param str csam_token: CSAM Bearer Token
|
|
696
|
-
:param str csam_url: CSAM URL
|
|
697
|
-
:param dict custom_fields_basic_map: map of custom fields in RegScale
|
|
698
|
-
:return: list of updated SSPs
|
|
699
|
-
:return_type: List[SecurityPlan]
|
|
700
|
-
"""
|
|
701
|
-
updated_ssps = []
|
|
702
|
-
if len(ssps) == 0:
|
|
703
|
-
return updated_ssps
|
|
704
|
-
for index in track(
|
|
705
|
-
range(len(ssps)),
|
|
706
|
-
description=f"Importing {len(ssps)} SSP agency details...",
|
|
707
|
-
):
|
|
708
|
-
ssp = ssps[index]
|
|
709
|
-
csam_id = ssp.otherIdentifier
|
|
710
|
-
if not csam_id:
|
|
711
|
-
logger.error(f"Could not find CSAM ID for SSP {ssp.systemName} id: {ssp.id}")
|
|
712
|
-
continue
|
|
713
|
-
else:
|
|
714
|
-
updated_ssps.append(ssp)
|
|
715
|
-
|
|
716
|
-
result = retrieve_from_csam(
|
|
717
|
-
csam_token=csam_token,
|
|
718
|
-
csam_url=csam_url,
|
|
719
|
-
csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/agencydefineddataitems",
|
|
720
|
-
)
|
|
721
|
-
if len(result) == 0:
|
|
722
|
-
logger.error(
|
|
723
|
-
f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP: Name: {ssp.systemName} id: {ssp.id}"
|
|
724
|
-
)
|
|
725
|
-
continue
|
|
726
|
-
# Get the custom fields
|
|
727
|
-
set_agency_details(result, ssp, custom_fields_basic_map)
|
|
728
|
-
|
|
729
|
-
logger.info(f"Updated {len(updated_ssps)} Security Plans with Agency Details")
|
|
730
|
-
return updated_ssps
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
def set_agency_details(result: list, ssp: SecurityPlan, custom_fields_basic_map: dict):
|
|
734
|
-
"""
|
|
735
|
-
Loop through results of agencydefineddataitems
|
|
736
|
-
and set the custom fields in RegScale
|
|
737
|
-
|
|
738
|
-
:param list result: list of dict objects from CSAM
|
|
739
|
-
:param SecurityPlan ssp: RegScale Security Plan
|
|
740
|
-
:param dict custom_fields_basic_map: map of custom field names to ids
|
|
741
|
-
"""
|
|
742
|
-
field_values = []
|
|
743
|
-
# Update the fields we need
|
|
744
|
-
for item in result:
|
|
745
|
-
if item.get("attributeName") == "High Value Asset":
|
|
746
|
-
ssp.hva = True if item.get("value") == "1" else False
|
|
747
|
-
|
|
748
|
-
# Binary Values
|
|
749
|
-
if item.get("attributeName") in [
|
|
750
|
-
"External Web Interface",
|
|
751
|
-
"CFO Designation",
|
|
752
|
-
"Law Enforcement Sensitive",
|
|
753
|
-
"AI/ML Components",
|
|
754
|
-
]:
|
|
755
|
-
field_values.append(set_binary_fields(item, ssp, custom_fields_basic_map))
|
|
756
|
-
|
|
757
|
-
if item.get("attributeName") == "Cloud System":
|
|
758
|
-
ssp = set_cloud_system(ssp, item)
|
|
759
|
-
|
|
760
|
-
if item.get("attributeName") == "Cloud Service Model":
|
|
761
|
-
ssp = set_cloud_service(ssp, item)
|
|
762
|
-
|
|
763
|
-
if item.get("attributeName") == "HVA Identifier":
|
|
764
|
-
field_values.append(set_custom_fields(item, ssp, custom_fields_basic_map))
|
|
765
|
-
|
|
766
|
-
# Save the SSP & Custom Fields
|
|
767
|
-
ssp.save()
|
|
768
|
-
if len(field_values) > 0:
|
|
769
|
-
FormFieldValue.save_custom_fields(field_values)
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
def set_front_matter_fields(
|
|
773
|
-
ssp: SecurityPlan, result: dict, custom_fields_basic_map: dict, custom_fields_financial_map: dict
|
|
774
|
-
) -> list:
|
|
812
|
+
def set_front_matter_fields(ssp: SecurityPlan, result: dict, custom_fields_basic_map: dict) -> list:
|
|
775
813
|
"""
|
|
776
814
|
parse the front matter custom fields
|
|
777
815
|
and return a list of field values to be saved
|
|
@@ -779,351 +817,122 @@ def set_front_matter_fields(
|
|
|
779
817
|
:param SecurityPlan ssp: RegScale Security Plan object
|
|
780
818
|
:param dict result: response from CSAM
|
|
781
819
|
:param dict custom_fields_basic_map: map of basic custom fields
|
|
782
|
-
:param dict custom_fields_financial_map: map of financial custom fields
|
|
783
820
|
:return: list of dictionaries with field values
|
|
784
821
|
:return_type: list
|
|
785
822
|
"""
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
823
|
+
custom_fields_financial_list = [
|
|
824
|
+
"Financial System",
|
|
825
|
+
"omb Exhibit",
|
|
826
|
+
"Investment Name",
|
|
827
|
+
"Portfolio",
|
|
828
|
+
"Prior Fy Funding",
|
|
829
|
+
"Current Fy Funding",
|
|
830
|
+
"Next Fy Funding",
|
|
831
|
+
"Funding Import Status",
|
|
832
|
+
]
|
|
833
|
+
|
|
834
|
+
custom_fields_financial_map = FormFieldValue.check_custom_fields(
|
|
835
|
+
custom_fields_financial_list, "securityplans", SSP_FINANCIAL_TAB
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
custom_fields_map = {
|
|
839
|
+
"acronym": "acronym",
|
|
840
|
+
"classification": "Classification",
|
|
841
|
+
"fismaReportable": "FISMA Reportable",
|
|
842
|
+
"contractorSystem": "Contractor System",
|
|
843
|
+
"criticalInfrastructure": "Critical Infrastructure",
|
|
844
|
+
"missionCritical": "Mission Essential",
|
|
845
|
+
"uiiCode": "uiiCode",
|
|
846
|
+
}
|
|
847
|
+
custom_fields_fin_map = {
|
|
848
|
+
"financialSystem": "Financial System",
|
|
849
|
+
"ombExhibit": "omb Exhibit",
|
|
850
|
+
"investmentName": "Investment Name",
|
|
851
|
+
"portfolio": "Portfolio",
|
|
852
|
+
"priorFyFunding": "Prior Fy Funding",
|
|
853
|
+
"currentFyFunding": "Current Fy Funding",
|
|
854
|
+
"nextFyFunding": "Next Fy Funding",
|
|
855
|
+
"fundingImportStatus": "Funding Import Status",
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
# Pre-compute field mappings to avoid nested lookups
|
|
859
|
+
basic_field_mapping = {
|
|
860
|
+
field: custom_fields_basic_map[mapped_name]
|
|
861
|
+
for field, mapped_name in custom_fields_map.items()
|
|
862
|
+
if mapped_name in custom_fields_basic_map and field in result
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
financial_field_mapping = {
|
|
866
|
+
field: custom_fields_financial_map[mapped_name]
|
|
867
|
+
for field, mapped_name in custom_fields_fin_map.items()
|
|
868
|
+
if mapped_name in custom_fields_financial_map and field in result
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
# Start with required ID fields
|
|
872
|
+
field_values = [
|
|
789
873
|
{
|
|
790
874
|
"record_id": ssp.id,
|
|
875
|
+
"record_module": "securityplans",
|
|
791
876
|
"form_field_id": custom_fields_basic_map[FISMA_FIELD_NAME],
|
|
792
877
|
"field_value": str(result["externalId"]),
|
|
793
|
-
}
|
|
794
|
-
)
|
|
795
|
-
# CSAM ID
|
|
796
|
-
field_values.append(
|
|
878
|
+
},
|
|
797
879
|
{
|
|
798
880
|
"record_id": ssp.id,
|
|
881
|
+
"record_module": "securityplans",
|
|
799
882
|
"form_field_id": custom_fields_basic_map[CSAM_FIELD_NAME],
|
|
800
883
|
"field_value": str(result["id"]),
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
# Basic Tab
|
|
804
|
-
for key in result.keys():
|
|
805
|
-
if key in custom_fields_basic_map.keys() and key not in [CSAM_FIELD_NAME, FISMA_FIELD_NAME]:
|
|
806
|
-
if isinstance(result.get(key), bool):
|
|
807
|
-
field_values.append(
|
|
808
|
-
{
|
|
809
|
-
"record_id": ssp.id,
|
|
810
|
-
"form_field_id": custom_fields_basic_map[key],
|
|
811
|
-
"field_value": "Yes" if result.get(key) else "No",
|
|
812
|
-
}
|
|
813
|
-
)
|
|
814
|
-
else:
|
|
815
|
-
field_values.append(
|
|
816
|
-
{
|
|
817
|
-
"record_id": ssp.id,
|
|
818
|
-
"form_field_id": custom_fields_basic_map[key],
|
|
819
|
-
"field_value": str(result.get(key)),
|
|
820
|
-
}
|
|
821
|
-
)
|
|
822
|
-
|
|
823
|
-
# Financial Info Tab
|
|
824
|
-
# custom fields and csam values match
|
|
825
|
-
for key in result.keys():
|
|
826
|
-
if key in custom_fields_financial_map.keys():
|
|
827
|
-
field_values.append(
|
|
828
|
-
{
|
|
829
|
-
"record_id": ssp.id,
|
|
830
|
-
"form_field_id": custom_fields_financial_map[key],
|
|
831
|
-
"field_value": str(result.get(key)),
|
|
832
|
-
}
|
|
833
|
-
)
|
|
834
|
-
return field_values
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
def set_cloud_system(ssp: SecurityPlan, item: dict) -> SecurityPlan:
|
|
838
|
-
"""
|
|
839
|
-
Set the cloud system values in the SSP
|
|
840
|
-
:param SeucrityPlan ssp: RegScale Security Plan
|
|
841
|
-
:param dict item: record from CSAM
|
|
842
|
-
:return: SecurityPlan object with updated cloud system values
|
|
843
|
-
:return_type: SecurityPlan
|
|
844
|
-
"""
|
|
845
|
-
ssp.bDeployPublic = True if item.get("value") == "Public" else False
|
|
846
|
-
ssp.bDeployPrivate = True if item.get("value") == "Private" else False
|
|
847
|
-
ssp.bDeployHybrid = True if item.get("value") == "Hybrid" else False
|
|
848
|
-
ssp.bDeployGov = True if item.get("value") == "GovCloud" else False
|
|
849
|
-
ssp.bDeployOther = True if item.get("value") == "Community" else False
|
|
850
|
-
if ssp.bDeployHybrid or ssp.bDeployOther:
|
|
851
|
-
ssp.deployOtherRemarks = "Hybrid or Community"
|
|
852
|
-
|
|
853
|
-
return ssp
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
def set_cloud_service(ssp: SecurityPlan, item: dict) -> SecurityPlan:
|
|
857
|
-
"""
|
|
858
|
-
Set the cloud service model values in the SSP
|
|
859
|
-
|
|
860
|
-
:param SecurityPlan ssp: RegScale Security Plan
|
|
861
|
-
:param dict item: record from CSAM
|
|
862
|
-
:return: Updated SecurityPlan object
|
|
863
|
-
:return_type: SecurityPlan
|
|
864
|
-
"""
|
|
865
|
-
ssp.bModelIaaS = True if "IaaS" in item.get("value") else False
|
|
866
|
-
ssp.bModelPaaS = True if "PaaS" in item.get("value") else False
|
|
867
|
-
ssp.bModelSaaS = True if "SaaS" in item.get("value") else False
|
|
868
|
-
return ssp
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
def set_binary_fields(item: dict, ssp: SecurityPlan, custom_fields_map: dict) -> dict:
|
|
872
|
-
return {
|
|
873
|
-
"record_id": ssp.id,
|
|
874
|
-
"form_field_id": custom_fields_map[item.get("attributeName")],
|
|
875
|
-
"field_value": "Yes" if (item.get("value")) == "1" else "No",
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
def set_custom_fields(item: dict, ssp: SecurityPlan, custom_fields_map: dict) -> dict:
|
|
880
|
-
"""
|
|
881
|
-
Set the custom fields for the SSP
|
|
882
|
-
|
|
883
|
-
:param dict item: record from CSAM
|
|
884
|
-
:param SecurityPlan ssp: RegScale Security Plan
|
|
885
|
-
:param dict custom_fields_map: map of custom fields in RegScale
|
|
886
|
-
:return: dictionary of field values to be saved
|
|
887
|
-
:return_type: dict
|
|
888
|
-
"""
|
|
889
|
-
return {
|
|
890
|
-
"record_id": ssp.id,
|
|
891
|
-
"form_field_id": custom_fields_map[item.get("attributeName")],
|
|
892
|
-
"field_value": str(item.get("value")),
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
def get_catalogs() -> Tuple[Optional[int], Optional[int]]:
|
|
897
|
-
"""
|
|
898
|
-
Get the catalog ids for NIST SP 800-53 Rev 5 and Rev 4
|
|
899
|
-
|
|
900
|
-
:return: tuple of catalog ids
|
|
901
|
-
:return_type: Tuple[Optional[int], Optional[int]]
|
|
902
|
-
"""
|
|
903
|
-
# Find the Catalogs
|
|
904
|
-
rev5_catalog = Catalog.find_by_guid("b0c40faa-fda4-4ed3-83df-368908d9e9b2") # NIST SP 800-53 Rev 5
|
|
905
|
-
rev5_catalog_id = rev5_catalog.id if rev5_catalog else None
|
|
906
|
-
rev4_catalog = Catalog.find_by_guid("02158108-e491-49de-b9a8-3cb1cb8197dd") # NIST SP 800-53 Rev 4
|
|
907
|
-
rev4_catalog_id = rev4_catalog.id if rev4_catalog else None
|
|
884
|
+
},
|
|
885
|
+
]
|
|
908
886
|
|
|
909
|
-
|
|
887
|
+
# Process basic tab fields
|
|
888
|
+
field_values.extend(_create_basic_field_values(ssp.id, result, basic_field_mapping))
|
|
910
889
|
|
|
890
|
+
# Process financial tab fields
|
|
891
|
+
field_values.extend(_create_financial_field_values(ssp.id, result, financial_field_mapping))
|
|
911
892
|
|
|
912
|
-
|
|
913
|
-
"""
|
|
914
|
-
Build out the control implementations
|
|
915
|
-
from the results returned from CSAM
|
|
893
|
+
return field_values
|
|
916
894
|
|
|
917
|
-
:param list results: records from CSAM
|
|
918
|
-
:param int csam_id: CSAM System Id
|
|
919
|
-
:param int regscale_id: RegScale SSP Id
|
|
920
|
-
:return: list of ControlImplementation objects
|
|
921
|
-
:return_type: list
|
|
922
|
-
"""
|
|
923
|
-
existing_implementations = ControlImplementation.get_list_by_parent(
|
|
924
|
-
regscale_id=regscale_id, regscale_module="securityplans"
|
|
925
|
-
)
|
|
926
|
-
implementations_map = {normalize_controlid(impl["controlId"]): impl["id"] for impl in existing_implementations}
|
|
927
|
-
control_implementations = []
|
|
928
|
-
# Loop through the results and create or update the controls
|
|
929
|
-
for index in track(
|
|
930
|
-
range(len(results)),
|
|
931
|
-
description=f"Importing {len(results)} controls for system id: {csam_id}...",
|
|
932
|
-
):
|
|
933
|
-
result = results[index]
|
|
934
|
-
# Debug
|
|
935
|
-
imp_id = (
|
|
936
|
-
implementations_map.get(normalize_controlid(result["controlId"]))
|
|
937
|
-
if normalize_controlid(result["controlId"]) in implementations_map
|
|
938
|
-
else 0
|
|
939
|
-
)
|
|
940
|
-
|
|
941
|
-
control_implementations.append(
|
|
942
|
-
ControlImplementation(
|
|
943
|
-
id=imp_id,
|
|
944
|
-
status=(
|
|
945
|
-
"Fully Implemented" if result["statedImplementationStatus"] == "Implemented" else "Not Implemented"
|
|
946
|
-
), # Implemented
|
|
947
|
-
responsibility=(
|
|
948
|
-
result["applicability"]
|
|
949
|
-
if result["applicability"] in ["Hybrid", "Inherited"]
|
|
950
|
-
else "Provider (System Specific)"
|
|
951
|
-
), # Hybrid, Applicable
|
|
952
|
-
controlSource="Baseline",
|
|
953
|
-
implementation=result["implementationStatement"],
|
|
954
|
-
controlID=result["controlID"],
|
|
955
|
-
parentId=result["securityPlanId"],
|
|
956
|
-
parentModule="securityplans",
|
|
957
|
-
)
|
|
958
|
-
)
|
|
959
|
-
return control_implementations
|
|
960
895
|
|
|
896
|
+
def _create_basic_field_values(record_id: int, result: dict, field_mapping: dict) -> list:
|
|
897
|
+
"""Helper function to create basic field values with proper type handling"""
|
|
898
|
+
field_values = []
|
|
899
|
+
for field, field_id in field_mapping.items():
|
|
900
|
+
value = result.get(field)
|
|
901
|
+
if isinstance(value, bool):
|
|
902
|
+
field_value = "Yes" if value else "No"
|
|
903
|
+
else:
|
|
904
|
+
field_value = str(value)
|
|
961
905
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
:param str csam_url: url for CSAM API
|
|
970
|
-
:param int system_id: CSAM system id
|
|
971
|
-
:param str framework: Framework name
|
|
972
|
-
:param list controls: list of possible controls
|
|
973
|
-
:param int regscale_id: RegScale SSP Id
|
|
974
|
-
:return: list of control implementations
|
|
975
|
-
:return_type: list
|
|
976
|
-
"""
|
|
977
|
-
imps = []
|
|
978
|
-
# Loop through the controls and get the implementations
|
|
979
|
-
for index in track(
|
|
980
|
-
range(len(controls)),
|
|
981
|
-
description=f"Retrieving implementations for system id: {csam_id}...",
|
|
982
|
-
):
|
|
983
|
-
control = controls[index]
|
|
984
|
-
implementations = retrieve_from_csam(
|
|
985
|
-
csam_token=csam_token,
|
|
986
|
-
csam_url=csam_url,
|
|
987
|
-
csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/controls/{control.controlId}",
|
|
906
|
+
field_values.append(
|
|
907
|
+
{
|
|
908
|
+
"record_id": record_id,
|
|
909
|
+
"record_module": "securityplans",
|
|
910
|
+
"form_field_id": field_id,
|
|
911
|
+
"field_value": field_value,
|
|
912
|
+
}
|
|
988
913
|
)
|
|
914
|
+
return field_values
|
|
989
915
|
|
|
990
|
-
if len(implementations) == 0:
|
|
991
|
-
logger.debug(f"No implementations found for control {control.controlId} in system id: {csam_id}")
|
|
992
|
-
continue
|
|
993
|
-
|
|
994
|
-
# Add the RegScale SSP Id and controlID to the implementation
|
|
995
|
-
for impl in implementations:
|
|
996
|
-
if "NotApplicable" in impl["applicability"]:
|
|
997
|
-
continue
|
|
998
|
-
|
|
999
|
-
impl["securityPlanId"] = regscale_id
|
|
1000
|
-
impl["controlID"] = control.id
|
|
1001
|
-
imps.append(impl)
|
|
1002
|
-
return imps
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
def set_inheritable(regscale_id: int):
|
|
1006
|
-
"""
|
|
1007
|
-
Given a RegScale SSP Id
|
|
1008
|
-
Sets the inheritable flag on all control implementations
|
|
1009
|
-
|
|
1010
|
-
:param int regscale_id: id of Security Plan
|
|
1011
|
-
:return: None
|
|
1012
|
-
"""
|
|
1013
|
-
|
|
1014
|
-
# Get list of existing controlimplementations
|
|
1015
|
-
implementations = ControlImplementation.get_list_by_parent(regscale_id=regscale_id, regscale_module="securityplans")
|
|
1016
|
-
|
|
1017
|
-
for index in track(
|
|
1018
|
-
range(len(implementations)),
|
|
1019
|
-
description="Setting controls Inheritable...",
|
|
1020
|
-
):
|
|
1021
|
-
implementation = implementations[index]
|
|
1022
|
-
imp = ControlImplementation.get_object(object_id=implementation["id"])
|
|
1023
|
-
imp.inheritable = True
|
|
1024
|
-
imp.save()
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
def process_inheritances(
|
|
1028
|
-
inheritances: List[Dict[str, Any]],
|
|
1029
|
-
ssp: SecurityPlan,
|
|
1030
|
-
ssp_map: Dict[str, int],
|
|
1031
|
-
imp_map: Dict[str, int],
|
|
1032
|
-
linked_ssps: List[SecurityPlan],
|
|
1033
|
-
):
|
|
1034
|
-
for inheritance in inheritances:
|
|
1035
|
-
# Check if the control exists in plan
|
|
1036
|
-
control_id = normalize_controlid(inheritance.get("controlId"))
|
|
1037
|
-
if control_id not in imp_map:
|
|
1038
|
-
logger.debug(f"Control {control_id} not found in RegScale for SSP {ssp.systemName} (ID: {ssp.id})")
|
|
1039
|
-
continue
|
|
1040
|
-
|
|
1041
|
-
# Find the baseControl in RegScale
|
|
1042
|
-
# Find the SSP
|
|
1043
|
-
base_ssp = ssp_map.get(inheritance.get("offeringSystemName"))
|
|
1044
|
-
if not base_ssp:
|
|
1045
|
-
logger.debug(f"Base SSP {inheritance.get('offeringSystemName')} not found in RegScale, skipping")
|
|
1046
|
-
continue
|
|
1047
916
|
|
|
1048
|
-
|
|
1049
|
-
|
|
917
|
+
def _create_financial_field_values(record_id: int, result: dict, field_mapping: dict) -> list:
|
|
918
|
+
"""Helper function to create financial field values with proper handling of funding fields"""
|
|
919
|
+
funding_fields = ["priorFyFunding", "currentFyFunding", "nextFyFunding"]
|
|
1050
920
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
# Create the records
|
|
1060
|
-
create_inheritance(
|
|
1061
|
-
parent_id=ssp.id,
|
|
1062
|
-
parent_module="securityplans",
|
|
1063
|
-
hybrid=inheritance.get("isHybrid", True),
|
|
1064
|
-
base_id=base_ssp,
|
|
1065
|
-
control_id=imp_map[control_id],
|
|
1066
|
-
base_control_id=base_control_id,
|
|
1067
|
-
)
|
|
921
|
+
field_values = []
|
|
922
|
+
for field, field_id in field_mapping.items():
|
|
923
|
+
value = result.get(field)
|
|
924
|
+
# Handle blank dollar values
|
|
925
|
+
if field in funding_fields:
|
|
926
|
+
field_value = str(value) if value else "0"
|
|
927
|
+
else:
|
|
928
|
+
field_value = str(value)
|
|
1068
929
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
930
|
+
field_values.append(
|
|
931
|
+
{
|
|
932
|
+
"record_id": record_id,
|
|
933
|
+
"record_module": "securityplans",
|
|
934
|
+
"form_field_id": field_id,
|
|
935
|
+
"field_value": field_value,
|
|
936
|
+
}
|
|
1075
937
|
)
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
def create_inheritance(
|
|
1079
|
-
parent_id: int, parent_module: str, base_id: int, hybrid: bool, control_id: int, base_control_id: int
|
|
1080
|
-
):
|
|
1081
|
-
"""
|
|
1082
|
-
Creates the records for inheritance
|
|
1083
|
-
|
|
1084
|
-
:param int parent_id: Id of inheriting record
|
|
1085
|
-
:param str parent_module: Module of inheriting record
|
|
1086
|
-
:param int base_id: Id of inherited record
|
|
1087
|
-
:param bool hybrid: Is the control hybrid
|
|
1088
|
-
:param int control_id: Id of inheriting control
|
|
1089
|
-
:param int base_control_id: Id of inherited control
|
|
1090
|
-
:return: None
|
|
1091
|
-
"""
|
|
1092
|
-
|
|
1093
|
-
# Update the control implementation
|
|
1094
|
-
control_impl = ControlImplementation.get_object(object_id=control_id)
|
|
1095
|
-
if control_impl:
|
|
1096
|
-
control_impl.bInherited = True
|
|
1097
|
-
control_impl.responsibility = "Hybrid" if hybrid else "Inherited"
|
|
1098
|
-
control_impl.inheritedControlId = base_control_id
|
|
1099
|
-
control_impl.inheritedSecurityPlanId = base_id
|
|
1100
|
-
control_impl.save()
|
|
1101
|
-
|
|
1102
|
-
# Check if the Inherited Control already exists
|
|
1103
|
-
existing = InheritedControl.get_all_by_control(control_id=control_id)
|
|
1104
|
-
for exists in existing:
|
|
1105
|
-
if exists["inheritedControlId"] == base_control_id:
|
|
1106
|
-
return
|
|
1107
|
-
|
|
1108
|
-
InheritedControl(
|
|
1109
|
-
parentId=parent_id, parentModule=parent_module, baseControlId=control_id, inheritedControlId=base_control_id
|
|
1110
|
-
).create()
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
def create_inheritance_linage(parent_id: int, parent_module: str, base_id: int):
|
|
1114
|
-
"""
|
|
1115
|
-
Creates a RegScale Inheritance Record
|
|
1116
|
-
|
|
1117
|
-
:param int parent_id: Id of inheriting record
|
|
1118
|
-
:param str parent_module: Module of inheriting record
|
|
1119
|
-
:param int base_control_id: Id of inherited control
|
|
1120
|
-
:return: None
|
|
1121
|
-
"""
|
|
1122
|
-
# Check if the Inheritance already exists
|
|
1123
|
-
existing = Inheritance.get_all_by_parent(parent_id=parent_id, parent_module=parent_module)
|
|
1124
|
-
for exists in existing:
|
|
1125
|
-
if exists.planId == base_id:
|
|
1126
|
-
return
|
|
1127
|
-
|
|
1128
|
-
# Update Lineage (no way to update.. only create)
|
|
1129
|
-
Inheritance(recordId=parent_id, recordModule=parent_module, planId=base_id).create()
|
|
938
|
+
return field_values
|