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.

Files changed (140) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/application.py +1 -0
  3. regscale/core/app/internal/control_editor.py +73 -21
  4. regscale/core/app/internal/login.py +4 -1
  5. regscale/core/app/internal/model_editor.py +219 -64
  6. regscale/core/app/utils/app_utils.py +11 -2
  7. regscale/core/login.py +21 -4
  8. regscale/core/utils/date.py +77 -1
  9. regscale/dev/cli.py +26 -0
  10. regscale/dev/version.py +72 -0
  11. regscale/integrations/commercial/__init__.py +15 -1
  12. regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
  13. regscale/integrations/commercial/amazon/amazon/common.py +204 -0
  14. regscale/integrations/commercial/amazon/common.py +48 -58
  15. regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
  16. regscale/integrations/commercial/aws/cli.py +3093 -55
  17. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  18. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  19. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  20. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  21. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  22. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  23. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  24. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  25. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  26. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  27. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  28. regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
  29. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  30. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  31. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  32. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  33. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  34. regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
  35. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  36. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  37. regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
  38. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  39. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  40. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  41. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  42. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  43. regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
  44. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  45. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  46. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  47. regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
  48. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  49. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  50. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  51. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  52. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  53. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  54. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  55. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  56. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  57. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  58. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  59. regscale/integrations/commercial/aws/scanner.py +853 -205
  60. regscale/integrations/commercial/aws/security_hub.py +319 -0
  61. regscale/integrations/commercial/aws/session_manager.py +282 -0
  62. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  63. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  64. regscale/integrations/commercial/synqly/query_builder.py +4 -1
  65. regscale/integrations/compliance_integration.py +308 -38
  66. regscale/integrations/control_matcher.py +78 -23
  67. regscale/integrations/due_date_handler.py +3 -0
  68. regscale/integrations/public/csam/csam.py +572 -763
  69. regscale/integrations/public/csam/csam_agency_defined.py +179 -0
  70. regscale/integrations/public/csam/csam_common.py +154 -0
  71. regscale/integrations/public/csam/csam_controls.py +432 -0
  72. regscale/integrations/public/csam/csam_poam.py +124 -0
  73. regscale/integrations/public/fedramp/click.py +17 -4
  74. regscale/integrations/public/fedramp/fedramp_cis_crm.py +271 -62
  75. regscale/integrations/public/fedramp/poam/scanner.py +74 -7
  76. regscale/integrations/scanner_integration.py +415 -85
  77. regscale/models/integration_models/cisa_kev_data.json +80 -20
  78. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  79. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +44 -3
  80. regscale/models/integration_models/synqly_models/ocsf_mapper.py +41 -12
  81. regscale/models/platform.py +3 -0
  82. regscale/models/regscale_models/__init__.py +5 -0
  83. regscale/models/regscale_models/assessment.py +2 -1
  84. regscale/models/regscale_models/component.py +1 -1
  85. regscale/models/regscale_models/control_implementation.py +55 -24
  86. regscale/models/regscale_models/control_objective.py +74 -5
  87. regscale/models/regscale_models/file.py +2 -0
  88. regscale/models/regscale_models/issue.py +2 -5
  89. regscale/models/regscale_models/organization.py +3 -0
  90. regscale/models/regscale_models/regscale_model.py +17 -5
  91. regscale/models/regscale_models/security_plan.py +1 -0
  92. regscale/regscale.py +11 -1
  93. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
  94. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +140 -57
  95. tests/regscale/core/test_login.py +171 -4
  96. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  97. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  98. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  99. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  100. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  101. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  102. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  103. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  104. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  105. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  106. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  107. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  108. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  109. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  110. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  111. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  112. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  113. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  114. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  115. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  116. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  117. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  118. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  119. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  120. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  121. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  122. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  123. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  124. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  125. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  126. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  127. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  128. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  129. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  130. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  131. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  132. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  133. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  134. tests/regscale/integrations/commercial/test_aws.py +55 -56
  135. tests/regscale/integrations/test_control_matcher.py +24 -0
  136. tests/regscale/models/test_control_implementation.py +118 -3
  137. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
  138. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
  139. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
  140. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.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