regscale-cli 6.20.1.1__py3-none-any.whl → 6.20.2.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/core/app/utils/variables.py +5 -3
- regscale/integrations/commercial/__init__.py +2 -0
- regscale/integrations/commercial/burp.py +14 -0
- regscale/integrations/commercial/grype/commands.py +8 -1
- regscale/integrations/commercial/grype/scanner.py +2 -1
- regscale/integrations/commercial/jira.py +290 -133
- regscale/integrations/commercial/opentext/commands.py +14 -5
- regscale/integrations/commercial/opentext/scanner.py +3 -2
- regscale/integrations/commercial/qualys/__init__.py +3 -3
- regscale/integrations/commercial/stigv2/click_commands.py +6 -37
- regscale/integrations/commercial/tenablev2/commands.py +12 -4
- regscale/integrations/commercial/tenablev2/sc_scanner.py +21 -1
- regscale/integrations/commercial/tenablev2/sync_compliance.py +3 -0
- regscale/integrations/commercial/trivy/commands.py +11 -4
- regscale/integrations/commercial/trivy/scanner.py +2 -1
- regscale/integrations/jsonl_scanner_integration.py +8 -1
- regscale/integrations/public/cisa.py +58 -63
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +88 -93
- regscale/integrations/scanner_integration.py +22 -6
- regscale/models/app_models/click.py +49 -1
- regscale/models/integration_models/burp.py +11 -8
- regscale/models/integration_models/cisa_kev_data.json +142 -21
- regscale/models/integration_models/flat_file_importer/__init__.py +36 -176
- regscale/models/integration_models/jira_task_sync.py +27 -0
- regscale/models/integration_models/qualys.py +6 -7
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/control_implementation.py +39 -2
- regscale/models/regscale_models/regscale_model.py +49 -1
- regscale/models/regscale_models/task.py +1 -0
- regscale/regscale.py +1 -4
- regscale/utils/string.py +13 -0
- {regscale_cli-6.20.1.1.dist-info → regscale_cli-6.20.2.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.20.1.1.dist-info → regscale_cli-6.20.2.0.dist-info}/RECORD +38 -38
- regscale/integrations/commercial/synqly_jira.py +0 -840
- {regscale_cli-6.20.1.1.dist-info → regscale_cli-6.20.2.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.1.1.dist-info → regscale_cli-6.20.2.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.1.1.dist-info → regscale_cli-6.20.2.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.1.1.dist-info → regscale_cli-6.20.2.0.dist-info}/top_level.txt +0 -0
|
@@ -49,11 +49,14 @@ progress = create_progress_object()
|
|
|
49
49
|
|
|
50
50
|
SERVICE_PROVIDER_CORPORATE = "Service Provider Corporate"
|
|
51
51
|
SERVICE_PROVIDER_SYSTEM_SPECIFIC = "Service Provider System Specific"
|
|
52
|
-
SERVICE_PROVIDER_HYBRID = "Service Provider Hybrid"
|
|
52
|
+
SERVICE_PROVIDER_HYBRID = "Service Provider Hybrid (Corporate and System Specific)"
|
|
53
53
|
PROVIDER_SYSTEM_SPECIFIC = "Provider (System Specific)"
|
|
54
|
-
CUSTOMER_PROVIDED = "Provided
|
|
54
|
+
CUSTOMER_PROVIDED = "Customer Provided"
|
|
55
55
|
CUSTOMER_CONFIGURED = "Customer Configured"
|
|
56
|
-
|
|
56
|
+
PROVIDED_BY_CUSTOMER = "Provided by Customer (Customer System Specific)"
|
|
57
|
+
CONFIGURED_BY_CUSTOMER = "Configured by Customer (Customer System Specific)"
|
|
58
|
+
INHERITED = "Inherited from pre-existing FedRAMP Authorization"
|
|
59
|
+
SHARED = "Shared (Service Provider and Customer Responsibility)"
|
|
57
60
|
NOT_IMPLEMENTED = ControlImplementationStatus.NotImplemented.value
|
|
58
61
|
PARTIALLY_IMPLEMENTED = ControlImplementationStatus.PartiallyImplemented.value
|
|
59
62
|
CONTROL_ID = "Control ID"
|
|
@@ -77,10 +80,10 @@ STATUS_MAPPING = {
|
|
|
77
80
|
|
|
78
81
|
RESPONSIBILITY_MAP = {
|
|
79
82
|
# Original keys
|
|
80
|
-
|
|
83
|
+
SERVICE_PROVIDER_CORPORATE: SERVICE_PROVIDER_CORPORATE,
|
|
81
84
|
SERVICE_PROVIDER_SYSTEM_SPECIFIC: PROVIDER_SYSTEM_SPECIFIC,
|
|
82
85
|
SERVICE_PROVIDER_HYBRID: "Hybrid",
|
|
83
|
-
|
|
86
|
+
PROVIDED_BY_CUSTOMER: "Customer",
|
|
84
87
|
CONFIGURED_BY_CUSTOMER: CUSTOMER_CONFIGURED,
|
|
85
88
|
"Shared": "Shared",
|
|
86
89
|
"Inherited": "Inherited",
|
|
@@ -241,52 +244,44 @@ def map_implementation_status(control_id: str, cis_data: dict) -> str:
|
|
|
241
244
|
|
|
242
245
|
def map_origination(control_id: str, cis_data: dict) -> dict:
|
|
243
246
|
"""
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
:param
|
|
247
|
-
:param
|
|
248
|
-
:return:
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
"
|
|
253
|
-
"
|
|
254
|
-
"
|
|
255
|
-
"
|
|
256
|
-
"bConfiguredByCustomer"
|
|
257
|
-
"
|
|
258
|
-
"
|
|
259
|
-
"record_text": "",
|
|
247
|
+
Map control implementation responsibility from CRM worksheet data.
|
|
248
|
+
|
|
249
|
+
:param control_id: RegScale control ID
|
|
250
|
+
:param cis_data: Data from the CRM worksheet
|
|
251
|
+
:return: Responsibility information in regscale format
|
|
252
|
+
"""
|
|
253
|
+
# Define mapping of origination strings to boolean keys
|
|
254
|
+
origination_mapping = {
|
|
255
|
+
SERVICE_PROVIDER_CORPORATE: "bServiceProviderCorporate",
|
|
256
|
+
SERVICE_PROVIDER_SYSTEM_SPECIFIC: "bServiceProviderSystemSpecific",
|
|
257
|
+
SERVICE_PROVIDER_HYBRID: "bServiceProviderHybrid",
|
|
258
|
+
PROVIDED_BY_CUSTOMER: "bProvidedByCustomer",
|
|
259
|
+
CONFIGURED_BY_CUSTOMER: "bConfiguredByCustomer",
|
|
260
|
+
SHARED: "bShared",
|
|
261
|
+
INHERITED: "bInherited",
|
|
260
262
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
+
|
|
264
|
+
# Initialize result with all flags set to False
|
|
265
|
+
result = {key: False for key in origination_mapping.values()}
|
|
266
|
+
result["record_text"] = ""
|
|
267
|
+
|
|
268
|
+
# Find matching CIS records
|
|
269
|
+
matching_records = [
|
|
270
|
+
record for record in cis_data.values() if gen_key(record["regscale_control_id"]).lower() == control_id.lower()
|
|
263
271
|
]
|
|
264
|
-
|
|
265
|
-
|
|
272
|
+
|
|
273
|
+
# Process each matching record
|
|
274
|
+
for record in matching_records:
|
|
266
275
|
control_origination = record.get("control_origination", "")
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if CUSTOMER_PROVIDED in control_origination:
|
|
277
|
-
# responsibility = "Customer"
|
|
278
|
-
origination_bools["bProvidedByCustomer"] = True
|
|
279
|
-
if CONFIGURED_BY_CUSTOMER in control_origination:
|
|
280
|
-
# responsibility = "Customer Configured"
|
|
281
|
-
origination_bools["bConfiguredByCustomer"] = True
|
|
282
|
-
if "Shared" in control_origination:
|
|
283
|
-
# responsibility = "Shared"
|
|
284
|
-
origination_bools["bShared"] = True
|
|
285
|
-
if "Inherited" in control_origination:
|
|
286
|
-
# responsibility = "Inherited"
|
|
287
|
-
origination_bools["bInherited"] = True
|
|
288
|
-
origination_bools["record_text"] += control_origination
|
|
289
|
-
return origination_bools
|
|
276
|
+
|
|
277
|
+
# Set flags based on origination string content
|
|
278
|
+
for origination_str, bool_key in origination_mapping.items():
|
|
279
|
+
if origination_str in control_origination:
|
|
280
|
+
result[bool_key] = True
|
|
281
|
+
|
|
282
|
+
result["record_text"] += control_origination
|
|
283
|
+
|
|
284
|
+
return result
|
|
290
285
|
|
|
291
286
|
|
|
292
287
|
def clean_customer_responsibility(value: str):
|
|
@@ -331,16 +326,6 @@ def update_imp_objective(
|
|
|
331
326
|
NOT_IMPLEMENTED: NOT_IMPLEMENTED,
|
|
332
327
|
}
|
|
333
328
|
|
|
334
|
-
responsibility_map = {
|
|
335
|
-
"Provider": SERVICE_PROVIDER_CORPORATE,
|
|
336
|
-
PROVIDER_SYSTEM_SPECIFIC: SERVICE_PROVIDER_SYSTEM_SPECIFIC,
|
|
337
|
-
"Customer": "Provided by Customer (Customer System Specific)",
|
|
338
|
-
"Hybrid": "Service Provider Hybrid (Corporate and System Specific)",
|
|
339
|
-
CUSTOMER_CONFIGURED: "Configured by Customer (Customer System Specific)",
|
|
340
|
-
"Shared": "Shared (Service Provider and Customer Responsibility)",
|
|
341
|
-
"Inherited": "Inherited from pre-existing FedRAMP Authorization",
|
|
342
|
-
}
|
|
343
|
-
|
|
344
329
|
cis_record = record.get("cis", {})
|
|
345
330
|
crm_record = record.get("crm", {})
|
|
346
331
|
# There could be multiples, take the first one as regscale will not allow multiples at the objective level.
|
|
@@ -349,17 +334,18 @@ def update_imp_objective(
|
|
|
349
334
|
control_originations[ix] = control_origination.strip()
|
|
350
335
|
|
|
351
336
|
try:
|
|
352
|
-
responsibility =
|
|
353
|
-
|
|
354
|
-
)
|
|
337
|
+
responsibility = next(origin for origin in control_originations)
|
|
338
|
+
|
|
355
339
|
except StopIteration:
|
|
356
|
-
responsibility
|
|
340
|
+
if imp.responsibility:
|
|
341
|
+
responsibility = imp.responsibility.split(",")[0] # only one responsiblity allowed here.
|
|
342
|
+
else:
|
|
343
|
+
responsibility = SERVICE_PROVIDER_CORPORATE
|
|
357
344
|
|
|
358
345
|
customer_responsibility = clean_customer_responsibility(
|
|
359
346
|
crm_record.get("specific_inheritance_and_customer_agency_csp_responsibilities")
|
|
360
347
|
)
|
|
361
348
|
existing_pairs = {(obj.objectiveId, obj.implementationId) for obj in existing_imp_obj}
|
|
362
|
-
responsibility = responsibility_map.get(responsibility, responsibility)
|
|
363
349
|
logger.debug(f"CRM Record: {crm_record}")
|
|
364
350
|
can_be_inherited_from_csp: str = crm_record.get("can_be_inherited_from_csp") or ""
|
|
365
351
|
for objective in objectives:
|
|
@@ -452,9 +438,14 @@ def parse_control_details(
|
|
|
452
438
|
control_imp.bServiceProviderHybrid = origination_bool["bServiceProviderHybrid"]
|
|
453
439
|
control_imp.bConfiguredByCustomer = origination_bool["bConfiguredByCustomer"]
|
|
454
440
|
control_imp.bProvidedByCustomer = origination_bool["bProvidedByCustomer"]
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
441
|
+
control_imp.responsibility = get_responsibility(origination_bool)
|
|
442
|
+
logger.debug(f"Control Implementation Responsibility: {control_imp.responsibility}")
|
|
443
|
+
logger.debug(f"Control Implementation Status: {control_imp.status}")
|
|
444
|
+
if status == ControlImplementationStatus.Planned:
|
|
445
|
+
control_imp.stepsToImplement = "PLANNED"
|
|
446
|
+
control_imp.plannedImplementationDate = get_current_datetime("%Y-%m-%d")
|
|
447
|
+
if status in [ControlImplementationStatus.Planned, ControlImplementationStatus.NotImplemented]:
|
|
448
|
+
control_imp.exclusionJustification = "Imported from FedRAMP CIS CRM Workbook"
|
|
458
449
|
if updated_control := control_imp.save():
|
|
459
450
|
logger.debug("Control Implementation #%s updated successfully", control_imp.id)
|
|
460
451
|
return updated_control
|
|
@@ -465,29 +456,31 @@ def parse_control_details(
|
|
|
465
456
|
def get_responsibility(origination_bool: dict) -> str:
|
|
466
457
|
"""
|
|
467
458
|
Function to map the responsibility based on origination booleans.
|
|
459
|
+
Returns comma-separated string of all responsibilities for True booleans.
|
|
468
460
|
|
|
469
461
|
:param dict origination_bool: Dictionary containing origination booleans
|
|
470
|
-
:return:
|
|
462
|
+
:return: Comma-separated responsibility string
|
|
471
463
|
:rtype: str
|
|
472
464
|
"""
|
|
473
|
-
|
|
465
|
+
responsibilities = []
|
|
474
466
|
|
|
475
|
-
if origination_bool
|
|
476
|
-
|
|
477
|
-
if origination_bool
|
|
478
|
-
|
|
479
|
-
if origination_bool
|
|
480
|
-
|
|
481
|
-
if origination_bool
|
|
482
|
-
|
|
483
|
-
if origination_bool
|
|
484
|
-
|
|
485
|
-
if origination_bool
|
|
486
|
-
|
|
487
|
-
if origination_bool
|
|
488
|
-
|
|
467
|
+
if origination_bool.get("bServiceProviderCorporate", False):
|
|
468
|
+
responsibilities.append(SERVICE_PROVIDER_CORPORATE)
|
|
469
|
+
if origination_bool.get("bServiceProviderSystemSpecific", False):
|
|
470
|
+
responsibilities.append(SERVICE_PROVIDER_SYSTEM_SPECIFIC)
|
|
471
|
+
if origination_bool.get("bServiceProviderHybrid", False):
|
|
472
|
+
responsibilities.append(SERVICE_PROVIDER_HYBRID)
|
|
473
|
+
if origination_bool.get("bProvidedByCustomer", False):
|
|
474
|
+
responsibilities.append(PROVIDED_BY_CUSTOMER)
|
|
475
|
+
if origination_bool.get("bConfiguredByCustomer", False):
|
|
476
|
+
responsibilities.append(CONFIGURED_BY_CUSTOMER)
|
|
477
|
+
if origination_bool.get("bInherited", False):
|
|
478
|
+
responsibilities.append(INHERITED)
|
|
479
|
+
if origination_bool.get("bShared", False):
|
|
480
|
+
responsibilities.append(SHARED)
|
|
489
481
|
|
|
490
|
-
|
|
482
|
+
# Return comma-separated string, or NA if no responsibilities found
|
|
483
|
+
return ",".join(responsibilities) if responsibilities else ControlImplementationStatus.NA.value
|
|
491
484
|
|
|
492
485
|
|
|
493
486
|
def fetch_and_update_imps(
|
|
@@ -706,6 +699,7 @@ def process_implementation(
|
|
|
706
699
|
errors.extend(method_errors)
|
|
707
700
|
if result:
|
|
708
701
|
processed_objectives.append(result)
|
|
702
|
+
# Update Control Origin at the Implementation Level
|
|
709
703
|
return errors, processed_objectives
|
|
710
704
|
|
|
711
705
|
|
|
@@ -930,9 +924,9 @@ def parse_cis_worksheet(file_path: click.Path, cis_sheet_name: str) -> dict:
|
|
|
930
924
|
SERVICE_PROVIDER_SYSTEM_SPECIFIC,
|
|
931
925
|
SERVICE_PROVIDER_HYBRID,
|
|
932
926
|
CONFIGURED_BY_CUSTOMER,
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
927
|
+
PROVIDED_BY_CUSTOMER,
|
|
928
|
+
SHARED,
|
|
929
|
+
INHERITED,
|
|
936
930
|
]
|
|
937
931
|
|
|
938
932
|
# Fill NaN values with an empty string for processing
|
|
@@ -973,9 +967,9 @@ def parse_cis_worksheet(file_path: click.Path, cis_sheet_name: str) -> dict:
|
|
|
973
967
|
SERVICE_PROVIDER_SYSTEM_SPECIFIC,
|
|
974
968
|
SERVICE_PROVIDER_HYBRID,
|
|
975
969
|
CONFIGURED_BY_CUSTOMER,
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
970
|
+
PROVIDED_BY_CUSTOMER,
|
|
971
|
+
SHARED,
|
|
972
|
+
INHERITED,
|
|
979
973
|
]:
|
|
980
974
|
if data_row[col]:
|
|
981
975
|
selected_origination.append(col)
|
|
@@ -1102,15 +1096,15 @@ def parse_instructions_worksheet(
|
|
|
1102
1096
|
return instructions_df.to_dict(orient="records")
|
|
1103
1097
|
|
|
1104
1098
|
|
|
1105
|
-
def
|
|
1099
|
+
def update_customer_text():
|
|
1106
1100
|
"""
|
|
1107
1101
|
Update the implementation responsibility texts from the objective data
|
|
1108
1102
|
"""
|
|
1109
1103
|
with ThreadPoolExecutor() as executor:
|
|
1110
|
-
executor.map(
|
|
1104
|
+
executor.map(_update_imp_customer, EXISTING_IMPLEMENTATIONS.values())
|
|
1111
1105
|
|
|
1112
1106
|
|
|
1113
|
-
def
|
|
1107
|
+
def _update_imp_customer(imp: ControlImplementation):
|
|
1114
1108
|
"""
|
|
1115
1109
|
Update the implementation responsibility text for a given implementation
|
|
1116
1110
|
|
|
@@ -1207,7 +1201,7 @@ def parse_and_map_data(
|
|
|
1207
1201
|
crm_data=crm_data,
|
|
1208
1202
|
version=version,
|
|
1209
1203
|
)
|
|
1210
|
-
|
|
1204
|
+
update_customer_text()
|
|
1211
1205
|
|
|
1212
1206
|
report(error_set)
|
|
1213
1207
|
|
|
@@ -1339,6 +1333,7 @@ def create_new_security_plan(profile_id: int, system_name: str):
|
|
|
1339
1333
|
|
|
1340
1334
|
else:
|
|
1341
1335
|
ret = next((plan for plan in existing_plan), None)
|
|
1336
|
+
logger.info(f"Found existing SSP# {ret.id}")
|
|
1342
1337
|
existing_imps = ControlImplementation.get_list_by_plan(ret.id)
|
|
1343
1338
|
for imp in existing_imps:
|
|
1344
1339
|
EXISTING_IMPLEMENTATIONS[imp.controlID] = imp
|
|
@@ -633,6 +633,8 @@ class ScannerIntegration(ABC):
|
|
|
633
633
|
self.plan_id: int = plan_id
|
|
634
634
|
self.tenant_id: int = tenant_id
|
|
635
635
|
self.is_component: bool = is_component
|
|
636
|
+
if self.is_component:
|
|
637
|
+
self.component = regscale_models.Component.get_object(self.plan_id)
|
|
636
638
|
self.components: ThreadSafeList[Any] = ThreadSafeList()
|
|
637
639
|
self.asset_map_by_identifier: ThreadSafeDict[str, regscale_models.Asset] = ThreadSafeDict()
|
|
638
640
|
self.software_to_create: ThreadSafeList[regscale_models.SoftwareInventory] = ThreadSafeList()
|
|
@@ -819,11 +821,12 @@ class ScannerIntegration(ABC):
|
|
|
819
821
|
@abstractmethod
|
|
820
822
|
def fetch_findings(self, *args, **kwargs) -> Iterator[IntegrationFinding]:
|
|
821
823
|
"""
|
|
822
|
-
Fetches findings from the integration
|
|
824
|
+
Fetches findings from the integration.
|
|
823
825
|
|
|
824
|
-
:return:
|
|
825
|
-
:
|
|
826
|
+
:return: An iterator of findings
|
|
827
|
+
:yield: Iterator[IntegrationFinding]
|
|
826
828
|
"""
|
|
829
|
+
pass
|
|
827
830
|
|
|
828
831
|
@abstractmethod
|
|
829
832
|
def fetch_assets(self, *args, **kwargs) -> Iterator[IntegrationAsset]:
|
|
@@ -831,7 +834,7 @@ class ScannerIntegration(ABC):
|
|
|
831
834
|
Fetches assets from the integration
|
|
832
835
|
|
|
833
836
|
:return: An iterator of assets
|
|
834
|
-
:
|
|
837
|
+
:yield: Iterator[IntegrationAsset]
|
|
835
838
|
"""
|
|
836
839
|
|
|
837
840
|
def get_finding_status(self, status: Optional[str]) -> regscale_models.IssueStatus:
|
|
@@ -1024,10 +1027,10 @@ class ScannerIntegration(ABC):
|
|
|
1024
1027
|
logger.warning("Asset has no identifier, skipping")
|
|
1025
1028
|
return
|
|
1026
1029
|
|
|
1027
|
-
component = None
|
|
1030
|
+
component = getattr(self, "component") if self.is_component else None
|
|
1028
1031
|
if component_name:
|
|
1029
1032
|
logger.debug("Searching for component: %s...", component_name)
|
|
1030
|
-
component = self.components_by_title.get(component_name)
|
|
1033
|
+
component = component or self.components_by_title.get(component_name)
|
|
1031
1034
|
if not component:
|
|
1032
1035
|
logger.debug("No existing component found with name %s, proceeding to create it...", component_name)
|
|
1033
1036
|
component = regscale_models.Component(
|
|
@@ -2702,6 +2705,7 @@ class ScannerIntegration(ABC):
|
|
|
2702
2705
|
logger.info("Syncing %s findings...", kwargs.get("title", cls.title))
|
|
2703
2706
|
instance = cls(plan_id=plan_id, **kwargs)
|
|
2704
2707
|
instance.set_keys(**kwargs)
|
|
2708
|
+
instance.ensure_data_types()
|
|
2705
2709
|
# If a progress object was passed, use it instead of creating a new one
|
|
2706
2710
|
instance.finding_progress = kwargs.pop("progress") if "progress" in kwargs else create_progress_object()
|
|
2707
2711
|
instance.enable_finding_date_update = kwargs.get("enable_finding_date_update", False)
|
|
@@ -2771,6 +2775,7 @@ class ScannerIntegration(ABC):
|
|
|
2771
2775
|
logger.info("Syncing %s assets...", kwargs.get("title", cls.title))
|
|
2772
2776
|
instance = cls(plan_id=plan_id, **kwargs)
|
|
2773
2777
|
instance.set_keys(**kwargs)
|
|
2778
|
+
instance.ensure_data_types()
|
|
2774
2779
|
instance.asset_progress = kwargs.pop("progress") if "progress" in kwargs else create_progress_object()
|
|
2775
2780
|
if asset_count := kwargs.get("asset_count"):
|
|
2776
2781
|
instance.num_assets_to_process = asset_count
|
|
@@ -2813,6 +2818,17 @@ class ScannerIntegration(ABC):
|
|
|
2813
2818
|
else:
|
|
2814
2819
|
logger.debug("Unable to set the %s attribute", key)
|
|
2815
2820
|
|
|
2821
|
+
def ensure_data_types(self) -> None:
|
|
2822
|
+
"""
|
|
2823
|
+
A method to enforce kwarg data types.
|
|
2824
|
+
|
|
2825
|
+
:return: None
|
|
2826
|
+
:rtype: None
|
|
2827
|
+
"""
|
|
2828
|
+
# Ensure scan_date is a string
|
|
2829
|
+
if not isinstance(self.scan_date, str):
|
|
2830
|
+
self.scan_date = date_str(self.scan_date)
|
|
2831
|
+
|
|
2816
2832
|
def log_error(self, msg: str, *args) -> None:
|
|
2817
2833
|
"""
|
|
2818
2834
|
Logs an error message
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""Module to allow dynamic click arguments and store commonly used click commands"""
|
|
4
4
|
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Any, Callable, Optional, Tuple
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
8
|
from pathlib import Path
|
|
@@ -128,6 +128,54 @@ def regscale_ssp_id(
|
|
|
128
128
|
)
|
|
129
129
|
|
|
130
130
|
|
|
131
|
+
def ssp_or_component_id(
|
|
132
|
+
ssp_kwargs: Optional[dict] = None,
|
|
133
|
+
component_kwargs: Optional[dict] = None,
|
|
134
|
+
) -> Tuple[click.option, click.option]:
|
|
135
|
+
"""
|
|
136
|
+
Function to return click.option for RegScale Component ID and SSP ID, user must provide either SSP ID or Component ID
|
|
137
|
+
|
|
138
|
+
:param Optional[dict] ssp_kwargs: kwargs to pass to click.option for RegScale SSP ID
|
|
139
|
+
:param Optional[dict] component_kwargs: kwargs to pass to click.option for RegScale Component ID
|
|
140
|
+
:return: click.option for RegScale Component ID and SSP ID
|
|
141
|
+
:rtype: click.option
|
|
142
|
+
"""
|
|
143
|
+
if ssp_kwargs is None:
|
|
144
|
+
ssp_kwargs = {}
|
|
145
|
+
if component_kwargs is None:
|
|
146
|
+
component_kwargs = {}
|
|
147
|
+
|
|
148
|
+
def decorator(this_func) -> Callable[[Callable], click.option]:
|
|
149
|
+
"""
|
|
150
|
+
Decorator to return click.option for RegScale Component ID and SSP ID
|
|
151
|
+
"""
|
|
152
|
+
this_func = click.option(
|
|
153
|
+
"-id",
|
|
154
|
+
"-p",
|
|
155
|
+
"--regscale_ssp_id",
|
|
156
|
+
"--plan_id",
|
|
157
|
+
type=click.INT,
|
|
158
|
+
help=ssp_kwargs.pop("help", "The ID number from RegScale of the System Security Plan."),
|
|
159
|
+
prompt=ssp_kwargs.pop("prompt", None),
|
|
160
|
+
cls=NotRequiredIf,
|
|
161
|
+
not_required_if=["component_id"],
|
|
162
|
+
**ssp_kwargs,
|
|
163
|
+
)(this_func)
|
|
164
|
+
this_func = click.option(
|
|
165
|
+
"-c",
|
|
166
|
+
"--component_id",
|
|
167
|
+
type=click.INT,
|
|
168
|
+
help=component_kwargs.pop("help", "The ID number from RegScale of the Component."),
|
|
169
|
+
prompt=component_kwargs.pop("prompt", None),
|
|
170
|
+
cls=NotRequiredIf,
|
|
171
|
+
not_required_if=["regscale_ssp_id"],
|
|
172
|
+
**component_kwargs,
|
|
173
|
+
)(this_func)
|
|
174
|
+
return this_func
|
|
175
|
+
|
|
176
|
+
return decorator
|
|
177
|
+
|
|
178
|
+
|
|
131
179
|
def regscale_id(
|
|
132
180
|
help: str = "Enter the desired ID # from RegScale.",
|
|
133
181
|
required: bool = True,
|
|
@@ -9,10 +9,10 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Any, Generator, List, Optional, TextIO
|
|
10
10
|
from urllib.parse import urlparse
|
|
11
11
|
from xml.etree.ElementTree import Element, ParseError, fromstring, parse
|
|
12
|
+
from logging import getLogger
|
|
12
13
|
|
|
13
14
|
from regscale.core.app.api import Api
|
|
14
15
|
from regscale.core.app.application import Application
|
|
15
|
-
from regscale.core.app.logz import create_logger
|
|
16
16
|
from regscale.core.app.utils.app_utils import check_file_path, get_current_datetime
|
|
17
17
|
from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
|
|
18
18
|
from regscale.models.integration_models.burp_models import BurpRequest, BurpResponse, Issue, RequestResponse
|
|
@@ -26,7 +26,7 @@ class Burp:
|
|
|
26
26
|
"""Burp Scan information"""
|
|
27
27
|
|
|
28
28
|
def __init__(self, app: Application, file_path: str, encoding="utf-8", **kwargs) -> "Burp":
|
|
29
|
-
logger =
|
|
29
|
+
logger = getLogger("regscale")
|
|
30
30
|
logger.info("Now processing %s", file_path)
|
|
31
31
|
self.integration_assets: Generator[IntegrationAsset, None, None] = (x for x in [])
|
|
32
32
|
self.integration_findings: Generator[IntegrationFinding, None, None] = (x for x in [])
|
|
@@ -293,10 +293,10 @@ class Burp:
|
|
|
293
293
|
:rtype: RequestResponse
|
|
294
294
|
"""
|
|
295
295
|
request_data = item.find(".//request")
|
|
296
|
-
if response_data := item.find(".//response"):
|
|
297
|
-
base64_dat =
|
|
296
|
+
if (response_data := item.find(".//response")) is not None:
|
|
297
|
+
base64_dat = request_data.attrib.get("base64", "false").lower() == "true"
|
|
298
298
|
response_data_is_base64 = BurpResponse.is_base64(response_data.text)
|
|
299
|
-
method = request_data.attrib
|
|
299
|
+
method = request_data.attrib.get("method", "GET")
|
|
300
300
|
request = (
|
|
301
301
|
BurpRequest(dataString=request_data.text, base64=base64_dat, method=method)
|
|
302
302
|
if BurpRequest.is_base64(request_data.text)
|
|
@@ -395,7 +395,8 @@ class Burp:
|
|
|
395
395
|
root = fromstring(html)
|
|
396
396
|
return [link.text.strip() for link in root.iter("a")]
|
|
397
397
|
|
|
398
|
-
|
|
398
|
+
@staticmethod
|
|
399
|
+
def get_domain_name(url: str) -> str:
|
|
399
400
|
"""
|
|
400
401
|
Get the domain name from a URL
|
|
401
402
|
|
|
@@ -407,7 +408,8 @@ class Burp:
|
|
|
407
408
|
domain_name = parsed_url.hostname
|
|
408
409
|
return domain_name
|
|
409
410
|
|
|
410
|
-
|
|
411
|
+
@staticmethod
|
|
412
|
+
def strip_html_tags(text: str) -> str:
|
|
411
413
|
"""
|
|
412
414
|
Strip HTML tags from a string.
|
|
413
415
|
|
|
@@ -420,7 +422,8 @@ class Burp:
|
|
|
420
422
|
clean = re.sub(r"<.*?>", "", text)
|
|
421
423
|
return clean
|
|
422
424
|
|
|
423
|
-
|
|
425
|
+
@staticmethod
|
|
426
|
+
def extract_cve(input_string: str) -> Optional[str]:
|
|
424
427
|
"""
|
|
425
428
|
Extract CVEs from a string.
|
|
426
429
|
|