runbooks 1.0.0__py3-none-any.whl → 1.0.2__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 +1 -1
- runbooks/cfat/WEIGHT_CONFIG_README.md +368 -0
- runbooks/cfat/app.ts +27 -19
- runbooks/cfat/assessment/runner.py +6 -5
- runbooks/cfat/tests/test_weight_configuration.ts +449 -0
- runbooks/cfat/weight_config.ts +574 -0
- runbooks/cloudops/models.py +20 -14
- runbooks/common/__init__.py +26 -9
- runbooks/common/aws_pricing.py +1070 -105
- runbooks/common/aws_pricing_api.py +276 -44
- runbooks/common/date_utils.py +115 -0
- runbooks/common/dry_run_examples.py +587 -0
- runbooks/common/dry_run_framework.py +520 -0
- runbooks/common/enhanced_exception_handler.py +10 -7
- runbooks/common/mcp_cost_explorer_integration.py +5 -4
- runbooks/common/memory_optimization.py +533 -0
- runbooks/common/performance_optimization_engine.py +1153 -0
- runbooks/common/profile_utils.py +86 -118
- runbooks/common/rich_utils.py +3 -3
- runbooks/common/sre_performance_suite.py +574 -0
- runbooks/finops/business_case_config.py +314 -0
- runbooks/finops/cost_processor.py +19 -4
- runbooks/finops/dashboard_runner.py +47 -28
- runbooks/finops/ebs_cost_optimizer.py +1 -1
- runbooks/finops/ebs_optimizer.py +56 -9
- runbooks/finops/embedded_mcp_validator.py +642 -36
- runbooks/finops/enhanced_trend_visualization.py +7 -2
- runbooks/finops/executive_export.py +789 -0
- runbooks/finops/finops_dashboard.py +6 -5
- runbooks/finops/finops_scenarios.py +34 -27
- runbooks/finops/iam_guidance.py +6 -1
- runbooks/finops/nat_gateway_optimizer.py +46 -27
- runbooks/finops/notebook_utils.py +1 -1
- runbooks/finops/schemas.py +73 -58
- runbooks/finops/single_dashboard.py +20 -4
- runbooks/finops/tests/test_integration.py +3 -1
- runbooks/finops/vpc_cleanup_exporter.py +2 -1
- runbooks/finops/vpc_cleanup_optimizer.py +22 -29
- runbooks/inventory/core/collector.py +51 -28
- runbooks/inventory/discovery.md +197 -247
- runbooks/inventory/inventory_modules.py +2 -2
- runbooks/inventory/list_ec2_instances.py +3 -3
- runbooks/inventory/models/account.py +5 -3
- runbooks/inventory/models/inventory.py +1 -1
- runbooks/inventory/models/resource.py +5 -3
- runbooks/inventory/organizations_discovery.py +102 -13
- runbooks/inventory/unified_validation_engine.py +2 -15
- runbooks/main.py +255 -92
- runbooks/operate/base.py +9 -6
- runbooks/operate/deployment_framework.py +5 -4
- runbooks/operate/deployment_validator.py +6 -5
- runbooks/operate/mcp_integration.py +6 -5
- runbooks/operate/networking_cost_heatmap.py +17 -13
- runbooks/operate/vpc_operations.py +82 -13
- runbooks/remediation/base.py +3 -1
- runbooks/remediation/commons.py +5 -5
- runbooks/remediation/commvault_ec2_analysis.py +66 -18
- runbooks/remediation/config/accounts_example.json +31 -0
- runbooks/remediation/multi_account.py +120 -7
- runbooks/remediation/remediation_cli.py +710 -0
- runbooks/remediation/universal_account_discovery.py +377 -0
- runbooks/remediation/workspaces_list.py +2 -2
- runbooks/security/compliance_automation_engine.py +99 -20
- runbooks/security/config/__init__.py +24 -0
- runbooks/security/config/compliance_config.py +255 -0
- runbooks/security/config/compliance_weights_example.json +22 -0
- runbooks/security/config_template_generator.py +500 -0
- runbooks/security/security_cli.py +377 -0
- runbooks/validation/cli.py +8 -7
- runbooks/validation/comprehensive_2way_validator.py +26 -15
- runbooks/validation/mcp_validator.py +62 -8
- runbooks/vpc/config.py +49 -15
- runbooks/vpc/cross_account_session.py +5 -1
- runbooks/vpc/heatmap_engine.py +438 -59
- runbooks/vpc/mcp_no_eni_validator.py +115 -36
- runbooks/vpc/performance_optimized_analyzer.py +546 -0
- runbooks/vpc/runbooks_adapter.py +33 -12
- runbooks/vpc/tests/conftest.py +4 -2
- runbooks/vpc/tests/test_cost_engine.py +3 -1
- {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/METADATA +1 -1
- {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/RECORD +85 -79
- runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/finops/runbooks.security.report_generator.log +0 -0
- runbooks/finops/runbooks.security.run_script.log +0 -0
- runbooks/finops/runbooks.security.security_export.log +0 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +0 -1
- runbooks/inventory/artifacts/scale-optimize-status.txt +0 -12
- runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/runbooks.security.security_export.log +0 -0
- runbooks/vpc/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/vpc/runbooks.security.report_generator.log +0 -0
- runbooks/vpc/runbooks.security.run_script.log +0 -0
- runbooks/vpc/runbooks.security.security_export.log +0 -0
- {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/WHEEL +0 -0
- {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/entry_points.txt +0 -0
- {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,377 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Universal Account Discovery for Remediation Operations
|
4
|
+
=====================================================
|
5
|
+
|
6
|
+
This module provides truly universal AWS account discovery that works with ANY AWS setup:
|
7
|
+
- Single account setups
|
8
|
+
- Multi-account Organizations setups
|
9
|
+
- Mixed environments
|
10
|
+
- Any profile naming convention
|
11
|
+
|
12
|
+
Features:
|
13
|
+
- Dynamic account discovery (no hardcoded account arrays)
|
14
|
+
- Profile-based account resolution
|
15
|
+
- Environment variable configuration
|
16
|
+
- Configuration file support
|
17
|
+
- Universal compatibility across all AWS setups
|
18
|
+
|
19
|
+
Author: DevOps Security Engineer (Claude Code Enterprise Team)
|
20
|
+
Version: 1.0.0 - Universal Account Discovery
|
21
|
+
"""
|
22
|
+
|
23
|
+
import json
|
24
|
+
import os
|
25
|
+
from dataclasses import dataclass
|
26
|
+
from typing import Dict, List, Optional, Set
|
27
|
+
|
28
|
+
import boto3
|
29
|
+
from botocore.exceptions import ClientError
|
30
|
+
|
31
|
+
from runbooks.common.profile_utils import get_profile_for_operation
|
32
|
+
from runbooks.common.rich_utils import console, print_error, print_info, print_warning
|
33
|
+
|
34
|
+
|
35
|
+
@dataclass
|
36
|
+
class AWSAccount:
|
37
|
+
"""Universal AWS account representation."""
|
38
|
+
|
39
|
+
account_id: str
|
40
|
+
account_name: Optional[str] = None
|
41
|
+
status: str = "ACTIVE"
|
42
|
+
email: Optional[str] = None
|
43
|
+
joined_method: Optional[str] = None
|
44
|
+
profile_name: Optional[str] = None
|
45
|
+
|
46
|
+
|
47
|
+
class UniversalAccountDiscovery:
|
48
|
+
"""
|
49
|
+
Universal AWS account discovery that works with ANY AWS setup.
|
50
|
+
|
51
|
+
Discovery methods (in priority order):
|
52
|
+
1. Environment variables (REMEDIATION_TARGET_ACCOUNTS)
|
53
|
+
2. Configuration file (REMEDIATION_ACCOUNT_CONFIG)
|
54
|
+
3. AWS Organizations API (if available)
|
55
|
+
4. Current account (single account mode)
|
56
|
+
|
57
|
+
No hardcoded account arrays - fully dynamic discovery.
|
58
|
+
"""
|
59
|
+
|
60
|
+
def __init__(self, profile: Optional[str] = None):
|
61
|
+
"""Initialize universal account discovery."""
|
62
|
+
self.profile = profile
|
63
|
+
self.resolved_profile = get_profile_for_operation("management", profile)
|
64
|
+
self.session = self._create_session()
|
65
|
+
|
66
|
+
def _create_session(self) -> boto3.Session:
|
67
|
+
"""Create AWS session using universal profile management."""
|
68
|
+
return boto3.Session(profile_name=self.resolved_profile)
|
69
|
+
|
70
|
+
def discover_target_accounts(self, include_current: bool = True) -> List[AWSAccount]:
|
71
|
+
"""
|
72
|
+
Discover target accounts for remediation using universal approach.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
include_current: Include current account in results
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
List[AWSAccount]: Discovered target accounts
|
79
|
+
"""
|
80
|
+
console.log("[cyan]🔍 Starting universal account discovery...[/]")
|
81
|
+
|
82
|
+
discovered_accounts = []
|
83
|
+
|
84
|
+
# Method 1: Environment variables (highest priority)
|
85
|
+
env_accounts = self._get_accounts_from_environment()
|
86
|
+
if env_accounts:
|
87
|
+
console.log(f"[green]✓ Found {len(env_accounts)} accounts from environment variables[/]")
|
88
|
+
discovered_accounts.extend(env_accounts)
|
89
|
+
return discovered_accounts
|
90
|
+
|
91
|
+
# Method 2: Configuration file
|
92
|
+
config_accounts = self._get_accounts_from_config()
|
93
|
+
if config_accounts:
|
94
|
+
console.log(f"[green]✓ Found {len(config_accounts)} accounts from configuration file[/]")
|
95
|
+
discovered_accounts.extend(config_accounts)
|
96
|
+
return discovered_accounts
|
97
|
+
|
98
|
+
# Method 3: AWS Organizations API (if available)
|
99
|
+
org_accounts = self._get_accounts_from_organizations()
|
100
|
+
if org_accounts:
|
101
|
+
console.log(f"[green]✓ Found {len(org_accounts)} accounts from AWS Organizations[/]")
|
102
|
+
discovered_accounts.extend(org_accounts)
|
103
|
+
return discovered_accounts
|
104
|
+
|
105
|
+
# Method 4: Current account fallback (single account mode)
|
106
|
+
if include_current:
|
107
|
+
current_account = self._get_current_account()
|
108
|
+
if current_account:
|
109
|
+
console.log("[yellow]🔍 Single account mode: Using current account[/]")
|
110
|
+
discovered_accounts.append(current_account)
|
111
|
+
|
112
|
+
if not discovered_accounts:
|
113
|
+
print_warning("No target accounts discovered. Check configuration or permissions.")
|
114
|
+
|
115
|
+
return discovered_accounts
|
116
|
+
|
117
|
+
def _get_accounts_from_environment(self) -> List[AWSAccount]:
|
118
|
+
"""Get accounts from environment variables."""
|
119
|
+
env_accounts = os.getenv("REMEDIATION_TARGET_ACCOUNTS")
|
120
|
+
if not env_accounts:
|
121
|
+
return []
|
122
|
+
|
123
|
+
try:
|
124
|
+
account_ids = [acc.strip() for acc in env_accounts.split(",")]
|
125
|
+
return [
|
126
|
+
AWSAccount(
|
127
|
+
account_id=account_id,
|
128
|
+
account_name=f"Env-Account-{account_id}",
|
129
|
+
profile_name=self.resolved_profile
|
130
|
+
)
|
131
|
+
for account_id in account_ids
|
132
|
+
if account_id
|
133
|
+
]
|
134
|
+
except Exception as e:
|
135
|
+
print_warning(f"Failed to parse REMEDIATION_TARGET_ACCOUNTS: {e}")
|
136
|
+
return []
|
137
|
+
|
138
|
+
def _get_accounts_from_config(self) -> List[AWSAccount]:
|
139
|
+
"""Get accounts from configuration file."""
|
140
|
+
config_path = os.getenv("REMEDIATION_ACCOUNT_CONFIG")
|
141
|
+
if not config_path or not os.path.exists(config_path):
|
142
|
+
return []
|
143
|
+
|
144
|
+
try:
|
145
|
+
with open(config_path, 'r') as f:
|
146
|
+
config = json.load(f)
|
147
|
+
|
148
|
+
accounts = []
|
149
|
+
for account_config in config.get("target_accounts", []):
|
150
|
+
account = AWSAccount(
|
151
|
+
account_id=account_config["account_id"],
|
152
|
+
account_name=account_config.get("account_name"),
|
153
|
+
status=account_config.get("status", "ACTIVE"),
|
154
|
+
email=account_config.get("email"),
|
155
|
+
profile_name=account_config.get("profile_name", self.resolved_profile)
|
156
|
+
)
|
157
|
+
accounts.append(account)
|
158
|
+
|
159
|
+
console.log(f"[dim cyan]Loaded account configuration from: {config_path}[/]")
|
160
|
+
return accounts
|
161
|
+
|
162
|
+
except Exception as e:
|
163
|
+
print_warning(f"Failed to load account configuration from {config_path}: {e}")
|
164
|
+
return []
|
165
|
+
|
166
|
+
def _get_accounts_from_organizations(self) -> List[AWSAccount]:
|
167
|
+
"""Get accounts from AWS Organizations API."""
|
168
|
+
try:
|
169
|
+
# Check if Organizations API is available
|
170
|
+
orgs_client = self.session.client("organizations")
|
171
|
+
|
172
|
+
# Try to list accounts
|
173
|
+
paginator = orgs_client.get_paginator("list_accounts")
|
174
|
+
accounts = []
|
175
|
+
|
176
|
+
for page in paginator.paginate():
|
177
|
+
for account in page["Accounts"]:
|
178
|
+
aws_account = AWSAccount(
|
179
|
+
account_id=account["Id"],
|
180
|
+
account_name=account.get("Name"),
|
181
|
+
status=account.get("Status", "ACTIVE"),
|
182
|
+
email=account.get("Email"),
|
183
|
+
joined_method=account.get("JoinedMethod"),
|
184
|
+
profile_name=self.resolved_profile
|
185
|
+
)
|
186
|
+
accounts.append(aws_account)
|
187
|
+
|
188
|
+
# Filter to active accounts only
|
189
|
+
active_accounts = [acc for acc in accounts if acc.status == "ACTIVE"]
|
190
|
+
console.log(f"[dim cyan]Discovered {len(active_accounts)} active accounts via Organizations API[/]")
|
191
|
+
|
192
|
+
return active_accounts
|
193
|
+
|
194
|
+
except ClientError as e:
|
195
|
+
error_code = e.response.get("Error", {}).get("Code", "Unknown")
|
196
|
+
if error_code in ["AccessDenied", "AWSOrganizationsNotInUseException"]:
|
197
|
+
console.log("[dim yellow]Organizations API not available (not in use or no permissions)[/]")
|
198
|
+
else:
|
199
|
+
print_warning(f"Organizations API error: {error_code}")
|
200
|
+
return []
|
201
|
+
except Exception as e:
|
202
|
+
print_warning(f"Failed to access Organizations API: {e}")
|
203
|
+
return []
|
204
|
+
|
205
|
+
def _get_current_account(self) -> Optional[AWSAccount]:
|
206
|
+
"""Get current account as fallback."""
|
207
|
+
try:
|
208
|
+
sts_client = self.session.client("sts")
|
209
|
+
identity = sts_client.get_caller_identity()
|
210
|
+
|
211
|
+
return AWSAccount(
|
212
|
+
account_id=identity["Account"],
|
213
|
+
account_name=f"Current-Account-{identity['Account']}",
|
214
|
+
status="ACTIVE",
|
215
|
+
profile_name=self.resolved_profile
|
216
|
+
)
|
217
|
+
|
218
|
+
except Exception as e:
|
219
|
+
print_error(f"Failed to get current account identity: {e}")
|
220
|
+
return None
|
221
|
+
|
222
|
+
def filter_accounts_by_criteria(
|
223
|
+
self,
|
224
|
+
accounts: List[AWSAccount],
|
225
|
+
include_patterns: Optional[List[str]] = None,
|
226
|
+
exclude_patterns: Optional[List[str]] = None,
|
227
|
+
max_accounts: Optional[int] = None
|
228
|
+
) -> List[AWSAccount]:
|
229
|
+
"""
|
230
|
+
Filter discovered accounts by various criteria.
|
231
|
+
|
232
|
+
Args:
|
233
|
+
accounts: List of discovered accounts
|
234
|
+
include_patterns: Account ID or name patterns to include
|
235
|
+
exclude_patterns: Account ID or name patterns to exclude
|
236
|
+
max_accounts: Maximum number of accounts to return
|
237
|
+
|
238
|
+
Returns:
|
239
|
+
List[AWSAccount]: Filtered accounts
|
240
|
+
"""
|
241
|
+
filtered_accounts = accounts.copy()
|
242
|
+
|
243
|
+
# Apply include patterns
|
244
|
+
if include_patterns:
|
245
|
+
filtered_accounts = [
|
246
|
+
acc for acc in filtered_accounts
|
247
|
+
if any(pattern in acc.account_id or (acc.account_name and pattern in acc.account_name)
|
248
|
+
for pattern in include_patterns)
|
249
|
+
]
|
250
|
+
|
251
|
+
# Apply exclude patterns
|
252
|
+
if exclude_patterns:
|
253
|
+
filtered_accounts = [
|
254
|
+
acc for acc in filtered_accounts
|
255
|
+
if not any(pattern in acc.account_id or (acc.account_name and pattern in acc.account_name)
|
256
|
+
for pattern in exclude_patterns)
|
257
|
+
]
|
258
|
+
|
259
|
+
# Apply max accounts limit
|
260
|
+
if max_accounts and len(filtered_accounts) > max_accounts:
|
261
|
+
console.log(f"[yellow]Limiting to {max_accounts} accounts (found {len(filtered_accounts)})[/]")
|
262
|
+
filtered_accounts = filtered_accounts[:max_accounts]
|
263
|
+
|
264
|
+
return filtered_accounts
|
265
|
+
|
266
|
+
def validate_account_access(self, accounts: List[AWSAccount]) -> List[AWSAccount]:
|
267
|
+
"""
|
268
|
+
Validate access to discovered accounts.
|
269
|
+
|
270
|
+
Args:
|
271
|
+
accounts: List of accounts to validate
|
272
|
+
|
273
|
+
Returns:
|
274
|
+
List[AWSAccount]: Accounts with validated access
|
275
|
+
"""
|
276
|
+
validated_accounts = []
|
277
|
+
|
278
|
+
for account in accounts:
|
279
|
+
try:
|
280
|
+
# Try to get caller identity to validate access
|
281
|
+
session = boto3.Session(profile_name=account.profile_name or self.resolved_profile)
|
282
|
+
sts_client = session.client("sts")
|
283
|
+
identity = sts_client.get_caller_identity()
|
284
|
+
|
285
|
+
# Verify account ID matches
|
286
|
+
if identity["Account"] == account.account_id:
|
287
|
+
validated_accounts.append(account)
|
288
|
+
console.log(f"[green]✓ Validated access to account: {account.account_id}[/]")
|
289
|
+
else:
|
290
|
+
print_warning(f"Account ID mismatch for {account.account_id}: got {identity['Account']}")
|
291
|
+
|
292
|
+
except Exception as e:
|
293
|
+
print_warning(f"Failed to validate access to account {account.account_id}: {e}")
|
294
|
+
|
295
|
+
return validated_accounts
|
296
|
+
|
297
|
+
def export_account_config_template(self, output_path: str) -> None:
|
298
|
+
"""
|
299
|
+
Export account configuration template for enterprise customization.
|
300
|
+
|
301
|
+
Args:
|
302
|
+
output_path: Path to save the configuration template
|
303
|
+
"""
|
304
|
+
template = {
|
305
|
+
"target_accounts": [
|
306
|
+
{
|
307
|
+
"account_id": "111122223333",
|
308
|
+
"account_name": "Production Environment",
|
309
|
+
"status": "ACTIVE",
|
310
|
+
"email": "prod@company.com",
|
311
|
+
"profile_name": "prod-profile"
|
312
|
+
},
|
313
|
+
{
|
314
|
+
"account_id": "444455556666",
|
315
|
+
"account_name": "Staging Environment",
|
316
|
+
"status": "ACTIVE",
|
317
|
+
"email": "staging@company.com",
|
318
|
+
"profile_name": "staging-profile"
|
319
|
+
}
|
320
|
+
],
|
321
|
+
"discovery_settings": {
|
322
|
+
"max_concurrent_accounts": 10,
|
323
|
+
"validation_timeout_seconds": 30,
|
324
|
+
"include_suspended_accounts": False
|
325
|
+
}
|
326
|
+
}
|
327
|
+
|
328
|
+
try:
|
329
|
+
with open(output_path, 'w') as f:
|
330
|
+
json.dump(template, f, indent=2)
|
331
|
+
console.log(f"[green]Account configuration template exported to: {output_path}[/]")
|
332
|
+
except Exception as e:
|
333
|
+
print_error(f"Failed to export account configuration template: {e}")
|
334
|
+
|
335
|
+
|
336
|
+
def discover_remediation_accounts(profile: Optional[str] = None) -> List[AWSAccount]:
|
337
|
+
"""
|
338
|
+
Convenience function for universal account discovery.
|
339
|
+
|
340
|
+
Args:
|
341
|
+
profile: AWS profile to use for discovery
|
342
|
+
|
343
|
+
Returns:
|
344
|
+
List[AWSAccount]: Discovered accounts for remediation
|
345
|
+
"""
|
346
|
+
discovery = UniversalAccountDiscovery(profile=profile)
|
347
|
+
return discovery.discover_target_accounts()
|
348
|
+
|
349
|
+
|
350
|
+
def get_account_by_id(account_id: str, profile: Optional[str] = None) -> Optional[AWSAccount]:
|
351
|
+
"""
|
352
|
+
Get specific account by ID using universal discovery.
|
353
|
+
|
354
|
+
Args:
|
355
|
+
account_id: Target account ID
|
356
|
+
profile: AWS profile to use
|
357
|
+
|
358
|
+
Returns:
|
359
|
+
Optional[AWSAccount]: Account if found, None otherwise
|
360
|
+
"""
|
361
|
+
discovery = UniversalAccountDiscovery(profile=profile)
|
362
|
+
accounts = discovery.discover_target_accounts()
|
363
|
+
|
364
|
+
for account in accounts:
|
365
|
+
if account.account_id == account_id:
|
366
|
+
return account
|
367
|
+
|
368
|
+
return None
|
369
|
+
|
370
|
+
|
371
|
+
# Export public interface
|
372
|
+
__all__ = [
|
373
|
+
"AWSAccount",
|
374
|
+
"UniversalAccountDiscovery",
|
375
|
+
"discover_remediation_accounts",
|
376
|
+
"get_account_by_id",
|
377
|
+
]
|
@@ -1,7 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
🚨 HIGH-RISK: WorkSpaces Management - Analyze and manage WorkSpaces with deletion capabilities.
|
3
3
|
|
4
|
-
|
4
|
+
WorkSpaces Resource Optimization: Enhanced cleanup with dynamic cost calculation using business case configuration
|
5
5
|
Accounts: 339712777494, 802669565615, 142964829704, 507583929055
|
6
6
|
Types: STANDARD, PERFORMANCE, VALUE in AUTO_STOP mode
|
7
7
|
"""
|
@@ -114,7 +114,7 @@ def get_workspaces(
|
|
114
114
|
"""
|
115
115
|
🚨 HIGH-RISK: Analyze WorkSpaces usage and optionally delete unused ones.
|
116
116
|
|
117
|
-
|
117
|
+
WorkSpaces Resource Optimization: Enhanced cleanup with dynamic cost calculation using business case configuration
|
118
118
|
"""
|
119
119
|
|
120
120
|
print_header("WorkSpaces Cost Optimization Analysis", "v0.9.1")
|
@@ -47,6 +47,7 @@ from .enterprise_security_framework import (
|
|
47
47
|
SecurityFinding,
|
48
48
|
SecuritySeverity,
|
49
49
|
)
|
50
|
+
from .config import get_universal_compliance_config
|
50
51
|
|
51
52
|
|
52
53
|
class ComplianceStatus(Enum):
|
@@ -143,6 +144,9 @@ class ComplianceAutomationEngine:
|
|
143
144
|
self.profile = profile
|
144
145
|
self.output_dir = Path(output_dir)
|
145
146
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
147
|
+
|
148
|
+
# Initialize universal compliance configuration
|
149
|
+
self.compliance_config = get_universal_compliance_config()
|
146
150
|
|
147
151
|
# Initialize AWS session
|
148
152
|
self.session = self._create_session()
|
@@ -170,6 +174,60 @@ class ComplianceAutomationEngine:
|
|
170
174
|
"""Create secure AWS session using enterprise profile management."""
|
171
175
|
# Use management profile for compliance operations requiring cross-account access
|
172
176
|
return create_management_session(profile=self.profile)
|
177
|
+
|
178
|
+
def _get_compliance_weight(self, control_id: str, default_weight: float) -> float:
|
179
|
+
"""
|
180
|
+
Get compliance weight for control using universal configuration system.
|
181
|
+
|
182
|
+
Uses the universal compliance configuration with priority:
|
183
|
+
1. Environment variables: COMPLIANCE_WEIGHT_<CONTROL_ID>
|
184
|
+
2. Configuration file: COMPLIANCE_CONFIG_PATH
|
185
|
+
3. Framework-specific defaults
|
186
|
+
|
187
|
+
Args:
|
188
|
+
control_id: Control identifier
|
189
|
+
default_weight: Framework-specific default weight
|
190
|
+
|
191
|
+
Returns:
|
192
|
+
float: Compliance weight for the control
|
193
|
+
"""
|
194
|
+
return self.compliance_config.get_control_weight(control_id, default_weight)
|
195
|
+
|
196
|
+
def _get_compliance_threshold(self, framework: ComplianceFramework) -> float:
|
197
|
+
"""
|
198
|
+
Get compliance threshold for framework using universal configuration system.
|
199
|
+
|
200
|
+
Uses the universal compliance configuration with framework-specific defaults:
|
201
|
+
- PCI DSS: 100.0% (requires perfect compliance)
|
202
|
+
- HIPAA: 95.0% (healthcare requires high compliance)
|
203
|
+
- SOC2 Type II: 95.0% (service organization controls)
|
204
|
+
- AWS Well-Architected: 90.0% (recommended practices)
|
205
|
+
- ISO 27001: 90.0% (information security management)
|
206
|
+
- NIST Cybersecurity: 85.0% (cybersecurity framework)
|
207
|
+
- CIS Benchmarks: 85.0% (security benchmarks)
|
208
|
+
|
209
|
+
Args:
|
210
|
+
framework: Compliance framework
|
211
|
+
|
212
|
+
Returns:
|
213
|
+
float: Compliance threshold for the framework
|
214
|
+
"""
|
215
|
+
# Framework-specific defaults based on industry standards
|
216
|
+
framework_defaults = {
|
217
|
+
ComplianceFramework.PCI_DSS: 100.0, # PCI DSS requires 100% compliance
|
218
|
+
ComplianceFramework.HIPAA: 95.0, # HIPAA requires high compliance
|
219
|
+
ComplianceFramework.SOC2_TYPE_II: 95.0, # SOC2 requires high compliance
|
220
|
+
ComplianceFramework.AWS_WELL_ARCHITECTED: 90.0,
|
221
|
+
ComplianceFramework.ISO27001: 90.0,
|
222
|
+
ComplianceFramework.NIST_CYBERSECURITY: 85.0,
|
223
|
+
ComplianceFramework.CIS_BENCHMARKS: 85.0,
|
224
|
+
}
|
225
|
+
|
226
|
+
# Get framework name for configuration lookup
|
227
|
+
framework_name = framework.value.lower().replace(' ', '-').replace('_', '-')
|
228
|
+
default_threshold = framework_defaults.get(framework, 90.0)
|
229
|
+
|
230
|
+
return self.compliance_config.get_framework_threshold(framework_name, default_threshold)
|
173
231
|
|
174
232
|
def _load_framework_controls(self) -> Dict[ComplianceFramework, List[ComplianceControl]]:
|
175
233
|
"""Load compliance framework control definitions."""
|
@@ -189,7 +247,7 @@ class ComplianceAutomationEngine:
|
|
189
247
|
automated_assessment=True,
|
190
248
|
assessment_method="iam_policy_analysis",
|
191
249
|
remediation_available=True,
|
192
|
-
compliance_score_weight=2.0,
|
250
|
+
compliance_score_weight=self._get_compliance_weight("SEC-1", 2.0),
|
193
251
|
evidence_requirements=["iam_policies", "access_logs", "mfa_status"],
|
194
252
|
testing_frequency="monthly",
|
195
253
|
),
|
@@ -203,7 +261,7 @@ class ComplianceAutomationEngine:
|
|
203
261
|
automated_assessment=True,
|
204
262
|
assessment_method="multi_layer_security_check",
|
205
263
|
remediation_available=True,
|
206
|
-
compliance_score_weight=1.5,
|
264
|
+
compliance_score_weight=self._get_compliance_weight("SEC-2", 1.5),
|
207
265
|
evidence_requirements=["security_groups", "nacls", "waf_rules"],
|
208
266
|
testing_frequency="monthly",
|
209
267
|
),
|
@@ -222,7 +280,7 @@ class ComplianceAutomationEngine:
|
|
222
280
|
automated_assessment=True,
|
223
281
|
assessment_method="access_control_assessment",
|
224
282
|
remediation_available=True,
|
225
|
-
compliance_score_weight=3.0,
|
283
|
+
compliance_score_weight=self._get_compliance_weight("CC6.1", 3.0),
|
226
284
|
evidence_requirements=["access_logs", "user_provisioning", "termination_procedures"],
|
227
285
|
testing_frequency="quarterly",
|
228
286
|
),
|
@@ -236,7 +294,7 @@ class ComplianceAutomationEngine:
|
|
236
294
|
automated_assessment=True,
|
237
295
|
assessment_method="authentication_assessment",
|
238
296
|
remediation_available=True,
|
239
|
-
compliance_score_weight=2.5,
|
297
|
+
compliance_score_weight=self._get_compliance_weight("CC6.2", 2.5),
|
240
298
|
evidence_requirements=["authentication_logs", "mfa_usage", "password_policies"],
|
241
299
|
testing_frequency="quarterly",
|
242
300
|
),
|
@@ -255,7 +313,7 @@ class ComplianceAutomationEngine:
|
|
255
313
|
automated_assessment=True,
|
256
314
|
assessment_method="firewall_configuration_check",
|
257
315
|
remediation_available=True,
|
258
|
-
compliance_score_weight=2.0,
|
316
|
+
compliance_score_weight=self._get_compliance_weight("PCI-1", 2.0),
|
259
317
|
evidence_requirements=["firewall_rules", "change_logs", "review_procedures"],
|
260
318
|
testing_frequency="quarterly",
|
261
319
|
),
|
@@ -274,7 +332,7 @@ class ComplianceAutomationEngine:
|
|
274
332
|
automated_assessment=True,
|
275
333
|
assessment_method="hipaa_access_control_check",
|
276
334
|
remediation_available=True,
|
277
|
-
compliance_score_weight=2.5,
|
335
|
+
compliance_score_weight=self._get_compliance_weight("HIPAA-164.312(a)(1)", 2.5),
|
278
336
|
evidence_requirements=["access_procedures", "user_access_logs", "phi_access_controls"],
|
279
337
|
testing_frequency="annually",
|
280
338
|
),
|
@@ -410,18 +468,8 @@ class ComplianceAutomationEngine:
|
|
410
468
|
def _determine_compliance_status(self, score: float, framework: ComplianceFramework) -> ComplianceStatus:
|
411
469
|
"""Determine compliance status based on score and framework requirements."""
|
412
470
|
|
413
|
-
#
|
414
|
-
|
415
|
-
ComplianceFramework.PCI_DSS: 100.0, # PCI DSS requires 100% compliance
|
416
|
-
ComplianceFramework.HIPAA: 95.0, # HIPAA requires high compliance
|
417
|
-
ComplianceFramework.SOC2_TYPE_II: 95.0, # SOC2 requires high compliance
|
418
|
-
ComplianceFramework.AWS_WELL_ARCHITECTED: 90.0,
|
419
|
-
ComplianceFramework.ISO27001: 90.0,
|
420
|
-
ComplianceFramework.NIST_CYBERSECURITY: 85.0,
|
421
|
-
ComplianceFramework.CIS_BENCHMARKS: 85.0,
|
422
|
-
}
|
423
|
-
|
424
|
-
threshold = framework_thresholds.get(framework, 90.0)
|
471
|
+
# Use dynamic threshold configuration
|
472
|
+
threshold = self._get_compliance_threshold(framework)
|
425
473
|
|
426
474
|
if score >= threshold:
|
427
475
|
return ComplianceStatus.COMPLIANT
|
@@ -611,9 +659,38 @@ class ComplianceAutomationEngine:
|
|
611
659
|
return summary
|
612
660
|
|
613
661
|
async def _discover_target_accounts(self) -> List[str]:
|
614
|
-
"""
|
662
|
+
"""
|
663
|
+
Discover target accounts for compliance assessment using configuration-driven approach.
|
664
|
+
|
665
|
+
Priority:
|
666
|
+
1. Environment variable: COMPLIANCE_TARGET_ACCOUNTS (comma-separated)
|
667
|
+
2. Configuration file: COMPLIANCE_ACCOUNTS_CONFIG
|
668
|
+
3. AWS Organizations API discovery
|
669
|
+
4. Current account fallback
|
670
|
+
"""
|
671
|
+
# Try environment variable first
|
672
|
+
env_accounts = os.getenv("COMPLIANCE_TARGET_ACCOUNTS")
|
673
|
+
if env_accounts:
|
674
|
+
account_ids = [acc.strip() for acc in env_accounts.split(",")]
|
675
|
+
print_info(f"Using {len(account_ids)} accounts from COMPLIANCE_TARGET_ACCOUNTS environment variable")
|
676
|
+
return account_ids
|
677
|
+
|
678
|
+
# Try configuration file
|
679
|
+
config_path = os.getenv("COMPLIANCE_ACCOUNTS_CONFIG")
|
680
|
+
if config_path and os.path.exists(config_path):
|
681
|
+
try:
|
682
|
+
with open(config_path, 'r') as f:
|
683
|
+
config = json.load(f)
|
684
|
+
account_ids = config.get("target_accounts", [])
|
685
|
+
if account_ids:
|
686
|
+
print_info(f"Using {len(account_ids)} accounts from configuration file: {config_path}")
|
687
|
+
return account_ids
|
688
|
+
except Exception as e:
|
689
|
+
print_warning(f"Failed to load account configuration from {config_path}: {e}")
|
690
|
+
|
691
|
+
# Fall back to Organizations API discovery
|
615
692
|
try:
|
616
|
-
|
693
|
+
print_info("Discovering accounts via AWS Organizations API...")
|
617
694
|
org_client = self.session.client("organizations")
|
618
695
|
paginator = org_client.get_paginator("list_accounts")
|
619
696
|
|
@@ -623,6 +700,7 @@ class ComplianceAutomationEngine:
|
|
623
700
|
if account["Status"] == "ACTIVE":
|
624
701
|
accounts.append(account["Id"])
|
625
702
|
|
703
|
+
print_info(f"Discovered {len(accounts)} active accounts via Organizations API")
|
626
704
|
return accounts
|
627
705
|
|
628
706
|
except ClientError as e:
|
@@ -630,6 +708,7 @@ class ComplianceAutomationEngine:
|
|
630
708
|
print_warning(f"Could not discover organization accounts: {str(e)}")
|
631
709
|
sts_client = self.session.client("sts")
|
632
710
|
current_account = sts_client.get_caller_identity()["Account"]
|
711
|
+
print_info(f"Using current account for assessment: {current_account}")
|
633
712
|
return [current_account]
|
634
713
|
|
635
714
|
async def _export_compliance_report(self, report: ComplianceReport):
|
@@ -0,0 +1,24 @@
|
|
1
|
+
"""
|
2
|
+
Security Configuration Package
|
3
|
+
==============================
|
4
|
+
|
5
|
+
Universal configuration management for enterprise security and compliance operations.
|
6
|
+
Provides dynamic configuration with no hardcoded values.
|
7
|
+
|
8
|
+
Modules:
|
9
|
+
- compliance_config: Universal compliance configuration management
|
10
|
+
"""
|
11
|
+
|
12
|
+
from .compliance_config import (
|
13
|
+
ComplianceConfiguration,
|
14
|
+
UniversalComplianceConfig,
|
15
|
+
get_universal_compliance_config,
|
16
|
+
reset_compliance_config,
|
17
|
+
)
|
18
|
+
|
19
|
+
__all__ = [
|
20
|
+
"ComplianceConfiguration",
|
21
|
+
"UniversalComplianceConfig",
|
22
|
+
"get_universal_compliance_config",
|
23
|
+
"reset_compliance_config",
|
24
|
+
]
|