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
regscale/_version.py
CHANGED
regscale/airflow/hierarchy.py
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
-
from regscale.regscale import cli
|
|
6
|
-
from regscale.models.click_models import ClickCommand
|
|
7
5
|
from regscale.airflow.click_mixins import AirflowClickGroup
|
|
6
|
+
from regscale.models.click_models import ClickCommand
|
|
7
|
+
from regscale.regscale import cli
|
|
8
8
|
|
|
9
9
|
AIRFLOW_CLICK_GROUP = AirflowClickGroup.from_group(cli, prefix="regscale")
|
|
10
10
|
AIRFLOW_CLICK_OPERATORS = AIRFLOW_CLICK_GROUP.flatten_operator()
|
regscale/core/app/application.py
CHANGED
|
@@ -269,7 +269,7 @@ class Application(metaclass=Singleton):
|
|
|
269
269
|
"token": DEFAULT_POPULATED,
|
|
270
270
|
"userId": "enter RegScale user id here",
|
|
271
271
|
"useMilestones": False,
|
|
272
|
-
"preventAutoClose":
|
|
272
|
+
"preventAutoClose": False,
|
|
273
273
|
"otx": "enter AlienVault API key here",
|
|
274
274
|
"wizAccessToken": DEFAULT_POPULATED,
|
|
275
275
|
"wizAuthUrl": "https://auth.wiz.io/oauth/token",
|
|
@@ -664,7 +664,8 @@ class Application(metaclass=Singleton):
|
|
|
664
664
|
|
|
665
665
|
def save_config(self, conf: dict) -> None:
|
|
666
666
|
"""
|
|
667
|
-
Save Configuration to init.yaml
|
|
667
|
+
Save Configuration to init.yaml using atomic file operations to prevent corruption
|
|
668
|
+
during parallel writes.
|
|
668
669
|
|
|
669
670
|
:param dict conf: Application configuration
|
|
670
671
|
:rtype: None
|
|
@@ -681,8 +682,22 @@ class Application(metaclass=Singleton):
|
|
|
681
682
|
try:
|
|
682
683
|
self.logger.debug(f"Saving config to {self.config_file}.")
|
|
683
684
|
with self._config_lock:
|
|
684
|
-
|
|
685
|
-
|
|
685
|
+
# Use atomic file operations: write to temp file, then rename
|
|
686
|
+
# This prevents corruption when multiple processes write simultaneously
|
|
687
|
+
import tempfile
|
|
688
|
+
|
|
689
|
+
config_dir = os.path.dirname(self.config_file) or "."
|
|
690
|
+
temp_fd, temp_path = tempfile.mkstemp(dir=config_dir, prefix=".tmp_", suffix=".yaml", text=True)
|
|
691
|
+
try:
|
|
692
|
+
with os.fdopen(temp_fd, "w", encoding="utf-8") as temp_file:
|
|
693
|
+
yaml.dump(conf, temp_file)
|
|
694
|
+
# Atomic rename - this is atomic on POSIX systems
|
|
695
|
+
os.replace(temp_path, self.config_file)
|
|
696
|
+
except Exception:
|
|
697
|
+
# Clean up temp file if something goes wrong
|
|
698
|
+
with contextlib.suppress(OSError):
|
|
699
|
+
os.unlink(temp_path)
|
|
700
|
+
raise
|
|
686
701
|
except OSError:
|
|
687
702
|
self.logger.error(f"Could not save config to {self.config_file}.")
|
|
688
703
|
|
|
@@ -24,7 +24,8 @@ from regscale.core.app.api import Api
|
|
|
24
24
|
from regscale.core.app.application import Application
|
|
25
25
|
from regscale.core.app.utils.app_utils import check_file_path, create_progress_object, error_and_exit
|
|
26
26
|
from regscale.models.app_models.click import regscale_ssp_id
|
|
27
|
-
from regscale.models.regscale_models import Assessment, File, Project, SecurityPlan
|
|
27
|
+
from regscale.models.regscale_models import Assessment, File, Project, SecurityPlan, Evidence, Component
|
|
28
|
+
from regscale.models.regscale_models.control_implementation import ControlImplementation
|
|
28
29
|
|
|
29
30
|
logger = getLogger("regscale")
|
|
30
31
|
|
|
@@ -198,7 +199,7 @@ def package_builder(ssp_id: int, path: Path):
|
|
|
198
199
|
app = Application()
|
|
199
200
|
api = Api()
|
|
200
201
|
with create_progress_object() as progress:
|
|
201
|
-
task = progress.add_task("[white]Building and zipping evidence folder for audit...", total=
|
|
202
|
+
task = progress.add_task("[white]Building and zipping evidence folder for audit...", total=8)
|
|
202
203
|
try:
|
|
203
204
|
# Obtaining MEGA Api for given Organizer Record.
|
|
204
205
|
ssp = SecurityPlan.fetch_mega_api_data(ssp_id)
|
|
@@ -221,6 +222,16 @@ def package_builder(ssp_id: int, path: Path):
|
|
|
221
222
|
|
|
222
223
|
progress.update(task, advance=1)
|
|
223
224
|
|
|
225
|
+
# Process evidence lockers at SSP level
|
|
226
|
+
process_ssp_evidence_lockers(
|
|
227
|
+
ssp_id=ssp_id,
|
|
228
|
+
path=path,
|
|
229
|
+
module_folder=module_folder,
|
|
230
|
+
api=api,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
progress.update(task, advance=1)
|
|
234
|
+
|
|
224
235
|
# Checking MEGA Api for Attachments at Control level
|
|
225
236
|
process_control_attachments(
|
|
226
237
|
ssp=ssp,
|
|
@@ -231,6 +242,19 @@ def package_builder(ssp_id: int, path: Path):
|
|
|
231
242
|
api=api,
|
|
232
243
|
task=task,
|
|
233
244
|
)
|
|
245
|
+
|
|
246
|
+
progress.update(task, advance=1)
|
|
247
|
+
|
|
248
|
+
# Process components and their evidence
|
|
249
|
+
process_components_evidence(
|
|
250
|
+
ssp_id=ssp_id,
|
|
251
|
+
path=path,
|
|
252
|
+
module_folder=module_folder,
|
|
253
|
+
api=api,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
progress.update(task, advance=1)
|
|
257
|
+
|
|
234
258
|
# Creating zip file and removing temporary Evidence Folder
|
|
235
259
|
new_path = Path("./evidence.zip")
|
|
236
260
|
zip_folder(path, new_path)
|
|
@@ -349,6 +373,9 @@ def process_control_attachments(
|
|
|
349
373
|
# Adding any Attachments at Control level to corresponding folder
|
|
350
374
|
_download_control_attachments(control_attachments, api, path, module_folder_name)
|
|
351
375
|
|
|
376
|
+
# Process evidence lockers for controls
|
|
377
|
+
_process_control_evidence_lockers(control_attachments, api, path, module_folder_name)
|
|
378
|
+
|
|
352
379
|
progress.update(task, advance=1)
|
|
353
380
|
|
|
354
381
|
else:
|
|
@@ -388,6 +415,396 @@ def _download_control_attachments(
|
|
|
388
415
|
json.dump(f, file_drop, indent=4, separators=(", ", ": "))
|
|
389
416
|
|
|
390
417
|
|
|
418
|
+
def _get_control_folder_name(control_attachments: list[dict], control_id: int) -> str | None:
|
|
419
|
+
"""
|
|
420
|
+
Get the control folder name for a given control ID
|
|
421
|
+
|
|
422
|
+
:param list[dict] control_attachments: List of control attachments
|
|
423
|
+
:param int control_id: Control ID to find folder name for
|
|
424
|
+
:return: Control folder name or None
|
|
425
|
+
:rtype: str | None
|
|
426
|
+
"""
|
|
427
|
+
for f in control_attachments:
|
|
428
|
+
if f["parentId"] == control_id:
|
|
429
|
+
return f["controlId"]
|
|
430
|
+
return None
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _download_control_evidence_items(
|
|
434
|
+
evidence_items: list[dict], control_folder_name: str, path: Path, module_folder_name: str, api: Api
|
|
435
|
+
) -> None:
|
|
436
|
+
"""
|
|
437
|
+
Download evidence items for a control
|
|
438
|
+
|
|
439
|
+
:param list[dict] evidence_items: List of evidence items
|
|
440
|
+
:param str control_folder_name: Name of the control folder
|
|
441
|
+
:param Path path: Base path for downloads
|
|
442
|
+
:param str module_folder_name: Module folder name
|
|
443
|
+
:param Api api: API object
|
|
444
|
+
:rtype: None
|
|
445
|
+
"""
|
|
446
|
+
logger.info(f"Found {len(evidence_items)} evidence items for control {control_folder_name}")
|
|
447
|
+
|
|
448
|
+
for evidence_item in evidence_items:
|
|
449
|
+
file_name = evidence_item.get("trustedDisplayName", f"evidence_{evidence_item.get('id', 'unknown')}")
|
|
450
|
+
output_path = f"{path}/{module_folder_name}/{control_folder_name}/{file_name}"
|
|
451
|
+
|
|
452
|
+
if download_evidence_file(api, evidence_item, output_path):
|
|
453
|
+
logger.info(f"Downloaded evidence file: {file_name}")
|
|
454
|
+
else:
|
|
455
|
+
logger.warning(f"Failed to download evidence file: {file_name}")
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _process_control_evidence_lockers(
|
|
459
|
+
control_attachments: list[dict], api: Api, path: Path, module_folder_name: str
|
|
460
|
+
) -> None:
|
|
461
|
+
"""
|
|
462
|
+
Process evidence lockers for controls
|
|
463
|
+
|
|
464
|
+
:param list[dict] control_attachments: List of control attachments
|
|
465
|
+
:param Api api: RegScale CLI API object
|
|
466
|
+
:param Path path: directory for file location
|
|
467
|
+
:param str module_folder_name: name of the module folder
|
|
468
|
+
:rtype: None
|
|
469
|
+
"""
|
|
470
|
+
# Get unique control IDs
|
|
471
|
+
control_ids = list({f["parentId"] for f in control_attachments})
|
|
472
|
+
|
|
473
|
+
for control_id in control_ids:
|
|
474
|
+
try:
|
|
475
|
+
# Get evidence from evidence lockers for this control
|
|
476
|
+
evidence_items = get_evidence_by_control(api, control_id)
|
|
477
|
+
|
|
478
|
+
if evidence_items:
|
|
479
|
+
# Find the control ID for folder naming
|
|
480
|
+
control_folder_name = _get_control_folder_name(control_attachments, control_id)
|
|
481
|
+
|
|
482
|
+
if control_folder_name:
|
|
483
|
+
_download_control_evidence_items(evidence_items, control_folder_name, path, module_folder_name, api)
|
|
484
|
+
except Exception as e:
|
|
485
|
+
logger.warning(f"Failed to process evidence lockers for control {control_id}: {e}")
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def get_evidence_by_control(api: Api, control_id: int) -> list[dict]:
|
|
489
|
+
"""
|
|
490
|
+
Get evidence for a specific control
|
|
491
|
+
|
|
492
|
+
:param Api api: RegScale CLI API object (kept for backward compatibility)
|
|
493
|
+
:param int control_id: Control ID
|
|
494
|
+
:return: List of evidence items
|
|
495
|
+
:rtype: list[dict]
|
|
496
|
+
"""
|
|
497
|
+
# Suppress unused parameter warning for backward compatibility
|
|
498
|
+
_ = api
|
|
499
|
+
|
|
500
|
+
try:
|
|
501
|
+
# Use Evidence model method instead of direct API call
|
|
502
|
+
evidence_items = Evidence.get_all_by_parent(parent_id=control_id, parent_module="controls")
|
|
503
|
+
# Convert to dict format for compatibility
|
|
504
|
+
return [evidence.dict() for evidence in evidence_items]
|
|
505
|
+
except Exception as e:
|
|
506
|
+
logger.warning(f"Failed to get evidence for control {control_id}: {e}")
|
|
507
|
+
return []
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def get_evidence_by_security_plan(api: Api, ssp_id: int) -> list[dict]:
|
|
511
|
+
"""
|
|
512
|
+
Get evidence for a specific security plan
|
|
513
|
+
|
|
514
|
+
:param Api api: RegScale CLI API object (kept for backward compatibility)
|
|
515
|
+
:param int ssp_id: Security Plan ID
|
|
516
|
+
:return: List of evidence items
|
|
517
|
+
:rtype: list[dict]
|
|
518
|
+
"""
|
|
519
|
+
# Suppress unused parameter warning for backward compatibility
|
|
520
|
+
_ = api
|
|
521
|
+
|
|
522
|
+
try:
|
|
523
|
+
# Use Evidence model method instead of direct API call
|
|
524
|
+
evidence_items = Evidence.get_all_by_parent(parent_id=ssp_id, parent_module="securityplans")
|
|
525
|
+
# Convert to dict format for compatibility
|
|
526
|
+
return [evidence.dict() for evidence in evidence_items]
|
|
527
|
+
except Exception as e:
|
|
528
|
+
logger.warning(f"Failed to get evidence for security plan {ssp_id}: {e}")
|
|
529
|
+
return []
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def get_components_by_ssp(api: Api, ssp_id: int) -> list[dict]:
|
|
533
|
+
"""
|
|
534
|
+
Get components for a specific security plan
|
|
535
|
+
|
|
536
|
+
:param Api api: RegScale CLI API object (kept for backward compatibility)
|
|
537
|
+
:param int ssp_id: Security Plan ID
|
|
538
|
+
:return: List of active components
|
|
539
|
+
:rtype: list[dict]
|
|
540
|
+
"""
|
|
541
|
+
# Suppress unused parameter warning for backward compatibility
|
|
542
|
+
_ = api
|
|
543
|
+
|
|
544
|
+
try:
|
|
545
|
+
# Use Component model method instead of direct API call
|
|
546
|
+
components = Component.get_all_by_parent(parent_id=ssp_id, parent_module="securityplans")
|
|
547
|
+
# Filter for active components only and convert to dict format
|
|
548
|
+
return [comp.dict() for comp in components if comp.status == "Active"]
|
|
549
|
+
except Exception as e:
|
|
550
|
+
logger.warning(f"Failed to get components for security plan {ssp_id}: {e}")
|
|
551
|
+
return []
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def get_controls_by_parent(api: Api, parent_id: int, parent_module: str) -> list[dict]:
|
|
555
|
+
"""
|
|
556
|
+
Get controls for a specific parent (SSP or Component)
|
|
557
|
+
|
|
558
|
+
:param Api api: RegScale CLI API object (kept for backward compatibility)
|
|
559
|
+
:param int parent_id: Parent ID
|
|
560
|
+
:param str parent_module: Parent module (securityplans or components)
|
|
561
|
+
:return: List of controls
|
|
562
|
+
:rtype: list[dict]
|
|
563
|
+
"""
|
|
564
|
+
# Suppress unused parameter warning for backward compatibility
|
|
565
|
+
_ = api
|
|
566
|
+
|
|
567
|
+
try:
|
|
568
|
+
# Use ControlImplementation model method instead of direct API call
|
|
569
|
+
controls = ControlImplementation.get_all_by_parent(parent_id=parent_id, parent_module=parent_module)
|
|
570
|
+
# Convert to dict format for compatibility
|
|
571
|
+
return [control.dict() for control in controls]
|
|
572
|
+
except Exception as e:
|
|
573
|
+
logger.warning(f"Failed to get controls for parent {parent_id} in module {parent_module}: {e}")
|
|
574
|
+
return []
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def download_evidence_file(api: Api, evidence_item: dict, output_path: str) -> bool:
|
|
578
|
+
"""
|
|
579
|
+
Download an evidence file
|
|
580
|
+
|
|
581
|
+
:param Api api: RegScale CLI API object
|
|
582
|
+
:param dict evidence_item: Evidence item data
|
|
583
|
+
:param str output_path: Path to save the file
|
|
584
|
+
:return: True if successful, False otherwise
|
|
585
|
+
:rtype: bool
|
|
586
|
+
"""
|
|
587
|
+
try:
|
|
588
|
+
file_data = File.download_file_from_regscale_to_memory(
|
|
589
|
+
api=api,
|
|
590
|
+
record_id=evidence_item["parentId"],
|
|
591
|
+
module=evidence_item["parentModule"],
|
|
592
|
+
stored_name=evidence_item["trustedStorageName"],
|
|
593
|
+
file_hash=evidence_item.get("fileHash") or evidence_item.get("shaHash"),
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
if file_data is None:
|
|
597
|
+
logger.warning(f"No data received for evidence file {evidence_item.get('trustedDisplayName', 'unknown')}")
|
|
598
|
+
return False
|
|
599
|
+
|
|
600
|
+
with open(output_path, "wb") as f:
|
|
601
|
+
f.write(file_data)
|
|
602
|
+
return True
|
|
603
|
+
except Exception as e:
|
|
604
|
+
logger.warning(f"Failed to download evidence file {evidence_item.get('trustedDisplayName', 'unknown')}: {e}")
|
|
605
|
+
return False
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def process_ssp_evidence_lockers(ssp_id: int, path: Path, module_folder: Path, api: Api) -> None:
|
|
609
|
+
"""
|
|
610
|
+
Process evidence lockers at SSP level
|
|
611
|
+
|
|
612
|
+
:param int ssp_id: Security Plan ID
|
|
613
|
+
:param Path path: directory for file location
|
|
614
|
+
:param str module_folder_name: name of the module folder
|
|
615
|
+
:param Path module_folder: path to module folder
|
|
616
|
+
:param Api api: RegScale CLI API object
|
|
617
|
+
:rtype: None
|
|
618
|
+
"""
|
|
619
|
+
try:
|
|
620
|
+
# Get evidence from evidence lockers for the SSP
|
|
621
|
+
evidence_items = get_evidence_by_security_plan(api, ssp_id)
|
|
622
|
+
|
|
623
|
+
if evidence_items:
|
|
624
|
+
logger.info(f"Found {len(evidence_items)} evidence items from evidence lockers for SSP {ssp_id}")
|
|
625
|
+
|
|
626
|
+
for evidence_item in evidence_items:
|
|
627
|
+
file_name = evidence_item.get("trustedDisplayName", f"evidence_{evidence_item.get('id', 'unknown')}")
|
|
628
|
+
output_path = module_folder / file_name
|
|
629
|
+
|
|
630
|
+
if download_evidence_file(api, evidence_item, str(output_path)):
|
|
631
|
+
logger.info(f"Downloaded evidence file: {file_name}")
|
|
632
|
+
else:
|
|
633
|
+
logger.warning(f"Failed to download evidence file: {file_name}")
|
|
634
|
+
else:
|
|
635
|
+
logger.info("No evidence found in evidence lockers for SSP")
|
|
636
|
+
|
|
637
|
+
except Exception as e:
|
|
638
|
+
logger.warning(f"Error processing SSP evidence lockers: {e}")
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _download_files_for_parent(
|
|
642
|
+
parent_id: int, parent_module: str, output_folder: Path, api: Api, module_name: str = None
|
|
643
|
+
) -> None:
|
|
644
|
+
"""
|
|
645
|
+
Generalized function to download files for any parent module
|
|
646
|
+
|
|
647
|
+
:param int parent_id: Parent ID (component, control, etc.)
|
|
648
|
+
:param str parent_module: Parent module name (components, controls, etc.)
|
|
649
|
+
:param Path output_folder: Path to output folder
|
|
650
|
+
:param Api api: API object
|
|
651
|
+
:param str module_name: Human-readable module name for logging (optional)
|
|
652
|
+
:rtype: None
|
|
653
|
+
"""
|
|
654
|
+
if module_name is None:
|
|
655
|
+
module_name = parent_module
|
|
656
|
+
|
|
657
|
+
try:
|
|
658
|
+
# Use File model method instead of direct API call
|
|
659
|
+
files_data = File.get_files_for_parent_from_regscale(api=api, parent_id=parent_id, parent_module=parent_module)
|
|
660
|
+
|
|
661
|
+
for file_item in files_data:
|
|
662
|
+
file_name = file_item.trustedDisplayName or f"file_{file_item.id}"
|
|
663
|
+
output_path = output_folder / file_name
|
|
664
|
+
|
|
665
|
+
try:
|
|
666
|
+
file_data = File.download_file_from_regscale_to_memory(
|
|
667
|
+
api=api,
|
|
668
|
+
record_id=file_item.id,
|
|
669
|
+
module=parent_module,
|
|
670
|
+
stored_name=file_item.trustedStorageName,
|
|
671
|
+
file_hash=file_item.fileHash or file_item.shaHash,
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
if file_data is None:
|
|
675
|
+
logger.warning(f"No data received for {module_name} file {file_name}")
|
|
676
|
+
continue
|
|
677
|
+
|
|
678
|
+
with open(output_path, "wb") as f:
|
|
679
|
+
f.write(file_data)
|
|
680
|
+
logger.info(f"Downloaded {module_name} file: {file_name}")
|
|
681
|
+
except Exception as e:
|
|
682
|
+
logger.warning(f"Failed to download {module_name} file {file_name}: {e}")
|
|
683
|
+
except Exception as e:
|
|
684
|
+
logger.warning(f"Failed to get {module_name} files for {parent_module} {parent_id}: {e}")
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def _download_component_files(component_id: int, component_folder: Path, api: Api) -> None:
|
|
688
|
+
"""
|
|
689
|
+
Download files directly attached to a component
|
|
690
|
+
|
|
691
|
+
:param int component_id: Component ID
|
|
692
|
+
:param Path component_folder: Path to component folder
|
|
693
|
+
:param Api api: API object
|
|
694
|
+
:rtype: None
|
|
695
|
+
"""
|
|
696
|
+
_download_files_for_parent(
|
|
697
|
+
parent_id=component_id,
|
|
698
|
+
parent_module="components",
|
|
699
|
+
output_folder=component_folder,
|
|
700
|
+
api=api,
|
|
701
|
+
module_name="component",
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _download_control_files(control_id: int, control_folder: Path, api: Api) -> None:
|
|
706
|
+
"""
|
|
707
|
+
Download files for a control
|
|
708
|
+
|
|
709
|
+
:param int control_id: Control ID
|
|
710
|
+
:param Path control_folder: Path to control folder
|
|
711
|
+
:param Api api: API object
|
|
712
|
+
:rtype: None
|
|
713
|
+
"""
|
|
714
|
+
_download_files_for_parent(
|
|
715
|
+
parent_id=control_id, parent_module="controls", output_folder=control_folder, api=api, module_name="control"
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def _download_control_evidence(control_id: int, control_folder: Path, api: Api) -> None:
|
|
720
|
+
"""
|
|
721
|
+
Download evidence from evidence lockers for a control
|
|
722
|
+
|
|
723
|
+
:param int control_id: Control ID
|
|
724
|
+
:param Path control_folder: Path to control folder
|
|
725
|
+
:param Api api: API object
|
|
726
|
+
:rtype: None
|
|
727
|
+
"""
|
|
728
|
+
evidence_items = get_evidence_by_control(api, control_id)
|
|
729
|
+
|
|
730
|
+
if evidence_items:
|
|
731
|
+
logger.info(f"Found {len(evidence_items)} evidence items for control {control_folder.name}")
|
|
732
|
+
|
|
733
|
+
for evidence_item in evidence_items:
|
|
734
|
+
file_name = evidence_item.get("trustedDisplayName", f"evidence_{evidence_item.get('id', 'unknown')}")
|
|
735
|
+
output_path = control_folder / file_name
|
|
736
|
+
|
|
737
|
+
if download_evidence_file(api, evidence_item, str(output_path)):
|
|
738
|
+
logger.info(f"Downloaded evidence file: {file_name}")
|
|
739
|
+
else:
|
|
740
|
+
logger.warning(f"Failed to download evidence file: {file_name}")
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def _process_component_controls(component_id: int, component_folder: Path, api: Api) -> None:
|
|
744
|
+
"""
|
|
745
|
+
Process controls for a component
|
|
746
|
+
|
|
747
|
+
:param int component_id: Component ID
|
|
748
|
+
:param Path component_folder: Path to component folder
|
|
749
|
+
:param Api api: API object
|
|
750
|
+
:rtype: None
|
|
751
|
+
"""
|
|
752
|
+
controls = get_controls_by_parent(api, component_id, "components")
|
|
753
|
+
|
|
754
|
+
if controls:
|
|
755
|
+
logger.info(f"Found {len(controls)} controls for component {component_folder.name}")
|
|
756
|
+
|
|
757
|
+
for control in controls:
|
|
758
|
+
control_id = control.get("id")
|
|
759
|
+
control_name = control.get("controlId", f"Control_{control_id}")
|
|
760
|
+
|
|
761
|
+
# Create control folder within component folder
|
|
762
|
+
control_folder = component_folder / control_name
|
|
763
|
+
os.makedirs(control_folder, exist_ok=True)
|
|
764
|
+
|
|
765
|
+
# Download control files and evidence
|
|
766
|
+
_download_control_files(control_id, control_folder, api)
|
|
767
|
+
_download_control_evidence(control_id, control_folder, api)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def process_components_evidence(ssp_id: int, path: Path, module_folder: Path, api: Api) -> None:
|
|
771
|
+
"""
|
|
772
|
+
Process components and their evidence
|
|
773
|
+
|
|
774
|
+
:param int ssp_id: Security Plan ID
|
|
775
|
+
:param Path path: directory for file location
|
|
776
|
+
:param Path module_folder: path to module folder
|
|
777
|
+
:param Api api: RegScale CLI API object
|
|
778
|
+
:rtype: None
|
|
779
|
+
"""
|
|
780
|
+
try:
|
|
781
|
+
# Get components for the SSP
|
|
782
|
+
components = get_components_by_ssp(api, ssp_id)
|
|
783
|
+
|
|
784
|
+
if not components:
|
|
785
|
+
logger.info("No active components found for SSP")
|
|
786
|
+
return
|
|
787
|
+
|
|
788
|
+
logger.info(f"Found {len(components)} active components for SSP {ssp_id}")
|
|
789
|
+
|
|
790
|
+
for component in components:
|
|
791
|
+
component_id = component.get("id")
|
|
792
|
+
component_title = component.get("title", f"Component_{component_id}")
|
|
793
|
+
|
|
794
|
+
# Create component folder
|
|
795
|
+
component_folder = module_folder / component_title
|
|
796
|
+
os.makedirs(component_folder, exist_ok=True)
|
|
797
|
+
|
|
798
|
+
# Download component files
|
|
799
|
+
_download_component_files(component_id, component_folder, api)
|
|
800
|
+
|
|
801
|
+
# Process component controls
|
|
802
|
+
_process_component_controls(component_id, component_folder, api)
|
|
803
|
+
|
|
804
|
+
except Exception as e:
|
|
805
|
+
logger.warning(f"Error processing components evidence: {e}")
|
|
806
|
+
|
|
807
|
+
|
|
391
808
|
def remove_directory(directory_path: Path) -> None:
|
|
392
809
|
"""
|
|
393
810
|
This function removes a given directory even if files stored there
|
|
@@ -136,7 +136,6 @@ def login(
|
|
|
136
136
|
logger.info("New RegScale Token has been updated and saved in init.yaml")
|
|
137
137
|
# Truncate token for logging purposes
|
|
138
138
|
logger.debug("Token: %s", regscale_auth.token[:20])
|
|
139
|
-
config["domain"] = host
|
|
140
139
|
app.save_config(config)
|
|
141
140
|
return regscale_auth.token
|
|
142
141
|
|
|
@@ -87,5 +87,5 @@ def objective_to_control_dot(input_string: str) -> str:
|
|
|
87
87
|
if match:
|
|
88
88
|
return match.group(1)
|
|
89
89
|
else:
|
|
90
|
-
logger.
|
|
90
|
+
logger.debug(f"Failed to convert objective to control: {input_string}")
|
|
91
91
|
return input_string
|
regscale/dev/code_gen.py
CHANGED
|
@@ -170,29 +170,27 @@ def _build_op_kwargs_and_docstring(
|
|
|
170
170
|
capabilities=capabilities,
|
|
171
171
|
)
|
|
172
172
|
|
|
173
|
-
if ConnectorType.Vulnerabilities.lower() in integration
|
|
174
|
-
|
|
173
|
+
if ConnectorType.Vulnerabilities.lower() in integration:
|
|
174
|
+
vuln_params = {}
|
|
175
|
+
if param_type == "optional_params":
|
|
175
176
|
vuln_params: dict[str, Param] = {
|
|
176
|
-
"vuln_filter": Param(
|
|
177
|
-
name="vuln_filter",
|
|
178
|
-
type="choice",
|
|
179
|
-
description="Filter the vulnerabilities for the selected severity. (Options: critical, high, medium, low, info)",
|
|
180
|
-
default=None,
|
|
181
|
-
),
|
|
182
177
|
"scan_date": Param(
|
|
183
178
|
name="scan_date",
|
|
184
179
|
type="string",
|
|
185
180
|
description="The date of the scan to sync vulnerabilities into RegScale.",
|
|
186
181
|
default=None,
|
|
187
182
|
),
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
183
|
+
}
|
|
184
|
+
elif param_type == "expected_params":
|
|
185
|
+
vuln_params: dict[str, Param] = {
|
|
186
|
+
"minimum_severity_filter": Param(
|
|
187
|
+
name="minimum_severity_filter",
|
|
188
|
+
type="choice",
|
|
189
|
+
description="Minimum severity of the vulnerabilities to sync. (Options: critical, high, medium, low, info, all), e.g. providing 'high' will sync all vulnerabilities with a severity of high and critical.",
|
|
190
|
+
default=None,
|
|
193
191
|
),
|
|
194
192
|
}
|
|
195
|
-
|
|
193
|
+
config[param_type] = {**config[param_type], **vuln_params}
|
|
196
194
|
|
|
197
195
|
# Add filter parameter for Assets and Vulnerabilities connectors
|
|
198
196
|
if (
|
|
@@ -214,7 +212,7 @@ def _build_op_kwargs_and_docstring(
|
|
|
214
212
|
doc_string += f"{proper_type}:\n"
|
|
215
213
|
for param in config[param_type]:
|
|
216
214
|
op_kwargs, doc_string = _build_other_params(
|
|
217
|
-
|
|
215
|
+
param_obj=config[param_type][param],
|
|
218
216
|
param=param,
|
|
219
217
|
param_type=param_type,
|
|
220
218
|
proper_type=proper_type,
|
|
@@ -258,11 +256,13 @@ def _build_expected_params(
|
|
|
258
256
|
|
|
259
257
|
|
|
260
258
|
def _build_other_params(
|
|
261
|
-
|
|
259
|
+
param_obj: Param, param: str, param_type: str, proper_type: str, integration: str, op_kwargs: str, doc_string: str
|
|
262
260
|
) -> tuple[str, str]:
|
|
263
261
|
"""
|
|
264
262
|
Build the other params for the DAG by adding them to the op_kwargs and docstring
|
|
265
263
|
|
|
264
|
+
:param Param param_obj: The parameter object
|
|
265
|
+
:param str param: The name of the parameter
|
|
266
266
|
:param str param_type: The type of parameter to build
|
|
267
267
|
:param str integration: The name of the integration, typically connector_integration
|
|
268
268
|
:param str op_kwargs: The op_kwargs to add to the DAG
|
|
@@ -270,7 +270,6 @@ def _build_other_params(
|
|
|
270
270
|
:return: The op_kwargs and docstring
|
|
271
271
|
:rtype: tuple[str, str]
|
|
272
272
|
"""
|
|
273
|
-
param_obj: Param = config[param_type][param]
|
|
274
273
|
param_name = f"{integration.lower()}_{param}" if param_type == "required_secrets" else param
|
|
275
274
|
jinja_key = f"'{param_name}'"
|
|
276
275
|
if default := param_obj.default:
|
|
@@ -447,14 +446,19 @@ def _build_all_params(
|
|
|
447
446
|
:rtype: tuple[list[str], list[str], list[str]]
|
|
448
447
|
"""
|
|
449
448
|
if connector == ConnectorType.Vulnerabilities:
|
|
450
|
-
vuln_filter_option = "@click.option(\n '--
|
|
449
|
+
vuln_filter_option = "@click.option(\n '--minimum_severity_filter','-s',\n 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.',\n required=False,\n type=click.Choice(['critical', 'high', 'medium', 'low', 'info']),\n default=None)\n"
|
|
451
450
|
scan_date_option = f"@click.option(\n '--scan_date',\n help='The date of the scan to sync vulnerabilities from {integration_name}',\n required=False,\n type=click.DateTime(formats=['%Y-%m-%d']),\n default=None)\n"
|
|
452
451
|
all_vulns_flag = f"@click.option(\n '--all_scans',\n help='Whether to sync all vulnerabilities from {integration_name}',\n required=False,\n is_flag=True,\n default=False)\n"
|
|
453
452
|
click_options = ["@regscale_ssp_id()", vuln_filter_option, scan_date_option, all_vulns_flag]
|
|
454
|
-
function_params = [
|
|
453
|
+
function_params = [
|
|
454
|
+
"regscale_ssp_id: int",
|
|
455
|
+
"minimum_severity_filter: str",
|
|
456
|
+
"scan_date: datetime",
|
|
457
|
+
"all_scans: bool",
|
|
458
|
+
]
|
|
455
459
|
function_kwargs = [
|
|
456
460
|
"regscale_ssp_id=regscale_ssp_id",
|
|
457
|
-
"
|
|
461
|
+
"minimum_severity_filter=minimum_severity_filter",
|
|
458
462
|
"scan_date=scan_date",
|
|
459
463
|
"all_scans=all_scans",
|
|
460
464
|
]
|