regscale-cli 6.25.1.0__py3-none-any.whl → 6.27.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.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/application.py +19 -4
- regscale/core/app/internal/evidence.py +419 -2
- regscale/core/app/internal/login.py +0 -1
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/dev/code_gen.py +24 -20
- regscale/integrations/commercial/jira.py +367 -126
- regscale/integrations/commercial/qualys/__init__.py +7 -8
- regscale/integrations/commercial/qualys/scanner.py +8 -3
- regscale/integrations/commercial/sicura/api.py +14 -13
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +49 -39
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/synqly/assets.py +17 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +45 -28
- regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
- regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
- regscale/integrations/commercial/tenablev2/commands.py +142 -1
- regscale/integrations/commercial/tenablev2/scanner.py +0 -1
- regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
- regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +64 -79
- regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
- regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
- regscale/integrations/commercial/wizv2/compliance_report.py +161 -165
- regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
- regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +3 -3
- regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +1 -17
- regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
- regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +5 -9
- regscale/integrations/commercial/wizv2/issue.py +1 -1
- regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
- regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
- regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
- regscale/integrations/commercial/wizv2/reports.py +1 -1
- regscale/integrations/commercial/wizv2/sbom.py +1 -1
- regscale/integrations/commercial/wizv2/scanner.py +39 -99
- regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
- regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
- regscale/integrations/commercial/wizv2/variables.py +89 -3
- regscale/integrations/compliance_integration.py +60 -41
- regscale/integrations/control_matcher.py +377 -0
- regscale/integrations/due_date_handler.py +14 -8
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +1 -0
- regscale/integrations/public/cci_importer.py +37 -38
- regscale/integrations/public/fedramp/click.py +60 -2
- regscale/integrations/public/fedramp/docx_parser.py +10 -1
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +393 -340
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +277 -153
- regscale/models/integration_models/cisa_kev_data.json +282 -9
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/qualys.py +3 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +24 -7
- regscale/models/integration_models/synqly_models/synqly_model.py +8 -1
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +1 -2
- regscale/models/regscale_models/control_implementation.py +47 -22
- regscale/models/regscale_models/issue.py +256 -95
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/regscale_model.py +6 -1
- regscale/templates/__init__.py +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/METADATA +1 -17
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/RECORD +145 -65
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3731 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +2204 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +350 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1365 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
- tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
- tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
- tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
- tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
- tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
- tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
- tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
- tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
- tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
- tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
- tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1132 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
- tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/public/test_fedramp.py +301 -0
- tests/regscale/integrations/test_control_matcher.py +1397 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_issue.py +378 -1
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3543
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/top_level.txt +0 -0
|
@@ -34,9 +34,8 @@ from regscale.integrations.commercial.qualys.scanner import QualysTotalCloudJSON
|
|
|
34
34
|
from regscale.integrations.commercial.qualys.variables import QualysVariables
|
|
35
35
|
from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
|
|
36
36
|
from regscale.integrations.variables import ScannerVariables
|
|
37
|
-
from regscale.models import Asset, Issue, Search, regscale_models
|
|
38
|
-
from regscale.models.app_models.click import NotRequiredIf,
|
|
39
|
-
from regscale.models.integration_models.ecr_models.data import Finding
|
|
37
|
+
from regscale.models import Asset, Issue, Search, regscale_models, IssueStatus, IssueSeverity
|
|
38
|
+
from regscale.models.app_models.click import NotRequiredIf, save_output_to, ssp_or_component_id
|
|
40
39
|
from regscale.models.integration_models.flat_file_importer import FlatFileImporter
|
|
41
40
|
from regscale.models.integration_models.qualys import (
|
|
42
41
|
Qualys,
|
|
@@ -2116,7 +2115,7 @@ def lookup_asset(asset_list: list, asset_id: str = None) -> list[Asset]:
|
|
|
2116
2115
|
return list(set(asset_list)) or []
|
|
2117
2116
|
|
|
2118
2117
|
|
|
2119
|
-
def map_qualys_severity_to_regscale(severity: int) -> tuple[
|
|
2118
|
+
def map_qualys_severity_to_regscale(severity: int) -> tuple[IssueSeverity, str]:
|
|
2120
2119
|
"""
|
|
2121
2120
|
Map Qualys vulnerability severity to RegScale Issue severity
|
|
2122
2121
|
|
|
@@ -2125,12 +2124,12 @@ def map_qualys_severity_to_regscale(severity: int) -> tuple[str, str]:
|
|
|
2125
2124
|
:rtype: tuple[str, str]
|
|
2126
2125
|
"""
|
|
2127
2126
|
if severity <= 2:
|
|
2128
|
-
return
|
|
2127
|
+
return IssueSeverity.Low, "low"
|
|
2129
2128
|
if severity == 3:
|
|
2130
|
-
return
|
|
2129
|
+
return IssueSeverity.Moderate, "moderate"
|
|
2131
2130
|
if severity > 3:
|
|
2132
|
-
return
|
|
2133
|
-
return
|
|
2131
|
+
return IssueSeverity.High, "high"
|
|
2132
|
+
return IssueSeverity.NotAssigned, "low"
|
|
2134
2133
|
|
|
2135
2134
|
|
|
2136
2135
|
def create_regscale_issue_from_vuln(
|
|
@@ -4,6 +4,7 @@ Qualys Total Cloud scanner integration class using JSONLScannerIntegration.
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
|
+
import threading
|
|
7
8
|
import time
|
|
8
9
|
import traceback
|
|
9
10
|
import xml.etree.ElementTree as ET
|
|
@@ -1554,17 +1555,21 @@ class QualysTotalCloudJSONLIntegration(JSONLScannerIntegration):
|
|
|
1554
1555
|
|
|
1555
1556
|
return vulnerability_id
|
|
1556
1557
|
|
|
1557
|
-
def set_severity_count_for_scan(
|
|
1558
|
+
def set_severity_count_for_scan(
|
|
1559
|
+
self, severity: str, scan_history: regscale_models.ScanHistory, lock: Optional[threading.RLock] = None
|
|
1560
|
+
) -> None:
|
|
1558
1561
|
"""
|
|
1559
1562
|
Override parent method to ensure Qualys scan history severity counts are properly updated.
|
|
1560
1563
|
This ensures that the vulnerability counts are accurately reflected in the scan history.
|
|
1561
1564
|
|
|
1562
1565
|
:param str severity: Severity of the vulnerability
|
|
1563
1566
|
:param regscale_models.ScanHistory scan_history: Scan history object
|
|
1567
|
+
:param Optional[threading.RLock] lock: Thread lock for synchronization
|
|
1564
1568
|
:rtype: None
|
|
1565
1569
|
"""
|
|
1566
|
-
# Use parent method to update severity counts
|
|
1567
|
-
|
|
1570
|
+
# Use parent method to update severity counts with thread-safe locking
|
|
1571
|
+
# Pass lock if provided, otherwise use our instance lock
|
|
1572
|
+
super().set_severity_count_for_scan(severity, scan_history, lock or self.scan_history_lock)
|
|
1568
1573
|
|
|
1569
1574
|
def create_scan_history(self) -> regscale_models.ScanHistory:
|
|
1570
1575
|
"""
|
|
@@ -356,7 +356,7 @@ class SicuraAPI:
|
|
|
356
356
|
try:
|
|
357
357
|
response = self._make_request(
|
|
358
358
|
"GET",
|
|
359
|
-
"/
|
|
359
|
+
"/api/jaeger/v1/nodes",
|
|
360
360
|
params={
|
|
361
361
|
"verbose": "true",
|
|
362
362
|
"attributes": "platforms,scannable_profiles,most_recent_scan",
|
|
@@ -425,7 +425,7 @@ class SicuraAPI:
|
|
|
425
425
|
"scanAttributes": {"platform": platform, "profile": profile},
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
-
result = self._make_request("POST", "/
|
|
428
|
+
result = self._make_request("POST", "/api/jaeger/v1/tasks/", data=payload)
|
|
429
429
|
|
|
430
430
|
if result:
|
|
431
431
|
logger.info(f"Successfully created scan task with ID: {result}")
|
|
@@ -448,7 +448,7 @@ class SicuraAPI:
|
|
|
448
448
|
"""
|
|
449
449
|
try:
|
|
450
450
|
response = self._make_request(
|
|
451
|
-
"GET", "/
|
|
451
|
+
"GET", "/api/jaeger/v1/jobs", params={"verbose": "true", self.FILTER_TASK_ID: task_id}
|
|
452
452
|
)
|
|
453
453
|
|
|
454
454
|
# Handle 404 or empty response
|
|
@@ -503,7 +503,7 @@ class SicuraAPI:
|
|
|
503
503
|
if profile:
|
|
504
504
|
params["profile"] = profile
|
|
505
505
|
|
|
506
|
-
response = self._make_request("GET", "/
|
|
506
|
+
response = self._make_request("GET", "/api/jaeger/v1/nodes", params=params)
|
|
507
507
|
|
|
508
508
|
# Handle 404 or empty response
|
|
509
509
|
if not response or (isinstance(response, list) and not response):
|
|
@@ -524,16 +524,17 @@ class SicuraAPI:
|
|
|
524
524
|
return None # Return None if no scans available
|
|
525
525
|
|
|
526
526
|
# Calculate summary stats
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
527
|
+
scan_results = device.get("scans", {}).get("results", [])
|
|
528
|
+
pass_count = sum(1 for scan in scan_results if scan.get("result") == "pass")
|
|
529
|
+
fail_count = sum(1 for scan in scan_results if scan.get("result") == "fail")
|
|
530
|
+
total_count = len(scan_results)
|
|
530
531
|
|
|
531
532
|
# Create the raw result data
|
|
532
533
|
result_data = {
|
|
533
534
|
"device_id": device.get("id"),
|
|
534
535
|
"fqdn": device.get("fqdn"),
|
|
535
536
|
"ip_address": device.get("ip_address"),
|
|
536
|
-
"scans":
|
|
537
|
+
"scans": scan_results,
|
|
537
538
|
"summary": {
|
|
538
539
|
"total": total_count,
|
|
539
540
|
"pass": pass_count,
|
|
@@ -638,7 +639,7 @@ class SicuraAPI:
|
|
|
638
639
|
if ip_address:
|
|
639
640
|
params[self.FILTER_IP_ADDRESS] = ip_address
|
|
640
641
|
|
|
641
|
-
response = self._make_request("GET", "/
|
|
642
|
+
response = self._make_request("GET", "/api/jaeger/v1/node_templates/", params=params)
|
|
642
643
|
|
|
643
644
|
# Handle 404 or empty response
|
|
644
645
|
if not response or (isinstance(response, dict) and "detail" in response):
|
|
@@ -670,7 +671,7 @@ class SicuraAPI:
|
|
|
670
671
|
# Use PUT method with the correct endpoint and payload
|
|
671
672
|
result = self._make_request(
|
|
672
673
|
"PUT",
|
|
673
|
-
f"/
|
|
674
|
+
f"/api/jaeger/v1/node_templates/{device_id}",
|
|
674
675
|
params={"verbose": "true", "include_controls": "true", "action": "promote"},
|
|
675
676
|
)
|
|
676
677
|
|
|
@@ -697,7 +698,7 @@ class SicuraAPI:
|
|
|
697
698
|
# Use PUT method with the correct endpoint and payload
|
|
698
699
|
result = self._make_request(
|
|
699
700
|
"PUT",
|
|
700
|
-
f"/
|
|
701
|
+
f"/api/jaeger/v1/node_templates/{device_id}",
|
|
701
702
|
params={"verbose": "true", "include_controls": "true", "action": "reject"},
|
|
702
703
|
)
|
|
703
704
|
|
|
@@ -721,7 +722,7 @@ class SicuraAPI:
|
|
|
721
722
|
:rtype: bool
|
|
722
723
|
"""
|
|
723
724
|
try:
|
|
724
|
-
result = self._make_request("DELETE", f"/
|
|
725
|
+
result = self._make_request("DELETE", f"/api/jaeger/v1/nodes/{device_id}")
|
|
725
726
|
|
|
726
727
|
# For DELETE operations, an empty result typically indicates success
|
|
727
728
|
if result is None or result == "" or (isinstance(result, dict) and not result):
|
|
@@ -854,7 +855,7 @@ class SicuraAPI:
|
|
|
854
855
|
}
|
|
855
856
|
|
|
856
857
|
logger.info(f"Creating enforcement task for device {device_id} with {len(ce_names)} CE names")
|
|
857
|
-
result = self._make_request("POST", "/
|
|
858
|
+
result = self._make_request("POST", "/api/jaeger/v1/tasks/", data=payload)
|
|
858
859
|
|
|
859
860
|
if result:
|
|
860
861
|
logger.info(f"Successfully created enforcement task with ID: {result}")
|
|
@@ -46,7 +46,13 @@ def sync_assets(regscale_id: int):
|
|
|
46
46
|
|
|
47
47
|
@sicura.command(name="sync_findings")
|
|
48
48
|
@regscale_id(help="RegScale will create and update findings as children of this record.")
|
|
49
|
-
|
|
49
|
+
@click.option(
|
|
50
|
+
"--trigger_scan",
|
|
51
|
+
"-s",
|
|
52
|
+
is_flag=True,
|
|
53
|
+
help="Trigger a new scan on Sicura assets before syncing.",
|
|
54
|
+
)
|
|
55
|
+
def sync_findings(regscale_id: int, trigger_scan: bool):
|
|
50
56
|
"""
|
|
51
57
|
Sync Sicura findings to RegScale.
|
|
52
58
|
|
|
@@ -60,7 +66,7 @@ def sync_findings(regscale_id: int):
|
|
|
60
66
|
)
|
|
61
67
|
|
|
62
68
|
# Using import_findings method which handles the synchronization
|
|
63
|
-
integration.sync_findings(plan_id=regscale_id)
|
|
69
|
+
integration.sync_findings(plan_id=regscale_id, trigger_scan=trigger_scan)
|
|
64
70
|
|
|
65
71
|
logger.info("[bold green]Finding synchronization complete.")
|
|
66
72
|
|
|
@@ -74,6 +74,10 @@ class SicuraIntegration(ScannerIntegration):
|
|
|
74
74
|
logger.warning("No devices found in Sicura")
|
|
75
75
|
return
|
|
76
76
|
|
|
77
|
+
if kwargs.pop("trigger_scan", False):
|
|
78
|
+
logger.info(f"Triggering scans on Sicura {len(devices)} devices...")
|
|
79
|
+
self.trigger_scans(devices)
|
|
80
|
+
|
|
77
81
|
self.num_findings_to_process = 0
|
|
78
82
|
findings_count = 0
|
|
79
83
|
|
|
@@ -206,7 +210,8 @@ class SicuraIntegration(ScannerIntegration):
|
|
|
206
210
|
mitigation=mitigation,
|
|
207
211
|
)
|
|
208
212
|
|
|
209
|
-
|
|
213
|
+
@staticmethod
|
|
214
|
+
def _extract_scan_data(scan: Any) -> dict:
|
|
210
215
|
"""
|
|
211
216
|
Extract scan data from the scan object
|
|
212
217
|
|
|
@@ -235,7 +240,8 @@ class SicuraIntegration(ScannerIntegration):
|
|
|
235
240
|
"controls": scan.controls if hasattr(scan, "controls") else {},
|
|
236
241
|
}
|
|
237
242
|
|
|
238
|
-
|
|
243
|
+
@staticmethod
|
|
244
|
+
def _extract_control_refs(controls: dict) -> tuple:
|
|
239
245
|
"""
|
|
240
246
|
Extract CCI and SRG references from controls
|
|
241
247
|
|
|
@@ -353,8 +359,9 @@ class SicuraIntegration(ScannerIntegration):
|
|
|
353
359
|
baseline=platform,
|
|
354
360
|
)
|
|
355
361
|
|
|
362
|
+
@staticmethod
|
|
356
363
|
def _create_results_text(
|
|
357
|
-
|
|
364
|
+
title, ce_name, result, description, state, state_reason, cci_ref, srg_refs, is_cci_finding
|
|
358
365
|
) -> str:
|
|
359
366
|
"""
|
|
360
367
|
Create the results text for a finding
|
|
@@ -401,42 +408,6 @@ class SicuraIntegration(ScannerIntegration):
|
|
|
401
408
|
:rtype: Iterator[IntegrationAsset]
|
|
402
409
|
"""
|
|
403
410
|
logger.info("Fetching assets from Sicura...")
|
|
404
|
-
|
|
405
|
-
# Get all devices
|
|
406
|
-
pending_devices = self.api.get_pending_devices()
|
|
407
|
-
for pending_device in pending_devices:
|
|
408
|
-
print(f"Pending device: {pending_device}")
|
|
409
|
-
self.api.accept_pending_device(pending_device.id)
|
|
410
|
-
task_id = self.api.create_scan_task(
|
|
411
|
-
device_id=pending_device.id,
|
|
412
|
-
platform=pending_device.platform,
|
|
413
|
-
profile=SicuraProfile.I_MISSION_CRITICAL_CLASSIFIED,
|
|
414
|
-
)
|
|
415
|
-
if task_id:
|
|
416
|
-
self.api.wait_for_scan_results(
|
|
417
|
-
task_id=task_id,
|
|
418
|
-
fqdn=pending_device.fqdn,
|
|
419
|
-
platform=pending_device.platform,
|
|
420
|
-
profile=SicuraProfile.I_MISSION_CRITICAL_CLASSIFIED,
|
|
421
|
-
)
|
|
422
|
-
else:
|
|
423
|
-
logger.warning(f"Failed to create scan task for device {pending_device.fqdn}")
|
|
424
|
-
continue # Continue to next device if creating scan task failed
|
|
425
|
-
|
|
426
|
-
task_id = self.api.create_scan_task(
|
|
427
|
-
device_id=pending_device.id, platform=pending_device.platform, profile=SicuraProfile.LEVEL_1_SERVER
|
|
428
|
-
)
|
|
429
|
-
if task_id:
|
|
430
|
-
self.api.wait_for_scan_results(
|
|
431
|
-
task_id=task_id,
|
|
432
|
-
fqdn=pending_device.fqdn,
|
|
433
|
-
platform=pending_device.platform,
|
|
434
|
-
profile=SicuraProfile.LEVEL_1_SERVER,
|
|
435
|
-
)
|
|
436
|
-
else:
|
|
437
|
-
logger.warning(f"Failed to create scan task for device {pending_device.fqdn}")
|
|
438
|
-
# No continue needed here as we're at the end of the loop iteration
|
|
439
|
-
|
|
440
411
|
devices = self.api.get_devices()
|
|
441
412
|
|
|
442
413
|
if not devices:
|
|
@@ -479,3 +450,42 @@ class SicuraIntegration(ScannerIntegration):
|
|
|
479
450
|
)
|
|
480
451
|
|
|
481
452
|
self.asset_progress.update(loading_devices, advance=1)
|
|
453
|
+
|
|
454
|
+
def trigger_and_wait_for_scan(self, device: Device) -> None:
|
|
455
|
+
"""
|
|
456
|
+
Trigger a scan and wait for the results
|
|
457
|
+
|
|
458
|
+
:param Device device: The device to trigger a scan for
|
|
459
|
+
:return: None
|
|
460
|
+
"""
|
|
461
|
+
task_id = self.api.create_scan_task(
|
|
462
|
+
device_id=device.id,
|
|
463
|
+
platform=device.platforms,
|
|
464
|
+
profile=SicuraProfile.I_MISSION_CRITICAL_CLASSIFIED,
|
|
465
|
+
)
|
|
466
|
+
if task_id:
|
|
467
|
+
self.api.wait_for_scan_results(
|
|
468
|
+
task_id=task_id,
|
|
469
|
+
fqdn=device.fqdn,
|
|
470
|
+
platform=device.platforms,
|
|
471
|
+
profile=SicuraProfile.I_MISSION_CRITICAL_CLASSIFIED,
|
|
472
|
+
)
|
|
473
|
+
else:
|
|
474
|
+
logger.warning(f"Failed to create scan task for device {device.fqdn}")
|
|
475
|
+
|
|
476
|
+
def trigger_scans(self, devices: list[Device]) -> None:
|
|
477
|
+
"""
|
|
478
|
+
Trigger scans for a list of devices
|
|
479
|
+
|
|
480
|
+
:param list[Device] devices: The devices to trigger scans for
|
|
481
|
+
:return: None
|
|
482
|
+
"""
|
|
483
|
+
if len(devices) > 1:
|
|
484
|
+
from regscale.utils.threading import ThreadManager
|
|
485
|
+
|
|
486
|
+
# use multithreading to trigger scans for multiple devices
|
|
487
|
+
thread_manager = ThreadManager(max_workers=10)
|
|
488
|
+
thread_manager.submit_tasks_from_list(self.trigger_and_wait_for_scan, devices)
|
|
489
|
+
thread_manager.execute_and_verify()
|
|
490
|
+
else:
|
|
491
|
+
self.trigger_and_wait_for_scan(devices[0])
|
|
@@ -82,15 +82,15 @@ class STIGInfo(BaseModel):
|
|
|
82
82
|
"""Data model for STIG Information with optional and required attributes."""
|
|
83
83
|
|
|
84
84
|
version: str
|
|
85
|
-
classification: str
|
|
85
|
+
classification: Optional[str] = None
|
|
86
86
|
customname: Optional[str] = None
|
|
87
87
|
stigid: str
|
|
88
88
|
description: Optional[str] = None
|
|
89
89
|
filename: Optional[str] = None
|
|
90
90
|
releaseinfo: str
|
|
91
91
|
title: str
|
|
92
|
-
uuid: str
|
|
93
|
-
notice: str
|
|
92
|
+
uuid: Optional[str] = None
|
|
93
|
+
notice: Optional[str] = None
|
|
94
94
|
source: Optional[str] = None
|
|
95
95
|
|
|
96
96
|
|
|
@@ -115,10 +115,10 @@ class Vuln(BaseModel):
|
|
|
115
115
|
check_content: Optional[str] = None
|
|
116
116
|
fix_text: str
|
|
117
117
|
check_content_ref: Optional[str] = None
|
|
118
|
-
weight: str
|
|
118
|
+
weight: Optional[str] = None
|
|
119
119
|
stigref: Optional[str] = None
|
|
120
120
|
targetkey: Optional[str] = None
|
|
121
|
-
stig_uuid: str
|
|
121
|
+
stig_uuid: Optional[str] = None
|
|
122
122
|
vuln_discuss: Optional[str] = None
|
|
123
123
|
ia_controls: Optional[str] = None
|
|
124
124
|
class_: Optional[str] = None
|
|
@@ -74,6 +74,23 @@ def sync_axonius(regscale_ssp_id: int, filter: str) -> None:
|
|
|
74
74
|
assets_axonius.run_sync(regscale_ssp_id=regscale_ssp_id, filter=filter.split(";") if filter else [])
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
@assets.command(name="sync_claroty_xdome")
|
|
78
|
+
@regscale_ssp_id()
|
|
79
|
+
@click.option(
|
|
80
|
+
"--filter",
|
|
81
|
+
help='STRING: Apply filters to the query. Can be a single filter "field[operator]value" or semicolon-separated filters "field1[op]value1;field2[op]value2"',
|
|
82
|
+
required=False,
|
|
83
|
+
type=str,
|
|
84
|
+
default=None,
|
|
85
|
+
)
|
|
86
|
+
def sync_claroty_xdome(regscale_ssp_id: int, filter: str) -> None:
|
|
87
|
+
"""Sync Assets from Claroty Xdome to RegScale."""
|
|
88
|
+
from regscale.models.integration_models.synqly_models.connectors import Assets
|
|
89
|
+
|
|
90
|
+
assets_claroty_xdome = Assets("claroty_xdome")
|
|
91
|
+
assets_claroty_xdome.run_sync(regscale_ssp_id=regscale_ssp_id, filter=filter.split(";") if filter else [])
|
|
92
|
+
|
|
93
|
+
|
|
77
94
|
@assets.command(name="sync_crowdstrike")
|
|
78
95
|
@regscale_ssp_id()
|
|
79
96
|
@click.option(
|
|
@@ -44,8 +44,9 @@ def build_query(provider, validate, list_fields):
|
|
|
44
44
|
@vulnerabilities.command(name="sync_crowdstrike")
|
|
45
45
|
@regscale_ssp_id()
|
|
46
46
|
@click.option(
|
|
47
|
-
"--
|
|
48
|
-
|
|
47
|
+
"--minimum_severity_filter",
|
|
48
|
+
"-s",
|
|
49
|
+
help="Minimum severity of the vulnerabilities to sync. (Options: critical, high, medium, low, info), e.g. providing high will sync all vulnerabilities with a severity of high and critical.",
|
|
49
50
|
required=False,
|
|
50
51
|
type=click.Choice(["critical", "high", "medium", "low", "info"]),
|
|
51
52
|
default=None,
|
|
@@ -78,7 +79,12 @@ def build_query(provider, validate, list_fields):
|
|
|
78
79
|
required=False,
|
|
79
80
|
)
|
|
80
81
|
def sync_crowdstrike(
|
|
81
|
-
regscale_ssp_id: int,
|
|
82
|
+
regscale_ssp_id: int,
|
|
83
|
+
minimum_severity_filter: str,
|
|
84
|
+
scan_date: datetime,
|
|
85
|
+
all_scans: bool,
|
|
86
|
+
asset_filter: str,
|
|
87
|
+
url: str,
|
|
82
88
|
) -> None:
|
|
83
89
|
"""Sync Vulnerabilities from Crowdstrike to RegScale."""
|
|
84
90
|
from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
|
|
@@ -86,7 +92,7 @@ def sync_crowdstrike(
|
|
|
86
92
|
vulnerabilities_crowdstrike = Vulnerabilities("crowdstrike")
|
|
87
93
|
vulnerabilities_crowdstrike.run_sync(
|
|
88
94
|
regscale_ssp_id=regscale_ssp_id,
|
|
89
|
-
|
|
95
|
+
minimum_severity_filter=minimum_severity_filter,
|
|
90
96
|
scan_date=scan_date,
|
|
91
97
|
all_scans=all_scans,
|
|
92
98
|
filter=asset_filter.split(";") if asset_filter else [],
|
|
@@ -97,8 +103,9 @@ def sync_crowdstrike(
|
|
|
97
103
|
@vulnerabilities.command(name="sync_nucleus")
|
|
98
104
|
@regscale_ssp_id()
|
|
99
105
|
@click.option(
|
|
100
|
-
"--
|
|
101
|
-
|
|
106
|
+
"--minimum_severity_filter",
|
|
107
|
+
"-s",
|
|
108
|
+
help="Minimum severity of the vulnerabilities to sync. (Options: critical, high, medium, low, info), e.g. providing high will sync all vulnerabilities with a severity of high and critical.",
|
|
102
109
|
required=False,
|
|
103
110
|
type=click.Choice(["critical", "high", "medium", "low", "info"]),
|
|
104
111
|
default=None,
|
|
@@ -121,7 +128,7 @@ def sync_crowdstrike(
|
|
|
121
128
|
default=None,
|
|
122
129
|
)
|
|
123
130
|
def sync_nucleus(
|
|
124
|
-
regscale_ssp_id: int,
|
|
131
|
+
regscale_ssp_id: int, minimum_severity_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str
|
|
125
132
|
) -> None:
|
|
126
133
|
"""Sync Vulnerabilities from Nucleus to RegScale."""
|
|
127
134
|
from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
|
|
@@ -129,7 +136,7 @@ def sync_nucleus(
|
|
|
129
136
|
vulnerabilities_nucleus = Vulnerabilities("nucleus")
|
|
130
137
|
vulnerabilities_nucleus.run_sync(
|
|
131
138
|
regscale_ssp_id=regscale_ssp_id,
|
|
132
|
-
|
|
139
|
+
minimum_severity_filter=minimum_severity_filter,
|
|
133
140
|
scan_date=scan_date,
|
|
134
141
|
all_scans=all_scans,
|
|
135
142
|
filter=asset_filter.split(";") if asset_filter else [],
|
|
@@ -139,8 +146,9 @@ def sync_nucleus(
|
|
|
139
146
|
@vulnerabilities.command(name="sync_qualys_cloud")
|
|
140
147
|
@regscale_ssp_id()
|
|
141
148
|
@click.option(
|
|
142
|
-
"--
|
|
143
|
-
|
|
149
|
+
"--minimum_severity_filter",
|
|
150
|
+
"-s",
|
|
151
|
+
help="Minimum severity of the vulnerabilities to sync. (Options: critical, high, medium, low, info), e.g. providing high will sync all vulnerabilities with a severity of high and critical.",
|
|
144
152
|
required=False,
|
|
145
153
|
type=click.Choice(["critical", "high", "medium", "low", "info"]),
|
|
146
154
|
default=None,
|
|
@@ -167,7 +175,7 @@ def sync_nucleus(
|
|
|
167
175
|
default=None,
|
|
168
176
|
)
|
|
169
177
|
def sync_qualys_cloud(
|
|
170
|
-
regscale_ssp_id: int,
|
|
178
|
+
regscale_ssp_id: int, minimum_severity_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str
|
|
171
179
|
) -> None:
|
|
172
180
|
"""Sync Vulnerabilities from Qualys Cloud to RegScale."""
|
|
173
181
|
from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
|
|
@@ -175,7 +183,7 @@ def sync_qualys_cloud(
|
|
|
175
183
|
vulnerabilities_qualys_cloud = Vulnerabilities("qualys_cloud")
|
|
176
184
|
vulnerabilities_qualys_cloud.run_sync(
|
|
177
185
|
regscale_ssp_id=regscale_ssp_id,
|
|
178
|
-
|
|
186
|
+
minimum_severity_filter=minimum_severity_filter,
|
|
179
187
|
scan_date=scan_date,
|
|
180
188
|
all_scans=all_scans,
|
|
181
189
|
filter=asset_filter.split(";") if asset_filter else [],
|
|
@@ -185,8 +193,9 @@ def sync_qualys_cloud(
|
|
|
185
193
|
@vulnerabilities.command(name="sync_rapid7_insight_cloud")
|
|
186
194
|
@regscale_ssp_id()
|
|
187
195
|
@click.option(
|
|
188
|
-
"--
|
|
189
|
-
|
|
196
|
+
"--minimum_severity_filter",
|
|
197
|
+
"-s",
|
|
198
|
+
help="Minimum severity of the vulnerabilities to sync. (Options: critical, high, medium, low, info), e.g. providing high will sync all vulnerabilities with a severity of high and critical.",
|
|
190
199
|
required=False,
|
|
191
200
|
type=click.Choice(["critical", "high", "medium", "low", "info"]),
|
|
192
201
|
default=None,
|
|
@@ -213,7 +222,7 @@ def sync_qualys_cloud(
|
|
|
213
222
|
default=None,
|
|
214
223
|
)
|
|
215
224
|
def sync_rapid7_insight_cloud(
|
|
216
|
-
regscale_ssp_id: int,
|
|
225
|
+
regscale_ssp_id: int, minimum_severity_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str
|
|
217
226
|
) -> None:
|
|
218
227
|
"""Sync Vulnerabilities from Rapid7 Insight Cloud to RegScale."""
|
|
219
228
|
from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
|
|
@@ -221,7 +230,7 @@ def sync_rapid7_insight_cloud(
|
|
|
221
230
|
vulnerabilities_rapid7_insight_cloud = Vulnerabilities("rapid7_insight_cloud")
|
|
222
231
|
vulnerabilities_rapid7_insight_cloud.run_sync(
|
|
223
232
|
regscale_ssp_id=regscale_ssp_id,
|
|
224
|
-
|
|
233
|
+
minimum_severity_filter=minimum_severity_filter,
|
|
225
234
|
scan_date=scan_date,
|
|
226
235
|
all_scans=all_scans,
|
|
227
236
|
filter=asset_filter.split(";") if asset_filter else [],
|
|
@@ -231,8 +240,9 @@ def sync_rapid7_insight_cloud(
|
|
|
231
240
|
@vulnerabilities.command(name="sync_servicenow_vr")
|
|
232
241
|
@regscale_ssp_id()
|
|
233
242
|
@click.option(
|
|
234
|
-
"--
|
|
235
|
-
|
|
243
|
+
"--minimum_severity_filter",
|
|
244
|
+
"-s",
|
|
245
|
+
help="Minimum severity of the vulnerabilities to sync. (Options: critical, high, medium, low, info), e.g. providing high will sync all vulnerabilities with a severity of high and critical.",
|
|
236
246
|
required=False,
|
|
237
247
|
type=click.Choice(["critical", "high", "medium", "low", "info"]),
|
|
238
248
|
default=None,
|
|
@@ -259,7 +269,7 @@ def sync_rapid7_insight_cloud(
|
|
|
259
269
|
default=None,
|
|
260
270
|
)
|
|
261
271
|
def sync_servicenow_vr(
|
|
262
|
-
regscale_ssp_id: int,
|
|
272
|
+
regscale_ssp_id: int, minimum_severity_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str
|
|
263
273
|
) -> None:
|
|
264
274
|
"""Sync Vulnerabilities from Servicenow Vr to RegScale."""
|
|
265
275
|
from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
|
|
@@ -267,7 +277,7 @@ def sync_servicenow_vr(
|
|
|
267
277
|
vulnerabilities_servicenow_vr = Vulnerabilities("servicenow_vr")
|
|
268
278
|
vulnerabilities_servicenow_vr.run_sync(
|
|
269
279
|
regscale_ssp_id=regscale_ssp_id,
|
|
270
|
-
|
|
280
|
+
minimum_severity_filter=minimum_severity_filter,
|
|
271
281
|
scan_date=scan_date,
|
|
272
282
|
all_scans=all_scans,
|
|
273
283
|
filter=asset_filter.split(";") if asset_filter else [],
|
|
@@ -277,8 +287,9 @@ def sync_servicenow_vr(
|
|
|
277
287
|
@vulnerabilities.command(name="sync_tanium_cloud")
|
|
278
288
|
@regscale_ssp_id()
|
|
279
289
|
@click.option(
|
|
280
|
-
"--
|
|
281
|
-
|
|
290
|
+
"--minimum_severity_filter",
|
|
291
|
+
"-s",
|
|
292
|
+
help="Minimum severity of the vulnerabilities to sync. (Options: critical, high, medium, low, info), e.g. providing high will sync all vulnerabilities with a severity of high and critical.",
|
|
282
293
|
required=False,
|
|
283
294
|
type=click.Choice(["critical", "high", "medium", "low", "info"]),
|
|
284
295
|
default=None,
|
|
@@ -305,7 +316,7 @@ def sync_servicenow_vr(
|
|
|
305
316
|
default=None,
|
|
306
317
|
)
|
|
307
318
|
def sync_tanium_cloud(
|
|
308
|
-
regscale_ssp_id: int,
|
|
319
|
+
regscale_ssp_id: int, minimum_severity_filter: str, scan_date: datetime, all_scans: bool, asset_filter: str
|
|
309
320
|
) -> None:
|
|
310
321
|
"""Sync Vulnerabilities from Tanium Cloud to RegScale."""
|
|
311
322
|
from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
|
|
@@ -313,7 +324,7 @@ def sync_tanium_cloud(
|
|
|
313
324
|
vulnerabilities_tanium_cloud = Vulnerabilities("tanium_cloud")
|
|
314
325
|
vulnerabilities_tanium_cloud.run_sync(
|
|
315
326
|
regscale_ssp_id=regscale_ssp_id,
|
|
316
|
-
|
|
327
|
+
minimum_severity_filter=minimum_severity_filter,
|
|
317
328
|
scan_date=scan_date,
|
|
318
329
|
all_scans=all_scans,
|
|
319
330
|
filter=asset_filter.split(";") if asset_filter else [],
|
|
@@ -323,8 +334,9 @@ def sync_tanium_cloud(
|
|
|
323
334
|
@vulnerabilities.command(name="sync_tenable_cloud")
|
|
324
335
|
@regscale_ssp_id()
|
|
325
336
|
@click.option(
|
|
326
|
-
"--
|
|
327
|
-
|
|
337
|
+
"--minimum_severity_filter",
|
|
338
|
+
"-s",
|
|
339
|
+
help="Minimum severity of the vulnerabilities to sync. (Options: critical, high, medium, low, info), e.g. providing high will sync all vulnerabilities with a severity of high and critical.",
|
|
328
340
|
required=False,
|
|
329
341
|
type=click.Choice(["critical", "high", "medium", "low", "info"]),
|
|
330
342
|
default=None,
|
|
@@ -357,7 +369,12 @@ def sync_tanium_cloud(
|
|
|
357
369
|
required=False,
|
|
358
370
|
)
|
|
359
371
|
def sync_tenable_cloud(
|
|
360
|
-
regscale_ssp_id: int,
|
|
372
|
+
regscale_ssp_id: int,
|
|
373
|
+
minimum_severity_filter: str,
|
|
374
|
+
scan_date: datetime,
|
|
375
|
+
all_scans: bool,
|
|
376
|
+
asset_filter: str,
|
|
377
|
+
url: str,
|
|
361
378
|
) -> None:
|
|
362
379
|
"""Sync Vulnerabilities from Tenable Cloud to RegScale."""
|
|
363
380
|
from regscale.models.integration_models.synqly_models.connectors import Vulnerabilities
|
|
@@ -365,7 +382,7 @@ def sync_tenable_cloud(
|
|
|
365
382
|
vulnerabilities_tenable_cloud = Vulnerabilities("tenable_cloud")
|
|
366
383
|
vulnerabilities_tenable_cloud.run_sync(
|
|
367
384
|
regscale_ssp_id=regscale_ssp_id,
|
|
368
|
-
|
|
385
|
+
minimum_severity_filter=minimum_severity_filter,
|
|
369
386
|
scan_date=scan_date,
|
|
370
387
|
all_scans=all_scans,
|
|
371
388
|
filter=asset_filter.split(";") if asset_filter else [],
|