regscale-cli 6.27.2.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/application.py +1 -0
- regscale/core/app/internal/control_editor.py +73 -21
- regscale/core/app/internal/login.py +4 -1
- regscale/core/app/internal/model_editor.py +219 -64
- regscale/core/app/utils/app_utils.py +11 -2
- regscale/core/login.py +21 -4
- regscale/core/utils/date.py +77 -1
- 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 +853 -205
- 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/commercial/synqly/query_builder.py +4 -1
- regscale/integrations/compliance_integration.py +308 -38
- regscale/integrations/control_matcher.py +78 -23
- regscale/integrations/due_date_handler.py +3 -0
- regscale/integrations/public/csam/csam.py +572 -763
- regscale/integrations/public/csam/csam_agency_defined.py +179 -0
- regscale/integrations/public/csam/csam_common.py +154 -0
- regscale/integrations/public/csam/csam_controls.py +432 -0
- regscale/integrations/public/csam/csam_poam.py +124 -0
- regscale/integrations/public/fedramp/click.py +17 -4
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +271 -62
- regscale/integrations/public/fedramp/poam/scanner.py +74 -7
- regscale/integrations/scanner_integration.py +415 -85
- regscale/models/integration_models/cisa_kev_data.json +80 -20
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +44 -3
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +41 -12
- regscale/models/platform.py +3 -0
- regscale/models/regscale_models/__init__.py +5 -0
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/component.py +1 -1
- regscale/models/regscale_models/control_implementation.py +55 -24
- 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/models/regscale_models/organization.py +3 -0
- regscale/models/regscale_models/regscale_model.py +17 -5
- regscale/models/regscale_models/security_plan.py +1 -0
- regscale/regscale.py +11 -1
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +140 -57
- tests/regscale/core/test_login.py +171 -4
- 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
- tests/regscale/integrations/test_control_matcher.py +24 -0
- tests/regscale/models/test_control_implementation.py +118 -3
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""AWS S3 resource collection."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from botocore.exceptions import ClientError
|
|
7
|
+
|
|
8
|
+
from regscale.integrations.commercial.aws.inventory.base import BaseCollector
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("regscale")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class S3Collector(BaseCollector):
|
|
14
|
+
"""Collector for AWS S3 resources."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self, session: Any, region: str, account_id: Optional[str] = None, tags: Optional[Dict[str, str]] = None
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Initialize S3 collector.
|
|
21
|
+
|
|
22
|
+
:param session: AWS session to use for API calls
|
|
23
|
+
:param str region: AWS region to collect from
|
|
24
|
+
:param str account_id: Optional AWS account ID to filter resources
|
|
25
|
+
:param dict tags: Optional tags to filter resources (key-value pairs)
|
|
26
|
+
"""
|
|
27
|
+
super().__init__(session, region, account_id, tags)
|
|
28
|
+
|
|
29
|
+
def collect(self) -> Dict[str, Any]:
|
|
30
|
+
"""
|
|
31
|
+
Collect AWS S3 resources.
|
|
32
|
+
|
|
33
|
+
:return: Dictionary containing S3 bucket information
|
|
34
|
+
:rtype: Dict[str, Any]
|
|
35
|
+
"""
|
|
36
|
+
result = {"Buckets": []}
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
client = self._get_client("s3")
|
|
40
|
+
|
|
41
|
+
# Get all buckets
|
|
42
|
+
buckets = self._list_buckets(client)
|
|
43
|
+
result["Buckets"] = buckets
|
|
44
|
+
|
|
45
|
+
logger.info(f"Collected {len(buckets)} S3 bucket(s) from {self.region}")
|
|
46
|
+
|
|
47
|
+
except ClientError as e:
|
|
48
|
+
self._handle_error(e, "S3 buckets")
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error(f"Unexpected error collecting S3 resources: {e}", exc_info=True)
|
|
51
|
+
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
def _list_buckets(self, client: Any) -> List[Dict[str, Any]]:
|
|
55
|
+
"""
|
|
56
|
+
List S3 buckets with enhanced details.
|
|
57
|
+
|
|
58
|
+
:param client: S3 client
|
|
59
|
+
:return: List of bucket information
|
|
60
|
+
:rtype: List[Dict[str, Any]]
|
|
61
|
+
"""
|
|
62
|
+
buckets = []
|
|
63
|
+
try:
|
|
64
|
+
response = client.list_buckets()
|
|
65
|
+
|
|
66
|
+
for bucket in response.get("Buckets", []):
|
|
67
|
+
bucket_name = bucket["Name"]
|
|
68
|
+
bucket_dict = self._process_bucket(client, bucket, bucket_name)
|
|
69
|
+
|
|
70
|
+
if bucket_dict:
|
|
71
|
+
buckets.append(bucket_dict)
|
|
72
|
+
|
|
73
|
+
except ClientError as e:
|
|
74
|
+
self._handle_list_buckets_error(e)
|
|
75
|
+
|
|
76
|
+
return buckets
|
|
77
|
+
|
|
78
|
+
def _process_bucket(self, client: Any, bucket: Dict[str, Any], bucket_name: str) -> Optional[Dict[str, Any]]:
|
|
79
|
+
"""
|
|
80
|
+
Process a single bucket and return its details if it passes filters.
|
|
81
|
+
|
|
82
|
+
:param client: S3 client
|
|
83
|
+
:param dict bucket: Bucket information from list_buckets
|
|
84
|
+
:param str bucket_name: Bucket name
|
|
85
|
+
:return: Bucket details dictionary or None if bucket should be skipped
|
|
86
|
+
:rtype: Optional[Dict[str, Any]]
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
# Get bucket location
|
|
90
|
+
location = self._get_bucket_location(client, bucket_name)
|
|
91
|
+
|
|
92
|
+
# Only include buckets in the target region
|
|
93
|
+
if location != self.region:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
# Build bucket details dictionary
|
|
97
|
+
bucket_dict = self._build_bucket_details(client, bucket, bucket_name, location)
|
|
98
|
+
|
|
99
|
+
# Apply tag filtering if configured
|
|
100
|
+
if not self._should_include_bucket(bucket_dict):
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
return bucket_dict
|
|
104
|
+
|
|
105
|
+
except ClientError as e:
|
|
106
|
+
self._handle_bucket_processing_error(e, bucket_name)
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
def _build_bucket_details(
|
|
110
|
+
self, client: Any, bucket: Dict[str, Any], bucket_name: str, location: str
|
|
111
|
+
) -> Dict[str, Any]:
|
|
112
|
+
"""
|
|
113
|
+
Build complete details dictionary for a bucket.
|
|
114
|
+
|
|
115
|
+
:param client: S3 client
|
|
116
|
+
:param dict bucket: Bucket information from list_buckets
|
|
117
|
+
:param str bucket_name: Bucket name
|
|
118
|
+
:param str location: Bucket location/region
|
|
119
|
+
:return: Complete bucket details dictionary
|
|
120
|
+
:rtype: Dict[str, Any]
|
|
121
|
+
"""
|
|
122
|
+
bucket_dict = {
|
|
123
|
+
"Region": self.region,
|
|
124
|
+
"Name": bucket_name,
|
|
125
|
+
"CreationDate": str(bucket["CreationDate"]),
|
|
126
|
+
"Location": location,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Get encryption configuration
|
|
130
|
+
encryption = self._get_bucket_encryption(client, bucket_name)
|
|
131
|
+
bucket_dict["Encryption"] = encryption
|
|
132
|
+
|
|
133
|
+
# Get versioning configuration
|
|
134
|
+
versioning = self._get_bucket_versioning(client, bucket_name)
|
|
135
|
+
bucket_dict["Versioning"] = versioning
|
|
136
|
+
|
|
137
|
+
# Get public access block configuration
|
|
138
|
+
public_access_block = self._get_public_access_block(client, bucket_name)
|
|
139
|
+
bucket_dict["PublicAccessBlock"] = public_access_block
|
|
140
|
+
|
|
141
|
+
# Get bucket policy status
|
|
142
|
+
policy_status = self._get_bucket_policy_status(client, bucket_name)
|
|
143
|
+
bucket_dict["PolicyStatus"] = policy_status
|
|
144
|
+
|
|
145
|
+
# Get bucket ACL
|
|
146
|
+
acl = self._get_bucket_acl(client, bucket_name)
|
|
147
|
+
bucket_dict["ACL"] = acl
|
|
148
|
+
|
|
149
|
+
# Get bucket tagging
|
|
150
|
+
tags = self._get_bucket_tagging(client, bucket_name)
|
|
151
|
+
bucket_dict["Tags"] = tags
|
|
152
|
+
|
|
153
|
+
# Get bucket logging
|
|
154
|
+
logging_config = self._get_bucket_logging(client, bucket_name)
|
|
155
|
+
bucket_dict["Logging"] = logging_config
|
|
156
|
+
|
|
157
|
+
return bucket_dict
|
|
158
|
+
|
|
159
|
+
def _should_include_bucket(self, bucket_dict: Dict[str, Any]) -> bool:
|
|
160
|
+
"""
|
|
161
|
+
Check if bucket should be included based on tag filters.
|
|
162
|
+
|
|
163
|
+
:param dict bucket_dict: Bucket details dictionary
|
|
164
|
+
:return: True if bucket should be included, False otherwise
|
|
165
|
+
:rtype: bool
|
|
166
|
+
"""
|
|
167
|
+
if not self.tags:
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
tags = bucket_dict.get("Tags", [])
|
|
171
|
+
bucket_tags_dict = self._convert_tags_to_dict(tags)
|
|
172
|
+
|
|
173
|
+
if not self._matches_tags(bucket_tags_dict):
|
|
174
|
+
bucket_name = bucket_dict.get("Name", "unknown")
|
|
175
|
+
logger.debug("Skipping bucket %s - does not match tag filters", bucket_name)
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
def _handle_list_buckets_error(self, error: ClientError) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Handle errors from list_buckets operation.
|
|
183
|
+
|
|
184
|
+
:param ClientError error: The client error to handle
|
|
185
|
+
"""
|
|
186
|
+
error_code = error.response["Error"]["Code"]
|
|
187
|
+
if error_code == "AccessDenied":
|
|
188
|
+
logger.warning("Access denied to list S3 buckets")
|
|
189
|
+
else:
|
|
190
|
+
logger.error("Error listing S3 buckets: %s", error)
|
|
191
|
+
|
|
192
|
+
def _handle_bucket_processing_error(self, error: ClientError, bucket_name: str) -> None:
|
|
193
|
+
"""
|
|
194
|
+
Handle errors during bucket detail processing.
|
|
195
|
+
|
|
196
|
+
:param ClientError error: The client error to handle
|
|
197
|
+
:param str bucket_name: Name of the bucket being processed
|
|
198
|
+
"""
|
|
199
|
+
error_code = error.response["Error"]["Code"]
|
|
200
|
+
if error_code not in ["NoSuchBucket", "AccessDenied"]:
|
|
201
|
+
logger.error("Error getting details for bucket %s: %s", bucket_name, error)
|
|
202
|
+
|
|
203
|
+
def _get_bucket_location(self, client: Any, bucket_name: str) -> str:
|
|
204
|
+
"""
|
|
205
|
+
Get bucket location.
|
|
206
|
+
|
|
207
|
+
:param client: S3 client
|
|
208
|
+
:param str bucket_name: Bucket name
|
|
209
|
+
:return: Bucket region
|
|
210
|
+
:rtype: str
|
|
211
|
+
"""
|
|
212
|
+
try:
|
|
213
|
+
response = client.get_bucket_location(Bucket=bucket_name)
|
|
214
|
+
location = response.get("LocationConstraint") or "us-east-1"
|
|
215
|
+
return location
|
|
216
|
+
except ClientError:
|
|
217
|
+
return "unknown"
|
|
218
|
+
|
|
219
|
+
def _get_bucket_encryption(self, client: Any, bucket_name: str) -> Dict[str, Any]:
|
|
220
|
+
"""
|
|
221
|
+
Get bucket encryption configuration.
|
|
222
|
+
|
|
223
|
+
:param client: S3 client
|
|
224
|
+
:param str bucket_name: Bucket name
|
|
225
|
+
:return: Encryption configuration
|
|
226
|
+
:rtype: Dict[str, Any]
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
response = client.get_bucket_encryption(Bucket=bucket_name)
|
|
230
|
+
rules = response.get("ServerSideEncryptionConfiguration", {}).get("Rules", [])
|
|
231
|
+
if rules:
|
|
232
|
+
return {
|
|
233
|
+
"Enabled": True,
|
|
234
|
+
"Algorithm": rules[0].get("ApplyServerSideEncryptionByDefault", {}).get("SSEAlgorithm"),
|
|
235
|
+
"KMSMasterKeyID": rules[0].get("ApplyServerSideEncryptionByDefault", {}).get("KMSMasterKeyID"),
|
|
236
|
+
}
|
|
237
|
+
return {"Enabled": False}
|
|
238
|
+
except ClientError as e:
|
|
239
|
+
if e.response["Error"]["Code"] == "ServerSideEncryptionConfigurationNotFoundError":
|
|
240
|
+
return {"Enabled": False}
|
|
241
|
+
logger.debug(f"Error getting encryption for bucket {bucket_name}: {e}")
|
|
242
|
+
return {}
|
|
243
|
+
|
|
244
|
+
def _get_bucket_versioning(self, client: Any, bucket_name: str) -> Dict[str, Any]:
|
|
245
|
+
"""
|
|
246
|
+
Get bucket versioning configuration.
|
|
247
|
+
|
|
248
|
+
:param client: S3 client
|
|
249
|
+
:param str bucket_name: Bucket name
|
|
250
|
+
:return: Versioning configuration
|
|
251
|
+
:rtype: Dict[str, Any]
|
|
252
|
+
"""
|
|
253
|
+
try:
|
|
254
|
+
response = client.get_bucket_versioning(Bucket=bucket_name)
|
|
255
|
+
return {"Status": response.get("Status", "Disabled"), "MFADelete": response.get("MFADelete", "Disabled")}
|
|
256
|
+
except ClientError as e:
|
|
257
|
+
logger.debug(f"Error getting versioning for bucket {bucket_name}: {e}")
|
|
258
|
+
return {}
|
|
259
|
+
|
|
260
|
+
def _get_public_access_block(self, client: Any, bucket_name: str) -> Dict[str, Any]:
|
|
261
|
+
"""
|
|
262
|
+
Get public access block configuration.
|
|
263
|
+
|
|
264
|
+
:param client: S3 client
|
|
265
|
+
:param str bucket_name: Bucket name
|
|
266
|
+
:return: Public access block configuration
|
|
267
|
+
:rtype: Dict[str, Any]
|
|
268
|
+
"""
|
|
269
|
+
try:
|
|
270
|
+
response = client.get_public_access_block(Bucket=bucket_name)
|
|
271
|
+
config = response.get("PublicAccessBlockConfiguration", {})
|
|
272
|
+
return {
|
|
273
|
+
"BlockPublicAcls": config.get("BlockPublicAcls", False),
|
|
274
|
+
"IgnorePublicAcls": config.get("IgnorePublicAcls", False),
|
|
275
|
+
"BlockPublicPolicy": config.get("BlockPublicPolicy", False),
|
|
276
|
+
"RestrictPublicBuckets": config.get("RestrictPublicBuckets", False),
|
|
277
|
+
}
|
|
278
|
+
except ClientError as e:
|
|
279
|
+
if e.response["Error"]["Code"] == "NoSuchPublicAccessBlockConfiguration":
|
|
280
|
+
return {
|
|
281
|
+
"BlockPublicAcls": False,
|
|
282
|
+
"IgnorePublicAcls": False,
|
|
283
|
+
"BlockPublicPolicy": False,
|
|
284
|
+
"RestrictPublicBuckets": False,
|
|
285
|
+
}
|
|
286
|
+
logger.debug(f"Error getting public access block for bucket {bucket_name}: {e}")
|
|
287
|
+
return {}
|
|
288
|
+
|
|
289
|
+
def _get_bucket_policy_status(self, client: Any, bucket_name: str) -> Dict[str, Any]:
|
|
290
|
+
"""
|
|
291
|
+
Get bucket policy status.
|
|
292
|
+
|
|
293
|
+
:param client: S3 client
|
|
294
|
+
:param str bucket_name: Bucket name
|
|
295
|
+
:return: Policy status
|
|
296
|
+
:rtype: Dict[str, Any]
|
|
297
|
+
"""
|
|
298
|
+
try:
|
|
299
|
+
response = client.get_bucket_policy_status(Bucket=bucket_name)
|
|
300
|
+
policy_status = response.get("PolicyStatus", {})
|
|
301
|
+
return {"IsPublic": policy_status.get("IsPublic", False)}
|
|
302
|
+
except ClientError as e:
|
|
303
|
+
if e.response["Error"]["Code"] == "NoSuchBucketPolicy":
|
|
304
|
+
return {"IsPublic": False}
|
|
305
|
+
logger.debug(f"Error getting policy status for bucket {bucket_name}: {e}")
|
|
306
|
+
return {}
|
|
307
|
+
|
|
308
|
+
def _get_bucket_acl(self, client: Any, bucket_name: str) -> Dict[str, Any]:
|
|
309
|
+
"""
|
|
310
|
+
Get bucket ACL.
|
|
311
|
+
|
|
312
|
+
:param client: S3 client
|
|
313
|
+
:param str bucket_name: Bucket name
|
|
314
|
+
:return: ACL information
|
|
315
|
+
:rtype: Dict[str, Any]
|
|
316
|
+
"""
|
|
317
|
+
try:
|
|
318
|
+
response = client.get_bucket_acl(Bucket=bucket_name)
|
|
319
|
+
return {"Owner": response.get("Owner", {}), "GrantCount": len(response.get("Grants", []))}
|
|
320
|
+
except ClientError as e:
|
|
321
|
+
logger.debug(f"Error getting ACL for bucket {bucket_name}: {e}")
|
|
322
|
+
return {}
|
|
323
|
+
|
|
324
|
+
def _get_bucket_tagging(self, client: Any, bucket_name: str) -> List[Dict[str, str]]:
|
|
325
|
+
"""
|
|
326
|
+
Get bucket tags.
|
|
327
|
+
|
|
328
|
+
:param client: S3 client
|
|
329
|
+
:param str bucket_name: Bucket name
|
|
330
|
+
:return: List of tags
|
|
331
|
+
:rtype: List[Dict[str, str]]
|
|
332
|
+
"""
|
|
333
|
+
try:
|
|
334
|
+
response = client.get_bucket_tagging(Bucket=bucket_name)
|
|
335
|
+
return response.get("TagSet", [])
|
|
336
|
+
except ClientError as e:
|
|
337
|
+
if e.response["Error"]["Code"] == "NoSuchTagSet":
|
|
338
|
+
return []
|
|
339
|
+
logger.debug(f"Error getting tags for bucket {bucket_name}: {e}")
|
|
340
|
+
return []
|
|
341
|
+
|
|
342
|
+
def _get_bucket_logging(self, client: Any, bucket_name: str) -> Dict[str, Any]:
|
|
343
|
+
"""
|
|
344
|
+
Get bucket logging configuration.
|
|
345
|
+
|
|
346
|
+
:param client: S3 client
|
|
347
|
+
:param str bucket_name: Bucket name
|
|
348
|
+
:return: Logging configuration
|
|
349
|
+
:rtype: Dict[str, Any]
|
|
350
|
+
"""
|
|
351
|
+
try:
|
|
352
|
+
response = client.get_bucket_logging(Bucket=bucket_name)
|
|
353
|
+
logging_enabled = response.get("LoggingEnabled", {})
|
|
354
|
+
if logging_enabled:
|
|
355
|
+
return {
|
|
356
|
+
"Enabled": True,
|
|
357
|
+
"TargetBucket": logging_enabled.get("TargetBucket"),
|
|
358
|
+
"TargetPrefix": logging_enabled.get("TargetPrefix"),
|
|
359
|
+
}
|
|
360
|
+
return {"Enabled": False}
|
|
361
|
+
except ClientError as e:
|
|
362
|
+
logger.debug(f"Error getting logging for bucket {bucket_name}: {e}")
|
|
363
|
+
return {}
|
|
364
|
+
|
|
365
|
+
def _convert_tags_to_dict(self, tags_list: List[Dict[str, str]]) -> Dict[str, str]:
|
|
366
|
+
"""
|
|
367
|
+
Convert S3 tags list format to dictionary format.
|
|
368
|
+
|
|
369
|
+
S3 returns tags as list of dicts: [{"Key": "k1", "Value": "v1"}]
|
|
370
|
+
Convert to dict format: {"k1": "v1"}
|
|
371
|
+
|
|
372
|
+
:param list tags_list: List of tag dictionaries
|
|
373
|
+
:return: Dictionary of tags (Key -> Value)
|
|
374
|
+
:rtype: Dict[str, str]
|
|
375
|
+
"""
|
|
376
|
+
return {tag.get("Key", ""): tag.get("Value", "") for tag in tags_list}
|
|
377
|
+
|
|
378
|
+
def _matches_tags(self, resource_tags: Dict[str, str]) -> bool:
|
|
379
|
+
"""
|
|
380
|
+
Check if resource tags match the specified filter tags.
|
|
381
|
+
|
|
382
|
+
:param dict resource_tags: Tags on the resource
|
|
383
|
+
:return: True if all filter tags match
|
|
384
|
+
:rtype: bool
|
|
385
|
+
"""
|
|
386
|
+
if not self.tags:
|
|
387
|
+
return True
|
|
388
|
+
|
|
389
|
+
# All filter tags must match
|
|
390
|
+
for key, value in self.tags.items():
|
|
391
|
+
if resource_tags.get(key) != value:
|
|
392
|
+
return False
|
|
393
|
+
|
|
394
|
+
return True
|