regscale-cli 6.20.4.1__py3-none-any.whl → 6.20.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/__init__.py +1 -1
- regscale/_version.py +39 -0
- regscale/core/app/internal/__init__.py +13 -0
- regscale/core/app/internal/model_editor.py +3 -3
- regscale/core/app/internal/set_permissions.py +173 -0
- regscale/core/app/utils/file_utils.py +11 -1
- regscale/core/app/utils/regscale_utils.py +34 -129
- regscale/core/utils/date.py +86 -30
- regscale/integrations/commercial/defender.py +3 -0
- regscale/integrations/commercial/qualys/__init__.py +40 -14
- regscale/integrations/commercial/qualys/containers.py +324 -0
- regscale/integrations/commercial/qualys/scanner.py +203 -8
- regscale/integrations/commercial/synqly/edr.py +10 -0
- regscale/integrations/commercial/wizv2/click.py +11 -7
- regscale/integrations/commercial/wizv2/constants.py +28 -0
- regscale/integrations/commercial/wizv2/issue.py +3 -2
- regscale/integrations/commercial/wizv2/parsers.py +23 -0
- regscale/integrations/commercial/wizv2/scanner.py +89 -30
- regscale/integrations/commercial/wizv2/utils.py +208 -75
- regscale/integrations/commercial/wizv2/variables.py +2 -1
- regscale/integrations/commercial/wizv2/wiz_auth.py +3 -3
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +98 -20
- regscale/integrations/public/fedramp/fedramp_docx.py +2 -3
- regscale/integrations/scanner_integration.py +7 -2
- regscale/models/integration_models/cisa_kev_data.json +187 -5
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/__init__.py +2 -0
- regscale/models/regscale_models/asset.py +1 -1
- regscale/models/regscale_models/catalog.py +16 -0
- regscale/models/regscale_models/file.py +2 -1
- regscale/models/regscale_models/form_field_value.py +59 -1
- regscale/models/regscale_models/issue.py +47 -0
- regscale/models/regscale_models/modules.py +88 -1
- regscale/models/regscale_models/organization.py +30 -0
- regscale/models/regscale_models/regscale_model.py +20 -6
- regscale/models/regscale_models/security_control.py +47 -0
- regscale/models/regscale_models/security_plan.py +32 -0
- regscale/models/regscale_models/vulnerability.py +3 -3
- regscale/models/regscale_models/vulnerability_mapping.py +2 -2
- regscale/regscale.py +2 -0
- {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/RECORD +49 -44
- tests/fixtures/test_fixture.py +33 -4
- tests/regscale/core/test_app.py +53 -32
- tests/regscale/test_init.py +94 -0
- {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""RegScale models."""
|
|
2
2
|
|
|
3
|
+
from .assessment_plan import *
|
|
3
4
|
from .assessment import *
|
|
4
5
|
from .asset import *
|
|
5
6
|
from .asset_mapping import *
|
|
6
7
|
from .business_impact_assessment import *
|
|
7
8
|
from .catalog import *
|
|
9
|
+
from .case import *
|
|
8
10
|
from .cci import *
|
|
9
11
|
from .change import *
|
|
10
12
|
from .checklist import *
|
|
@@ -259,3 +259,19 @@ class Catalog(RegScaleModel):
|
|
|
259
259
|
return response
|
|
260
260
|
cls.log_response_error(response, suppress_error=True)
|
|
261
261
|
return response
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def find_by_guid(cls, guid: str) -> Optional["Catalog"]:
|
|
265
|
+
"""
|
|
266
|
+
Find a catalog by its GUID.
|
|
267
|
+
|
|
268
|
+
:param str guid: The GUID of the catalog
|
|
269
|
+
:return: The catalog object or None if not found
|
|
270
|
+
:rtype: Optional["Catalog"]
|
|
271
|
+
"""
|
|
272
|
+
endpoint = cls.get_endpoint("find_by_guid").format(model_slug=cls.get_module_slug(), strID=guid)
|
|
273
|
+
response = cls._get_api_handler().get(endpoint)
|
|
274
|
+
|
|
275
|
+
if response and response.ok and response.status_code not in [204, 404]:
|
|
276
|
+
return cls.model_validate(response.json())
|
|
277
|
+
return None
|
|
@@ -188,10 +188,11 @@ class File(BaseModel):
|
|
|
188
188
|
file_res = api.post(
|
|
189
189
|
url=f"{api.config['domain']}/api/files",
|
|
190
190
|
headers=file_headers,
|
|
191
|
-
json=new_file.
|
|
191
|
+
json=new_file.model_dump(),
|
|
192
192
|
)
|
|
193
193
|
if file_res.ok and return_object:
|
|
194
194
|
return File(**file_res.json()).model_dump()
|
|
195
|
+
File.create_tag_mappings(file_res)
|
|
195
196
|
else:
|
|
196
197
|
return False
|
|
197
198
|
return file_res.ok
|
|
@@ -8,6 +8,7 @@ from typing import Any, List, Optional, Dict
|
|
|
8
8
|
from pydantic import ConfigDict, Field
|
|
9
9
|
|
|
10
10
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
11
|
+
from regscale.models.regscale_models.module import Module
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
@@ -79,7 +80,7 @@ class FormFieldValue(RegScaleModel):
|
|
|
79
80
|
Get custom data for a record
|
|
80
81
|
:param record_id: record id
|
|
81
82
|
:param module_name: module name
|
|
82
|
-
:param form_id: form id
|
|
83
|
+
:param form_id: form tab id
|
|
83
84
|
:return: list of custom fields
|
|
84
85
|
"""
|
|
85
86
|
result = cls._get_api_handler().get(
|
|
@@ -92,3 +93,60 @@ class FormFieldValue(RegScaleModel):
|
|
|
92
93
|
else:
|
|
93
94
|
cls.log_response_error(response=result)
|
|
94
95
|
return []
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def check_custom_fields(fields_list: list, module_name: str, tab_name: str) -> dict:
|
|
99
|
+
"""
|
|
100
|
+
Check if the custom fields exist and
|
|
101
|
+
create if not
|
|
102
|
+
|
|
103
|
+
:param list fields_list: list list of custom fields
|
|
104
|
+
:param str module_name: name of the module in RegScale
|
|
105
|
+
:param str tab_name: name of the tab in the module
|
|
106
|
+
:return: map of custom fields names to ids
|
|
107
|
+
:return_type: dict
|
|
108
|
+
"""
|
|
109
|
+
# Check the custom fields exist in RegScale
|
|
110
|
+
custom_form_fields = Module.get_form_fields_by_tab_id(module_name=module_name, tab_name=tab_name)
|
|
111
|
+
if custom_form_fields:
|
|
112
|
+
custom_form_field_id_dict = {cf.regScaleName: cf.id for cf in custom_form_fields}
|
|
113
|
+
else:
|
|
114
|
+
custom_form_field_id_dict = {}
|
|
115
|
+
missing_custom_fields = []
|
|
116
|
+
for field in fields_list:
|
|
117
|
+
if field not in custom_form_field_id_dict.keys():
|
|
118
|
+
missing_custom_fields.append(field)
|
|
119
|
+
|
|
120
|
+
if len(missing_custom_fields) > 0:
|
|
121
|
+
logger.error(
|
|
122
|
+
f"The following custom fields are missing:\n \
|
|
123
|
+
{missing_custom_fields}\n \
|
|
124
|
+
Load these custom fields in RegScale \
|
|
125
|
+
using the file: security_plan_custom_fields.json\
|
|
126
|
+
and run this command again"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return custom_form_field_id_dict
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def save_custom_fields(form_field_values: list):
|
|
133
|
+
"""
|
|
134
|
+
Populate Custom Fields form a list of dict of
|
|
135
|
+
record_id, form_field_id, and field_value
|
|
136
|
+
|
|
137
|
+
:param list form_field_values: list of custom form
|
|
138
|
+
fields, values, and the record to which to post
|
|
139
|
+
:return: None
|
|
140
|
+
"""
|
|
141
|
+
logger.debug("Creating custom form field values...")
|
|
142
|
+
# FormFieldValue class will throw errors if encountered
|
|
143
|
+
for form_field_value in form_field_values:
|
|
144
|
+
data = [
|
|
145
|
+
FormFieldValue(
|
|
146
|
+
formFieldId=form_field_value["form_field_id"], fieldValue=form_field_value["field_value"]
|
|
147
|
+
),
|
|
148
|
+
]
|
|
149
|
+
if data:
|
|
150
|
+
FormFieldValue.save_custom_data(
|
|
151
|
+
record_id=form_field_value["record_id"], module_name="securityplans", data=data
|
|
152
|
+
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""Model for a RegScale Issue"""
|
|
4
|
+
import datetime
|
|
4
5
|
from collections import defaultdict
|
|
5
6
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
6
7
|
from enum import Enum
|
|
@@ -387,6 +388,52 @@ class Issue(RegScaleModel):
|
|
|
387
388
|
return "High"
|
|
388
389
|
return ""
|
|
389
390
|
|
|
391
|
+
@staticmethod
|
|
392
|
+
def get_due_date(
|
|
393
|
+
severity: Union[IssueSeverity, str],
|
|
394
|
+
config: dict,
|
|
395
|
+
key: str,
|
|
396
|
+
start_date: Union[str, datetime.datetime, None] = None,
|
|
397
|
+
dt_format: Optional[str] = "%Y-%m-%dT%H:%M:%S",
|
|
398
|
+
) -> str:
|
|
399
|
+
"""
|
|
400
|
+
Function to return due date based on the severity of the issue; the values are in the init.yaml
|
|
401
|
+
and if not, it will default to 60 days from the current date.
|
|
402
|
+
|
|
403
|
+
:param Union[IssueSeverity, str] severity: Severity of the issue, can be an IssueSeverity enum or a string
|
|
404
|
+
:param dict config: Application config
|
|
405
|
+
:param str key: The key to use for init.yaml from the issues section to determine the due date
|
|
406
|
+
:param Union[str, datetime.datetime, None] start_date: The date to start the due date calculation from, defaults to current date
|
|
407
|
+
:param Optional[str] dt_format: String of the date format to use, defaults to "%Y-%m-%dT%H:%M:%S"
|
|
408
|
+
:return: Due date for the issue
|
|
409
|
+
:rtype: str
|
|
410
|
+
"""
|
|
411
|
+
if isinstance(start_date, str):
|
|
412
|
+
from regscale.core.utils.date import datetime_obj
|
|
413
|
+
|
|
414
|
+
start_date = datetime_obj(start_date)
|
|
415
|
+
elif start_date is None:
|
|
416
|
+
start_date = datetime.datetime.now()
|
|
417
|
+
|
|
418
|
+
# if the severity is an enum, get the value
|
|
419
|
+
if isinstance(severity, IssueSeverity):
|
|
420
|
+
severity = severity.value
|
|
421
|
+
elif severity.lower() not in [severity.value.lower() for severity in IssueSeverity]:
|
|
422
|
+
severity = IssueSeverity.NotAssigned.value
|
|
423
|
+
|
|
424
|
+
if severity == IssueSeverity.Critical.value:
|
|
425
|
+
days = config["issues"].get(key, {}).get("critical", 0)
|
|
426
|
+
elif severity == IssueSeverity.High.value:
|
|
427
|
+
days = config["issues"].get(key, {}).get("high", 0)
|
|
428
|
+
elif severity == IssueSeverity.Moderate.value:
|
|
429
|
+
days = config["issues"].get(key, {}).get("moderate", 0) or config["issues"].get(key, {}).get("medium", 0)
|
|
430
|
+
elif severity == IssueSeverity.Low.value:
|
|
431
|
+
days = config["issues"].get(key, {}).get("low", 0)
|
|
432
|
+
else:
|
|
433
|
+
days = 60
|
|
434
|
+
due_date = start_date + datetime.timedelta(days=days)
|
|
435
|
+
return due_date.strftime(dt_format)
|
|
436
|
+
|
|
390
437
|
@staticmethod
|
|
391
438
|
def assign_severity(value: Optional[Any] = None) -> str:
|
|
392
439
|
"""
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# standard python imports
|
|
6
6
|
from dataclasses import asdict, dataclass
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any, Optional
|
|
8
8
|
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
from rich.table import Table
|
|
@@ -189,3 +189,90 @@ class Modules:
|
|
|
189
189
|
|
|
190
190
|
# return the string
|
|
191
191
|
return output
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def module_to_class(module: str) -> Optional[Any]:
|
|
195
|
+
"""
|
|
196
|
+
Function to convert RegScale module to RegScale class
|
|
197
|
+
|
|
198
|
+
:param str module: RegScale module
|
|
199
|
+
:return: RegScale class if found in the mapping, else None
|
|
200
|
+
:rtype: Optional[Any]
|
|
201
|
+
"""
|
|
202
|
+
from regscale.models import (
|
|
203
|
+
AssessmentPlan,
|
|
204
|
+
Assessment,
|
|
205
|
+
Asset,
|
|
206
|
+
Case,
|
|
207
|
+
Catalog,
|
|
208
|
+
Change,
|
|
209
|
+
Component,
|
|
210
|
+
Evidence,
|
|
211
|
+
Incident,
|
|
212
|
+
Issue,
|
|
213
|
+
Policy,
|
|
214
|
+
Project,
|
|
215
|
+
Questionnaires,
|
|
216
|
+
Requirement,
|
|
217
|
+
Risk,
|
|
218
|
+
SecurityPlan,
|
|
219
|
+
SupplyChain,
|
|
220
|
+
Task,
|
|
221
|
+
Threat,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
regscale_models = {
|
|
225
|
+
"assessmentplans": AssessmentPlan,
|
|
226
|
+
"assessments": Assessment,
|
|
227
|
+
"assets": Asset,
|
|
228
|
+
"cases": Case,
|
|
229
|
+
"catalogues": Catalog,
|
|
230
|
+
"changes": Change,
|
|
231
|
+
"components": Component,
|
|
232
|
+
"evidence": Evidence,
|
|
233
|
+
"incidents": Incident,
|
|
234
|
+
"issues": Issue,
|
|
235
|
+
"policies": Policy,
|
|
236
|
+
"projects": Project,
|
|
237
|
+
"questionnaires": Questionnaires,
|
|
238
|
+
"requirements": Requirement,
|
|
239
|
+
"risks": Risk,
|
|
240
|
+
"securityplans": SecurityPlan,
|
|
241
|
+
"supplychain": SupplyChain,
|
|
242
|
+
"tasks": Task,
|
|
243
|
+
"threats": Threat,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return regscale_models.get(module) or None
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def get_module_to_id(module: str) -> Optional[int]:
|
|
250
|
+
"""
|
|
251
|
+
Returns the id of a RegScale module
|
|
252
|
+
|
|
253
|
+
:return: id of RegScale module, if found in the mapping, else None
|
|
254
|
+
:rtype: Optional[int]
|
|
255
|
+
"""
|
|
256
|
+
module_to_id = {
|
|
257
|
+
"assessmentplans": 33,
|
|
258
|
+
"assessments": 2,
|
|
259
|
+
"assets": 3,
|
|
260
|
+
"cases": 28,
|
|
261
|
+
"catalogues": 4,
|
|
262
|
+
"changes": 31,
|
|
263
|
+
"components": 27,
|
|
264
|
+
"evidence": 32,
|
|
265
|
+
"incidents": 8,
|
|
266
|
+
"issues": 10,
|
|
267
|
+
"policies": 11,
|
|
268
|
+
"projects": 12,
|
|
269
|
+
"questionnaires": 26,
|
|
270
|
+
"requirements": 13,
|
|
271
|
+
"risks": 14,
|
|
272
|
+
"securityplans": 16,
|
|
273
|
+
"supplychain": 25,
|
|
274
|
+
"tasks": 18,
|
|
275
|
+
"threats": 19,
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return module_to_id.get(module)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Organization model for RegScale."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import ConfigDict
|
|
6
|
+
|
|
7
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Organization(RegScaleModel):
|
|
11
|
+
"""Data Model for Organizations"""
|
|
12
|
+
|
|
13
|
+
_module_slug = "organizations"
|
|
14
|
+
|
|
15
|
+
id: int = 0
|
|
16
|
+
name: Optional[str] = ""
|
|
17
|
+
description: Optional[str] = ""
|
|
18
|
+
status: Optional[str] = "Active"
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def _get_additional_endpoints() -> dict:
|
|
22
|
+
"""
|
|
23
|
+
Get additional endpoints for the Facility model.
|
|
24
|
+
|
|
25
|
+
:return: A dictionary of additional endpoints
|
|
26
|
+
:rtype: dict
|
|
27
|
+
"""
|
|
28
|
+
return ConfigDict(
|
|
29
|
+
get_list="/api/{model_slug}/getList",
|
|
30
|
+
)
|
|
@@ -410,12 +410,12 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
410
410
|
"""
|
|
411
411
|
hidden_fields = set(
|
|
412
412
|
attribute_name
|
|
413
|
-
for attribute_name, model_field in self.model_fields.items()
|
|
413
|
+
for attribute_name, model_field in self.__class__.model_fields.items()
|
|
414
414
|
if model_field.from_field("hidden") == "hidden"
|
|
415
415
|
)
|
|
416
416
|
unset_fields = set(
|
|
417
417
|
attribute_name
|
|
418
|
-
for attribute_name, model_field in self.model_fields.items()
|
|
418
|
+
for attribute_name, model_field in self.__class__.model_fields.items()
|
|
419
419
|
if getattr(self, attribute_name, None) is None
|
|
420
420
|
)
|
|
421
421
|
excluded_fields = hidden_fields.union(unset_fields)
|
|
@@ -963,24 +963,32 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
963
963
|
return items
|
|
964
964
|
|
|
965
965
|
@classmethod
|
|
966
|
-
def get_field_names(cls) -> List[str]:
|
|
966
|
+
def get_field_names(cls, use_aliases: bool = False) -> List[str]:
|
|
967
967
|
"""
|
|
968
968
|
Get the field names for the Asset model.
|
|
969
969
|
|
|
970
|
+
:param bool use_aliases: Whether to use aliases for the field names, defaults to False
|
|
970
971
|
:return: List of field names
|
|
971
972
|
:rtype: List[str]
|
|
972
973
|
"""
|
|
974
|
+
if use_aliases:
|
|
975
|
+
return [val.alias or key for key, val in cls.model_fields.items() if not key.startswith("_")]
|
|
973
976
|
return [x for x in get_type_hints(cls).keys() if not x.startswith("_")]
|
|
974
977
|
|
|
975
978
|
@classmethod
|
|
976
|
-
def build_graphql_fields(cls) -> str:
|
|
979
|
+
def build_graphql_fields(cls, use_aliases: bool = False) -> str:
|
|
977
980
|
"""
|
|
978
981
|
Dynamically builds a GraphQL query for a given Pydantic model class.
|
|
979
982
|
|
|
983
|
+
:param bool use_aliases: Whether to use aliases for the field names, defaults to False
|
|
980
984
|
:return: A string representing the GraphQL query
|
|
981
985
|
:rtype: str
|
|
982
986
|
"""
|
|
983
|
-
return "\n".join(
|
|
987
|
+
return "\n".join(
|
|
988
|
+
x
|
|
989
|
+
for x in cls.get_field_names(use_aliases=use_aliases)
|
|
990
|
+
if x not in cls._exclude_graphql_fields and x != "extra_data"
|
|
991
|
+
)
|
|
984
992
|
|
|
985
993
|
@classmethod
|
|
986
994
|
def get_by_parent(cls, parent_id: int, parent_module: str) -> List[T]:
|
|
@@ -1142,13 +1150,18 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1142
1150
|
:return: The saved object
|
|
1143
1151
|
:rtype: T
|
|
1144
1152
|
"""
|
|
1145
|
-
if
|
|
1153
|
+
# Check if the model has change tracking and if there are changes
|
|
1154
|
+
has_change_tracking = hasattr(self, "has_changed") and callable(getattr(self, "has_changed", None))
|
|
1155
|
+
should_save = not has_change_tracking or self.has_changed()
|
|
1156
|
+
|
|
1157
|
+
if should_save:
|
|
1146
1158
|
if bulk:
|
|
1147
1159
|
logger.debug(f"Adding {self.__class__.__name__} {self.id} to pending updates")
|
|
1148
1160
|
self._get_pending_updates().add(self._get_cache_key(self))
|
|
1149
1161
|
self.cache_object(self) # Update the cache with the current state
|
|
1150
1162
|
return self
|
|
1151
1163
|
else:
|
|
1164
|
+
logger.debug(f"Saving {self.__class__.__name__} {self.id}")
|
|
1152
1165
|
return self._perform_save()
|
|
1153
1166
|
else:
|
|
1154
1167
|
logger.debug(f"No changes detected for {self.__class__.__name__} {self.id}")
|
|
@@ -1276,6 +1289,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1276
1289
|
endpoint = self.get_endpoint("update").format(id=self.id)
|
|
1277
1290
|
response = self._get_api_handler().put(endpoint=endpoint, data=self.dict(), headers=self._get_headers())
|
|
1278
1291
|
if hasattr(response, "ok") and response.ok:
|
|
1292
|
+
logger.debug(f"Successfully saved {self.__class__.__name__} {self.id}")
|
|
1279
1293
|
obj = self.__class__(**response.json())
|
|
1280
1294
|
self.cache_object(obj)
|
|
1281
1295
|
return obj
|
|
@@ -24,8 +24,10 @@ class SecurityControl(RegScaleModel):
|
|
|
24
24
|
["controlId", "catalogueId"],
|
|
25
25
|
]
|
|
26
26
|
_parent_id_field = "catalogueId"
|
|
27
|
+
_exclude_graphql_fields = ["objectives", "tests", "parameters"]
|
|
27
28
|
|
|
28
29
|
id: int = 0
|
|
30
|
+
otherId: Optional[str] = None
|
|
29
31
|
isPublic: bool = True
|
|
30
32
|
uuid: Optional[str] = None
|
|
31
33
|
controlId: Optional[str] = None
|
|
@@ -130,3 +132,48 @@ class SecurityControl(RegScaleModel):
|
|
|
130
132
|
config = api.config
|
|
131
133
|
res = api.get(config["domain"] + f"/api/securitycontrols/findByUniqueId/{control_name}/{catalog_id}")
|
|
132
134
|
return SecurityControl(**res.json()) if res.status_code == 200 else None
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def get_controls_by_parent_id_and_module(
|
|
138
|
+
cls, parent_id: int, parent_module: str, return_dicts: bool = False
|
|
139
|
+
) -> List["SecurityControl"]:
|
|
140
|
+
"""
|
|
141
|
+
Get a list of Security Controls by parent ID and module using GraphQL
|
|
142
|
+
|
|
143
|
+
:param int parent_id: The ID of the parent
|
|
144
|
+
:param str parent_module: The module of the parent
|
|
145
|
+
:param bool return_dicts: Whether to return the controls as a list of dicts, defaults to False
|
|
146
|
+
:return: A list of Security Controls
|
|
147
|
+
:rtype: List["SecurityControl"]
|
|
148
|
+
"""
|
|
149
|
+
query = f"""
|
|
150
|
+
query {{
|
|
151
|
+
controlImplementations(skip: 0, take: 50, where: {{
|
|
152
|
+
parentId: {{
|
|
153
|
+
eq: {parent_id}
|
|
154
|
+
}},
|
|
155
|
+
parentModule: {{
|
|
156
|
+
eq: "{parent_module}"
|
|
157
|
+
}}
|
|
158
|
+
control: {{
|
|
159
|
+
id: {{
|
|
160
|
+
gt: 0
|
|
161
|
+
}}
|
|
162
|
+
}}
|
|
163
|
+
}}) {{
|
|
164
|
+
items {{
|
|
165
|
+
control {{
|
|
166
|
+
{cls.build_graphql_fields(use_aliases=True)}
|
|
167
|
+
}}
|
|
168
|
+
}}
|
|
169
|
+
totalCount
|
|
170
|
+
pageInfo {{
|
|
171
|
+
hasNextPage
|
|
172
|
+
}}
|
|
173
|
+
}}
|
|
174
|
+
}}"""
|
|
175
|
+
data = cls._get_api_handler().graph(query=query)
|
|
176
|
+
controls = data.get("controlImplementations", {}).get("items", [])
|
|
177
|
+
if return_dicts:
|
|
178
|
+
return [control["control"] for control in controls if control.get("control")]
|
|
179
|
+
return [cls(**control["control"]) for control in controls if control.get("control")]
|
|
@@ -27,6 +27,7 @@ class SecurityPlan(RegScaleModel):
|
|
|
27
27
|
defaultAssessmentDays: int = 0
|
|
28
28
|
planInformationSystemSecurityOfficerId: Optional[str] = None
|
|
29
29
|
planAuthorizingOfficialId: Optional[str] = None
|
|
30
|
+
systemSecurityManagerId: Optional[str] = None
|
|
30
31
|
systemOwnerId: Optional[str] = None # this could be userID
|
|
31
32
|
otherIdentifier: Optional[str] = ""
|
|
32
33
|
confidentiality: str = ""
|
|
@@ -138,6 +139,8 @@ class SecurityPlan(RegScaleModel):
|
|
|
138
139
|
return ConfigDict( # type: ignore
|
|
139
140
|
get_control_implementations="/api/controlImplementation/getAllByPlan/{plan_id}",
|
|
140
141
|
mega_api="/api/{module_slug}/megaAPI/{intId}",
|
|
142
|
+
export_cis_crm="/api/{module_slug}/exportFedrampRev5CisCrm/{intId}",
|
|
143
|
+
list="/api/{module_slug}/getList",
|
|
141
144
|
)
|
|
142
145
|
|
|
143
146
|
@classmethod
|
|
@@ -156,6 +159,35 @@ class SecurityPlan(RegScaleModel):
|
|
|
156
159
|
return response.json()
|
|
157
160
|
return {}
|
|
158
161
|
|
|
162
|
+
@classmethod
|
|
163
|
+
def export_cis_crm(cls, ssp_id: int) -> dict:
|
|
164
|
+
"""
|
|
165
|
+
Export to a new CIS/CRM workbook with an existing SSP ID
|
|
166
|
+
|
|
167
|
+
:param int ssp_id: RegScale SSP ID
|
|
168
|
+
:return: A status message
|
|
169
|
+
:rtype: dict
|
|
170
|
+
"""
|
|
171
|
+
response = cls._get_api_handler().get(
|
|
172
|
+
endpoint=cls.get_endpoint("export_cis_crm").format(module_slug=cls._module_slug, intId=ssp_id)
|
|
173
|
+
)
|
|
174
|
+
if response.ok:
|
|
175
|
+
return response.json()
|
|
176
|
+
return {}
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
def get_list(cls) -> list:
|
|
180
|
+
"""
|
|
181
|
+
Get a list of objects.
|
|
182
|
+
|
|
183
|
+
:return: A list of objects
|
|
184
|
+
:rtype: list
|
|
185
|
+
"""
|
|
186
|
+
response = cls._get_api_handler().get(endpoint=cls.get_endpoint("list").format(module_slug=cls._module_slug))
|
|
187
|
+
if not response.raise_for_status():
|
|
188
|
+
return response.json()
|
|
189
|
+
return []
|
|
190
|
+
|
|
159
191
|
# Legacy code
|
|
160
192
|
def create_new_ssp(self, api: Api, return_id: Optional[bool] = False) -> Union[int, None, "SecurityPlan"]:
|
|
161
193
|
"""
|
|
@@ -14,9 +14,9 @@ from regscale.core.app.api import Api
|
|
|
14
14
|
from regscale.core.app.application import Application
|
|
15
15
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
16
16
|
from regscale.models import regscale_models
|
|
17
|
+
from regscale.models.regscale_models.issue import IssueStatus
|
|
17
18
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
18
19
|
|
|
19
|
-
|
|
20
20
|
logger = logging.getLogger("regscale")
|
|
21
21
|
|
|
22
22
|
|
|
@@ -68,7 +68,7 @@ class Vulnerability(RegScaleModel):
|
|
|
68
68
|
firstSeen: Optional[str] = None
|
|
69
69
|
daysOpen: Optional[int] = None
|
|
70
70
|
dns: Optional[str] = None
|
|
71
|
-
ipAddress: Optional[str] =
|
|
71
|
+
ipAddress: Optional[str] = None
|
|
72
72
|
mitigated: Optional[bool] = None
|
|
73
73
|
operatingSystem: Optional[str] = None
|
|
74
74
|
port: Optional[Union[str, int]] = None
|
|
@@ -86,7 +86,7 @@ class Vulnerability(RegScaleModel):
|
|
|
86
86
|
tenantsId: int = Field(default=0)
|
|
87
87
|
isPublic: bool = Field(default=False)
|
|
88
88
|
dateClosed: Optional[str] = None
|
|
89
|
-
status: Optional[
|
|
89
|
+
status: Optional[Union[str, IssueStatus]] = Field(default_factory=IssueStatus.Open)
|
|
90
90
|
buildVersion: Optional[str] = None
|
|
91
91
|
cvsSv3BaseVector: Optional[str] = None
|
|
92
92
|
cvsSv2BaseVector: Optional[str] = None
|
|
@@ -23,7 +23,7 @@ class VulnerabilityMapping(RegScaleModel):
|
|
|
23
23
|
vulnerabilityId: int
|
|
24
24
|
assetId: int
|
|
25
25
|
scanId: Optional[int] = None
|
|
26
|
-
|
|
26
|
+
securityPlanId: Optional[int] = None
|
|
27
27
|
status: Optional[str] = Field(max_length=450)
|
|
28
28
|
firstSeen: str
|
|
29
29
|
lastSeen: str
|
|
@@ -33,7 +33,7 @@ class VulnerabilityMapping(RegScaleModel):
|
|
|
33
33
|
lastUpdatedById: str = Field(default_factory=RegScaleModel.get_user_id)
|
|
34
34
|
dateCreated: str
|
|
35
35
|
dateLastUpdated: Optional[str] = None
|
|
36
|
-
tenantsId: int = Field(default=
|
|
36
|
+
tenantsId: int = Field(default=1)
|
|
37
37
|
|
|
38
38
|
@staticmethod
|
|
39
39
|
def _get_additional_endpoints() -> ConfigDict:
|
regscale/regscale.py
CHANGED
|
@@ -127,6 +127,7 @@ file_upload = import_command_with_timing("regscale.core.app.internal.file_upload
|
|
|
127
127
|
migrations = import_command_with_timing(INTERNAL, "migrations")
|
|
128
128
|
model = import_command_with_timing(INTERNAL, "model")
|
|
129
129
|
issues = import_command_with_timing(INTERNAL, "issues")
|
|
130
|
+
set_permissions = import_command_with_timing(INTERNAL, "set_permissions")
|
|
130
131
|
|
|
131
132
|
############################################################
|
|
132
133
|
# Public Integrations
|
|
@@ -757,6 +758,7 @@ cli.add_command(evidence) # add Evidence Feature
|
|
|
757
758
|
cli.add_command(migrations) # add data migration support
|
|
758
759
|
cli.add_command(issues) # add POAM(Issues) Editor Feature
|
|
759
760
|
cli.add_command(model) # add POAM(Issues) Editor Feature
|
|
761
|
+
cli.add_command(set_permissions) # add builk editor for record permissions
|
|
760
762
|
|
|
761
763
|
############################################################
|
|
762
764
|
# Add Commercial Integrations
|