regscale-cli 6.27.3.0__py3-none-any.whl → 6.28.1.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/commercial/synqly/ticketing.py +27 -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 +65 -5
- 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.1.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/RECORD +113 -34
- 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.1.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
"""AWS VPC 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 VPCCollector(BaseCollector):
|
|
14
|
+
"""Collector for AWS VPC 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 VPC 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)
|
|
28
|
+
self.account_id = account_id
|
|
29
|
+
self.tags = tags or {}
|
|
30
|
+
|
|
31
|
+
def collect(self) -> Dict[str, Any]:
|
|
32
|
+
"""
|
|
33
|
+
Collect AWS VPC resources.
|
|
34
|
+
|
|
35
|
+
:return: Dictionary containing VPC information
|
|
36
|
+
:rtype: Dict[str, Any]
|
|
37
|
+
"""
|
|
38
|
+
result = {
|
|
39
|
+
"VPCs": [],
|
|
40
|
+
"Subnets": [],
|
|
41
|
+
"SecurityGroups": [],
|
|
42
|
+
"NetworkACLs": [],
|
|
43
|
+
"RouteTables": [],
|
|
44
|
+
"InternetGateways": [],
|
|
45
|
+
"NATGateways": [],
|
|
46
|
+
"VPCEndpoints": [],
|
|
47
|
+
"VPCPeeringConnections": [],
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
client = self._get_client("ec2")
|
|
52
|
+
|
|
53
|
+
# Get all VPCs
|
|
54
|
+
vpcs = self._list_vpcs(client)
|
|
55
|
+
result["VPCs"] = vpcs
|
|
56
|
+
|
|
57
|
+
# Get subnets
|
|
58
|
+
subnets = self._list_subnets(client)
|
|
59
|
+
result["Subnets"] = subnets
|
|
60
|
+
|
|
61
|
+
# Get security groups
|
|
62
|
+
security_groups = self._list_security_groups(client)
|
|
63
|
+
result["SecurityGroups"] = security_groups
|
|
64
|
+
|
|
65
|
+
# Get network ACLs
|
|
66
|
+
network_acls = self._list_network_acls(client)
|
|
67
|
+
result["NetworkACLs"] = network_acls
|
|
68
|
+
|
|
69
|
+
# Get route tables
|
|
70
|
+
route_tables = self._list_route_tables(client)
|
|
71
|
+
result["RouteTables"] = route_tables
|
|
72
|
+
|
|
73
|
+
# Get internet gateways
|
|
74
|
+
internet_gateways = self._list_internet_gateways(client)
|
|
75
|
+
result["InternetGateways"] = internet_gateways
|
|
76
|
+
|
|
77
|
+
# Get NAT gateways
|
|
78
|
+
nat_gateways = self._list_nat_gateways(client)
|
|
79
|
+
result["NATGateways"] = nat_gateways
|
|
80
|
+
|
|
81
|
+
# Get VPC endpoints
|
|
82
|
+
vpc_endpoints = self._list_vpc_endpoints(client)
|
|
83
|
+
result["VPCEndpoints"] = vpc_endpoints
|
|
84
|
+
|
|
85
|
+
# Get VPC peering connections
|
|
86
|
+
vpc_peering = self._list_vpc_peering_connections(client)
|
|
87
|
+
result["VPCPeeringConnections"] = vpc_peering
|
|
88
|
+
|
|
89
|
+
logger.info(
|
|
90
|
+
f"Collected {len(vpcs)} VPC(s), {len(subnets)} subnet(s), "
|
|
91
|
+
f"{len(security_groups)} security group(s) from {self.region}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
except ClientError as e:
|
|
95
|
+
self._handle_error(e, "VPC resources")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"Unexpected error collecting VPC resources: {e}", exc_info=True)
|
|
98
|
+
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
def _list_vpcs(self, client: Any) -> List[Dict[str, Any]]:
|
|
102
|
+
"""
|
|
103
|
+
List VPCs with enhanced details.
|
|
104
|
+
|
|
105
|
+
:param client: EC2 client
|
|
106
|
+
:return: List of VPC information
|
|
107
|
+
:rtype: List[Dict[str, Any]]
|
|
108
|
+
"""
|
|
109
|
+
vpcs = []
|
|
110
|
+
try:
|
|
111
|
+
paginator = client.get_paginator("describe_vpcs")
|
|
112
|
+
|
|
113
|
+
for page in paginator.paginate():
|
|
114
|
+
for vpc in page.get("Vpcs", []):
|
|
115
|
+
# Filter by account ID if specified
|
|
116
|
+
if self.account_id and not self._matches_account_id(vpc.get("OwnerId", "")):
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
# Filter by tags if specified
|
|
120
|
+
vpc_tags = self._convert_tags_to_dict(vpc.get("Tags", []))
|
|
121
|
+
if self.tags and not self._matches_tags(vpc_tags):
|
|
122
|
+
logger.debug(f"Skipping VPC {vpc.get('VpcId')} - does not match tag filters")
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
vpc_dict = {
|
|
126
|
+
"Region": self.region,
|
|
127
|
+
"VpcId": vpc.get("VpcId"),
|
|
128
|
+
"OwnerId": vpc.get("OwnerId"),
|
|
129
|
+
"CidrBlock": vpc.get("CidrBlock"),
|
|
130
|
+
"CidrBlockAssociationSet": vpc.get("CidrBlockAssociationSet", []),
|
|
131
|
+
"Ipv6CidrBlockAssociationSet": vpc.get("Ipv6CidrBlockAssociationSet", []),
|
|
132
|
+
"State": vpc.get("State"),
|
|
133
|
+
"IsDefault": vpc.get("IsDefault", False),
|
|
134
|
+
"DhcpOptionsId": vpc.get("DhcpOptionsId"),
|
|
135
|
+
"InstanceTenancy": vpc.get("InstanceTenancy"),
|
|
136
|
+
"Tags": vpc.get("Tags", []),
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Get VPC attributes
|
|
140
|
+
vpc_attributes = self._get_vpc_attributes(client, vpc["VpcId"])
|
|
141
|
+
vpc_dict.update(vpc_attributes)
|
|
142
|
+
|
|
143
|
+
vpcs.append(vpc_dict)
|
|
144
|
+
|
|
145
|
+
except ClientError as e:
|
|
146
|
+
if e.response["Error"]["Code"] == "UnauthorizedOperation":
|
|
147
|
+
logger.warning(f"Unauthorized to list VPCs in {self.region}")
|
|
148
|
+
else:
|
|
149
|
+
logger.error(f"Error listing VPCs: {e}")
|
|
150
|
+
|
|
151
|
+
return vpcs
|
|
152
|
+
|
|
153
|
+
def _get_vpc_attributes(self, client: Any, vpc_id: str) -> Dict[str, Any]:
|
|
154
|
+
"""
|
|
155
|
+
Get VPC attributes.
|
|
156
|
+
|
|
157
|
+
:param client: EC2 client
|
|
158
|
+
:param str vpc_id: VPC ID
|
|
159
|
+
:return: VPC attributes
|
|
160
|
+
:rtype: Dict[str, Any]
|
|
161
|
+
"""
|
|
162
|
+
attributes = {}
|
|
163
|
+
try:
|
|
164
|
+
# Get DNS support
|
|
165
|
+
dns_support = client.describe_vpc_attribute(VpcId=vpc_id, Attribute="enableDnsSupport")
|
|
166
|
+
attributes["EnableDnsSupport"] = dns_support.get("EnableDnsSupport", {}).get("Value", False)
|
|
167
|
+
|
|
168
|
+
# Get DNS hostnames
|
|
169
|
+
dns_hostnames = client.describe_vpc_attribute(VpcId=vpc_id, Attribute="enableDnsHostnames")
|
|
170
|
+
attributes["EnableDnsHostnames"] = dns_hostnames.get("EnableDnsHostnames", {}).get("Value", False)
|
|
171
|
+
|
|
172
|
+
except ClientError as e:
|
|
173
|
+
if e.response["Error"]["Code"] not in ["UnauthorizedOperation", "InvalidVpcID.NotFound"]:
|
|
174
|
+
logger.debug(f"Error getting attributes for VPC {vpc_id}: {e}")
|
|
175
|
+
|
|
176
|
+
return attributes
|
|
177
|
+
|
|
178
|
+
def _list_subnets(self, client: Any) -> List[Dict[str, Any]]:
|
|
179
|
+
"""
|
|
180
|
+
List subnets.
|
|
181
|
+
|
|
182
|
+
:param client: EC2 client
|
|
183
|
+
:return: List of subnet information
|
|
184
|
+
:rtype: List[Dict[str, Any]]
|
|
185
|
+
"""
|
|
186
|
+
subnets = []
|
|
187
|
+
try:
|
|
188
|
+
paginator = client.get_paginator("describe_subnets")
|
|
189
|
+
|
|
190
|
+
for page in paginator.paginate():
|
|
191
|
+
for subnet in page.get("Subnets", []):
|
|
192
|
+
# Filter by account ID if specified
|
|
193
|
+
if self.account_id and not self._matches_account_id(subnet.get("OwnerId", "")):
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
# Filter by tags if specified
|
|
197
|
+
subnet_tags = self._convert_tags_to_dict(subnet.get("Tags", []))
|
|
198
|
+
if self.tags and not self._matches_tags(subnet_tags):
|
|
199
|
+
logger.debug(f"Skipping subnet {subnet.get('SubnetId')} - does not match tag filters")
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
subnets.append(
|
|
203
|
+
{
|
|
204
|
+
"Region": self.region,
|
|
205
|
+
"SubnetId": subnet.get("SubnetId"),
|
|
206
|
+
"VpcId": subnet.get("VpcId"),
|
|
207
|
+
"OwnerId": subnet.get("OwnerId"),
|
|
208
|
+
"AvailabilityZone": subnet.get("AvailabilityZone"),
|
|
209
|
+
"AvailabilityZoneId": subnet.get("AvailabilityZoneId"),
|
|
210
|
+
"CidrBlock": subnet.get("CidrBlock"),
|
|
211
|
+
"Ipv6CidrBlockAssociationSet": subnet.get("Ipv6CidrBlockAssociationSet", []),
|
|
212
|
+
"State": subnet.get("State"),
|
|
213
|
+
"AvailableIpAddressCount": subnet.get("AvailableIpAddressCount"),
|
|
214
|
+
"DefaultForAz": subnet.get("DefaultForAz", False),
|
|
215
|
+
"MapPublicIpOnLaunch": subnet.get("MapPublicIpOnLaunch", False),
|
|
216
|
+
"AssignIpv6AddressOnCreation": subnet.get("AssignIpv6AddressOnCreation", False),
|
|
217
|
+
"Tags": subnet.get("Tags", []),
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
except ClientError as e:
|
|
222
|
+
if e.response["Error"]["Code"] == "UnauthorizedOperation":
|
|
223
|
+
logger.warning(f"Unauthorized to list subnets in {self.region}")
|
|
224
|
+
else:
|
|
225
|
+
logger.error(f"Error listing subnets: {e}")
|
|
226
|
+
|
|
227
|
+
return subnets
|
|
228
|
+
|
|
229
|
+
def _list_security_groups(self, client: Any) -> List[Dict[str, Any]]:
|
|
230
|
+
"""
|
|
231
|
+
List security groups.
|
|
232
|
+
|
|
233
|
+
:param client: EC2 client
|
|
234
|
+
:return: List of security group information
|
|
235
|
+
:rtype: List[Dict[str, Any]]
|
|
236
|
+
"""
|
|
237
|
+
security_groups = []
|
|
238
|
+
try:
|
|
239
|
+
paginator = client.get_paginator("describe_security_groups")
|
|
240
|
+
|
|
241
|
+
for page in paginator.paginate():
|
|
242
|
+
for sg in page.get("SecurityGroups", []):
|
|
243
|
+
# Filter by account ID if specified
|
|
244
|
+
if self.account_id and not self._matches_account_id(sg.get("OwnerId", "")):
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
# Filter by tags if specified
|
|
248
|
+
sg_tags = self._convert_tags_to_dict(sg.get("Tags", []))
|
|
249
|
+
if self.tags and not self._matches_tags(sg_tags):
|
|
250
|
+
logger.debug(f"Skipping security group {sg.get('GroupId')} - does not match tag filters")
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
security_groups.append(
|
|
254
|
+
{
|
|
255
|
+
"Region": self.region,
|
|
256
|
+
"GroupId": sg.get("GroupId"),
|
|
257
|
+
"GroupName": sg.get("GroupName"),
|
|
258
|
+
"VpcId": sg.get("VpcId"),
|
|
259
|
+
"OwnerId": sg.get("OwnerId"),
|
|
260
|
+
"Description": sg.get("Description"),
|
|
261
|
+
"IpPermissions": sg.get("IpPermissions", []),
|
|
262
|
+
"IpPermissionsEgress": sg.get("IpPermissionsEgress", []),
|
|
263
|
+
"Tags": sg.get("Tags", []),
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
except ClientError as e:
|
|
268
|
+
if e.response["Error"]["Code"] == "UnauthorizedOperation":
|
|
269
|
+
logger.warning(f"Unauthorized to list security groups in {self.region}")
|
|
270
|
+
else:
|
|
271
|
+
logger.error(f"Error listing security groups: {e}")
|
|
272
|
+
|
|
273
|
+
return security_groups
|
|
274
|
+
|
|
275
|
+
def _list_network_acls(self, client: Any) -> List[Dict[str, Any]]:
|
|
276
|
+
"""
|
|
277
|
+
List network ACLs.
|
|
278
|
+
|
|
279
|
+
:param client: EC2 client
|
|
280
|
+
:return: List of network ACL information
|
|
281
|
+
:rtype: List[Dict[str, Any]]
|
|
282
|
+
"""
|
|
283
|
+
network_acls = []
|
|
284
|
+
try:
|
|
285
|
+
paginator = client.get_paginator("describe_network_acls")
|
|
286
|
+
|
|
287
|
+
for page in paginator.paginate():
|
|
288
|
+
for acl in page.get("NetworkAcls", []):
|
|
289
|
+
# Filter by account ID if specified
|
|
290
|
+
if self.account_id and not self._matches_account_id(acl.get("OwnerId", "")):
|
|
291
|
+
continue
|
|
292
|
+
|
|
293
|
+
# Filter by tags if specified
|
|
294
|
+
acl_tags = self._convert_tags_to_dict(acl.get("Tags", []))
|
|
295
|
+
if self.tags and not self._matches_tags(acl_tags):
|
|
296
|
+
logger.debug(f"Skipping network ACL {acl.get('NetworkAclId')} - does not match tag filters")
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
network_acls.append(
|
|
300
|
+
{
|
|
301
|
+
"Region": self.region,
|
|
302
|
+
"NetworkAclId": acl.get("NetworkAclId"),
|
|
303
|
+
"VpcId": acl.get("VpcId"),
|
|
304
|
+
"OwnerId": acl.get("OwnerId"),
|
|
305
|
+
"IsDefault": acl.get("IsDefault", False),
|
|
306
|
+
"Entries": acl.get("Entries", []),
|
|
307
|
+
"Associations": acl.get("Associations", []),
|
|
308
|
+
"Tags": acl.get("Tags", []),
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
except ClientError as e:
|
|
313
|
+
if e.response["Error"]["Code"] == "UnauthorizedOperation":
|
|
314
|
+
logger.warning(f"Unauthorized to list network ACLs in {self.region}")
|
|
315
|
+
else:
|
|
316
|
+
logger.error(f"Error listing network ACLs: {e}")
|
|
317
|
+
|
|
318
|
+
return network_acls
|
|
319
|
+
|
|
320
|
+
def _list_route_tables(self, client: Any) -> List[Dict[str, Any]]:
|
|
321
|
+
"""
|
|
322
|
+
List route tables.
|
|
323
|
+
|
|
324
|
+
:param client: EC2 client
|
|
325
|
+
:return: List of route table information
|
|
326
|
+
:rtype: List[Dict[str, Any]]
|
|
327
|
+
"""
|
|
328
|
+
route_tables = []
|
|
329
|
+
try:
|
|
330
|
+
paginator = client.get_paginator("describe_route_tables")
|
|
331
|
+
|
|
332
|
+
for page in paginator.paginate():
|
|
333
|
+
for rt in page.get("RouteTables", []):
|
|
334
|
+
# Filter by account ID if specified
|
|
335
|
+
if self.account_id and not self._matches_account_id(rt.get("OwnerId", "")):
|
|
336
|
+
continue
|
|
337
|
+
|
|
338
|
+
# Filter by tags if specified
|
|
339
|
+
rt_tags = self._convert_tags_to_dict(rt.get("Tags", []))
|
|
340
|
+
if self.tags and not self._matches_tags(rt_tags):
|
|
341
|
+
logger.debug(f"Skipping route table {rt.get('RouteTableId')} - does not match tag filters")
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
route_tables.append(
|
|
345
|
+
{
|
|
346
|
+
"Region": self.region,
|
|
347
|
+
"RouteTableId": rt.get("RouteTableId"),
|
|
348
|
+
"VpcId": rt.get("VpcId"),
|
|
349
|
+
"OwnerId": rt.get("OwnerId"),
|
|
350
|
+
"Routes": rt.get("Routes", []),
|
|
351
|
+
"Associations": rt.get("Associations", []),
|
|
352
|
+
"PropagatingVgws": rt.get("PropagatingVgws", []),
|
|
353
|
+
"Tags": rt.get("Tags", []),
|
|
354
|
+
}
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
except ClientError as e:
|
|
358
|
+
if e.response["Error"]["Code"] == "UnauthorizedOperation":
|
|
359
|
+
logger.warning(f"Unauthorized to list route tables in {self.region}")
|
|
360
|
+
else:
|
|
361
|
+
logger.error(f"Error listing route tables: {e}")
|
|
362
|
+
|
|
363
|
+
return route_tables
|
|
364
|
+
|
|
365
|
+
def _list_internet_gateways(self, client: Any) -> List[Dict[str, Any]]:
|
|
366
|
+
"""
|
|
367
|
+
List internet gateways.
|
|
368
|
+
|
|
369
|
+
:param client: EC2 client
|
|
370
|
+
:return: List of internet gateway information
|
|
371
|
+
:rtype: List[Dict[str, Any]]
|
|
372
|
+
"""
|
|
373
|
+
internet_gateways = []
|
|
374
|
+
try:
|
|
375
|
+
paginator = client.get_paginator("describe_internet_gateways")
|
|
376
|
+
|
|
377
|
+
for page in paginator.paginate():
|
|
378
|
+
for igw in page.get("InternetGateways", []):
|
|
379
|
+
# Filter by account ID if specified
|
|
380
|
+
if self.account_id and not self._matches_account_id(igw.get("OwnerId", "")):
|
|
381
|
+
continue
|
|
382
|
+
|
|
383
|
+
# Filter by tags if specified
|
|
384
|
+
igw_tags = self._convert_tags_to_dict(igw.get("Tags", []))
|
|
385
|
+
if self.tags and not self._matches_tags(igw_tags):
|
|
386
|
+
logger.debug(
|
|
387
|
+
f"Skipping internet gateway {igw.get('InternetGatewayId')} - does not match tag filters"
|
|
388
|
+
)
|
|
389
|
+
continue
|
|
390
|
+
|
|
391
|
+
internet_gateways.append(
|
|
392
|
+
{
|
|
393
|
+
"Region": self.region,
|
|
394
|
+
"InternetGatewayId": igw.get("InternetGatewayId"),
|
|
395
|
+
"OwnerId": igw.get("OwnerId"),
|
|
396
|
+
"Attachments": igw.get("Attachments", []),
|
|
397
|
+
"Tags": igw.get("Tags", []),
|
|
398
|
+
}
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
except ClientError as e:
|
|
402
|
+
if e.response["Error"]["Code"] == "UnauthorizedOperation":
|
|
403
|
+
logger.warning(f"Unauthorized to list internet gateways in {self.region}")
|
|
404
|
+
else:
|
|
405
|
+
logger.error(f"Error listing internet gateways: {e}")
|
|
406
|
+
|
|
407
|
+
return internet_gateways
|
|
408
|
+
|
|
409
|
+
def _list_nat_gateways(self, client: Any) -> List[Dict[str, Any]]:
|
|
410
|
+
"""
|
|
411
|
+
List NAT gateways.
|
|
412
|
+
|
|
413
|
+
:param client: EC2 client
|
|
414
|
+
:return: List of NAT gateway information
|
|
415
|
+
:rtype: List[Dict[str, Any]]
|
|
416
|
+
"""
|
|
417
|
+
nat_gateways = []
|
|
418
|
+
try:
|
|
419
|
+
paginator = client.get_paginator("describe_nat_gateways")
|
|
420
|
+
|
|
421
|
+
for page in paginator.paginate():
|
|
422
|
+
for nat in page.get("NatGateways", []):
|
|
423
|
+
# Filter by tags if specified
|
|
424
|
+
nat_tags = self._convert_tags_to_dict(nat.get("Tags", []))
|
|
425
|
+
if self.tags and not self._matches_tags(nat_tags):
|
|
426
|
+
logger.debug(f"Skipping NAT gateway {nat.get('NatGatewayId')} - does not match tag filters")
|
|
427
|
+
continue
|
|
428
|
+
|
|
429
|
+
nat_gateways.append(
|
|
430
|
+
{
|
|
431
|
+
"Region": self.region,
|
|
432
|
+
"NatGatewayId": nat.get("NatGatewayId"),
|
|
433
|
+
"VpcId": nat.get("VpcId"),
|
|
434
|
+
"SubnetId": nat.get("SubnetId"),
|
|
435
|
+
"State": nat.get("State"),
|
|
436
|
+
"ConnectivityType": nat.get("ConnectivityType"),
|
|
437
|
+
"NatGatewayAddresses": nat.get("NatGatewayAddresses", []),
|
|
438
|
+
"CreateTime": str(nat.get("CreateTime")),
|
|
439
|
+
"DeleteTime": str(nat.get("DeleteTime")) if nat.get("DeleteTime") else None,
|
|
440
|
+
"Tags": nat.get("Tags", []),
|
|
441
|
+
}
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
except ClientError as e:
|
|
445
|
+
if e.response["Error"]["Code"] == "UnauthorizedOperation":
|
|
446
|
+
logger.warning(f"Unauthorized to list NAT gateways in {self.region}")
|
|
447
|
+
else:
|
|
448
|
+
logger.error(f"Error listing NAT gateways: {e}")
|
|
449
|
+
|
|
450
|
+
return nat_gateways
|
|
451
|
+
|
|
452
|
+
def _list_vpc_endpoints(self, client: Any) -> List[Dict[str, Any]]:
|
|
453
|
+
"""
|
|
454
|
+
List VPC endpoints.
|
|
455
|
+
|
|
456
|
+
:param client: EC2 client
|
|
457
|
+
:return: List of VPC endpoint information
|
|
458
|
+
:rtype: List[Dict[str, Any]]
|
|
459
|
+
"""
|
|
460
|
+
vpc_endpoints = []
|
|
461
|
+
try:
|
|
462
|
+
paginator = client.get_paginator("describe_vpc_endpoints")
|
|
463
|
+
|
|
464
|
+
for page in paginator.paginate():
|
|
465
|
+
for endpoint in page.get("VpcEndpoints", []):
|
|
466
|
+
# Filter by account ID if specified
|
|
467
|
+
if self.account_id and not self._matches_account_id(endpoint.get("OwnerId", "")):
|
|
468
|
+
continue
|
|
469
|
+
|
|
470
|
+
# Filter by tags if specified
|
|
471
|
+
endpoint_tags = self._convert_tags_to_dict(endpoint.get("Tags", []))
|
|
472
|
+
if self.tags and not self._matches_tags(endpoint_tags):
|
|
473
|
+
logger.debug(
|
|
474
|
+
f"Skipping VPC endpoint {endpoint.get('VpcEndpointId')} - does not match tag filters"
|
|
475
|
+
)
|
|
476
|
+
continue
|
|
477
|
+
|
|
478
|
+
vpc_endpoints.append(
|
|
479
|
+
{
|
|
480
|
+
"Region": self.region,
|
|
481
|
+
"VpcEndpointId": endpoint.get("VpcEndpointId"),
|
|
482
|
+
"VpcId": endpoint.get("VpcId"),
|
|
483
|
+
"OwnerId": endpoint.get("OwnerId"),
|
|
484
|
+
"ServiceName": endpoint.get("ServiceName"),
|
|
485
|
+
"VpcEndpointType": endpoint.get("VpcEndpointType"),
|
|
486
|
+
"State": endpoint.get("State"),
|
|
487
|
+
"PolicyDocument": endpoint.get("PolicyDocument"),
|
|
488
|
+
"SubnetIds": endpoint.get("SubnetIds", []),
|
|
489
|
+
"RouteTableIds": endpoint.get("RouteTableIds", []),
|
|
490
|
+
"Groups": endpoint.get("Groups", []),
|
|
491
|
+
"PrivateDnsEnabled": endpoint.get("PrivateDnsEnabled", False),
|
|
492
|
+
"DnsEntries": endpoint.get("DnsEntries", []),
|
|
493
|
+
"CreationTimestamp": str(endpoint.get("CreationTimestamp")),
|
|
494
|
+
"Tags": endpoint.get("Tags", []),
|
|
495
|
+
}
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
except ClientError as e:
|
|
499
|
+
if e.response["Error"]["Code"] == "UnauthorizedOperation":
|
|
500
|
+
logger.warning(f"Unauthorized to list VPC endpoints in {self.region}")
|
|
501
|
+
else:
|
|
502
|
+
logger.error(f"Error listing VPC endpoints: {e}")
|
|
503
|
+
|
|
504
|
+
return vpc_endpoints
|
|
505
|
+
|
|
506
|
+
def _list_vpc_peering_connections(self, client: Any) -> List[Dict[str, Any]]:
|
|
507
|
+
"""
|
|
508
|
+
List VPC peering connections.
|
|
509
|
+
|
|
510
|
+
:param client: EC2 client
|
|
511
|
+
:return: List of VPC peering connection information
|
|
512
|
+
:rtype: List[Dict[str, Any]]
|
|
513
|
+
"""
|
|
514
|
+
vpc_peering = []
|
|
515
|
+
try:
|
|
516
|
+
paginator = client.get_paginator("describe_vpc_peering_connections")
|
|
517
|
+
|
|
518
|
+
for page in paginator.paginate():
|
|
519
|
+
for peering in page.get("VpcPeeringConnections", []):
|
|
520
|
+
if self._should_skip_peering_connection(peering):
|
|
521
|
+
continue
|
|
522
|
+
|
|
523
|
+
peering_dict = self._build_peering_connection_dict(peering)
|
|
524
|
+
vpc_peering.append(peering_dict)
|
|
525
|
+
|
|
526
|
+
except ClientError as e:
|
|
527
|
+
if e.response["Error"]["Code"] == "UnauthorizedOperation":
|
|
528
|
+
logger.warning(f"Unauthorized to list VPC peering connections in {self.region}")
|
|
529
|
+
else:
|
|
530
|
+
logger.error(f"Error listing VPC peering connections: {e}")
|
|
531
|
+
|
|
532
|
+
return vpc_peering
|
|
533
|
+
|
|
534
|
+
def _should_skip_peering_connection(self, peering: Dict[str, Any]) -> bool:
|
|
535
|
+
"""
|
|
536
|
+
Check if peering connection should be skipped based on filters.
|
|
537
|
+
|
|
538
|
+
:param dict peering: Peering connection data
|
|
539
|
+
:return: True if should be skipped
|
|
540
|
+
:rtype: bool
|
|
541
|
+
"""
|
|
542
|
+
if not self._matches_peering_account_filter(peering):
|
|
543
|
+
return True
|
|
544
|
+
|
|
545
|
+
if not self._matches_peering_tag_filter(peering):
|
|
546
|
+
return True
|
|
547
|
+
|
|
548
|
+
return False
|
|
549
|
+
|
|
550
|
+
def _matches_peering_account_filter(self, peering: Dict[str, Any]) -> bool:
|
|
551
|
+
"""
|
|
552
|
+
Check if peering connection matches account ID filter.
|
|
553
|
+
|
|
554
|
+
:param dict peering: Peering connection data
|
|
555
|
+
:return: True if matches or no filter specified
|
|
556
|
+
:rtype: bool
|
|
557
|
+
"""
|
|
558
|
+
if not self.account_id:
|
|
559
|
+
return True
|
|
560
|
+
|
|
561
|
+
requester_owner = peering.get("RequesterVpcInfo", {}).get("OwnerId", "")
|
|
562
|
+
accepter_owner = peering.get("AccepterVpcInfo", {}).get("OwnerId", "")
|
|
563
|
+
return self._matches_account_id(requester_owner) or self._matches_account_id(accepter_owner)
|
|
564
|
+
|
|
565
|
+
def _matches_peering_tag_filter(self, peering: Dict[str, Any]) -> bool:
|
|
566
|
+
"""
|
|
567
|
+
Check if peering connection matches tag filter.
|
|
568
|
+
|
|
569
|
+
:param dict peering: Peering connection data
|
|
570
|
+
:return: True if matches or no filter specified
|
|
571
|
+
:rtype: bool
|
|
572
|
+
"""
|
|
573
|
+
if not self.tags:
|
|
574
|
+
return True
|
|
575
|
+
|
|
576
|
+
peering_tags = self._convert_tags_to_dict(peering.get("Tags", []))
|
|
577
|
+
matches = self._matches_tags(peering_tags)
|
|
578
|
+
|
|
579
|
+
if not matches:
|
|
580
|
+
logger.debug(f"Skipping VPC peering {peering.get('VpcPeeringConnectionId')} - does not match tag filters")
|
|
581
|
+
|
|
582
|
+
return matches
|
|
583
|
+
|
|
584
|
+
def _build_peering_connection_dict(self, peering: Dict[str, Any]) -> Dict[str, Any]:
|
|
585
|
+
"""
|
|
586
|
+
Build dictionary representation of peering connection.
|
|
587
|
+
|
|
588
|
+
:param dict peering: Peering connection data
|
|
589
|
+
:return: Formatted peering connection dictionary
|
|
590
|
+
:rtype: Dict[str, Any]
|
|
591
|
+
"""
|
|
592
|
+
requester_info = peering.get("RequesterVpcInfo", {})
|
|
593
|
+
accepter_info = peering.get("AccepterVpcInfo", {})
|
|
594
|
+
status_info = peering.get("Status", {})
|
|
595
|
+
|
|
596
|
+
return {
|
|
597
|
+
"Region": self.region,
|
|
598
|
+
"VpcPeeringConnectionId": peering.get("VpcPeeringConnectionId"),
|
|
599
|
+
"RequesterVpcInfo": {
|
|
600
|
+
"VpcId": requester_info.get("VpcId"),
|
|
601
|
+
"OwnerId": requester_info.get("OwnerId"),
|
|
602
|
+
"CidrBlock": requester_info.get("CidrBlock"),
|
|
603
|
+
"Region": requester_info.get("Region"),
|
|
604
|
+
},
|
|
605
|
+
"AccepterVpcInfo": {
|
|
606
|
+
"VpcId": accepter_info.get("VpcId"),
|
|
607
|
+
"OwnerId": accepter_info.get("OwnerId"),
|
|
608
|
+
"CidrBlock": accepter_info.get("CidrBlock"),
|
|
609
|
+
"Region": accepter_info.get("Region"),
|
|
610
|
+
},
|
|
611
|
+
"Status": status_info.get("Code"),
|
|
612
|
+
"StatusMessage": status_info.get("Message"),
|
|
613
|
+
"ExpirationTime": str(peering.get("ExpirationTime")) if peering.get("ExpirationTime") else None,
|
|
614
|
+
"Tags": peering.get("Tags", []),
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
def _matches_account_id(self, owner_id: str) -> bool:
|
|
618
|
+
"""
|
|
619
|
+
Check if owner ID matches the specified account ID.
|
|
620
|
+
|
|
621
|
+
:param str owner_id: Owner ID to check
|
|
622
|
+
:return: True if matches or no account_id filter specified
|
|
623
|
+
:rtype: bool
|
|
624
|
+
"""
|
|
625
|
+
if not self.account_id:
|
|
626
|
+
return True
|
|
627
|
+
return owner_id == self.account_id
|
|
628
|
+
|
|
629
|
+
def _convert_tags_to_dict(self, tags_list: List[Dict[str, str]]) -> Dict[str, str]:
|
|
630
|
+
"""
|
|
631
|
+
Convert AWS tags list format to dictionary.
|
|
632
|
+
|
|
633
|
+
:param list tags_list: List of tags in format [{"Key": "k", "Value": "v"}]
|
|
634
|
+
:return: Dictionary of tags {key: value}
|
|
635
|
+
:rtype: Dict[str, str]
|
|
636
|
+
"""
|
|
637
|
+
return {tag.get("Key", ""): tag.get("Value", "") for tag in tags_list}
|
|
638
|
+
|
|
639
|
+
def _matches_tags(self, resource_tags: Dict[str, str]) -> bool:
|
|
640
|
+
"""
|
|
641
|
+
Check if resource tags match the specified filter tags.
|
|
642
|
+
|
|
643
|
+
:param dict resource_tags: Tags on the resource
|
|
644
|
+
:return: True if all filter tags match
|
|
645
|
+
:rtype: bool
|
|
646
|
+
"""
|
|
647
|
+
if not self.tags:
|
|
648
|
+
return True
|
|
649
|
+
|
|
650
|
+
# All filter tags must match
|
|
651
|
+
for key, value in self.tags.items():
|
|
652
|
+
if resource_tags.get(key) != value:
|
|
653
|
+
return False
|
|
654
|
+
|
|
655
|
+
return True
|