regscale-cli 6.27.3.0__py3-none-any.whl → 6.28.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/core/app/utils/app_utils.py +11 -2
- regscale/dev/cli.py +26 -0
- regscale/dev/version.py +72 -0
- regscale/integrations/commercial/__init__.py +15 -1
- regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
- regscale/integrations/commercial/amazon/amazon/common.py +204 -0
- regscale/integrations/commercial/amazon/common.py +48 -58
- regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
- regscale/integrations/commercial/aws/cli.py +3093 -55
- regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
- regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
- regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
- regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
- regscale/integrations/commercial/aws/config_compliance.py +914 -0
- regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
- regscale/integrations/commercial/aws/evidence_generator.py +283 -0
- regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
- regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
- regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
- regscale/integrations/commercial/aws/iam_evidence.py +574 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
- regscale/integrations/commercial/aws/inventory/base.py +107 -5
- regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
- regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
- regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
- regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
- regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
- regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
- regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
- regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
- regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
- regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
- regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
- regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
- regscale/integrations/commercial/aws/kms_evidence.py +879 -0
- regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
- regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
- regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
- regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
- regscale/integrations/commercial/aws/org_evidence.py +666 -0
- regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
- regscale/integrations/commercial/aws/s3_evidence.py +632 -0
- regscale/integrations/commercial/aws/scanner.py +851 -206
- regscale/integrations/commercial/aws/security_hub.py +319 -0
- regscale/integrations/commercial/aws/session_manager.py +282 -0
- regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
- regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
- regscale/integrations/compliance_integration.py +308 -38
- regscale/integrations/due_date_handler.py +3 -0
- regscale/integrations/scanner_integration.py +399 -84
- regscale/models/integration_models/cisa_kev_data.json +34 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/control_objective.py +74 -5
- regscale/models/regscale_models/file.py +2 -0
- regscale/models/regscale_models/issue.py +2 -5
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +112 -33
- tests/regscale/integrations/commercial/aws/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
- tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
- tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
- tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
- tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
- tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
- tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
- tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
- tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
- tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
- tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
- tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
- tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
- tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
- tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
- tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
- tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
- tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
- tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
- tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
- tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
- tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
- tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
- tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
- tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
- tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
- tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
- tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
- tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
- tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
- tests/regscale/integrations/commercial/test_aws.py +55 -56
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/top_level.txt +0 -0
regscale/_version.py
CHANGED
|
@@ -347,13 +347,22 @@ def create_progress_object(indeterminate: bool = False) -> Progress:
|
|
|
347
347
|
:return: Progress object for live progress in console
|
|
348
348
|
:rtype: Progress
|
|
349
349
|
"""
|
|
350
|
+
task_description = "{task.description}"
|
|
351
|
+
# Disable Rich progress bar on Windows to avoid Unicode encoding errors
|
|
352
|
+
if platform.system() == "Windows":
|
|
353
|
+
# Return a minimal progress object without visual elements that cause encoding issues
|
|
354
|
+
return Progress(
|
|
355
|
+
TextColumn(task_description),
|
|
356
|
+
disable=True, # Disable progress bar rendering on Windows
|
|
357
|
+
)
|
|
358
|
+
|
|
350
359
|
if indeterminate:
|
|
351
360
|
return Progress(
|
|
352
|
-
|
|
361
|
+
task_description,
|
|
353
362
|
SpinnerColumn(),
|
|
354
363
|
)
|
|
355
364
|
return Progress(
|
|
356
|
-
|
|
365
|
+
task_description,
|
|
357
366
|
SpinnerColumn(),
|
|
358
367
|
BarColumn(),
|
|
359
368
|
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
regscale/dev/cli.py
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
import sys
|
|
6
7
|
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
7
10
|
import click
|
|
8
11
|
from rich.console import Console
|
|
9
12
|
|
|
@@ -231,5 +234,28 @@ def update_docs(readme_token: str, confluence_user: str, confluence_token: str,
|
|
|
231
234
|
update_confluence(api, confluence_url, root, file)
|
|
232
235
|
|
|
233
236
|
|
|
237
|
+
@cli.command()
|
|
238
|
+
@click.option("--version", "-v", type=click.STRING, help="The version to upgrade the CLI to use.")
|
|
239
|
+
@click.option("--current", "-c", is_flag=True, help="Get the current version of the CLI.")
|
|
240
|
+
def version(version: str, current: bool) -> None:
|
|
241
|
+
"""Manage the version of the regscale-cli package."""
|
|
242
|
+
from regscale.dev.version import (
|
|
243
|
+
get_current_version,
|
|
244
|
+
update_fallback_version_in_version_py,
|
|
245
|
+
update_version_in_pyproject_toml,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if current:
|
|
249
|
+
print(get_current_version())
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
if not version:
|
|
253
|
+
print("❌ Please provide a version to upgrade to using the --version flag.")
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
update_version_in_pyproject_toml(version)
|
|
257
|
+
update_fallback_version_in_version_py(version)
|
|
258
|
+
|
|
259
|
+
|
|
234
260
|
if __name__ == "__main__":
|
|
235
261
|
cli()
|
regscale/dev/version.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Version management script for regscale-cli."""
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def update_version_in_pyproject_toml(version: str) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Update the version in pyproject.toml.
|
|
15
|
+
|
|
16
|
+
:param str version: The version to update to
|
|
17
|
+
"""
|
|
18
|
+
pyproject_file = "pyproject.toml"
|
|
19
|
+
|
|
20
|
+
pyproject_path = Path(pyproject_file)
|
|
21
|
+
content = pyproject_path.read_text()
|
|
22
|
+
|
|
23
|
+
# Update the version
|
|
24
|
+
pattern = r'version\s*=\s*["\']([^"\']+)["\']'
|
|
25
|
+
replacement = f'version = "{version}"'
|
|
26
|
+
|
|
27
|
+
if re.search(pattern, content):
|
|
28
|
+
content = re.sub(pattern, replacement, content)
|
|
29
|
+
pyproject_path.write_text(content)
|
|
30
|
+
console.print(f"[green]Updated version to {version} in {pyproject_file}")
|
|
31
|
+
else:
|
|
32
|
+
console.print(f"[red]Could not find version in {pyproject_file}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def update_fallback_version_in_version_py(version: str) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Update the fallback version in regscale/_version.py.
|
|
38
|
+
|
|
39
|
+
:param str version: The version to update to
|
|
40
|
+
"""
|
|
41
|
+
version_py_path = Path("regscale/_version.py")
|
|
42
|
+
content = version_py_path.read_text()
|
|
43
|
+
pattern = r'return\s*["\'](\d+\.\d+\.\d+\.\d+)["\']\s*# fallback version'
|
|
44
|
+
replacement = f'return "{version}" # fallback version'
|
|
45
|
+
if re.search(pattern, content):
|
|
46
|
+
content = re.sub(pattern, replacement, content)
|
|
47
|
+
version_py_path.write_text(content)
|
|
48
|
+
console.print(f"[green]Updated fallback version to {version} in regscale/_version.py")
|
|
49
|
+
else:
|
|
50
|
+
console.print("[red]Could not find fallback version in regscale/_version.py")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_current_version() -> str:
|
|
54
|
+
"""
|
|
55
|
+
Get the current version from the package.
|
|
56
|
+
|
|
57
|
+
:return: The current version
|
|
58
|
+
:rtype: str
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
# Add the project root to Python path to ensure we can import regscale
|
|
62
|
+
project_root = Path(__file__).parent.parent
|
|
63
|
+
if str(project_root) not in sys.path:
|
|
64
|
+
sys.path.insert(0, str(project_root))
|
|
65
|
+
|
|
66
|
+
from regscale import __version__
|
|
67
|
+
|
|
68
|
+
return __version__
|
|
69
|
+
except ImportError as e:
|
|
70
|
+
console.print(f"[red]Could not import version from regscale package: {e}")
|
|
71
|
+
console.print("[yellow]Make sure you're running this script from the project root directory")
|
|
72
|
+
sys.exit(1)
|
|
@@ -43,13 +43,27 @@ show_mapping(aqua, "aqua")
|
|
|
43
43
|
cls=LazyGroup,
|
|
44
44
|
lazy_subcommands={
|
|
45
45
|
"sync_assets": "regscale.integrations.commercial.aws.cli.sync_assets",
|
|
46
|
+
"sync_findings": "regscale.integrations.commercial.aws.cli.sync_findings",
|
|
46
47
|
"sync_findings_and_assets": "regscale.integrations.commercial.aws.cli.sync_findings_and_assets",
|
|
48
|
+
"sync_compliance": "regscale.integrations.commercial.aws.cli.sync_compliance",
|
|
49
|
+
"sync_config_compliance": "regscale.integrations.commercial.aws.cli.sync_config_compliance",
|
|
50
|
+
"sync_kms": "regscale.integrations.commercial.aws.cli.sync_kms",
|
|
51
|
+
"sync_org": "regscale.integrations.commercial.aws.cli.sync_org",
|
|
52
|
+
"sync_iam": "regscale.integrations.commercial.aws.cli.sync_iam",
|
|
53
|
+
"sync_guardduty": "regscale.integrations.commercial.aws.cli.sync_guardduty",
|
|
54
|
+
"sync_s3": "regscale.integrations.commercial.aws.cli.sync_s3",
|
|
55
|
+
"sync_cloudtrail": "regscale.integrations.commercial.aws.cli.sync_cloudtrail",
|
|
56
|
+
"sync_cloudwatch": "regscale.integrations.commercial.aws.cli.sync_cloudwatch",
|
|
57
|
+
"sync_ssm": "regscale.integrations.commercial.aws.cli.sync_ssm",
|
|
58
|
+
"inventory": "regscale.integrations.commercial.aws.cli.inventory",
|
|
59
|
+
"findings": "regscale.integrations.commercial.aws.cli.findings",
|
|
60
|
+
"auth": "regscale.integrations.commercial.aws.cli.auth",
|
|
47
61
|
"inspector": "regscale.integrations.commercial.aws.cli.inspector",
|
|
48
62
|
},
|
|
49
63
|
name="aws",
|
|
50
64
|
)
|
|
51
65
|
def aws():
|
|
52
|
-
"""AWS Integrations"""
|
|
66
|
+
"""AWS Integrations - Asset sync, findings, compliance, and inventory collection"""
|
|
53
67
|
pass
|
|
54
68
|
|
|
55
69
|
|
|
File without changes
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""RegScale AWS Integrations"""
|
|
4
|
+
import re
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
from typing import Any, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
from botocore.client import BaseClient
|
|
9
|
+
from botocore.exceptions import ClientError
|
|
10
|
+
from dateutil import parser
|
|
11
|
+
|
|
12
|
+
from regscale.core.app.utils.app_utils import create_logger
|
|
13
|
+
|
|
14
|
+
logger = create_logger()
|
|
15
|
+
|
|
16
|
+
# Error message constants
|
|
17
|
+
ERROR_MSG_FETCHING_AWS_RESOURCES = "Unexpected error when fetching resources from AWS: %s"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def check_finding_severity(comment: Optional[str]) -> str:
|
|
21
|
+
"""Check the severity of the finding
|
|
22
|
+
|
|
23
|
+
:param Optional[str] comment: Comment from AWS Security Hub finding
|
|
24
|
+
:return: Severity of the finding
|
|
25
|
+
:rtype: str
|
|
26
|
+
"""
|
|
27
|
+
result = ""
|
|
28
|
+
match = re.search(r"(?<=Finding Severity: ).*", comment)
|
|
29
|
+
if match:
|
|
30
|
+
severity = match.group()
|
|
31
|
+
result = severity # Output: "High"
|
|
32
|
+
return result
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_due_date(earliest_date_performed: datetime, days: int) -> datetime:
|
|
36
|
+
"""Returns the due date for an issue
|
|
37
|
+
|
|
38
|
+
:param datetime earliest_date_performed: Earliest date performed
|
|
39
|
+
:param int days: Days to add to the earliest date performed
|
|
40
|
+
:return: Due date
|
|
41
|
+
:rtype: datetime
|
|
42
|
+
"""
|
|
43
|
+
fmt = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
44
|
+
try:
|
|
45
|
+
due_date = datetime.strptime(earliest_date_performed, fmt) + timedelta(days=days)
|
|
46
|
+
except ValueError:
|
|
47
|
+
# Try to determine the date format from a string
|
|
48
|
+
due_date = parser.parse(earliest_date_performed) + timedelta(days)
|
|
49
|
+
return due_date
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def determine_status_and_results(finding: Any) -> Tuple[str, Optional[str]]:
|
|
53
|
+
"""
|
|
54
|
+
Determine Status and Results
|
|
55
|
+
|
|
56
|
+
:param Any finding: AWS Finding
|
|
57
|
+
:return: Status and Results
|
|
58
|
+
:rtype: Tuple[str, Optional[str]]
|
|
59
|
+
"""
|
|
60
|
+
status = "Pass"
|
|
61
|
+
results = None
|
|
62
|
+
if "Compliance" in finding.keys():
|
|
63
|
+
status = "Fail" if finding["Compliance"]["Status"] == "FAILED" else "Pass"
|
|
64
|
+
results = ", ".join(finding.get("Compliance", {}).get("RelatedRequirements", [])) or "N/A"
|
|
65
|
+
if "FindingProviderFields" in finding.keys():
|
|
66
|
+
status = (
|
|
67
|
+
"Fail"
|
|
68
|
+
if finding.get("FindingProviderFields", {}).get("Severity", {}).get("Label", "")
|
|
69
|
+
in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]
|
|
70
|
+
else "Pass"
|
|
71
|
+
)
|
|
72
|
+
if "PatchSummary" in finding.keys() and not results:
|
|
73
|
+
results = (
|
|
74
|
+
f"{finding.get('PatchSummary', {}).get('MissingCount', 0)} Missing Patch(s) of "
|
|
75
|
+
"{finding.get('PatchSummary', {}).get('InstalledCount', 0)}"
|
|
76
|
+
)
|
|
77
|
+
return status, results
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_comments(finding: dict) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Get Comments
|
|
83
|
+
|
|
84
|
+
:param dict finding: AWS Finding
|
|
85
|
+
:return: Comments
|
|
86
|
+
:rtype: str
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
return (
|
|
90
|
+
finding["Remediation"]["Recommendation"]["Text"]
|
|
91
|
+
+ "<br></br>"
|
|
92
|
+
+ finding["Remediation"]["Recommendation"]["Url"]
|
|
93
|
+
+ "<br></br>"
|
|
94
|
+
+ f"""Finding Severity: {finding["FindingProviderFields"]["Severity"]["Label"]}"""
|
|
95
|
+
)
|
|
96
|
+
except KeyError:
|
|
97
|
+
return "No remediation recommendation available"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def fetch_aws_findings(
|
|
101
|
+
aws_client: BaseClient, minimum_severity: Optional[str] = None, posture_management_only: bool = False
|
|
102
|
+
) -> list:
|
|
103
|
+
"""Fetch AWS Findings with optimized rate limiting and pagination
|
|
104
|
+
|
|
105
|
+
:param BaseClient aws_client: AWS Security Hub Client
|
|
106
|
+
:param Optional[str] minimum_severity: Minimum severity to filter (CRITICAL, HIGH, MEDIUM, LOW, INFORMATIONAL)
|
|
107
|
+
:param bool posture_management_only: If True, only fetch posture management findings (compliance checks)
|
|
108
|
+
:return: AWS Findings
|
|
109
|
+
:rtype: list
|
|
110
|
+
"""
|
|
111
|
+
findings = []
|
|
112
|
+
try:
|
|
113
|
+
# Use optimized SecurityHubPuller for better performance
|
|
114
|
+
from regscale.integrations.commercial.aws.security_hub import SecurityHubPuller
|
|
115
|
+
|
|
116
|
+
# Extract region from the client
|
|
117
|
+
region = aws_client.meta.region_name
|
|
118
|
+
|
|
119
|
+
# Create SecurityHubPuller with same region as the client
|
|
120
|
+
puller = SecurityHubPuller(region_name=region)
|
|
121
|
+
|
|
122
|
+
# Use existing client instead of creating new one to maintain credentials
|
|
123
|
+
puller.client = aws_client
|
|
124
|
+
|
|
125
|
+
# Fetch all findings with optimized pagination and rate limiting
|
|
126
|
+
logger.info("Using optimized SecurityHubPuller for findings retrieval...")
|
|
127
|
+
|
|
128
|
+
# Determine severity labels if minimum_severity is provided
|
|
129
|
+
severity_labels = None
|
|
130
|
+
if minimum_severity:
|
|
131
|
+
severity_labels = SecurityHubPuller.get_severity_filters_from_minimum(minimum_severity)
|
|
132
|
+
logger.info(f"Applying minimum severity filter '{minimum_severity}': {severity_labels}")
|
|
133
|
+
|
|
134
|
+
# Fetch findings based on type requested
|
|
135
|
+
if posture_management_only:
|
|
136
|
+
logger.info("Fetching posture management findings only (security standards compliance checks)")
|
|
137
|
+
findings = puller.get_posture_management_findings(severity_labels=severity_labels)
|
|
138
|
+
elif severity_labels:
|
|
139
|
+
findings = puller.get_findings_by_severity(severity_labels=severity_labels)
|
|
140
|
+
else:
|
|
141
|
+
findings = puller.get_all_findings_with_retries()
|
|
142
|
+
|
|
143
|
+
logger.info(f"Successfully fetched {len(findings)} findings with rate limiting")
|
|
144
|
+
|
|
145
|
+
except ImportError:
|
|
146
|
+
# Fallback to original method if SecurityHubPuller not available
|
|
147
|
+
logger.warning("SecurityHubPuller not available, falling back to basic client")
|
|
148
|
+
findings = fallback_fetch_aws_findings(aws_client)
|
|
149
|
+
except ClientError as cex:
|
|
150
|
+
logger.error("Unexpected error: %s", cex)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.error("Error using SecurityHubPuller, falling back to basic client: %s", e)
|
|
153
|
+
findings = fallback_fetch_aws_findings(aws_client)
|
|
154
|
+
|
|
155
|
+
return findings
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def fallback_fetch_aws_findings(aws_client: BaseClient) -> list:
|
|
159
|
+
"""Fallback method to fetch AWS Findings without pagination
|
|
160
|
+
|
|
161
|
+
:param BaseClient aws_client: AWS Security Hub Client
|
|
162
|
+
:return: AWS Findings
|
|
163
|
+
:rtype: list
|
|
164
|
+
"""
|
|
165
|
+
findings = []
|
|
166
|
+
try:
|
|
167
|
+
response = aws_client.get_findings()
|
|
168
|
+
findings = response.get("Findings", [])
|
|
169
|
+
except ClientError as cex:
|
|
170
|
+
create_logger().error(ERROR_MSG_FETCHING_AWS_RESOURCES, cex)
|
|
171
|
+
return findings
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def fetch_aws_findings_v2(aws_client: BaseClient) -> list:
|
|
175
|
+
"""Fetch AWS Findings
|
|
176
|
+
|
|
177
|
+
:param BaseClient aws_client: AWS Security Hub Client
|
|
178
|
+
:return: AWS Findings
|
|
179
|
+
:rtype: list
|
|
180
|
+
"""
|
|
181
|
+
findings = []
|
|
182
|
+
try:
|
|
183
|
+
response = aws_client.get_findings_v2()
|
|
184
|
+
findings = response.get("Findings", [])
|
|
185
|
+
except ClientError as cex:
|
|
186
|
+
create_logger().error(ERROR_MSG_FETCHING_AWS_RESOURCES, cex)
|
|
187
|
+
return findings
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def fetch_aws_resources(aws_client: BaseClient) -> list:
|
|
191
|
+
"""Fetch AWS Resources
|
|
192
|
+
|
|
193
|
+
:param BaseClient aws_client: AWS Security Hub Client
|
|
194
|
+
:return: AWS Resources
|
|
195
|
+
:rtype: list
|
|
196
|
+
"""
|
|
197
|
+
resources = []
|
|
198
|
+
try:
|
|
199
|
+
response = aws_client.get_resources_v2()
|
|
200
|
+
resources = response.get("Resources", [])
|
|
201
|
+
logger.info(f"Fetched {len(resources)} resources from Security Hub")
|
|
202
|
+
except ClientError as cex:
|
|
203
|
+
create_logger().error(ERROR_MSG_FETCHING_AWS_RESOURCES, cex)
|
|
204
|
+
return resources
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""RegScale AWS Integrations"""
|
|
4
4
|
import re
|
|
5
|
+
import logging
|
|
5
6
|
from datetime import datetime, timedelta
|
|
6
7
|
from typing import Any, Optional, Tuple
|
|
7
8
|
|
|
@@ -11,7 +12,7 @@ from dateutil import parser
|
|
|
11
12
|
|
|
12
13
|
from regscale.core.app.utils.app_utils import create_logger
|
|
13
14
|
|
|
14
|
-
logger =
|
|
15
|
+
logger = logging.getLogger("regscale")
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def check_finding_severity(comment: Optional[str]) -> str:
|
|
@@ -22,17 +23,17 @@ def check_finding_severity(comment: Optional[str]) -> str:
|
|
|
22
23
|
:rtype: str
|
|
23
24
|
"""
|
|
24
25
|
result = ""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
if comment:
|
|
27
|
+
match = re.search(r"(?<=Finding Severity: ).*", comment)
|
|
28
|
+
if match:
|
|
29
|
+
result = match.group()
|
|
29
30
|
return result
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
def get_due_date(earliest_date_performed:
|
|
33
|
+
def get_due_date(earliest_date_performed: str, days: int) -> datetime:
|
|
33
34
|
"""Returns the due date for an issue
|
|
34
35
|
|
|
35
|
-
:param
|
|
36
|
+
:param str earliest_date_performed: Earliest date performed (string format)
|
|
36
37
|
:param int days: Days to add to the earliest date performed
|
|
37
38
|
:return: Due date
|
|
38
39
|
:rtype: datetime
|
|
@@ -42,7 +43,7 @@ def get_due_date(earliest_date_performed: datetime, days: int) -> datetime:
|
|
|
42
43
|
due_date = datetime.strptime(earliest_date_performed, fmt) + timedelta(days=days)
|
|
43
44
|
except ValueError:
|
|
44
45
|
# Try to determine the date format from a string
|
|
45
|
-
due_date = parser.parse(earliest_date_performed) + timedelta(days)
|
|
46
|
+
due_date = parser.parse(earliest_date_performed) + timedelta(days=days)
|
|
46
47
|
return due_date
|
|
47
48
|
|
|
48
49
|
|
|
@@ -94,6 +95,31 @@ def get_comments(finding: dict) -> str:
|
|
|
94
95
|
return "No remediation recommendation available"
|
|
95
96
|
|
|
96
97
|
|
|
98
|
+
def _fetch_with_error_handling(
|
|
99
|
+
aws_client: BaseClient, method_name: str, result_key: str, resource_type: str = "items"
|
|
100
|
+
) -> list:
|
|
101
|
+
"""Generic fetch method with error handling for AWS Security Hub
|
|
102
|
+
|
|
103
|
+
:param BaseClient aws_client: AWS Security Hub Client
|
|
104
|
+
:param str method_name: Name of the method to call on the client
|
|
105
|
+
:param str result_key: Key to extract from the response
|
|
106
|
+
:param str resource_type: Type of resource being fetched (for logging)
|
|
107
|
+
:return: List of items from AWS
|
|
108
|
+
:rtype: list
|
|
109
|
+
"""
|
|
110
|
+
items = []
|
|
111
|
+
try:
|
|
112
|
+
method = getattr(aws_client, method_name)
|
|
113
|
+
response = method()
|
|
114
|
+
items = response.get(result_key, [])
|
|
115
|
+
logger.info("Fetched %d %s from Security Hub", len(items), resource_type)
|
|
116
|
+
except ClientError as cex:
|
|
117
|
+
logger.error("Unexpected error when fetching %s from AWS: %s", resource_type, cex)
|
|
118
|
+
except AttributeError as aex:
|
|
119
|
+
logger.error("Method %s not found on client: %s", method_name, aex)
|
|
120
|
+
return items
|
|
121
|
+
|
|
122
|
+
|
|
97
123
|
def fetch_aws_findings(aws_client: BaseClient) -> list:
|
|
98
124
|
"""Fetch AWS Findings with optimized rate limiting and pagination
|
|
99
125
|
|
|
@@ -101,70 +127,41 @@ def fetch_aws_findings(aws_client: BaseClient) -> list:
|
|
|
101
127
|
:return: AWS Findings
|
|
102
128
|
:rtype: list
|
|
103
129
|
"""
|
|
104
|
-
findings = []
|
|
105
130
|
try:
|
|
106
131
|
# Use optimized SecurityHubPuller for better performance
|
|
107
132
|
from regscale.integrations.commercial.aws.security_hub import SecurityHubPuller
|
|
108
133
|
|
|
109
|
-
# Extract
|
|
110
|
-
|
|
111
|
-
region = session.meta.region_name
|
|
134
|
+
# Extract region from the client's meta information
|
|
135
|
+
region = aws_client.meta.region_name
|
|
112
136
|
|
|
113
|
-
# Create SecurityHubPuller with same
|
|
137
|
+
# Create SecurityHubPuller with same region and credentials
|
|
114
138
|
puller = SecurityHubPuller(region_name=region)
|
|
115
|
-
|
|
116
|
-
# Use existing client instead of creating new one to maintain credentials
|
|
117
139
|
puller.client = aws_client
|
|
118
140
|
|
|
119
141
|
# Fetch all findings with optimized pagination and rate limiting
|
|
120
142
|
logger.info("Using optimized SecurityHubPuller for findings retrieval...")
|
|
121
143
|
findings = puller.get_all_findings_with_retries()
|
|
122
144
|
|
|
123
|
-
logger.info(
|
|
145
|
+
logger.info("Successfully fetched %d findings with rate limiting", len(findings))
|
|
146
|
+
return findings
|
|
124
147
|
|
|
125
|
-
except ImportError:
|
|
126
|
-
# Fallback to original method if SecurityHubPuller not available
|
|
127
|
-
logger.warning("SecurityHubPuller not available, falling back to basic client")
|
|
128
|
-
|
|
129
|
-
except ClientError as cex:
|
|
130
|
-
logger.error("Unexpected error: %s", cex)
|
|
148
|
+
except (ImportError, AttributeError) as e:
|
|
149
|
+
# Fallback to original method if SecurityHubPuller not available or region not accessible
|
|
150
|
+
logger.warning("SecurityHubPuller not available (%s), falling back to basic client", type(e).__name__)
|
|
151
|
+
return _fetch_with_error_handling(aws_client, "get_findings", "Findings", "findings")
|
|
131
152
|
except Exception as e:
|
|
132
|
-
logger.error("Error using SecurityHubPuller, falling back to basic client
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return findings
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def fallback_fetch_aws_findings(aws_client: BaseClient) -> list:
|
|
139
|
-
"""Fallback method to fetch AWS Findings without pagination
|
|
140
|
-
|
|
141
|
-
:param BaseClient aws_client: AWS Security Hub Client
|
|
142
|
-
:return: AWS Findings
|
|
143
|
-
:rtype: list
|
|
144
|
-
"""
|
|
145
|
-
findings = []
|
|
146
|
-
try:
|
|
147
|
-
response = aws_client.get_findings()
|
|
148
|
-
findings = response.get("Findings", [])
|
|
149
|
-
except ClientError as cex:
|
|
150
|
-
create_logger().error("Unexpected error when fetching resources from AWS: %s", cex)
|
|
151
|
-
return findings
|
|
153
|
+
logger.error("Error using SecurityHubPuller (%s), falling back to basic client", e)
|
|
154
|
+
return _fetch_with_error_handling(aws_client, "get_findings", "Findings", "findings")
|
|
152
155
|
|
|
153
156
|
|
|
154
157
|
def fetch_aws_findings_v2(aws_client: BaseClient) -> list:
|
|
155
|
-
"""Fetch AWS Findings
|
|
158
|
+
"""Fetch AWS Findings using v2 API
|
|
156
159
|
|
|
157
160
|
:param BaseClient aws_client: AWS Security Hub Client
|
|
158
161
|
:return: AWS Findings
|
|
159
162
|
:rtype: list
|
|
160
163
|
"""
|
|
161
|
-
|
|
162
|
-
try:
|
|
163
|
-
response = aws_client.get_findings_v2()
|
|
164
|
-
findings = response.get("Findings", [])
|
|
165
|
-
except ClientError as cex:
|
|
166
|
-
create_logger().error("Unexpected error when fetching resources from AWS: %s", cex)
|
|
167
|
-
return findings
|
|
164
|
+
return _fetch_with_error_handling(aws_client, "get_findings_v2", "Findings", "findings")
|
|
168
165
|
|
|
169
166
|
|
|
170
167
|
def fetch_aws_resources(aws_client: BaseClient) -> list:
|
|
@@ -174,11 +171,4 @@ def fetch_aws_resources(aws_client: BaseClient) -> list:
|
|
|
174
171
|
:return: AWS Resources
|
|
175
172
|
:rtype: list
|
|
176
173
|
"""
|
|
177
|
-
|
|
178
|
-
try:
|
|
179
|
-
response = aws_client.get_resources_v2()
|
|
180
|
-
resources = response.get("Resources", [])
|
|
181
|
-
logger.info(f"Fetched {len(resources)} resources from Security Hub")
|
|
182
|
-
except ClientError as cex:
|
|
183
|
-
create_logger().error("Unexpected error when fetching resources from AWS: %s", cex)
|
|
184
|
-
return resources
|
|
174
|
+
return _fetch_with_error_handling(aws_client, "get_resources_v2", "Resources", "resources")
|