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,447 @@
1
+ """AWS KMS 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 KMSCollector(BaseCollector):
14
+ """Collector for AWS KMS 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 KMS 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 KMS resources.
34
+
35
+ :return: Dictionary containing KMS key information
36
+ :rtype: Dict[str, Any]
37
+ """
38
+ result = {"Keys": [], "Aliases": []}
39
+
40
+ try:
41
+ client = self._get_client("kms")
42
+
43
+ # Get all keys
44
+ keys = self._list_keys(client)
45
+ result["Keys"] = keys
46
+
47
+ # Get all aliases
48
+ aliases = self._list_aliases(client)
49
+ result["Aliases"] = aliases
50
+
51
+ logger.info(f"Collected {len(keys)} KMS key(s), {len(aliases)} alias(es) from {self.region}")
52
+
53
+ except ClientError as e:
54
+ self._handle_error(e, "KMS keys")
55
+ except Exception as e:
56
+ logger.error(f"Unexpected error collecting KMS resources: {e}", exc_info=True)
57
+
58
+ return result
59
+
60
+ def _list_keys(self, client: Any) -> List[Dict[str, Any]]:
61
+ """
62
+ List KMS keys with enhanced details.
63
+
64
+ :param client: KMS client
65
+ :return: List of key information
66
+ :rtype: List[Dict[str, Any]]
67
+ """
68
+ keys = []
69
+ try:
70
+ paginator = client.get_paginator("list_keys")
71
+
72
+ for page in paginator.paginate():
73
+ for key in page.get("Keys", []):
74
+ key_id = key["KeyId"]
75
+ processed_key = self._process_key(client, key_id)
76
+ if processed_key:
77
+ keys.append(processed_key)
78
+
79
+ except ClientError as e:
80
+ self._handle_list_keys_error(e)
81
+
82
+ return keys
83
+
84
+ def _process_key(self, client: Any, key_id: str) -> Optional[Dict[str, Any]]:
85
+ """
86
+ Process a single KMS key and return its details if it passes filters.
87
+
88
+ :param client: KMS client
89
+ :param str key_id: Key ID to process
90
+ :return: Key information if it passes filters, None otherwise
91
+ :rtype: Optional[Dict[str, Any]]
92
+ """
93
+ try:
94
+ key_info = self._describe_key(client, key_id)
95
+ if not key_info:
96
+ return None
97
+
98
+ if not self._passes_account_filter(key_info):
99
+ return None
100
+
101
+ self._enrich_key_info(client, key_id, key_info)
102
+
103
+ if not self._passes_tag_filter(client, key_id):
104
+ return None
105
+
106
+ return key_info
107
+
108
+ except ClientError as e:
109
+ self._handle_key_processing_error(e, key_id)
110
+ return None
111
+
112
+ def _passes_account_filter(self, key_info: Dict[str, Any]) -> bool:
113
+ """
114
+ Check if key passes account ID filter.
115
+
116
+ :param dict key_info: Key information dictionary
117
+ :return: True if key passes account filter
118
+ :rtype: bool
119
+ """
120
+ if not self.account_id:
121
+ return True
122
+ return self._matches_account_id(key_info.get("Arn", ""))
123
+
124
+ def _enrich_key_info(self, client: Any, key_id: str, key_info: Dict[str, Any]) -> None:
125
+ """
126
+ Enrich key information with additional details.
127
+
128
+ :param client: KMS client
129
+ :param str key_id: Key ID
130
+ :param dict key_info: Key information dictionary to enrich
131
+ """
132
+ key_info["Region"] = self.region
133
+ key_info["RotationEnabled"] = self._get_key_rotation_status(client, key_id)
134
+ key_info["Policy"] = self._get_key_policy(client, key_id)
135
+
136
+ grants = self._list_grants(client, key_id)
137
+ key_info["GrantCount"] = len(grants)
138
+
139
+ tags = self._list_resource_tags(client, key_id)
140
+ key_info["Tags"] = tags
141
+
142
+ def _passes_tag_filter(self, client: Any, key_id: str) -> bool:
143
+ """
144
+ Check if key passes tag filter.
145
+
146
+ :param client: KMS client
147
+ :param str key_id: Key ID
148
+ :return: True if key passes tag filter
149
+ :rtype: bool
150
+ """
151
+ if not self.tags:
152
+ return True
153
+
154
+ key_tags = self._get_key_tags(client, key_id)
155
+ if not self._matches_tags(key_tags):
156
+ logger.debug(f"Skipping key {key_id} - does not match tag filters")
157
+ return False
158
+ return True
159
+
160
+ def _handle_list_keys_error(self, error: ClientError) -> None:
161
+ """
162
+ Handle errors from list_keys operation.
163
+
164
+ :param error: Client error exception
165
+ """
166
+ if error.response["Error"]["Code"] == "AccessDeniedException":
167
+ logger.warning(f"Access denied to list KMS keys in {self.region}")
168
+ else:
169
+ logger.error(f"Error listing KMS keys: {error}")
170
+
171
+ def _handle_key_processing_error(self, error: ClientError, key_id: str) -> None:
172
+ """
173
+ Handle errors from processing individual keys.
174
+
175
+ :param error: Client error exception
176
+ :param str key_id: Key ID being processed
177
+ """
178
+ if error.response["Error"]["Code"] not in ["NotFoundException", "AccessDeniedException"]:
179
+ logger.error(f"Error getting details for key {key_id}: {error}")
180
+
181
+ def _describe_key(self, client: Any, key_id: str) -> Optional[Dict[str, Any]]:
182
+ """
183
+ Get key metadata.
184
+
185
+ :param client: KMS client
186
+ :param str key_id: Key ID
187
+ :return: Key metadata
188
+ :rtype: Optional[Dict[str, Any]]
189
+ """
190
+ try:
191
+ response = client.describe_key(KeyId=key_id)
192
+ key_metadata = response["KeyMetadata"]
193
+
194
+ return {
195
+ "KeyId": key_metadata.get("KeyId"),
196
+ "Arn": key_metadata.get("Arn"),
197
+ "Description": key_metadata.get("Description"),
198
+ "Enabled": key_metadata.get("Enabled"),
199
+ "KeyState": key_metadata.get("KeyState"),
200
+ "CreationDate": str(key_metadata.get("CreationDate")),
201
+ "DeletionDate": str(key_metadata.get("DeletionDate")) if key_metadata.get("DeletionDate") else None,
202
+ "Origin": key_metadata.get("Origin"),
203
+ "KeyManager": key_metadata.get("KeyManager"),
204
+ "KeySpec": key_metadata.get("KeySpec"),
205
+ "KeyUsage": key_metadata.get("KeyUsage"),
206
+ "MultiRegion": key_metadata.get("MultiRegion", False),
207
+ "MultiRegionConfiguration": key_metadata.get("MultiRegionConfiguration"),
208
+ }
209
+ except ClientError as e:
210
+ if e.response["Error"]["Code"] not in ["NotFoundException", "AccessDeniedException"]:
211
+ logger.error(f"Error describing key {key_id}: {e}")
212
+ return None
213
+
214
+ def _get_key_rotation_status(self, client: Any, key_id: str) -> bool:
215
+ """
216
+ Get key rotation status.
217
+
218
+ :param client: KMS client
219
+ :param str key_id: Key ID
220
+ :return: Rotation enabled status
221
+ :rtype: bool
222
+ """
223
+ try:
224
+ response = client.get_key_rotation_status(KeyId=key_id)
225
+ return response.get("KeyRotationEnabled", False)
226
+ except ClientError as e:
227
+ if e.response["Error"]["Code"] in [
228
+ "NotFoundException",
229
+ "AccessDeniedException",
230
+ "UnsupportedOperationException",
231
+ ]:
232
+ return False
233
+ logger.debug(f"Error getting rotation status for key {key_id}: {e}")
234
+ return False
235
+
236
+ def _get_key_policy(self, client: Any, key_id: str) -> Optional[str]:
237
+ """
238
+ Get key policy.
239
+
240
+ :param client: KMS client
241
+ :param str key_id: Key ID
242
+ :return: Key policy as JSON string
243
+ :rtype: Optional[str]
244
+ """
245
+ try:
246
+ response = client.get_key_policy(KeyId=key_id, PolicyName="default")
247
+ return response.get("Policy")
248
+ except ClientError as e:
249
+ if e.response["Error"]["Code"] not in ["NotFoundException", "AccessDeniedException"]:
250
+ logger.debug(f"Error getting policy for key {key_id}: {e}")
251
+ return None
252
+
253
+ def _list_grants(self, client: Any, key_id: str) -> List[Dict[str, Any]]:
254
+ """
255
+ List grants for a key.
256
+
257
+ :param client: KMS client
258
+ :param str key_id: Key ID
259
+ :return: List of grants
260
+ :rtype: List[Dict[str, Any]]
261
+ """
262
+ grants = []
263
+ try:
264
+ paginator = client.get_paginator("list_grants")
265
+
266
+ for page in paginator.paginate(KeyId=key_id):
267
+ grants.extend(page.get("Grants", []))
268
+
269
+ except ClientError as e:
270
+ if e.response["Error"]["Code"] not in ["NotFoundException", "AccessDeniedException"]:
271
+ logger.debug(f"Error listing grants for key {key_id}: {e}")
272
+
273
+ return grants
274
+
275
+ def _list_resource_tags(self, client: Any, key_id: str) -> List[Dict[str, str]]:
276
+ """
277
+ List tags for a key.
278
+
279
+ :param client: KMS client
280
+ :param str key_id: Key ID
281
+ :return: List of tags
282
+ :rtype: List[Dict[str, str]]
283
+ """
284
+ try:
285
+ response = client.list_resource_tags(KeyId=key_id)
286
+ return response.get("Tags", [])
287
+ except ClientError as e:
288
+ if e.response["Error"]["Code"] not in ["NotFoundException", "AccessDeniedException"]:
289
+ logger.debug(f"Error listing tags for key {key_id}: {e}")
290
+ return []
291
+
292
+ def _list_aliases(self, client: Any) -> List[Dict[str, Any]]:
293
+ """
294
+ List KMS aliases.
295
+
296
+ :param client: KMS client
297
+ :return: List of aliases
298
+ :rtype: List[Dict[str, Any]]
299
+ """
300
+ aliases = []
301
+ try:
302
+ paginator = client.get_paginator("list_aliases")
303
+
304
+ for page in paginator.paginate():
305
+ for alias in page.get("Aliases", []):
306
+ processed_alias = self._process_alias(client, alias)
307
+ if processed_alias:
308
+ aliases.append(processed_alias)
309
+
310
+ except ClientError as e:
311
+ self._handle_list_aliases_error(e)
312
+
313
+ return aliases
314
+
315
+ def _process_alias(self, client: Any, alias: Dict[str, Any]) -> Optional[Dict[str, Any]]:
316
+ """
317
+ Process a single KMS alias and return its details if it passes filters.
318
+
319
+ :param client: KMS client
320
+ :param dict alias: Alias information from AWS API
321
+ :return: Alias dictionary if it passes filters, None otherwise
322
+ :rtype: Optional[Dict[str, Any]]
323
+ """
324
+ if not self._alias_passes_filters(client, alias):
325
+ return None
326
+
327
+ return {
328
+ "Region": self.region,
329
+ "AliasName": alias.get("AliasName"),
330
+ "AliasArn": alias.get("AliasArn"),
331
+ "TargetKeyId": alias.get("TargetKeyId"),
332
+ }
333
+
334
+ def _alias_passes_filters(self, client: Any, alias: Dict[str, Any]) -> bool:
335
+ """
336
+ Check if alias passes account filtering rules.
337
+
338
+ :param client: KMS client
339
+ :param dict alias: Alias information
340
+ :return: True if alias passes filters
341
+ :rtype: bool
342
+ """
343
+ if not self.account_id:
344
+ return True
345
+
346
+ if self._is_aws_managed_alias(alias):
347
+ return False
348
+
349
+ return self._alias_target_matches_account(client, alias)
350
+
351
+ def _is_aws_managed_alias(self, alias: Dict[str, Any]) -> bool:
352
+ """
353
+ Check if alias is AWS-managed.
354
+
355
+ :param dict alias: Alias information
356
+ :return: True if alias is AWS-managed
357
+ :rtype: bool
358
+ """
359
+ alias_name = alias.get("AliasName", "")
360
+ return alias_name.startswith("alias/aws/")
361
+
362
+ def _alias_target_matches_account(self, client: Any, alias: Dict[str, Any]) -> bool:
363
+ """
364
+ Check if alias target key matches the account filter.
365
+
366
+ :param client: KMS client
367
+ :param dict alias: Alias information
368
+ :return: True if target key matches account or no target key exists
369
+ :rtype: bool
370
+ """
371
+ target_key_id = alias.get("TargetKeyId")
372
+ if not target_key_id:
373
+ return True
374
+
375
+ key_info = self._describe_key(client, target_key_id)
376
+ if not key_info:
377
+ return True
378
+
379
+ return self._matches_account_id(key_info.get("Arn", ""))
380
+
381
+ def _handle_list_aliases_error(self, error: ClientError) -> None:
382
+ """
383
+ Handle errors from list_aliases operation.
384
+
385
+ :param error: Client error exception
386
+ """
387
+ if error.response["Error"]["Code"] == "AccessDeniedException":
388
+ logger.warning(f"Access denied to list KMS aliases in {self.region}")
389
+ else:
390
+ logger.error(f"Error listing KMS aliases: {error}")
391
+
392
+ def _matches_account_id(self, arn: str) -> bool:
393
+ """
394
+ Check if ARN matches the specified account ID.
395
+
396
+ :param str arn: ARN to check
397
+ :return: True if matches or no account_id filter specified
398
+ :rtype: bool
399
+ """
400
+ if not self.account_id:
401
+ return True
402
+
403
+ # ARN format: arn:aws:kms:region:account-id:key/key-id
404
+ try:
405
+ arn_parts = arn.split(":")
406
+ if len(arn_parts) >= 5:
407
+ key_account_id = arn_parts[4]
408
+ return key_account_id == self.account_id
409
+ except (IndexError, AttributeError):
410
+ logger.warning(f"Could not parse account ID from KMS key ARN: {arn}")
411
+
412
+ return False
413
+
414
+ def _get_key_tags(self, client: Any, key_id: str) -> Dict[str, str]:
415
+ """
416
+ Get tags for a KMS key.
417
+
418
+ :param client: KMS client
419
+ :param str key_id: Key ID
420
+ :return: Dictionary of tags (Key -> Value)
421
+ :rtype: Dict[str, str]
422
+ """
423
+ try:
424
+ response = client.list_resource_tags(KeyId=key_id)
425
+ tags_list = response.get("Tags", [])
426
+ return {tag["TagKey"]: tag["TagValue"] for tag in tags_list}
427
+ except ClientError as e:
428
+ logger.debug(f"Error getting tags for key {key_id}: {e}")
429
+ return {}
430
+
431
+ def _matches_tags(self, resource_tags: Dict[str, str]) -> bool:
432
+ """
433
+ Check if resource tags match the specified filter tags.
434
+
435
+ :param dict resource_tags: Tags on the resource
436
+ :return: True if all filter tags match
437
+ :rtype: bool
438
+ """
439
+ if not self.tags:
440
+ return True
441
+
442
+ # All filter tags must match
443
+ for key, value in self.tags.items():
444
+ if resource_tags.get(key) != value:
445
+ return False
446
+
447
+ return True
@@ -1,75 +1,59 @@
1
1
  """AWS networking resource collectors."""
2
2
 
3
- from typing import Dict, List, Any
3
+ from typing import Dict, List, Any, Optional
4
4
 
5
+ from regscale.integrations.commercial.aws.inventory.resources.vpc import VPCCollector
5
6
  from ..base import BaseCollector
6
7
 
7
8
 
8
9
  class NetworkingCollector(BaseCollector):
9
10
  """Collector for AWS networking resources."""
10
11
 
11
- def get_vpcs(self) -> List[Dict[str, Any]]:
12
+ def __init__(
13
+ self,
14
+ session: Any,
15
+ region: str,
16
+ account_id: Optional[str] = None,
17
+ tags: Optional[Dict[str, str]] = None,
18
+ enabled_services: Optional[Dict[str, bool]] = None,
19
+ ):
12
20
  """
13
- Get information about VPCs.
21
+ Initialize networking collector.
14
22
 
15
- :return: List of VPC information
16
- :rtype: List[Dict[str, Any]]
23
+ :param session: AWS session to use for API calls
24
+ :param str region: AWS region to collect from
25
+ :param str account_id: Optional AWS account ID to filter resources
26
+ :param dict tags: Optional tags to filter resources (key-value pairs)
27
+ :param dict enabled_services: Optional dict of service names to boolean flags for enabling/disabling collection
17
28
  """
18
- vpcs = []
19
- try:
20
- ec2 = self._get_client("ec2")
21
- paginator = ec2.get_paginator("describe_vpcs")
29
+ super().__init__(session, region)
30
+ self.account_id = account_id
31
+ self.tags = tags or {}
32
+ self.enabled_services = enabled_services or {}
22
33
 
23
- for page in paginator.paginate():
24
- for vpc in page.get("Vpcs", []):
25
- # Get subnets for this VPC
26
- subnets = []
27
- subnet_paginator = ec2.get_paginator("describe_subnets")
28
- for subnet_page in subnet_paginator.paginate(
29
- Filters=[{"Name": "vpc-id", "Values": [vpc["VpcId"]]}]
30
- ):
31
- subnets.extend(subnet_page.get("Subnets", []))
32
-
33
- # Get security groups for this VPC
34
- security_groups = []
35
- sg_paginator = ec2.get_paginator("describe_security_groups")
36
- for sg_page in sg_paginator.paginate(Filters=[{"Name": "vpc-id", "Values": [vpc["VpcId"]]}]):
37
- security_groups.extend(sg_page.get("SecurityGroups", []))
38
-
39
- vpcs.append(
40
- {
41
- "Region": self.region,
42
- "VpcId": vpc.get("VpcId"),
43
- "CidrBlock": vpc.get("CidrBlock"),
44
- "State": vpc.get("State"),
45
- "IsDefault": vpc.get("IsDefault"),
46
- "Tags": vpc.get("Tags", []),
47
- "Subnets": [
48
- {
49
- "SubnetId": subnet.get("SubnetId"),
50
- "CidrBlock": subnet.get("CidrBlock"),
51
- "AvailabilityZone": subnet.get("AvailabilityZone"),
52
- "State": subnet.get("State"),
53
- "Tags": subnet.get("Tags", []),
54
- }
55
- for subnet in subnets
56
- ],
57
- "SecurityGroups": [
58
- {
59
- "GroupId": sg.get("GroupId"),
60
- "GroupName": sg.get("GroupName"),
61
- "Description": sg.get("Description"),
62
- "IpPermissions": sg.get("IpPermissions", []),
63
- "IpPermissionsEgress": sg.get("IpPermissionsEgress", []),
64
- "Tags": sg.get("Tags", []),
65
- }
66
- for sg in security_groups
67
- ],
68
- }
69
- )
34
+ def get_vpcs(self) -> Dict[str, Any]:
35
+ """
36
+ Get information about VPC resources.
37
+
38
+ :return: Dictionary containing VPC resource information
39
+ :rtype: Dict[str, Any]
40
+ """
41
+ try:
42
+ vpc_collector = VPCCollector(self.session, self.region, self.account_id, self.tags)
43
+ return vpc_collector.collect()
70
44
  except Exception as e:
71
- self._handle_error(e, "VPCs")
72
- return vpcs
45
+ self._handle_error(e, "VPC resources")
46
+ return {
47
+ "VPCs": [],
48
+ "Subnets": [],
49
+ "SecurityGroups": [],
50
+ "NetworkACLs": [],
51
+ "RouteTables": [],
52
+ "InternetGateways": [],
53
+ "NATGateways": [],
54
+ "VPCEndpoints": [],
55
+ "VPCPeeringConnections": [],
56
+ }
73
57
 
74
58
  def get_elastic_ips(self) -> List[Dict[str, Any]]:
75
59
  """
@@ -84,6 +68,11 @@ class NetworkingCollector(BaseCollector):
84
68
  addresses = ec2.describe_addresses().get("Addresses", [])
85
69
 
86
70
  for addr in addresses:
71
+ # Filter by tags if specified
72
+ eip_tags = self._convert_tags_to_dict(addr.get("Tags", []))
73
+ if self.tags and not self._matches_tags(eip_tags):
74
+ continue
75
+
87
76
  eips.append(
88
77
  {
89
78
  "Region": self.region,
@@ -124,6 +113,7 @@ class NetworkingCollector(BaseCollector):
124
113
  {
125
114
  "Region": self.region,
126
115
  "LoadBalancerName": lb.get("LoadBalancerName"),
116
+ "LoadBalancerArn": lb.get("LoadBalancerArn"),
127
117
  "DNSName": lb.get("DNSName"),
128
118
  "Type": lb.get("Type"),
129
119
  "Scheme": lb.get("Scheme"),
@@ -131,6 +121,7 @@ class NetworkingCollector(BaseCollector):
131
121
  "State": lb.get("State", {}).get("Code"),
132
122
  "AvailabilityZones": lb.get("AvailabilityZones", []),
133
123
  "SecurityGroups": lb.get("SecurityGroups", []),
124
+ "Listeners": lb.get("Listeners", []),
134
125
  "TargetGroups": [
135
126
  {
136
127
  "TargetGroupName": tg.get("TargetGroupName"),
@@ -239,15 +230,60 @@ class NetworkingCollector(BaseCollector):
239
230
 
240
231
  def collect(self) -> Dict[str, Any]:
241
232
  """
242
- Collect all networking resources.
233
+ Collect networking resources based on enabled_services configuration.
243
234
 
244
- :return: Dictionary containing all networking resource information
235
+ :return: Dictionary containing enabled networking resource information
245
236
  :rtype: Dict[str, Any]
246
237
  """
247
- return {
248
- "VPCs": self.get_vpcs(),
249
- "ElasticIPs": self.get_elastic_ips(),
250
- "LoadBalancers": self.get_load_balancers(),
251
- "CloudFrontDistributions": self.get_cloudfront_distributions(),
252
- "Route53": self.get_route53_info(),
253
- }
238
+ result = {}
239
+
240
+ # VPC Resources
241
+ if self.enabled_services.get("vpc", True):
242
+ vpc_info = self.get_vpcs()
243
+ result.update(vpc_info)
244
+
245
+ # Elastic IPs
246
+ if self.enabled_services.get("elastic_ips", True):
247
+ result["ElasticIPs"] = self.get_elastic_ips()
248
+
249
+ # Load Balancers
250
+ if self.enabled_services.get("load_balancers", True):
251
+ result["LoadBalancers"] = self.get_load_balancers()
252
+
253
+ # CloudFront Distributions
254
+ if self.enabled_services.get("cloudfront", True):
255
+ result["CloudFrontDistributions"] = self.get_cloudfront_distributions()
256
+
257
+ # Route53
258
+ if self.enabled_services.get("route53", True):
259
+ result["Route53"] = self.get_route53_info()
260
+
261
+ return result
262
+
263
+ def _convert_tags_to_dict(self, tags_list: List[Dict[str, str]]) -> Dict[str, str]:
264
+ """
265
+ Convert AWS tags list format to dictionary.
266
+
267
+ :param list tags_list: List of tags in format [{"Key": "k", "Value": "v"}]
268
+ :return: Dictionary of tags {key: value}
269
+ :rtype: Dict[str, str]
270
+ """
271
+ return {tag.get("Key", ""): tag.get("Value", "") for tag in tags_list}
272
+
273
+ def _matches_tags(self, resource_tags: Dict[str, str]) -> bool:
274
+ """
275
+ Check if resource tags match the specified filter tags.
276
+
277
+ :param dict resource_tags: Tags on the resource
278
+ :return: True if all filter tags match
279
+ :rtype: bool
280
+ """
281
+ if not self.tags:
282
+ return True
283
+
284
+ # All filter tags must match
285
+ for key, value in self.tags.items():
286
+ if resource_tags.get(key) != value:
287
+ return False
288
+
289
+ return True