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.

Files changed (112) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/utils/app_utils.py +11 -2
  3. regscale/dev/cli.py +26 -0
  4. regscale/dev/version.py +72 -0
  5. regscale/integrations/commercial/__init__.py +15 -1
  6. regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
  7. regscale/integrations/commercial/amazon/amazon/common.py +204 -0
  8. regscale/integrations/commercial/amazon/common.py +48 -58
  9. regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
  10. regscale/integrations/commercial/aws/cli.py +3093 -55
  11. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  12. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  13. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  14. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  15. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  16. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  17. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  18. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  19. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  20. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  21. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  22. regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
  23. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  24. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  25. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  26. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  27. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  28. regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
  29. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  30. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  31. regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
  32. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  33. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  34. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  35. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  36. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  37. regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
  38. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  39. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  40. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  41. regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
  42. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  43. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  44. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  45. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  46. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  47. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  48. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  49. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  50. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  51. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  52. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  53. regscale/integrations/commercial/aws/scanner.py +851 -206
  54. regscale/integrations/commercial/aws/security_hub.py +319 -0
  55. regscale/integrations/commercial/aws/session_manager.py +282 -0
  56. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  57. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  58. regscale/integrations/compliance_integration.py +308 -38
  59. regscale/integrations/due_date_handler.py +3 -0
  60. regscale/integrations/scanner_integration.py +399 -84
  61. regscale/models/integration_models/cisa_kev_data.json +34 -4
  62. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  63. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
  64. regscale/models/regscale_models/assessment.py +2 -1
  65. regscale/models/regscale_models/control_objective.py +74 -5
  66. regscale/models/regscale_models/file.py +2 -0
  67. regscale/models/regscale_models/issue.py +2 -5
  68. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
  69. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +112 -33
  70. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  71. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  72. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  73. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  74. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  75. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  76. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  77. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  78. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  79. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  80. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  81. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  82. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  83. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  84. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  85. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  86. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  87. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  88. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  89. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  90. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  91. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  92. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  93. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  94. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  95. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  96. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  97. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  98. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  99. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  100. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  101. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  102. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  103. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  104. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  105. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  106. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  107. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  108. tests/regscale/integrations/commercial/test_aws.py +55 -56
  109. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
  110. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
  111. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
  112. {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
@@ -33,7 +33,7 @@ def get_version_from_pyproject() -> str:
33
33
  return match.group(1)
34
34
  except Exception:
35
35
  pass
36
- return "6.27.3.0" # fallback version
36
+ return "6.28.0.0" # fallback version
37
37
 
38
38
 
39
39
  __version__ = get_version_from_pyproject()
@@ -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
- "{task.description}",
361
+ task_description,
353
362
  SpinnerColumn(),
354
363
  )
355
364
  return Progress(
356
- "{task.description}",
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()
@@ -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
 
@@ -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 = create_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
- match = re.search(r"(?<=Finding Severity: ).*", comment)
26
- if match:
27
- severity = match.group()
28
- result = severity # Output: "High"
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: datetime, days: int) -> datetime:
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 datetime earliest_date_performed: Earliest date performed
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 credentials from the client to create SecurityHubPuller
110
- session = aws_client._client_config.__dict__.get("_user_provided_options", {})
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 credentials as the client
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(f"Successfully fetched {len(findings)} findings with rate limiting")
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
- findings = fallback_fetch_aws_findings(aws_client)
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: %s", e)
133
- findings = fallback_fetch_aws_findings(aws_client)
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
- findings = []
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
- resources = []
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")