runbooks 0.7.0__py3-none-any.whl → 0.7.6__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.
Files changed (132) hide show
  1. runbooks/__init__.py +87 -37
  2. runbooks/cfat/README.md +300 -49
  3. runbooks/cfat/__init__.py +2 -2
  4. runbooks/finops/__init__.py +1 -1
  5. runbooks/finops/cli.py +1 -1
  6. runbooks/inventory/collectors/__init__.py +8 -0
  7. runbooks/inventory/collectors/aws_management.py +791 -0
  8. runbooks/inventory/collectors/aws_networking.py +3 -3
  9. runbooks/main.py +3389 -782
  10. runbooks/operate/__init__.py +207 -0
  11. runbooks/operate/base.py +311 -0
  12. runbooks/operate/cloudformation_operations.py +619 -0
  13. runbooks/operate/cloudwatch_operations.py +496 -0
  14. runbooks/operate/dynamodb_operations.py +812 -0
  15. runbooks/operate/ec2_operations.py +926 -0
  16. runbooks/operate/iam_operations.py +569 -0
  17. runbooks/operate/s3_operations.py +1211 -0
  18. runbooks/operate/tagging_operations.py +655 -0
  19. runbooks/remediation/CLAUDE.md +100 -0
  20. runbooks/remediation/DOME9.md +218 -0
  21. runbooks/remediation/README.md +26 -0
  22. runbooks/remediation/Tests/__init__.py +0 -0
  23. runbooks/remediation/Tests/update_policy.py +74 -0
  24. runbooks/remediation/__init__.py +95 -0
  25. runbooks/remediation/acm_cert_expired_unused.py +98 -0
  26. runbooks/remediation/acm_remediation.py +875 -0
  27. runbooks/remediation/api_gateway_list.py +167 -0
  28. runbooks/remediation/base.py +643 -0
  29. runbooks/remediation/cloudtrail_remediation.py +908 -0
  30. runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
  31. runbooks/remediation/cognito_active_users.py +78 -0
  32. runbooks/remediation/cognito_remediation.py +856 -0
  33. runbooks/remediation/cognito_user_password_reset.py +163 -0
  34. runbooks/remediation/commons.py +455 -0
  35. runbooks/remediation/dynamodb_optimize.py +155 -0
  36. runbooks/remediation/dynamodb_remediation.py +744 -0
  37. runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
  38. runbooks/remediation/ec2_public_ips.py +134 -0
  39. runbooks/remediation/ec2_remediation.py +892 -0
  40. runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
  41. runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
  42. runbooks/remediation/ec2_unused_security_groups.py +202 -0
  43. runbooks/remediation/kms_enable_key_rotation.py +651 -0
  44. runbooks/remediation/kms_remediation.py +717 -0
  45. runbooks/remediation/lambda_list.py +243 -0
  46. runbooks/remediation/lambda_remediation.py +971 -0
  47. runbooks/remediation/multi_account.py +569 -0
  48. runbooks/remediation/rds_instance_list.py +199 -0
  49. runbooks/remediation/rds_remediation.py +873 -0
  50. runbooks/remediation/rds_snapshot_list.py +192 -0
  51. runbooks/remediation/requirements.txt +118 -0
  52. runbooks/remediation/s3_block_public_access.py +159 -0
  53. runbooks/remediation/s3_bucket_public_access.py +143 -0
  54. runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
  55. runbooks/remediation/s3_downloader.py +215 -0
  56. runbooks/remediation/s3_enable_access_logging.py +562 -0
  57. runbooks/remediation/s3_encryption.py +526 -0
  58. runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
  59. runbooks/remediation/s3_list.py +141 -0
  60. runbooks/remediation/s3_object_search.py +201 -0
  61. runbooks/remediation/s3_remediation.py +816 -0
  62. runbooks/remediation/scan_for_phrase.py +425 -0
  63. runbooks/remediation/workspaces_list.py +220 -0
  64. runbooks/security/__init__.py +9 -10
  65. runbooks/security/security_baseline_tester.py +4 -2
  66. runbooks-0.7.6.dist-info/METADATA +608 -0
  67. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/RECORD +84 -76
  68. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/entry_points.txt +0 -1
  69. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/top_level.txt +0 -1
  70. jupyter-agent/.env +0 -2
  71. jupyter-agent/.env.template +0 -2
  72. jupyter-agent/.gitattributes +0 -35
  73. jupyter-agent/.gradio/certificate.pem +0 -31
  74. jupyter-agent/README.md +0 -16
  75. jupyter-agent/__main__.log +0 -8
  76. jupyter-agent/app.py +0 -256
  77. jupyter-agent/cloudops-agent.png +0 -0
  78. jupyter-agent/ds-system-prompt.txt +0 -154
  79. jupyter-agent/jupyter-agent.png +0 -0
  80. jupyter-agent/llama3_template.jinja +0 -123
  81. jupyter-agent/requirements.txt +0 -9
  82. jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +0 -68
  83. jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +0 -91
  84. jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +0 -91
  85. jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +0 -57
  86. jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +0 -53
  87. jupyter-agent/tmp/jupyter-agent.ipynb +0 -27
  88. jupyter-agent/utils.py +0 -409
  89. runbooks/aws/__init__.py +0 -58
  90. runbooks/aws/dynamodb_operations.py +0 -231
  91. runbooks/aws/ec2_copy_image_cross-region.py +0 -195
  92. runbooks/aws/ec2_describe_instances.py +0 -202
  93. runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
  94. runbooks/aws/ec2_run_instances.py +0 -213
  95. runbooks/aws/ec2_start_stop_instances.py +0 -212
  96. runbooks/aws/ec2_terminate_instances.py +0 -143
  97. runbooks/aws/ec2_unused_eips.py +0 -196
  98. runbooks/aws/ec2_unused_volumes.py +0 -188
  99. runbooks/aws/s3_create_bucket.py +0 -142
  100. runbooks/aws/s3_list_buckets.py +0 -152
  101. runbooks/aws/s3_list_objects.py +0 -156
  102. runbooks/aws/s3_object_operations.py +0 -183
  103. runbooks/aws/tagging_lambda_handler.py +0 -183
  104. runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
  105. runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
  106. runbooks/inventory/aws_organization.png +0 -0
  107. runbooks/inventory/cfn_move_stack_instances.py +0 -1526
  108. runbooks/inventory/delete_s3_buckets_objects.py +0 -169
  109. runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
  110. runbooks/inventory/update_aws_actions.py +0 -173
  111. runbooks/inventory/update_cfn_stacksets.py +0 -1215
  112. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
  113. runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
  114. runbooks/inventory/update_s3_public_access_block.py +0 -539
  115. runbooks/organizations/__init__.py +0 -12
  116. runbooks/organizations/manager.py +0 -374
  117. runbooks-0.7.0.dist-info/METADATA +0 -375
  118. /runbooks/inventory/{tests → Tests}/common_test_data.py +0 -0
  119. /runbooks/inventory/{tests → Tests}/common_test_functions.py +0 -0
  120. /runbooks/inventory/{tests → Tests}/script_test_data.py +0 -0
  121. /runbooks/inventory/{tests → Tests}/setup.py +0 -0
  122. /runbooks/inventory/{tests → Tests}/src.py +0 -0
  123. /runbooks/inventory/{tests/test_inventory_modules.py → Tests/test_Inventory_Modules.py} +0 -0
  124. /runbooks/inventory/{tests → Tests}/test_cfn_describe_stacks.py +0 -0
  125. /runbooks/inventory/{tests → Tests}/test_ec2_describe_instances.py +0 -0
  126. /runbooks/inventory/{tests → Tests}/test_lambda_list_functions.py +0 -0
  127. /runbooks/inventory/{tests → Tests}/test_moto_integration_example.py +0 -0
  128. /runbooks/inventory/{tests → Tests}/test_org_list_accounts.py +0 -0
  129. /runbooks/inventory/{Inventory_Modules.py → inventory_modules.py} +0 -0
  130. /runbooks/{aws → operate}/tags.json +0 -0
  131. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/WHEEL +0 -0
  132. {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,569 @@
1
+ """
2
+ Multi-Account Remediation Engine - Enterprise AWS Organizations Support
3
+
4
+ ## Overview
5
+
6
+ This module provides enterprise-grade multi-account remediation capabilities,
7
+ enabling bulk security and compliance operations across AWS Organizations.
8
+ Enhanced and migrated from the original references/bulk_run.py with enterprise
9
+ features including SSO integration, parallel execution, and comprehensive auditing.
10
+
11
+ ## Original Functionality Enhanced
12
+
13
+ Migrated and enhanced from references/bulk_run.py with these improvements:
14
+ - Enterprise SSO integration with AWS IAM Identity Center
15
+ - Parallel execution across multiple accounts
16
+ - Comprehensive error handling and retry logic
17
+ - Progress tracking and status reporting
18
+ - Integration with remediation base classes
19
+ - Compliance evidence aggregation across accounts
20
+
21
+ ## Key Features
22
+
23
+ - **SSO Integration**: Seamless AWS SSO credential management
24
+ - **Parallel Execution**: Concurrent operations across multiple accounts
25
+ - **Progress Tracking**: Real-time status and progress reporting
26
+ - **Error Handling**: Comprehensive retry logic and failure management
27
+ - **Audit Trail**: Complete operation tracking across all accounts
28
+ - **Compliance Aggregation**: Consolidated compliance evidence
29
+
30
+ ## Example Usage
31
+
32
+ ```python
33
+ from runbooks.remediation import MultiAccountRemediator
34
+ from runbooks.inventory.models.account import AWSAccount
35
+
36
+ # Initialize multi-account remediator
37
+ remediator = MultiAccountRemediator(
38
+ sso_start_url="https://d-123456789.awsapps.com/start",
39
+ parallel_execution=True,
40
+ max_workers=5
41
+ )
42
+
43
+ # Define target accounts
44
+ accounts = [
45
+ AWSAccount("123456789012", "production"),
46
+ AWSAccount("987654321098", "staging"),
47
+ AWSAccount("456789012345", "development")
48
+ ]
49
+
50
+ # Execute bulk S3 security remediation
51
+ results = remediator.bulk_s3_security(
52
+ accounts=accounts,
53
+ operations=["block_public_access", "enforce_ssl"],
54
+ dry_run=False
55
+ )
56
+ ```
57
+
58
+ Version: 0.7.6 - Enterprise Production Ready
59
+ """
60
+
61
+ import concurrent.futures
62
+ import json
63
+ import os
64
+ import time
65
+ import uuid
66
+ import webbrowser
67
+ from typing import Any, Dict, List, Optional, Set, Tuple
68
+
69
+ import boto3
70
+ import botocore.exceptions
71
+ from botocore.exceptions import ClientError
72
+ from loguru import logger
73
+
74
+ from runbooks.inventory.models.account import AWSAccount
75
+ from runbooks.remediation.base import BaseRemediation, RemediationContext, RemediationResult, RemediationStatus
76
+
77
+
78
+ class MultiAccountRemediator:
79
+ """
80
+ Enterprise multi-account remediation orchestrator.
81
+
82
+ Provides comprehensive capabilities for executing remediation operations
83
+ across multiple AWS accounts with enterprise features including SSO
84
+ integration, parallel execution, and consolidated reporting.
85
+
86
+ ## Key Features
87
+
88
+ - **SSO Integration**: AWS IAM Identity Center support
89
+ - **Parallel Execution**: Concurrent operations with configurable workers
90
+ - **Progress Tracking**: Real-time status and completion tracking
91
+ - **Error Handling**: Comprehensive retry and failure management
92
+ - **Compliance Aggregation**: Consolidated compliance evidence
93
+ - **Audit Trail**: Complete operation tracking across accounts
94
+
95
+ ## Example Usage
96
+
97
+ ```python
98
+ # Initialize with SSO configuration
99
+ remediator = MultiAccountRemediator(
100
+ sso_start_url="https://d-123456789.awsapps.com/start",
101
+ role_name="CloudOpsRemediationRole",
102
+ parallel_execution=True
103
+ )
104
+
105
+ # Execute bulk remediation
106
+ results = remediator.execute_bulk_operation(
107
+ accounts=accounts,
108
+ operation_class="S3SecurityRemediation",
109
+ operation_method="block_public_access",
110
+ operation_kwargs={"bucket_name": "critical-bucket"}
111
+ )
112
+ ```
113
+ """
114
+
115
+ def __init__(
116
+ self,
117
+ sso_start_url: Optional[str] = None,
118
+ role_name: str = "power-user",
119
+ parallel_execution: bool = True,
120
+ max_workers: int = 5,
121
+ **kwargs,
122
+ ):
123
+ """
124
+ Initialize multi-account remediator.
125
+
126
+ Args:
127
+ sso_start_url: AWS SSO start URL for authentication
128
+ role_name: IAM role name for cross-account access
129
+ parallel_execution: Enable parallel execution across accounts
130
+ max_workers: Maximum number of concurrent workers
131
+ **kwargs: Additional configuration parameters
132
+ """
133
+ self.sso_start_url = sso_start_url or os.getenv("ACCESS_PORTAL_URL")
134
+ self.role_name = role_name
135
+ self.parallel_execution = parallel_execution
136
+ self.max_workers = max_workers
137
+
138
+ # Enterprise configuration
139
+ self.retry_attempts = kwargs.get("retry_attempts", 3)
140
+ self.retry_delay = kwargs.get("retry_delay", 5)
141
+ self.timeout_seconds = kwargs.get("timeout_seconds", 300)
142
+
143
+ # Progress tracking
144
+ self.operation_id = str(uuid.uuid4())
145
+ self.progress_callback = kwargs.get("progress_callback")
146
+
147
+ # Credential cache
148
+ self._credentials_cache = {}
149
+ self._sso_token = None
150
+
151
+ logger.info(f"MultiAccountRemediator initialized with operation ID: {self.operation_id}")
152
+
153
+ def get_all_sso_credentials(self) -> Dict[str, Dict[str, str]]:
154
+ """
155
+ Get AWS SSO credentials for all accessible accounts.
156
+
157
+ Enhanced from original bulk_run.py with improved error handling,
158
+ credential caching, and progress tracking.
159
+
160
+ Returns:
161
+ Dictionary mapping account profiles to credentials
162
+ """
163
+ if not self.sso_start_url:
164
+ raise ValueError("SSO start URL is required for multi-account operations")
165
+
166
+ credentials = {}
167
+
168
+ try:
169
+ # Create SSO OIDC client
170
+ sso_oidc = boto3.client("sso-oidc", region_name="us-east-1")
171
+
172
+ # Register client
173
+ client_creds = sso_oidc.register_client(clientName="CloudOpsRemediation", clientType="public")
174
+
175
+ # Get device authorization
176
+ device_auth = sso_oidc.start_device_authorization(
177
+ clientId=client_creds["clientId"],
178
+ clientSecret=client_creds["clientSecret"],
179
+ startUrl=self.sso_start_url,
180
+ )
181
+
182
+ logger.info(f"Please authorize access at: {device_auth['verificationUriComplete']}")
183
+ logger.info(f"User code: {device_auth['userCode']}")
184
+
185
+ # Open browser automatically
186
+ try:
187
+ webbrowser.open(device_auth["verificationUriComplete"])
188
+ except Exception as e:
189
+ logger.warning(f"Could not open browser automatically: {e}")
190
+
191
+ # Wait for user authorization
192
+ token = None
193
+ max_retries = 60 # 5 minutes with 5-second intervals
194
+ retry_count = 0
195
+
196
+ while not token and retry_count < max_retries:
197
+ try:
198
+ token = sso_oidc.create_token(
199
+ clientId=client_creds["clientId"],
200
+ clientSecret=client_creds["clientSecret"],
201
+ grantType="urn:ietf:params:oauth:grant-type:device_code",
202
+ deviceCode=device_auth["deviceCode"],
203
+ )
204
+ self._sso_token = token
205
+
206
+ except sso_oidc.exceptions.AuthorizationPendingException:
207
+ logger.info("Waiting for authorization... Please complete the process in your browser.")
208
+ time.sleep(5)
209
+ retry_count += 1
210
+ except Exception as e:
211
+ logger.error(f"Authorization error: {e}")
212
+ break
213
+
214
+ if not token:
215
+ raise Exception("SSO authorization timed out or failed")
216
+
217
+ # Create SSO client
218
+ sso = boto3.client("sso", region_name="us-east-1")
219
+
220
+ # List all accounts with pagination
221
+ all_accounts = []
222
+ paginator = sso.get_paginator("list_accounts")
223
+ for page in paginator.paginate(accessToken=token["accessToken"]):
224
+ all_accounts.extend(page["accountList"])
225
+
226
+ logger.info(f"Found {len(all_accounts)} accessible accounts")
227
+
228
+ # Get credentials for each account
229
+ for account in all_accounts:
230
+ account_id = account["accountId"]
231
+
232
+ # Get available roles for the account
233
+ roles = []
234
+ role_paginator = sso.get_paginator("list_account_roles")
235
+ for role_page in role_paginator.paginate(accessToken=token["accessToken"], accountId=account_id):
236
+ roles.extend(role_page["roleList"])
237
+
238
+ # Find the specified role
239
+ target_role = None
240
+ for role in roles:
241
+ if role["roleName"] == self.role_name:
242
+ target_role = role
243
+ break
244
+
245
+ if target_role:
246
+ try:
247
+ # Get temporary credentials
248
+ creds = sso.get_role_credentials(
249
+ accessToken=token["accessToken"], accountId=account_id, roleName=target_role["roleName"]
250
+ )
251
+
252
+ profile_key = f"{account_id}_{target_role['roleName']}"
253
+ credentials[profile_key] = {
254
+ "aws_access_key_id": creds["roleCredentials"]["accessKeyId"],
255
+ "aws_secret_access_key": creds["roleCredentials"]["secretAccessKey"],
256
+ "aws_session_token": creds["roleCredentials"]["sessionToken"],
257
+ "account_id": account_id,
258
+ "account_name": account.get("accountName", account_id),
259
+ "role_name": target_role["roleName"],
260
+ }
261
+
262
+ logger.debug(f"Retrieved credentials for account {account_id}")
263
+
264
+ except Exception as e:
265
+ logger.warning(f"Failed to get credentials for account {account_id}: {e}")
266
+ else:
267
+ logger.warning(f"Role {self.role_name} not found in account {account_id}")
268
+
269
+ logger.info(f"Successfully retrieved credentials for {len(credentials)} accounts")
270
+ self._credentials_cache = credentials
271
+
272
+ return credentials
273
+
274
+ except Exception as e:
275
+ logger.error(f"Failed to retrieve SSO credentials: {e}")
276
+ raise
277
+
278
+ def get_credentials_for_account(self, account_id: str) -> Optional[Dict[str, str]]:
279
+ """
280
+ Get credentials for a specific account.
281
+
282
+ Args:
283
+ account_id: AWS account ID
284
+
285
+ Returns:
286
+ Credentials dictionary or None if not found
287
+ """
288
+ # Check cache first
289
+ for profile_key, creds in self._credentials_cache.items():
290
+ if creds.get("account_id") == account_id:
291
+ return creds
292
+
293
+ # If not in cache, refresh credentials
294
+ if not self._credentials_cache:
295
+ self.get_all_sso_credentials()
296
+
297
+ # Check again after refresh
298
+ for profile_key, creds in self._credentials_cache.items():
299
+ if creds.get("account_id") == account_id:
300
+ return creds
301
+
302
+ logger.warning(f"No credentials found for account {account_id}")
303
+ return None
304
+
305
+ def execute_single_account_operation(
306
+ self,
307
+ account: AWSAccount,
308
+ operation_class: str,
309
+ operation_method: str,
310
+ operation_kwargs: Dict[str, Any],
311
+ **context_kwargs,
312
+ ) -> List[RemediationResult]:
313
+ """
314
+ Execute remediation operation on a single account.
315
+
316
+ Args:
317
+ account: Target AWS account
318
+ operation_class: Remediation class name (e.g., "S3SecurityRemediation")
319
+ operation_method: Method name to execute
320
+ operation_kwargs: Arguments for the operation method
321
+ **context_kwargs: Additional context parameters
322
+
323
+ Returns:
324
+ List of remediation results for the account
325
+ """
326
+ account_results = []
327
+
328
+ try:
329
+ # Get credentials for the account
330
+ credentials = self.get_credentials_for_account(account.account_id)
331
+ if not credentials:
332
+ raise Exception(f"No credentials available for account {account.account_id}")
333
+
334
+ # Set environment variables for AWS SDK
335
+ original_env = {}
336
+ for key in ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]:
337
+ original_env[key] = os.environ.get(key)
338
+
339
+ os.environ["AWS_ACCESS_KEY_ID"] = credentials["aws_access_key_id"]
340
+ os.environ["AWS_SECRET_ACCESS_KEY"] = credentials["aws_secret_access_key"]
341
+ os.environ["AWS_SESSION_TOKEN"] = credentials["aws_session_token"]
342
+
343
+ try:
344
+ # Create remediation context
345
+ context = RemediationContext(
346
+ account=account, operation_type=f"{operation_class}.{operation_method}", **context_kwargs
347
+ )
348
+
349
+ # Dynamically import and instantiate the remediation class
350
+ if operation_class == "S3SecurityRemediation":
351
+ from runbooks.remediation.s3_remediation import S3SecurityRemediation
352
+
353
+ remediation_instance = S3SecurityRemediation()
354
+ else:
355
+ raise ValueError(f"Unsupported remediation class: {operation_class}")
356
+
357
+ # Execute the operation
358
+ method = getattr(remediation_instance, operation_method)
359
+ operation_results = method(context, **operation_kwargs)
360
+
361
+ account_results.extend(operation_results)
362
+
363
+ logger.info(f"Completed operation {operation_method} for account {account.account_id}")
364
+
365
+ finally:
366
+ # Restore original environment variables
367
+ for key, value in original_env.items():
368
+ if value is None:
369
+ os.environ.pop(key, None)
370
+ else:
371
+ os.environ[key] = value
372
+
373
+ except Exception as e:
374
+ logger.error(f"Failed to execute operation {operation_method} for account {account.account_id}: {e}")
375
+
376
+ # Create error result
377
+ error_context = RemediationContext(
378
+ account=account, operation_type=f"{operation_class}.{operation_method}", **context_kwargs
379
+ )
380
+
381
+ error_result = RemediationResult(
382
+ context=error_context, status=RemediationStatus.FAILED, error_message=str(e)
383
+ )
384
+ account_results.append(error_result)
385
+
386
+ return account_results
387
+
388
+ def execute_bulk_operation(
389
+ self,
390
+ accounts: List[AWSAccount],
391
+ operation_class: str,
392
+ operation_method: str,
393
+ operation_kwargs: Dict[str, Any],
394
+ **context_kwargs,
395
+ ) -> List[RemediationResult]:
396
+ """
397
+ Execute remediation operation across multiple accounts.
398
+
399
+ Args:
400
+ accounts: List of target AWS accounts
401
+ operation_class: Remediation class name
402
+ operation_method: Method name to execute
403
+ operation_kwargs: Arguments for the operation method
404
+ **context_kwargs: Additional context parameters
405
+
406
+ Returns:
407
+ Consolidated list of remediation results
408
+ """
409
+ logger.info(f"Starting bulk operation {operation_method} across {len(accounts)} accounts")
410
+
411
+ all_results = []
412
+
413
+ if self.parallel_execution and len(accounts) > 1:
414
+ # Parallel execution
415
+ with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
416
+ # Submit all account operations
417
+ future_to_account = {
418
+ executor.submit(
419
+ self.execute_single_account_operation,
420
+ account,
421
+ operation_class,
422
+ operation_method,
423
+ operation_kwargs,
424
+ **context_kwargs,
425
+ ): account
426
+ for account in accounts
427
+ }
428
+
429
+ # Collect results as they complete
430
+ for future in concurrent.futures.as_completed(future_to_account, timeout=self.timeout_seconds):
431
+ account = future_to_account[future]
432
+ try:
433
+ account_results = future.result()
434
+ all_results.extend(account_results)
435
+
436
+ # Progress callback
437
+ if self.progress_callback:
438
+ self.progress_callback(account, account_results)
439
+
440
+ except Exception as e:
441
+ logger.error(f"Parallel execution error for account {account.account_id}: {e}")
442
+ else:
443
+ # Sequential execution
444
+ for account in accounts:
445
+ account_results = self.execute_single_account_operation(
446
+ account, operation_class, operation_method, operation_kwargs, **context_kwargs
447
+ )
448
+ all_results.extend(account_results)
449
+
450
+ # Progress callback
451
+ if self.progress_callback:
452
+ self.progress_callback(account, account_results)
453
+
454
+ # Generate summary statistics
455
+ successful_results = [r for r in all_results if r.success]
456
+ failed_results = [r for r in all_results if r.failed]
457
+
458
+ logger.info(
459
+ f"Bulk operation completed: {len(successful_results)} successful, "
460
+ f"{len(failed_results)} failed across {len(accounts)} accounts"
461
+ )
462
+
463
+ return all_results
464
+
465
+ def bulk_s3_security(
466
+ self, accounts: List[AWSAccount], operations: List[str], bucket_patterns: Optional[List[str]] = None, **kwargs
467
+ ) -> List[RemediationResult]:
468
+ """
469
+ Execute bulk S3 security operations across multiple accounts.
470
+
471
+ Args:
472
+ accounts: List of target AWS accounts
473
+ operations: List of S3 security operations to execute
474
+ bucket_patterns: Optional bucket name patterns to target
475
+ **kwargs: Additional parameters
476
+
477
+ Returns:
478
+ Consolidated list of remediation results
479
+ """
480
+ logger.info(f"Starting bulk S3 security operations: {operations} across {len(accounts)} accounts")
481
+
482
+ all_results = []
483
+
484
+ for operation in operations:
485
+ if operation == "block_public_access":
486
+ operation_results = self.execute_bulk_operation(
487
+ accounts=accounts,
488
+ operation_class="S3SecurityRemediation",
489
+ operation_method="block_public_access",
490
+ operation_kwargs=kwargs,
491
+ **kwargs,
492
+ )
493
+ elif operation == "enforce_ssl":
494
+ operation_results = self.execute_bulk_operation(
495
+ accounts=accounts,
496
+ operation_class="S3SecurityRemediation",
497
+ operation_method="enforce_ssl",
498
+ operation_kwargs=kwargs,
499
+ **kwargs,
500
+ )
501
+ elif operation == "enable_encryption":
502
+ operation_results = self.execute_bulk_operation(
503
+ accounts=accounts,
504
+ operation_class="S3SecurityRemediation",
505
+ operation_method="enable_encryption",
506
+ operation_kwargs=kwargs,
507
+ **kwargs,
508
+ )
509
+ else:
510
+ logger.warning(f"Unsupported S3 security operation: {operation}")
511
+ continue
512
+
513
+ all_results.extend(operation_results)
514
+
515
+ return all_results
516
+
517
+ def generate_compliance_report(self, results: List[RemediationResult]) -> Dict[str, Any]:
518
+ """
519
+ Generate consolidated compliance report from multi-account results.
520
+
521
+ Args:
522
+ results: List of remediation results from multiple accounts
523
+
524
+ Returns:
525
+ Comprehensive compliance report
526
+ """
527
+ # Group results by account
528
+ results_by_account = {}
529
+ for result in results:
530
+ account_id = result.context.account.account_id
531
+ if account_id not in results_by_account:
532
+ results_by_account[account_id] = []
533
+ results_by_account[account_id].append(result)
534
+
535
+ # Generate compliance summary
536
+ compliance_summary = {
537
+ "total_accounts": len(results_by_account),
538
+ "total_operations": len(results),
539
+ "successful_operations": len([r for r in results if r.success]),
540
+ "failed_operations": len([r for r in results if r.failed]),
541
+ "compliance_frameworks": set(),
542
+ "compliance_controls": set(),
543
+ "accounts_summary": {},
544
+ }
545
+
546
+ # Account-level summaries
547
+ for account_id, account_results in results_by_account.items():
548
+ account_summary = {
549
+ "account_id": account_id,
550
+ "total_operations": len(account_results),
551
+ "successful_operations": len([r for r in account_results if r.success]),
552
+ "failed_operations": len([r for r in account_results if r.failed]),
553
+ "compliance_controls": [],
554
+ }
555
+
556
+ # Aggregate compliance information
557
+ for result in account_results:
558
+ if hasattr(result.context, "compliance_mapping"):
559
+ mapping = result.context.compliance_mapping
560
+ compliance_summary["compliance_controls"].update(mapping.cis_controls)
561
+ compliance_summary["compliance_controls"].update(mapping.nist_categories)
562
+ account_summary["compliance_controls"].extend(mapping.cis_controls)
563
+
564
+ compliance_summary["accounts_summary"][account_id] = account_summary
565
+
566
+ # Convert sets to lists for JSON serialization
567
+ compliance_summary["compliance_controls"] = list(compliance_summary["compliance_controls"])
568
+
569
+ return compliance_summary