regscale-cli 6.22.0.0__py3-none-any.whl → 6.22.0.1__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/integrations/commercial/synqly/edr.py +2 -8
- regscale/integrations/public/csam/__init__.py +0 -0
- regscale/integrations/public/csam/csam.py +1129 -0
- regscale/models/integration_models/cisa_kev_data.json +33 -3
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- {regscale_cli-6.22.0.0.dist-info → regscale_cli-6.22.0.1.dist-info}/METADATA +1 -1
- {regscale_cli-6.22.0.0.dist-info → regscale_cli-6.22.0.1.dist-info}/RECORD +12 -10
- {regscale_cli-6.22.0.0.dist-info → regscale_cli-6.22.0.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.22.0.0.dist-info → regscale_cli-6.22.0.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.22.0.0.dist-info → regscale_cli-6.22.0.1.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.22.0.0.dist-info → regscale_cli-6.22.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1129 @@
|
|
|
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 urllib.parse import urljoin
|
|
9
|
+
from rich.progress import track
|
|
10
|
+
import click
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from regscale.core.app.api import Api
|
|
13
|
+
from regscale.core.app.application import Application
|
|
14
|
+
from regscale.core.utils.date import format_to_regscale_iso, date_obj
|
|
15
|
+
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
|
+
from regscale.models.regscale_models import (
|
|
18
|
+
Catalog,
|
|
19
|
+
ControlImplementation,
|
|
20
|
+
InheritedControl,
|
|
21
|
+
Inheritance,
|
|
22
|
+
Issue,
|
|
23
|
+
Organization,
|
|
24
|
+
SecurityControl,
|
|
25
|
+
SecurityPlan,
|
|
26
|
+
User,
|
|
27
|
+
)
|
|
28
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
29
|
+
from regscale.models.regscale_models.module import Module
|
|
30
|
+
from regscale.models.regscale_models.form_field_value import FormFieldValue
|
|
31
|
+
from regscale.core.app.utils.regscale_utils import normalize_controlid
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger("regscale")
|
|
34
|
+
console = Console()
|
|
35
|
+
|
|
36
|
+
####################################################################################################
|
|
37
|
+
#
|
|
38
|
+
# IMPORT SSP / POAM FROM DoJ's CSAM GRC
|
|
39
|
+
# CSAM API Docs: https://csam.dhs.gov/CSAM/api/docs/index.html (required PIV)
|
|
40
|
+
#
|
|
41
|
+
####################################################################################################
|
|
42
|
+
|
|
43
|
+
SSP_BASIC_TAB = "Basic Info"
|
|
44
|
+
SSP_SYSTEM_TAB = "System Information"
|
|
45
|
+
SSP_FINANCIAL_TAB = "Financial Info"
|
|
46
|
+
SSP_PRIVACY_TAB = "Privacy-Details"
|
|
47
|
+
CSAM_FIELD_NAME = "CSAM Id"
|
|
48
|
+
FISMA_FIELD_NAME = "FISMA Id"
|
|
49
|
+
POAM_ID = "POAM Id"
|
|
50
|
+
SYSTEM_ID = "System ID"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@click.group()
|
|
54
|
+
def csam():
|
|
55
|
+
"""Integrate CSAM."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@csam.command(name="import_ssp")
|
|
59
|
+
def import_ssp():
|
|
60
|
+
"""
|
|
61
|
+
Import SSP from CSAM
|
|
62
|
+
Into RegScale
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
import_csam_ssp()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@csam.command(name="import_poam")
|
|
69
|
+
def import_poam():
|
|
70
|
+
"""
|
|
71
|
+
Import POAMS from CSAM
|
|
72
|
+
Into RegScale
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
import_csam_poams()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def import_csam_ssp():
|
|
79
|
+
"""
|
|
80
|
+
Import an SSP from CSAM
|
|
81
|
+
Into RegScale
|
|
82
|
+
"""
|
|
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
|
+
|
|
112
|
+
# Check Custom Fields exist
|
|
113
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields(
|
|
114
|
+
custom_fields_basic_list, "securityplans", SSP_BASIC_TAB
|
|
115
|
+
)
|
|
116
|
+
custom_fields_financial_map = FormFieldValue.check_custom_fields(
|
|
117
|
+
custom_fields_financial_list, "securityplans", SSP_FINANCIAL_TAB
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Get a map of existing custom forms
|
|
121
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
122
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[FISMA_FIELD_NAME]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Get a list of orgs and create a map to id
|
|
126
|
+
orgs = Organization.get_list()
|
|
127
|
+
org_map = {org.name: org.id for org in orgs}
|
|
128
|
+
|
|
129
|
+
# Grab the data from CSAM
|
|
130
|
+
app = Application()
|
|
131
|
+
csam_token = app.config.get("csamToken")
|
|
132
|
+
csam_url = app.config.get("csamURL")
|
|
133
|
+
csam_filter = app.config.get("csamFilter", None)
|
|
134
|
+
|
|
135
|
+
results = retrieve_from_csam(
|
|
136
|
+
csam_token=csam_token,
|
|
137
|
+
csam_url=csam_url,
|
|
138
|
+
csam_endpoint="/CSAM/api/v1/systems",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
results = filter_list(results, csam_filter)
|
|
142
|
+
if not results:
|
|
143
|
+
error_and_exit(
|
|
144
|
+
"No results match filter in CSAM. \
|
|
145
|
+
Please check your CSAM configuration."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Parse the results
|
|
149
|
+
updated_ssps = []
|
|
150
|
+
updated_ssps = save_ssp_front_matter(
|
|
151
|
+
results=results,
|
|
152
|
+
ssp_map=ssp_map,
|
|
153
|
+
custom_fields_basic_map=custom_fields_basic_map,
|
|
154
|
+
custom_fields_financial_map=custom_fields_financial_map,
|
|
155
|
+
org_map=org_map,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Now have to get the system details for each system
|
|
159
|
+
update_ssp_agency_details(updated_ssps, csam_token, csam_url, custom_fields_basic_map)
|
|
160
|
+
|
|
161
|
+
# Import the Privacy date
|
|
162
|
+
import_csam_privacy_info(import_ids=[ssp.id for ssp in updated_ssps])
|
|
163
|
+
|
|
164
|
+
# Import the controls
|
|
165
|
+
import_csam_controls(import_ids=[ssp.id for ssp in updated_ssps])
|
|
166
|
+
|
|
167
|
+
# Set inheritance if system type = program
|
|
168
|
+
for result in results:
|
|
169
|
+
if result.get("systemType") == "Program":
|
|
170
|
+
# Get the RegScale SSP Id
|
|
171
|
+
program_id = ssp_map.get(result["externalId"])
|
|
172
|
+
if not program_id:
|
|
173
|
+
logger.error(
|
|
174
|
+
f"Could not find RegScale SSP for CSAM id: {result['externalId']}. \
|
|
175
|
+
Please create or import the Security Plan prior to importing inheritance."
|
|
176
|
+
)
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
# Set the inheritable flag
|
|
180
|
+
set_inheritable(regscale_id=result.get("id"))
|
|
181
|
+
|
|
182
|
+
# Import the Inheritance
|
|
183
|
+
import_csam_inheritance(import_ids=[ssp.id for ssp in updated_ssps])
|
|
184
|
+
|
|
185
|
+
# Import the POCs
|
|
186
|
+
import_csam_pocs(import_ids=[ssp.id for ssp in updated_ssps])
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def import_csam_controls(import_ids: Optional[List[int]] = None):
|
|
190
|
+
"""
|
|
191
|
+
Import Controls from CSAM
|
|
192
|
+
|
|
193
|
+
:param list import_ids: Filtered list of SSPs
|
|
194
|
+
:return: None
|
|
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
|
|
265
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields(
|
|
266
|
+
fields_list=[FISMA_FIELD_NAME, CSAM_FIELD_NAME], module_name="securityplans", tab_name=SSP_BASIC_TAB
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Get the SSPs
|
|
270
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
271
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Get a list of users and create a map to id
|
|
275
|
+
users = User.get_all()
|
|
276
|
+
user_map = {user.userName: user.id for user in users}
|
|
277
|
+
|
|
278
|
+
# Grab the data from CSAM
|
|
279
|
+
app = Application()
|
|
280
|
+
csam_token = app.config.get("csamToken")
|
|
281
|
+
csam_url = app.config.get("csamURL")
|
|
282
|
+
results = retrieve_from_csam(
|
|
283
|
+
csam_token=csam_token, csam_url=csam_url, csam_endpoint="/CSAM/api/v1/reports/POAM_Details_Report_CBP"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Parse the results
|
|
287
|
+
poam_list = []
|
|
288
|
+
for index in track(
|
|
289
|
+
range(len(results)),
|
|
290
|
+
description=f"Importing {len(results)} POA&Ms...",
|
|
291
|
+
):
|
|
292
|
+
result = results[index]
|
|
293
|
+
|
|
294
|
+
# Get the existing SSP:
|
|
295
|
+
ssp_id = ssp_map.get(str(result[SYSTEM_ID]))
|
|
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
|
|
302
|
+
|
|
303
|
+
# Check if the POAM exists:
|
|
304
|
+
existing_issue = Issue.find_by_other_identifier(result[POAM_ID])
|
|
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"]
|
|
334
|
+
|
|
335
|
+
poam_list.append(new_issue)
|
|
336
|
+
|
|
337
|
+
for index in track(
|
|
338
|
+
range(len(poam_list)),
|
|
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")
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def import_csam_pocs(import_ids: Optional[List[int]] = None):
|
|
350
|
+
"""
|
|
351
|
+
Import the Points of Contact from CSAM
|
|
352
|
+
Into RegScale
|
|
353
|
+
"""
|
|
354
|
+
custom_fields_system_list = [
|
|
355
|
+
"Certifying Official",
|
|
356
|
+
"Alternate Information System Security Manager",
|
|
357
|
+
"Alternate Information System Security Officer",
|
|
358
|
+
]
|
|
359
|
+
# Check Custom Fields exist
|
|
360
|
+
custom_fields_system_map = FormFieldValue.check_custom_fields(
|
|
361
|
+
custom_fields_system_list, "securityplans", SSP_SYSTEM_TAB
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Get existing ssps by CSAM Id
|
|
365
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
366
|
+
ssp_map = retrieve_custom_form_ssp_map(
|
|
367
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
plans = import_ids if import_ids else list(ssp_map.keys())
|
|
371
|
+
|
|
372
|
+
# Get a list of users and create a map to id
|
|
373
|
+
users = User.get_all()
|
|
374
|
+
user_map = {user.userName: user.id for user in users}
|
|
375
|
+
|
|
376
|
+
# TO DO... Add the rest of the logic
|
|
377
|
+
# Delete these lines: Added to shut up sonarqube
|
|
378
|
+
logger.debug(f"Custom Fields Map: {custom_fields_system_map}, User Map: {user_map}")
|
|
379
|
+
logger.debug(f"SSP Map: {ssp_map}, Plans: {plans}")
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def import_csam_privacy_info(import_ids: Optional[List[int]] = None):
|
|
383
|
+
"""
|
|
384
|
+
Import the Privacy Info from CSAM
|
|
385
|
+
Into RegScale
|
|
386
|
+
"""
|
|
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
|
+
|
|
399
|
+
# Get existing ssps by CSAM Id
|
|
400
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
401
|
+
ssp_map = retrieve_custom_form_ssp_map(
|
|
402
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
plans = import_ids if import_ids else list(ssp_map.keys())
|
|
406
|
+
|
|
407
|
+
for regscale_ssp_id in plans:
|
|
408
|
+
system_id = ssp_map.get(regscale_ssp_id)
|
|
409
|
+
|
|
410
|
+
# Get Privacy Status
|
|
411
|
+
privacy_status = retrieve_from_csam(
|
|
412
|
+
csam_token=csam_token,
|
|
413
|
+
csam_url=csam_url,
|
|
414
|
+
csam_endpoint=f"/CSAM/api/v1/systems/{system_id}/privacy",
|
|
415
|
+
)
|
|
416
|
+
pia_date = privacy_status.get("privacyImpactAssessmentDateCompleted")
|
|
417
|
+
pta_date = privacy_status.get("privacyThresholdAnalysisDateCompleted")
|
|
418
|
+
|
|
419
|
+
# Get SORN Status
|
|
420
|
+
sorn_statuses = retrieve_from_csam(
|
|
421
|
+
csam_token=csam_token,
|
|
422
|
+
csam_url=csam_url,
|
|
423
|
+
csam_endpoint=f"/CSAM/api/v1/systems/{system_id}/sorn",
|
|
424
|
+
)
|
|
425
|
+
sorn_date = 0
|
|
426
|
+
sorn_id = ""
|
|
427
|
+
for sorn_status in sorn_statuses:
|
|
428
|
+
if date_obj(sorn_status.get("publishedDate")) > date_obj(sorn_date):
|
|
429
|
+
sorn_date = sorn_status.get("publishedDate")
|
|
430
|
+
sorn_id = sorn_status.get("systemOfRecordsNoticeId").strip()
|
|
431
|
+
|
|
432
|
+
# Set the records
|
|
433
|
+
record = {"pia_date": pia_date, "pta_date": pta_date, "sorn_date": sorn_date, "sorn_id": sorn_id}
|
|
434
|
+
save_privacy_records(regscale_id=regscale_ssp_id, custom_fields_map=custom_fields_privacy_map, record=record)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def save_privacy_records(regscale_id: int, custom_fields_map: dict, record: dict):
|
|
438
|
+
privacy_fields = []
|
|
439
|
+
if record.get("pia_date"):
|
|
440
|
+
privacy_fields.append(
|
|
441
|
+
{
|
|
442
|
+
"record_id": regscale_id,
|
|
443
|
+
"form_field_id": custom_fields_map["PIA Date"],
|
|
444
|
+
"field_value": format_to_regscale_iso(record.get("pia_date")),
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
if record.get("pta_date"):
|
|
448
|
+
privacy_fields.append(
|
|
449
|
+
{
|
|
450
|
+
"record_id": regscale_id,
|
|
451
|
+
"form_field_id": custom_fields_map["PTA Date"],
|
|
452
|
+
"field_value": format_to_regscale_iso(record.get("pta_date")),
|
|
453
|
+
}
|
|
454
|
+
)
|
|
455
|
+
if record.get("sorn_date"):
|
|
456
|
+
privacy_fields.append(
|
|
457
|
+
{
|
|
458
|
+
"record_id": regscale_id,
|
|
459
|
+
"form_field_id": custom_fields_map["SORN Date"],
|
|
460
|
+
"field_value": format_to_regscale_iso(record.get("sorn_date")),
|
|
461
|
+
}
|
|
462
|
+
)
|
|
463
|
+
if record.get("sorn_id"):
|
|
464
|
+
privacy_fields.append(
|
|
465
|
+
{
|
|
466
|
+
"record_id": regscale_id,
|
|
467
|
+
"form_field_id": custom_fields_map["SORN Id"],
|
|
468
|
+
"field_value": record.get("sorn_id"),
|
|
469
|
+
}
|
|
470
|
+
)
|
|
471
|
+
if len(privacy_fields) > 0:
|
|
472
|
+
FormFieldValue.save_custom_fields(privacy_fields)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def import_csam_status():
|
|
476
|
+
"""
|
|
477
|
+
Import the Status Info from CSAM
|
|
478
|
+
Into RegScale
|
|
479
|
+
"""
|
|
480
|
+
# TO DO... Add the rest of the logic
|
|
481
|
+
pass
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def import_csam_inheritance(import_ids: Optional[List[int]] = None):
|
|
485
|
+
"""
|
|
486
|
+
Import control inheritance from CSAM
|
|
487
|
+
|
|
488
|
+
:param list import_ids: List of SSPs to import
|
|
489
|
+
:return: None
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
# Get list of existing SSPs in RegScale
|
|
493
|
+
existing_ssps = SecurityPlan.get_ssp_list()
|
|
494
|
+
ssp_map = {ssp["title"]: ssp["id"] for ssp in existing_ssps}
|
|
495
|
+
|
|
496
|
+
if not import_ids:
|
|
497
|
+
import_ids = [ssp["id"] for ssp in existing_ssps]
|
|
498
|
+
|
|
499
|
+
# Get Inheritance data from CSAM
|
|
500
|
+
app = Application()
|
|
501
|
+
for index in track(
|
|
502
|
+
range(len(import_ids)),
|
|
503
|
+
description=f"Importing inheritance for {len(import_ids)} Systems...",
|
|
504
|
+
):
|
|
505
|
+
ssp = SecurityPlan.get_object(object_id=import_ids[index])
|
|
506
|
+
linked_ssps = []
|
|
507
|
+
# Get the inheritance data from CSAM
|
|
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})")
|
|
516
|
+
continue
|
|
517
|
+
# Process each inheritance record
|
|
518
|
+
imp_map = ControlImplementation.get_control_label_map_by_plan(plan_id=ssp.id)
|
|
519
|
+
|
|
520
|
+
process_inheritances(
|
|
521
|
+
inheritances=inheritances, ssp=ssp, ssp_map=ssp_map, imp_map=imp_map, linked_ssps=linked_ssps
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def retrieve_from_csam(csam_url: str, csam_token: str, csam_endpoint: str) -> list:
|
|
526
|
+
"""
|
|
527
|
+
Connect to CSAM and retrieve data
|
|
528
|
+
|
|
529
|
+
:param str csam_url: URL of CSAM System
|
|
530
|
+
:param str csam_token: Bearer Token
|
|
531
|
+
:param str csam_endpoint: API Endpoint
|
|
532
|
+
:return: List of dict objects
|
|
533
|
+
:return_type: list
|
|
534
|
+
"""
|
|
535
|
+
logger.debug("Retrieving data from CSAM")
|
|
536
|
+
reg_api = Api()
|
|
537
|
+
if "Bearer" not in csam_token:
|
|
538
|
+
csam_token = f"Bearer {csam_token}"
|
|
539
|
+
|
|
540
|
+
url = urljoin(csam_url, csam_endpoint)
|
|
541
|
+
headers = {
|
|
542
|
+
"Content-Type": "application/json",
|
|
543
|
+
"Accept": "application/json",
|
|
544
|
+
"Authorization": csam_token,
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
issue_response = reg_api.get(url=url, headers=headers)
|
|
548
|
+
if not issue_response or issue_response.status_code in [204, 404]:
|
|
549
|
+
logger.warning(f"Call to {url} Returned error: {issue_response.text}")
|
|
550
|
+
return []
|
|
551
|
+
if issue_response and issue_response.ok:
|
|
552
|
+
return issue_response.json()
|
|
553
|
+
|
|
554
|
+
return []
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def retrieve_ssps_custom_form_map(tab_name: str, field_form_id: int) -> dict:
|
|
558
|
+
"""
|
|
559
|
+
Retreives a list of the SSPs in RegScale
|
|
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
|
+
)
|
|
577
|
+
|
|
578
|
+
for form in form_values:
|
|
579
|
+
if form.formFieldId == field_form_id and form.data:
|
|
580
|
+
field_form_map[form.data] = ssp["id"]
|
|
581
|
+
form_values = []
|
|
582
|
+
return field_form_map
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def retrieve_custom_form_ssp_map(tab_name: str, field_form_id: int) -> dict:
|
|
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
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def update_ssp_general(ssp: SecurityPlan, record: dict, org_map: dict) -> SecurityPlan:
|
|
613
|
+
"""
|
|
614
|
+
Update or Create the SSP Record
|
|
615
|
+
Based upon the values in Record
|
|
616
|
+
|
|
617
|
+
:param SecurityPlan ssp: RegScale Security Plan
|
|
618
|
+
:param dict record: record of values
|
|
619
|
+
:param dict org_map: map of org names to orgId
|
|
620
|
+
:return: SecurityPlan Object
|
|
621
|
+
:return_type: SecurityPlan
|
|
622
|
+
"""
|
|
623
|
+
|
|
624
|
+
ssp.otherIdentifier = record["id"]
|
|
625
|
+
ssp.overallCategorization = record["categorization"]
|
|
626
|
+
ssp.confidentiality = record["categorization"]
|
|
627
|
+
ssp.integrity = record["categorization"]
|
|
628
|
+
ssp.availability = record["categorization"]
|
|
629
|
+
ssp.status = record["operationalStatus"]
|
|
630
|
+
ssp.systemType = record["systemType"]
|
|
631
|
+
ssp.description = record["purpose"]
|
|
632
|
+
if record["organization"] and org_map.get(record["organization"]):
|
|
633
|
+
ssp.orgId = org_map.get(record["organization"])
|
|
634
|
+
|
|
635
|
+
if ssp.id == 0:
|
|
636
|
+
new_ssp = ssp.create()
|
|
637
|
+
else:
|
|
638
|
+
new_ssp = ssp.save()
|
|
639
|
+
|
|
640
|
+
return new_ssp
|
|
641
|
+
|
|
642
|
+
|
|
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:
|
|
646
|
+
"""
|
|
647
|
+
Save the SSP data from the /systems endpoint
|
|
648
|
+
|
|
649
|
+
:param list results: list of results from CSAM
|
|
650
|
+
:param dict ssp_map: map of existing SSPs in RegScale
|
|
651
|
+
: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
|
+
:param dict org_map: map of existing orgs in RegScale
|
|
654
|
+
:return: list of updated SSPs
|
|
655
|
+
:return_type: List[SecurityPlan]
|
|
656
|
+
"""
|
|
657
|
+
updated_ssps = []
|
|
658
|
+
for index in track(
|
|
659
|
+
range(len(results)),
|
|
660
|
+
description=f"Importing {len(results)} SSP front matter...",
|
|
661
|
+
):
|
|
662
|
+
result = results[index]
|
|
663
|
+
|
|
664
|
+
# Get the existing SSP:
|
|
665
|
+
ssp_id = ssp_map.get(result["externalId"])
|
|
666
|
+
if ssp_id:
|
|
667
|
+
ssp = SecurityPlan.get_object(ssp_id)
|
|
668
|
+
else:
|
|
669
|
+
ssp = SecurityPlan(systemName=result["name"])
|
|
670
|
+
# Update the SSP
|
|
671
|
+
ssp = update_ssp_general(ssp, result, org_map)
|
|
672
|
+
|
|
673
|
+
# 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
|
+
)
|
|
680
|
+
|
|
681
|
+
# System Custom Fields
|
|
682
|
+
FormFieldValue.save_custom_fields(field_values)
|
|
683
|
+
updated_ssps.append(ssp)
|
|
684
|
+
logger.info(f"Updated {len(results)} Security Plans Front Matter")
|
|
685
|
+
return updated_ssps
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def update_ssp_agency_details(ssps: list, csam_token: str, csam_url: str, custom_fields_basic_map: dict) -> list:
|
|
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:
|
|
775
|
+
"""
|
|
776
|
+
parse the front matter custom fields
|
|
777
|
+
and return a list of field values to be saved
|
|
778
|
+
|
|
779
|
+
:param SecurityPlan ssp: RegScale Security Plan object
|
|
780
|
+
:param dict result: response from CSAM
|
|
781
|
+
:param dict custom_fields_basic_map: map of basic custom fields
|
|
782
|
+
:param dict custom_fields_financial_map: map of financial custom fields
|
|
783
|
+
:return: list of dictionaries with field values
|
|
784
|
+
:return_type: list
|
|
785
|
+
"""
|
|
786
|
+
field_values = []
|
|
787
|
+
# FISMA ID
|
|
788
|
+
field_values.append(
|
|
789
|
+
{
|
|
790
|
+
"record_id": ssp.id,
|
|
791
|
+
"form_field_id": custom_fields_basic_map[FISMA_FIELD_NAME],
|
|
792
|
+
"field_value": str(result["externalId"]),
|
|
793
|
+
}
|
|
794
|
+
)
|
|
795
|
+
# CSAM ID
|
|
796
|
+
field_values.append(
|
|
797
|
+
{
|
|
798
|
+
"record_id": ssp.id,
|
|
799
|
+
"form_field_id": custom_fields_basic_map[CSAM_FIELD_NAME],
|
|
800
|
+
"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
|
|
908
|
+
|
|
909
|
+
return rev5_catalog_id, rev4_catalog_id
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
def build_implementations(results: list, csam_id: str, regscale_id: int) -> list:
|
|
913
|
+
"""
|
|
914
|
+
Build out the control implementations
|
|
915
|
+
from the results returned from CSAM
|
|
916
|
+
|
|
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
|
+
|
|
961
|
+
|
|
962
|
+
def retrieve_controls(csam_token: str, csam_url: str, csam_id: int, controls: list, regscale_id: int) -> list:
|
|
963
|
+
"""
|
|
964
|
+
Takes a system id and list of controls
|
|
965
|
+
returns a list of implmentations for
|
|
966
|
+
that system id and framework
|
|
967
|
+
|
|
968
|
+
:param str csam_token: access token for CSAM
|
|
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}",
|
|
988
|
+
)
|
|
989
|
+
|
|
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
|
+
|
|
1048
|
+
base_control_map = ControlImplementation.get_control_label_map_by_plan(plan_id=base_ssp)
|
|
1049
|
+
base_control_id = base_control_map.get(normalize_controlid(inheritance.get("controlId")))
|
|
1050
|
+
|
|
1051
|
+
# Create or update the inheritance record
|
|
1052
|
+
if inheritance.get("isInherited") is False:
|
|
1053
|
+
continue
|
|
1054
|
+
|
|
1055
|
+
# Add the parent if not already linked
|
|
1056
|
+
if base_ssp not in linked_ssps:
|
|
1057
|
+
linked_ssps.append(base_ssp)
|
|
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
|
+
)
|
|
1068
|
+
|
|
1069
|
+
# Create the Inheritance Record(s)
|
|
1070
|
+
for inheritance_ssp in linked_ssps:
|
|
1071
|
+
create_inheritance_linage(
|
|
1072
|
+
parent_id=ssp.id,
|
|
1073
|
+
parent_module="securityplans",
|
|
1074
|
+
base_id=inheritance_ssp,
|
|
1075
|
+
)
|
|
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()
|