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.

Files changed (40) 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/login.py +21 -4
  7. regscale/core/utils/date.py +77 -1
  8. regscale/integrations/commercial/aws/scanner.py +4 -1
  9. regscale/integrations/commercial/synqly/query_builder.py +4 -1
  10. regscale/integrations/control_matcher.py +78 -23
  11. regscale/integrations/public/csam/csam.py +572 -763
  12. regscale/integrations/public/csam/csam_agency_defined.py +179 -0
  13. regscale/integrations/public/csam/csam_common.py +154 -0
  14. regscale/integrations/public/csam/csam_controls.py +432 -0
  15. regscale/integrations/public/csam/csam_poam.py +124 -0
  16. regscale/integrations/public/fedramp/click.py +17 -4
  17. regscale/integrations/public/fedramp/fedramp_cis_crm.py +271 -62
  18. regscale/integrations/public/fedramp/poam/scanner.py +74 -7
  19. regscale/integrations/scanner_integration.py +16 -1
  20. regscale/models/integration_models/cisa_kev_data.json +49 -19
  21. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  22. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +35 -2
  23. regscale/models/integration_models/synqly_models/ocsf_mapper.py +41 -12
  24. regscale/models/platform.py +3 -0
  25. regscale/models/regscale_models/__init__.py +5 -0
  26. regscale/models/regscale_models/component.py +1 -1
  27. regscale/models/regscale_models/control_implementation.py +55 -24
  28. regscale/models/regscale_models/organization.py +3 -0
  29. regscale/models/regscale_models/regscale_model.py +17 -5
  30. regscale/models/regscale_models/security_plan.py +1 -0
  31. regscale/regscale.py +11 -1
  32. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/METADATA +1 -1
  33. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/RECORD +40 -36
  34. tests/regscale/core/test_login.py +171 -4
  35. tests/regscale/integrations/test_control_matcher.py +24 -0
  36. tests/regscale/models/test_control_implementation.py +118 -3
  37. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/LICENSE +0 -0
  38. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/WHEEL +0 -0
  39. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/entry_points.txt +0 -0
  40. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.27.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Integrates CSAM agencydefineddata into RegScale"""
4
+
5
+ from rich.progress import track
6
+ import logging
7
+ from regscale.models.regscale_models import SecurityPlan, FormFieldValue
8
+ from regscale.integrations.public.csam.csam_common import retrieve_from_csam, set_custom_fields
9
+
10
+ logger = logging.getLogger("regscale")
11
+
12
+ """This should be refactored as a custom field to custom field parser"""
13
+ """Add logic to grab all the fields from agencydefineddataitems"""
14
+ """Create custom fields in RegScale under "CSAM custom fields tab in SSP"""
15
+ """Shove in whatever attributename: value pairs"""
16
+
17
+
18
+ def update_ssp_agency_details(ssps: list, custom_fields_basic_map: dict) -> list:
19
+ """
20
+ Update the Agency Details of the SSPs
21
+ This requires a call to the /system/{id}/agencydefineddataitems
22
+ endpoint
23
+
24
+ :param list ssps: list of RegScale SSPs
25
+ :param dict custom_fields_basic_map: map of custom fields in RegScale
26
+ :return: list of updated SSPs
27
+ :return_type: List[SecurityPlan]
28
+ """
29
+ updated_ssps = []
30
+ # Check for no ssps:
31
+ if len(ssps) == 0:
32
+ return updated_ssps
33
+
34
+ # Check if existing customer
35
+ custom_fields_system_list = ["AI/ML Components", "IOT/OT"]
36
+ custom_fields_system_map = FormFieldValue.check_custom_fields(
37
+ custom_fields_system_list, "securityplans", "System Information"
38
+ )
39
+ if custom_fields_system_map:
40
+ # Specific
41
+ updated_ssps = update_specific_details(ssps, custom_fields_basic_map, custom_fields_system_map)
42
+ return updated_ssps
43
+
44
+ # Generic Agency
45
+ # STUB - REG-18030
46
+
47
+ return updated_ssps
48
+
49
+
50
+ def update_specific_details(ssps: list, custom_fields_basic_map: dict, custom_fields_system_map: dict):
51
+ """
52
+ Handles the CSAM custom fields for existing customer
53
+
54
+ :param ssps: List of SSP ids
55
+ :param custom_fields_basic_map: dictionary mapping custom field names to ids
56
+ :param custom_fields_system_map: dictionary mapping custom field names to ids
57
+ :return: list of ssps
58
+ :return_type: list
59
+ """
60
+ updated_ssps = []
61
+ if len(ssps) == 0:
62
+ return updated_ssps
63
+
64
+ for index in track(
65
+ range(len(ssps)),
66
+ description=f"Importing {len(ssps)} SSP agency details...",
67
+ ):
68
+ ssp = ssps[index]
69
+ csam_id = ssp.otherIdentifier
70
+ if not csam_id:
71
+ logger.error(f"Could not find CSAM ID for SSP {ssp.systemName} id: {ssp.id}")
72
+ continue
73
+ else:
74
+ updated_ssps.append(ssp)
75
+
76
+ result = retrieve_from_csam(
77
+ csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/agencydefineddataitems",
78
+ )
79
+ if len(result) == 0:
80
+ logger.error(
81
+ f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP: Name: {ssp.systemName} id: {ssp.id}"
82
+ )
83
+ continue
84
+ # Get the custom fields
85
+ set_agency_details(result, ssp, custom_fields_basic_map, custom_fields_system_map)
86
+
87
+ logger.info(f"Updated {len(updated_ssps)} Security Plans with Agency Details")
88
+ return updated_ssps
89
+
90
+
91
+ def set_agency_details(result: list, ssp: SecurityPlan, custom_fields_basic_map: dict, custom_fields_system_map: dict):
92
+ """
93
+ Loop through results of agencydefineddataitems
94
+ and set the custom fields in RegScale
95
+
96
+ :param list result: list of dict objects from CSAM
97
+ :param SecurityPlan ssp: RegScale Security Plan
98
+ :param dict custom_fields_basic_map: map of custom field names to ids
99
+ :param dict custom_fields_system_map: map of custom fields names to ids
100
+ """
101
+
102
+ field_values = []
103
+ # Update the fields we need
104
+ for item in result:
105
+ if item.get("attributeName") == "High Value Asset":
106
+ ssp.hva = True if item.get("value") == "1" else False
107
+
108
+ # Binary Values
109
+ elif item.get("attributeName") in ["External Web Interface", "CFO Designation", "Law Enforcement Sensitive"]:
110
+ field_values.append(set_binary_fields(item, ssp, custom_fields_basic_map))
111
+
112
+ elif item.get("attributeName") == "Cloud System":
113
+ ssp = set_cloud_system(ssp, item)
114
+
115
+ elif item.get("attributeName") == "Cloud Service Model":
116
+ ssp = set_cloud_service(ssp, item)
117
+
118
+ elif item.get("attributeName") == "HVA Identifier":
119
+ field_values.append(set_custom_fields(item, ssp, custom_fields_basic_map))
120
+
121
+ elif item.get("attributeName") in ["AI/ML Components", "IOT/OT"]:
122
+ field_values.append(set_custom_fields(item, ssp, custom_fields_system_map))
123
+
124
+ # Save the SSP & Custom Fields
125
+ ssp.defaultAssessmentDays = 0
126
+ ssp.save()
127
+ if len(field_values) > 0:
128
+ FormFieldValue.save_custom_fields(field_values)
129
+
130
+
131
+ def set_cloud_system(ssp: SecurityPlan, item: dict) -> SecurityPlan:
132
+ """
133
+ Set the cloud system values in the SSP
134
+ :param SeucrityPlan ssp: RegScale Security Plan
135
+ :param dict item: record from CSAM
136
+ :return: SecurityPlan object with updated cloud system values
137
+ :return_type: SecurityPlan
138
+ """
139
+ ssp.bDeployPublic = True if item.get("value") == "Public" else False
140
+ ssp.bDeployPrivate = True if item.get("value") == "Private" else False
141
+ ssp.bDeployHybrid = True if item.get("value") == "Hybrid" else False
142
+ ssp.bDeployGov = True if item.get("value") == "GovCloud" else False
143
+ ssp.bDeployOther = True if item.get("value") == "Community" else False
144
+ if ssp.bDeployHybrid or ssp.bDeployOther:
145
+ ssp.deployOtherRemarks = "Hybrid or Community"
146
+
147
+ return ssp
148
+
149
+
150
+ def set_cloud_service(ssp: SecurityPlan, item: dict) -> SecurityPlan:
151
+ """
152
+ Set the cloud service model values in the SSP
153
+
154
+ :param SecurityPlan ssp: RegScale Security Plan
155
+ :param dict item: record from CSAM
156
+ :return: Updated SecurityPlan object
157
+ :return_type: SecurityPlan
158
+ """
159
+ ssp.bModelIaaS = True if "IaaS" in item.get("value") else False
160
+ ssp.bModelPaaS = True if "PaaS" in item.get("value") else False
161
+ ssp.bModelSaaS = True if "SaaS" in item.get("value") else False
162
+ return ssp
163
+
164
+
165
+ def set_binary_fields(item: dict, ssp: SecurityPlan, custom_fields_map: dict) -> dict:
166
+ """
167
+ Logic to set the custom fields were the source are binary
168
+
169
+ :param dict item: record from CSAM
170
+ :param SecurityPlan ssp: RegScale Security Plan
171
+ :param custom_fields_map: map of custom field names to ids
172
+ :return: RegScale custom fields records
173
+ :return_type: dict
174
+ """
175
+ return {
176
+ "record_id": ssp.id,
177
+ "form_field_id": custom_fields_map[item.get("attributeName")],
178
+ "field_value": "Yes" if (item.get("value")) == "1" else "No",
179
+ }
@@ -0,0 +1,154 @@
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 urllib.parse import urljoin
9
+ from regscale.core.app.application import Application
10
+ from regscale.core.app.api import Api
11
+
12
+ from regscale.models.regscale_models import SecurityPlan, Module
13
+ from regscale.models.regscale_models.regscale_model import RegScaleModel
14
+ from regscale.integrations.control_matcher import ControlMatcher
15
+ from regscale.models.regscale_models.form_field_value import FormFieldValue
16
+
17
+ SSP_BASIC_TAB = "Basic Info"
18
+ CSAM_FIELD_NAME = "CSAM Id"
19
+ FISMA_FIELD_NAME = "FISMA Id"
20
+ SYSTEM_ID = "System ID"
21
+
22
+ logger = logging.getLogger("regscale")
23
+
24
+
25
+ def set_custom_fields(item: dict, ssp: SecurityPlan, custom_fields_map: dict) -> dict:
26
+ """
27
+ Set the custom fields for the SSP
28
+
29
+ :param dict item: record from CSAM
30
+ :param SecurityPlan ssp: RegScale Security Plan
31
+ :param dict custom_fields_map: map of custom fields in RegScale
32
+ :return: dictionary of field values to be saved
33
+ :return_type: dict
34
+ """
35
+ return {
36
+ "record_id": ssp.id,
37
+ "record_module": "securityplans",
38
+ "form_field_id": custom_fields_map[item.get("attributeName")],
39
+ "field_value": str(item.get("value")),
40
+ }
41
+
42
+
43
+ def retrieve_custom_form_ssps_map(tab_name: str, field_form_id: int) -> dict:
44
+ """
45
+ Retreives a list of the SSPs in RegScale
46
+ Returns a map of Custom Field Value: RegScale Id
47
+
48
+ :param str tab_name: The RegScale tab name where the custom field is located
49
+ :param int field_form_id: The RegScale Form Id of custom field
50
+ :param int tab_id: The RegScale tab id
51
+ :return: dictionary of FieldForm Id: regscale_ssp_id
52
+ :return_type: dict
53
+ """
54
+ tab = Module.get_tab_by_name(regscale_module_name="securityplans", regscale_tab_name=tab_name)
55
+
56
+ field_form_map = {}
57
+ ssps = SecurityPlan.get_ssp_list()
58
+ form_values = []
59
+ for ssp in ssps:
60
+ form_values = FormFieldValue.get_field_values(
61
+ record_id=ssp["id"], module_name=SecurityPlan.get_module_slug(), form_id=tab.id
62
+ )
63
+
64
+ for form in form_values:
65
+ if form.formFieldId == field_form_id and form.data:
66
+ field_form_map[form.data] = ssp["id"]
67
+ form_values = []
68
+ return field_form_map
69
+
70
+
71
+ def retrieve_ssps_custom_form_map(tab_name: str, field_form_id: int) -> dict:
72
+ """
73
+ Retreives a list of the SSPs in RegScale
74
+ Returns a map of Custom Field Value: RegScale Id
75
+
76
+ :param str tab_name: The RegScale tab name where the custom field is located
77
+ :param int field_form_id: The RegScale Form Id of custom field
78
+ :param int tab_id: The RegScale tab id
79
+ :return: dictionary of FieldForm Id: regscale_ssp_id
80
+ :return_type: dict
81
+ """
82
+ tab = Module.get_tab_by_name(regscale_module_name="securityplans", regscale_tab_name=tab_name)
83
+
84
+ field_form_map = {}
85
+ ssps = SecurityPlan.get_ssp_list()
86
+ form_values = []
87
+ for ssp in ssps:
88
+ form_values = FormFieldValue.get_field_values(
89
+ record_id=ssp["id"], module_name=SecurityPlan.get_module_slug(), form_id=tab.id
90
+ )
91
+
92
+ for form in form_values:
93
+ if form.formFieldId == field_form_id and form.data:
94
+ field_form_map[ssp["id"]] = form.data
95
+ form_values = []
96
+ return field_form_map
97
+
98
+
99
+ def retrieve_from_csam(csam_endpoint: str) -> list:
100
+ """
101
+ Connect to CSAM and retrieve data
102
+
103
+ :param str csam_endpoint: API Endpoint
104
+ :return: List of dict objects
105
+ :return_type: list
106
+ """
107
+ logger.debug("Retrieving data from CSAM")
108
+ app = Application()
109
+ api = Api()
110
+ csam_token = app.config.get("csamToken")
111
+ csam_url = app.config.get("csamURL")
112
+
113
+ if "Bearer" not in csam_token:
114
+ csam_token = f"Bearer {csam_token}"
115
+
116
+ url = urljoin(csam_url, csam_endpoint)
117
+ headers = {
118
+ "Content-Type": "application/json",
119
+ "Accept": "application/json",
120
+ "Authorization": csam_token,
121
+ }
122
+
123
+ issue_response = api.get(url=url, headers=headers)
124
+ if not issue_response or issue_response.status_code in [204, 404]:
125
+ logger.warning(f"Call to {url} Returned error: {issue_response.text}")
126
+ return []
127
+ if issue_response.ok:
128
+ return issue_response.json()
129
+
130
+ return []
131
+
132
+
133
+ def fix_form_field_value(form_fields: list) -> list:
134
+ """
135
+ Cleans up a list of FormFieldValue dicts to prevent
136
+ 400 errors due to misformed values
137
+
138
+ :param form_fields: list of formFieldValue dicts
139
+ :return: list of fixed formFieldValues dicts
140
+ """
141
+ new_field_values = []
142
+ for field_value in form_fields:
143
+ # Check if record_id, record_module, and form_field_id are set
144
+ if (field_value.get("record_id") is None) or (field_value.get("record_id") == 0):
145
+ continue
146
+ if (field_value.get("form_field_id") is None) or (field_value.get("form_field_id") == 0):
147
+ continue
148
+
149
+ # Check if value == "None"
150
+ if field_value.get("field_value") == "None":
151
+ field_value["field_value"] = ""
152
+
153
+ new_field_values.append(field_value)
154
+ return new_field_values