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.
- runbooks/__init__.py +87 -37
- runbooks/cfat/README.md +300 -49
- runbooks/cfat/__init__.py +2 -2
- runbooks/finops/__init__.py +1 -1
- runbooks/finops/cli.py +1 -1
- runbooks/inventory/collectors/__init__.py +8 -0
- runbooks/inventory/collectors/aws_management.py +791 -0
- runbooks/inventory/collectors/aws_networking.py +3 -3
- runbooks/main.py +3389 -782
- runbooks/operate/__init__.py +207 -0
- runbooks/operate/base.py +311 -0
- runbooks/operate/cloudformation_operations.py +619 -0
- runbooks/operate/cloudwatch_operations.py +496 -0
- runbooks/operate/dynamodb_operations.py +812 -0
- runbooks/operate/ec2_operations.py +926 -0
- runbooks/operate/iam_operations.py +569 -0
- runbooks/operate/s3_operations.py +1211 -0
- runbooks/operate/tagging_operations.py +655 -0
- runbooks/remediation/CLAUDE.md +100 -0
- runbooks/remediation/DOME9.md +218 -0
- runbooks/remediation/README.md +26 -0
- runbooks/remediation/Tests/__init__.py +0 -0
- runbooks/remediation/Tests/update_policy.py +74 -0
- runbooks/remediation/__init__.py +95 -0
- runbooks/remediation/acm_cert_expired_unused.py +98 -0
- runbooks/remediation/acm_remediation.py +875 -0
- runbooks/remediation/api_gateway_list.py +167 -0
- runbooks/remediation/base.py +643 -0
- runbooks/remediation/cloudtrail_remediation.py +908 -0
- runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
- runbooks/remediation/cognito_active_users.py +78 -0
- runbooks/remediation/cognito_remediation.py +856 -0
- runbooks/remediation/cognito_user_password_reset.py +163 -0
- runbooks/remediation/commons.py +455 -0
- runbooks/remediation/dynamodb_optimize.py +155 -0
- runbooks/remediation/dynamodb_remediation.py +744 -0
- runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
- runbooks/remediation/ec2_public_ips.py +134 -0
- runbooks/remediation/ec2_remediation.py +892 -0
- runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
- runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
- runbooks/remediation/ec2_unused_security_groups.py +202 -0
- runbooks/remediation/kms_enable_key_rotation.py +651 -0
- runbooks/remediation/kms_remediation.py +717 -0
- runbooks/remediation/lambda_list.py +243 -0
- runbooks/remediation/lambda_remediation.py +971 -0
- runbooks/remediation/multi_account.py +569 -0
- runbooks/remediation/rds_instance_list.py +199 -0
- runbooks/remediation/rds_remediation.py +873 -0
- runbooks/remediation/rds_snapshot_list.py +192 -0
- runbooks/remediation/requirements.txt +118 -0
- runbooks/remediation/s3_block_public_access.py +159 -0
- runbooks/remediation/s3_bucket_public_access.py +143 -0
- runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
- runbooks/remediation/s3_downloader.py +215 -0
- runbooks/remediation/s3_enable_access_logging.py +562 -0
- runbooks/remediation/s3_encryption.py +526 -0
- runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
- runbooks/remediation/s3_list.py +141 -0
- runbooks/remediation/s3_object_search.py +201 -0
- runbooks/remediation/s3_remediation.py +816 -0
- runbooks/remediation/scan_for_phrase.py +425 -0
- runbooks/remediation/workspaces_list.py +220 -0
- runbooks/security/__init__.py +9 -10
- runbooks/security/security_baseline_tester.py +4 -2
- runbooks-0.7.6.dist-info/METADATA +608 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/RECORD +84 -76
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/entry_points.txt +0 -1
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/top_level.txt +0 -1
- jupyter-agent/.env +0 -2
- jupyter-agent/.env.template +0 -2
- jupyter-agent/.gitattributes +0 -35
- jupyter-agent/.gradio/certificate.pem +0 -31
- jupyter-agent/README.md +0 -16
- jupyter-agent/__main__.log +0 -8
- jupyter-agent/app.py +0 -256
- jupyter-agent/cloudops-agent.png +0 -0
- jupyter-agent/ds-system-prompt.txt +0 -154
- jupyter-agent/jupyter-agent.png +0 -0
- jupyter-agent/llama3_template.jinja +0 -123
- jupyter-agent/requirements.txt +0 -9
- jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +0 -68
- jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +0 -57
- jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +0 -53
- jupyter-agent/tmp/jupyter-agent.ipynb +0 -27
- jupyter-agent/utils.py +0 -409
- runbooks/aws/__init__.py +0 -58
- runbooks/aws/dynamodb_operations.py +0 -231
- runbooks/aws/ec2_copy_image_cross-region.py +0 -195
- runbooks/aws/ec2_describe_instances.py +0 -202
- runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
- runbooks/aws/ec2_run_instances.py +0 -213
- runbooks/aws/ec2_start_stop_instances.py +0 -212
- runbooks/aws/ec2_terminate_instances.py +0 -143
- runbooks/aws/ec2_unused_eips.py +0 -196
- runbooks/aws/ec2_unused_volumes.py +0 -188
- runbooks/aws/s3_create_bucket.py +0 -142
- runbooks/aws/s3_list_buckets.py +0 -152
- runbooks/aws/s3_list_objects.py +0 -156
- runbooks/aws/s3_object_operations.py +0 -183
- runbooks/aws/tagging_lambda_handler.py +0 -183
- runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
- runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
- runbooks/inventory/aws_organization.png +0 -0
- runbooks/inventory/cfn_move_stack_instances.py +0 -1526
- runbooks/inventory/delete_s3_buckets_objects.py +0 -169
- runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
- runbooks/inventory/update_aws_actions.py +0 -173
- runbooks/inventory/update_cfn_stacksets.py +0 -1215
- runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
- runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
- runbooks/inventory/update_s3_public_access_block.py +0 -539
- runbooks/organizations/__init__.py +0 -12
- runbooks/organizations/manager.py +0 -374
- runbooks-0.7.0.dist-info/METADATA +0 -375
- /runbooks/inventory/{tests → Tests}/common_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/common_test_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/script_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/setup.py +0 -0
- /runbooks/inventory/{tests → Tests}/src.py +0 -0
- /runbooks/inventory/{tests/test_inventory_modules.py → Tests/test_Inventory_Modules.py} +0 -0
- /runbooks/inventory/{tests → Tests}/test_cfn_describe_stacks.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_ec2_describe_instances.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_lambda_list_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_moto_integration_example.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_org_list_accounts.py +0 -0
- /runbooks/inventory/{Inventory_Modules.py → inventory_modules.py} +0 -0
- /runbooks/{aws → operate}/tags.json +0 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/WHEEL +0 -0
- {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
|