regscale-cli 6.27.2.0__py3-none-any.whl → 6.28.0.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.

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