runbooks 1.1.4__py3-none-any.whl → 1.1.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 +31 -2
- runbooks/__init___optimized.py +18 -4
- runbooks/_platform/__init__.py +1 -5
- runbooks/_platform/core/runbooks_wrapper.py +141 -138
- runbooks/aws2/accuracy_validator.py +812 -0
- runbooks/base.py +7 -0
- runbooks/cfat/assessment/compliance.py +1 -1
- runbooks/cfat/assessment/runner.py +1 -0
- runbooks/cfat/cloud_foundations_assessment.py +227 -239
- runbooks/cli/__init__.py +1 -1
- runbooks/cli/commands/cfat.py +64 -23
- runbooks/cli/commands/finops.py +1005 -54
- runbooks/cli/commands/inventory.py +135 -91
- runbooks/cli/commands/operate.py +9 -36
- runbooks/cli/commands/security.py +42 -18
- runbooks/cli/commands/validation.py +432 -18
- runbooks/cli/commands/vpc.py +81 -17
- runbooks/cli/registry.py +22 -10
- runbooks/cloudops/__init__.py +20 -27
- runbooks/cloudops/base.py +96 -107
- runbooks/cloudops/cost_optimizer.py +544 -542
- runbooks/cloudops/infrastructure_optimizer.py +5 -4
- runbooks/cloudops/interfaces.py +224 -225
- runbooks/cloudops/lifecycle_manager.py +5 -4
- runbooks/cloudops/mcp_cost_validation.py +252 -235
- runbooks/cloudops/models.py +78 -53
- runbooks/cloudops/monitoring_automation.py +5 -4
- runbooks/cloudops/notebook_framework.py +177 -213
- runbooks/cloudops/security_enforcer.py +125 -159
- runbooks/common/accuracy_validator.py +17 -12
- runbooks/common/aws_pricing.py +349 -326
- runbooks/common/aws_pricing_api.py +211 -212
- runbooks/common/aws_profile_manager.py +40 -36
- runbooks/common/aws_utils.py +74 -79
- runbooks/common/business_logic.py +126 -104
- runbooks/common/cli_decorators.py +36 -60
- runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
- runbooks/common/cross_account_manager.py +197 -204
- runbooks/common/date_utils.py +27 -39
- runbooks/common/decorators.py +29 -19
- runbooks/common/dry_run_examples.py +173 -208
- runbooks/common/dry_run_framework.py +157 -155
- runbooks/common/enhanced_exception_handler.py +15 -4
- runbooks/common/enhanced_logging_example.py +50 -64
- runbooks/common/enhanced_logging_integration_example.py +65 -37
- runbooks/common/env_utils.py +16 -16
- runbooks/common/error_handling.py +40 -38
- runbooks/common/lazy_loader.py +41 -23
- runbooks/common/logging_integration_helper.py +79 -86
- runbooks/common/mcp_cost_explorer_integration.py +476 -493
- runbooks/common/mcp_integration.py +99 -79
- runbooks/common/memory_optimization.py +140 -118
- runbooks/common/module_cli_base.py +37 -58
- runbooks/common/organizations_client.py +175 -193
- runbooks/common/patterns.py +23 -25
- runbooks/common/performance_monitoring.py +67 -71
- runbooks/common/performance_optimization_engine.py +283 -274
- runbooks/common/profile_utils.py +111 -37
- runbooks/common/rich_utils.py +315 -141
- runbooks/common/sre_performance_suite.py +177 -186
- runbooks/enterprise/__init__.py +1 -1
- runbooks/enterprise/logging.py +144 -106
- runbooks/enterprise/security.py +187 -204
- runbooks/enterprise/validation.py +43 -56
- runbooks/finops/__init__.py +26 -30
- runbooks/finops/account_resolver.py +1 -1
- runbooks/finops/advanced_optimization_engine.py +980 -0
- runbooks/finops/automation_core.py +268 -231
- runbooks/finops/business_case_config.py +184 -179
- runbooks/finops/cli.py +660 -139
- runbooks/finops/commvault_ec2_analysis.py +157 -164
- runbooks/finops/compute_cost_optimizer.py +336 -320
- runbooks/finops/config.py +20 -20
- runbooks/finops/cost_optimizer.py +484 -618
- runbooks/finops/cost_processor.py +332 -214
- runbooks/finops/dashboard_runner.py +1006 -172
- runbooks/finops/ebs_cost_optimizer.py +991 -657
- runbooks/finops/elastic_ip_optimizer.py +317 -257
- runbooks/finops/enhanced_mcp_integration.py +340 -0
- runbooks/finops/enhanced_progress.py +32 -29
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/enterprise_wrappers.py +223 -285
- runbooks/finops/executive_export.py +203 -160
- runbooks/finops/helpers.py +130 -288
- runbooks/finops/iam_guidance.py +1 -1
- runbooks/finops/infrastructure/__init__.py +80 -0
- runbooks/finops/infrastructure/commands.py +506 -0
- runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
- runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
- runbooks/finops/markdown_exporter.py +337 -174
- runbooks/finops/mcp_validator.py +1952 -0
- runbooks/finops/nat_gateway_optimizer.py +1512 -481
- runbooks/finops/network_cost_optimizer.py +657 -587
- runbooks/finops/notebook_utils.py +226 -188
- runbooks/finops/optimization_engine.py +1136 -0
- runbooks/finops/optimizer.py +19 -23
- runbooks/finops/rds_snapshot_optimizer.py +367 -411
- runbooks/finops/reservation_optimizer.py +427 -363
- runbooks/finops/scenario_cli_integration.py +64 -65
- runbooks/finops/scenarios.py +1277 -438
- runbooks/finops/schemas.py +218 -182
- runbooks/finops/snapshot_manager.py +2289 -0
- runbooks/finops/types.py +3 -3
- runbooks/finops/validation_framework.py +259 -265
- runbooks/finops/vpc_cleanup_exporter.py +189 -144
- runbooks/finops/vpc_cleanup_optimizer.py +591 -573
- runbooks/finops/workspaces_analyzer.py +171 -182
- runbooks/integration/__init__.py +89 -0
- runbooks/integration/mcp_integration.py +1920 -0
- runbooks/inventory/CLAUDE.md +816 -0
- runbooks/inventory/__init__.py +2 -2
- runbooks/inventory/aws_decorators.py +2 -3
- runbooks/inventory/check_cloudtrail_compliance.py +2 -4
- runbooks/inventory/check_controltower_readiness.py +152 -151
- runbooks/inventory/check_landingzone_readiness.py +85 -84
- runbooks/inventory/cloud_foundations_integration.py +144 -149
- runbooks/inventory/collectors/aws_comprehensive.py +1 -1
- runbooks/inventory/collectors/aws_networking.py +109 -99
- runbooks/inventory/collectors/base.py +4 -0
- runbooks/inventory/core/collector.py +495 -313
- runbooks/inventory/core/formatter.py +11 -0
- runbooks/inventory/draw_org_structure.py +8 -9
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/ec2_vpc_utils.py +2 -2
- runbooks/inventory/find_cfn_drift_detection.py +5 -7
- runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
- runbooks/inventory/find_cfn_stackset_drift.py +5 -6
- runbooks/inventory/find_ec2_security_groups.py +48 -42
- runbooks/inventory/find_landingzone_versions.py +4 -6
- runbooks/inventory/find_vpc_flow_logs.py +7 -9
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/inventory_modules.py +103 -91
- runbooks/inventory/list_cfn_stacks.py +9 -10
- runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
- runbooks/inventory/list_cfn_stackset_operations.py +79 -57
- runbooks/inventory/list_cfn_stacksets.py +8 -10
- runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
- runbooks/inventory/list_ds_directories.py +65 -53
- runbooks/inventory/list_ec2_availability_zones.py +2 -4
- runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
- runbooks/inventory/list_ec2_instances.py +23 -28
- runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
- runbooks/inventory/list_elbs_load_balancers.py +22 -20
- runbooks/inventory/list_enis_network_interfaces.py +26 -33
- runbooks/inventory/list_guardduty_detectors.py +2 -4
- runbooks/inventory/list_iam_policies.py +2 -4
- runbooks/inventory/list_iam_roles.py +5 -7
- runbooks/inventory/list_iam_saml_providers.py +4 -6
- runbooks/inventory/list_lambda_functions.py +38 -38
- runbooks/inventory/list_org_accounts.py +6 -8
- runbooks/inventory/list_org_accounts_users.py +55 -44
- runbooks/inventory/list_rds_db_instances.py +31 -33
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/list_route53_hosted_zones.py +3 -5
- runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
- runbooks/inventory/list_sns_topics.py +2 -4
- runbooks/inventory/list_ssm_parameters.py +4 -7
- runbooks/inventory/list_vpc_subnets.py +2 -4
- runbooks/inventory/list_vpcs.py +7 -10
- runbooks/inventory/mcp_inventory_validator.py +554 -468
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +63 -55
- runbooks/inventory/recover_cfn_stack_ids.py +7 -8
- runbooks/inventory/requirements.txt +0 -1
- runbooks/inventory/rich_inventory_display.py +35 -34
- runbooks/inventory/run_on_multi_accounts.py +3 -5
- runbooks/inventory/unified_validation_engine.py +281 -253
- runbooks/inventory/verify_ec2_security_groups.py +1 -1
- runbooks/inventory/vpc_analyzer.py +735 -697
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +384 -380
- runbooks/inventory/vpc_flow_analyzer.py +1 -1
- runbooks/main.py +49 -34
- runbooks/main_final.py +91 -60
- runbooks/main_minimal.py +22 -10
- runbooks/main_optimized.py +131 -100
- runbooks/main_ultra_minimal.py +7 -2
- runbooks/mcp/__init__.py +36 -0
- runbooks/mcp/integration.py +679 -0
- runbooks/monitoring/performance_monitor.py +9 -4
- runbooks/operate/dynamodb_operations.py +3 -1
- runbooks/operate/ec2_operations.py +145 -137
- runbooks/operate/iam_operations.py +146 -152
- runbooks/operate/networking_cost_heatmap.py +29 -8
- runbooks/operate/rds_operations.py +223 -254
- runbooks/operate/s3_operations.py +107 -118
- runbooks/operate/vpc_operations.py +646 -616
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commons.py +10 -7
- runbooks/remediation/commvault_ec2_analysis.py +70 -66
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
- runbooks/remediation/multi_account.py +24 -21
- runbooks/remediation/rds_snapshot_list.py +86 -60
- runbooks/remediation/remediation_cli.py +92 -146
- runbooks/remediation/universal_account_discovery.py +83 -79
- runbooks/remediation/workspaces_list.py +46 -41
- runbooks/security/__init__.py +19 -0
- runbooks/security/assessment_runner.py +1150 -0
- runbooks/security/baseline_checker.py +812 -0
- runbooks/security/cloudops_automation_security_validator.py +509 -535
- runbooks/security/compliance_automation_engine.py +17 -17
- runbooks/security/config/__init__.py +2 -2
- runbooks/security/config/compliance_config.py +50 -50
- runbooks/security/config_template_generator.py +63 -76
- runbooks/security/enterprise_security_framework.py +1 -1
- runbooks/security/executive_security_dashboard.py +519 -508
- runbooks/security/multi_account_security_controls.py +959 -1210
- runbooks/security/real_time_security_monitor.py +422 -444
- runbooks/security/security_baseline_tester.py +1 -1
- runbooks/security/security_cli.py +143 -112
- runbooks/security/test_2way_validation.py +439 -0
- runbooks/security/two_way_validation_framework.py +852 -0
- runbooks/sre/production_monitoring_framework.py +167 -177
- runbooks/tdd/__init__.py +15 -0
- runbooks/tdd/cli.py +1071 -0
- runbooks/utils/__init__.py +14 -17
- runbooks/utils/logger.py +7 -2
- runbooks/utils/version_validator.py +50 -47
- runbooks/validation/__init__.py +6 -6
- runbooks/validation/cli.py +9 -3
- runbooks/validation/comprehensive_2way_validator.py +745 -704
- runbooks/validation/mcp_validator.py +906 -228
- runbooks/validation/terraform_citations_validator.py +104 -115
- runbooks/validation/terraform_drift_detector.py +461 -454
- runbooks/vpc/README.md +617 -0
- runbooks/vpc/__init__.py +8 -1
- runbooks/vpc/analyzer.py +577 -0
- runbooks/vpc/cleanup_wrapper.py +476 -413
- runbooks/vpc/cli_cloudtrail_commands.py +339 -0
- runbooks/vpc/cli_mcp_validation_commands.py +480 -0
- runbooks/vpc/cloudtrail_audit_integration.py +717 -0
- runbooks/vpc/config.py +92 -97
- runbooks/vpc/cost_engine.py +411 -148
- runbooks/vpc/cost_explorer_integration.py +553 -0
- runbooks/vpc/cross_account_session.py +101 -106
- runbooks/vpc/enhanced_mcp_validation.py +917 -0
- runbooks/vpc/eni_gate_validator.py +961 -0
- runbooks/vpc/heatmap_engine.py +185 -160
- runbooks/vpc/mcp_no_eni_validator.py +680 -639
- runbooks/vpc/nat_gateway_optimizer.py +358 -0
- runbooks/vpc/networking_wrapper.py +15 -8
- runbooks/vpc/pdca_remediation_planner.py +528 -0
- runbooks/vpc/performance_optimized_analyzer.py +219 -231
- runbooks/vpc/runbooks_adapter.py +1167 -241
- runbooks/vpc/tdd_red_phase_stubs.py +601 -0
- runbooks/vpc/test_data_loader.py +358 -0
- runbooks/vpc/tests/conftest.py +314 -4
- runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
- runbooks/vpc/tests/test_cost_engine.py +0 -2
- runbooks/vpc/topology_generator.py +326 -0
- runbooks/vpc/unified_scenarios.py +1297 -1124
- runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
- runbooks-1.1.6.dist-info/METADATA +327 -0
- runbooks-1.1.6.dist-info/RECORD +489 -0
- runbooks/finops/README.md +0 -414
- runbooks/finops/accuracy_cross_validator.py +0 -647
- runbooks/finops/business_cases.py +0 -950
- runbooks/finops/dashboard_router.py +0 -922
- runbooks/finops/ebs_optimizer.py +0 -973
- runbooks/finops/embedded_mcp_validator.py +0 -1629
- runbooks/finops/enhanced_dashboard_runner.py +0 -527
- runbooks/finops/finops_dashboard.py +0 -584
- runbooks/finops/finops_scenarios.py +0 -1218
- runbooks/finops/legacy_migration.py +0 -730
- runbooks/finops/multi_dashboard.py +0 -1519
- runbooks/finops/single_dashboard.py +0 -1113
- runbooks/finops/unlimited_scenarios.py +0 -393
- runbooks-1.1.4.dist-info/METADATA +0 -800
- runbooks-1.1.4.dist-info/RECORD +0 -468
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
@@ -24,6 +24,7 @@ from rich.console import Console
|
|
24
24
|
|
25
25
|
from runbooks.common.rich_utils import console, print_error, print_success, print_warning, print_info
|
26
26
|
|
27
|
+
|
27
28
|
class AWSProfileManager:
|
28
29
|
"""
|
29
30
|
Universal AWS Profile Manager for CloudOps v1.1.x compatibility.
|
@@ -58,14 +59,14 @@ class AWSProfileManager:
|
|
58
59
|
return user_profile
|
59
60
|
|
60
61
|
# Tier 2: Environment variable
|
61
|
-
env_profile = os.getenv(
|
62
|
+
env_profile = os.getenv("AWS_PROFILE")
|
62
63
|
if env_profile:
|
63
64
|
return env_profile
|
64
65
|
|
65
66
|
# Tier 3: Default (no explicit profile)
|
66
67
|
return None
|
67
68
|
|
68
|
-
def get_session(self, region: str =
|
69
|
+
def get_session(self, region: str = "us-east-1") -> boto3.Session:
|
69
70
|
"""
|
70
71
|
Get boto3 session with resolved profile.
|
71
72
|
|
@@ -89,7 +90,7 @@ class AWSProfileManager:
|
|
89
90
|
print_info("Using default AWS credentials")
|
90
91
|
|
91
92
|
# Validate credentials by getting caller identity
|
92
|
-
sts = self.session.client(
|
93
|
+
sts = self.session.client("sts")
|
93
94
|
identity = sts.get_caller_identity()
|
94
95
|
print_success(f"✅ Authenticated as: {identity.get('Arn', 'Unknown')}")
|
95
96
|
|
@@ -108,7 +109,7 @@ class AWSProfileManager:
|
|
108
109
|
|
109
110
|
return self.session
|
110
111
|
|
111
|
-
def get_account_id(self, region: str =
|
112
|
+
def get_account_id(self, region: str = "us-east-1") -> str:
|
112
113
|
"""
|
113
114
|
Get current AWS account ID dynamically.
|
114
115
|
|
@@ -123,9 +124,9 @@ class AWSProfileManager:
|
|
123
124
|
if cache_key not in self._account_cache:
|
124
125
|
try:
|
125
126
|
session = self.get_session(region)
|
126
|
-
sts = session.client(
|
127
|
+
sts = session.client("sts")
|
127
128
|
identity = sts.get_caller_identity()
|
128
|
-
account_id = identity[
|
129
|
+
account_id = identity["Account"]
|
129
130
|
self._account_cache[cache_key] = account_id
|
130
131
|
print_info(f"Current account ID: {account_id}")
|
131
132
|
|
@@ -136,7 +137,7 @@ class AWSProfileManager:
|
|
136
137
|
|
137
138
|
return self._account_cache[cache_key]
|
138
139
|
|
139
|
-
def discover_organization_accounts(self, region: str =
|
140
|
+
def discover_organization_accounts(self, region: str = "us-east-1") -> List[Dict[str, Any]]:
|
140
141
|
"""
|
141
142
|
Discover all accounts in AWS Organizations (if available).
|
142
143
|
|
@@ -148,7 +149,7 @@ class AWSProfileManager:
|
|
148
149
|
"""
|
149
150
|
try:
|
150
151
|
session = self.get_session(region)
|
151
|
-
org_client = session.client(
|
152
|
+
org_client = session.client("organizations")
|
152
153
|
|
153
154
|
# Get organization information
|
154
155
|
try:
|
@@ -160,17 +161,19 @@ class AWSProfileManager:
|
|
160
161
|
|
161
162
|
# List all accounts
|
162
163
|
accounts = []
|
163
|
-
paginator = org_client.get_paginator(
|
164
|
+
paginator = org_client.get_paginator("list_accounts")
|
164
165
|
|
165
166
|
for page in paginator.paginate():
|
166
|
-
for account in page[
|
167
|
-
accounts.append(
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
167
|
+
for account in page["Accounts"]:
|
168
|
+
accounts.append(
|
169
|
+
{
|
170
|
+
"Id": account["Id"],
|
171
|
+
"Name": account["Name"],
|
172
|
+
"Email": account["Email"],
|
173
|
+
"Status": account["Status"],
|
174
|
+
"JoinedMethod": account.get("JoinedMethod", "UNKNOWN"),
|
175
|
+
}
|
176
|
+
)
|
174
177
|
|
175
178
|
print_success(f"✅ Discovered {len(accounts)} organization accounts")
|
176
179
|
return accounts
|
@@ -190,7 +193,7 @@ class AWSProfileManager:
|
|
190
193
|
Dict mapping service names to access status
|
191
194
|
"""
|
192
195
|
if not required_services:
|
193
|
-
required_services = [
|
196
|
+
required_services = ["sts", "ce", "ec2", "s3"]
|
194
197
|
|
195
198
|
access_status = {}
|
196
199
|
session = self.get_session()
|
@@ -200,21 +203,22 @@ class AWSProfileManager:
|
|
200
203
|
client = session.client(service)
|
201
204
|
|
202
205
|
# Service-specific health checks
|
203
|
-
if service ==
|
206
|
+
if service == "sts":
|
204
207
|
client.get_caller_identity()
|
205
|
-
elif service ==
|
208
|
+
elif service == "ce":
|
206
209
|
# Test Cost Explorer access
|
207
210
|
from datetime import datetime, timedelta
|
208
|
-
|
209
|
-
|
211
|
+
|
212
|
+
end_date = datetime.now().strftime("%Y-%m-%d")
|
213
|
+
start_date = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
|
210
214
|
client.get_cost_and_usage(
|
211
|
-
TimePeriod={
|
212
|
-
Granularity=
|
213
|
-
Metrics=[
|
215
|
+
TimePeriod={"Start": start_date, "End": end_date},
|
216
|
+
Granularity="MONTHLY",
|
217
|
+
Metrics=["UnblendedCost"],
|
214
218
|
)
|
215
|
-
elif service ==
|
219
|
+
elif service == "ec2":
|
216
220
|
client.describe_regions(MaxResults=1)
|
217
|
-
elif service ==
|
221
|
+
elif service == "s3":
|
218
222
|
client.list_buckets()
|
219
223
|
|
220
224
|
access_status[service] = True
|
@@ -232,8 +236,8 @@ class AWSProfileManager:
|
|
232
236
|
import configparser
|
233
237
|
import os.path
|
234
238
|
|
235
|
-
credentials_path = os.path.expanduser(
|
236
|
-
config_path = os.path.expanduser(
|
239
|
+
credentials_path = os.path.expanduser("~/.aws/credentials")
|
240
|
+
config_path = os.path.expanduser("~/.aws/config")
|
237
241
|
|
238
242
|
profiles = set()
|
239
243
|
|
@@ -248,10 +252,10 @@ class AWSProfileManager:
|
|
248
252
|
config_config = configparser.ConfigParser()
|
249
253
|
config_config.read(config_path)
|
250
254
|
for section in config_config.sections():
|
251
|
-
if section.startswith(
|
255
|
+
if section.startswith("profile "):
|
252
256
|
profiles.add(section[8:]) # Remove 'profile ' prefix
|
253
|
-
elif section ==
|
254
|
-
profiles.add(
|
257
|
+
elif section == "default":
|
258
|
+
profiles.add("default")
|
255
259
|
|
256
260
|
if profiles:
|
257
261
|
for profile in sorted(profiles):
|
@@ -263,7 +267,7 @@ class AWSProfileManager:
|
|
263
267
|
print_warning(f"⚠️ Could not list profiles: {e}")
|
264
268
|
|
265
269
|
@classmethod
|
266
|
-
def create_mock_account_context(cls, mock_account_id: str = "123456789012") ->
|
270
|
+
def create_mock_account_context(cls, mock_account_id: str = "123456789012") -> "AWSProfileManager":
|
267
271
|
"""
|
268
272
|
Create mock profile manager for testing scenarios.
|
269
273
|
|
@@ -274,7 +278,7 @@ class AWSProfileManager:
|
|
274
278
|
ProfileManager configured for testing
|
275
279
|
"""
|
276
280
|
manager = cls()
|
277
|
-
manager._account_cache[
|
281
|
+
manager._account_cache["mock"] = mock_account_id
|
278
282
|
return manager
|
279
283
|
|
280
284
|
def get_profile_display_name(self) -> str:
|
@@ -294,7 +298,7 @@ class AWSProfileManager:
|
|
294
298
|
|
295
299
|
|
296
300
|
# Global convenience functions for backward compatibility
|
297
|
-
def get_current_account_id(profile: Optional[str] = None, region: str =
|
301
|
+
def get_current_account_id(profile: Optional[str] = None, region: str = "us-east-1") -> str:
|
298
302
|
"""
|
299
303
|
Convenience function to get current account ID.
|
300
304
|
|
@@ -334,4 +338,4 @@ def validate_profile_or_exit(profile: Optional[str] = None, required_services: L
|
|
334
338
|
except (ProfileNotFound, NoCredentialsError):
|
335
339
|
print_error("❌ Cannot proceed without valid AWS credentials")
|
336
340
|
print_info("Please configure AWS credentials and try again.")
|
337
|
-
exit(1)
|
341
|
+
exit(1)
|
runbooks/common/aws_utils.py
CHANGED
@@ -31,67 +31,67 @@ from runbooks.common.rich_utils import console
|
|
31
31
|
class AWSProfileSanitizer:
|
32
32
|
"""
|
33
33
|
Enterprise-grade AWS profile name sanitization for security logging.
|
34
|
-
|
34
|
+
|
35
35
|
Prevents AWS account ID exposure in logs while maintaining audit trail integrity.
|
36
36
|
Following FAANG security-as-code principles for sensitive identifier protection.
|
37
37
|
"""
|
38
|
-
|
38
|
+
|
39
39
|
# Pattern to detect AWS account IDs in profile names
|
40
|
-
ACCOUNT_ID_PATTERN = re.compile(r
|
41
|
-
|
40
|
+
ACCOUNT_ID_PATTERN = re.compile(r"\b\d{12}\b")
|
41
|
+
|
42
42
|
# Pattern to detect enterprise profile patterns
|
43
|
-
ENTERPRISE_PROFILE_PATTERN = re.compile(r
|
44
|
-
|
43
|
+
ENTERPRISE_PROFILE_PATTERN = re.compile(r"(ams|aws)-.*-ReadOnlyAccess-(\d{12})")
|
44
|
+
|
45
45
|
@classmethod
|
46
46
|
def sanitize_profile_name(cls, profile_name: str, mask_style: str = "***masked***") -> str:
|
47
47
|
"""
|
48
48
|
Sanitize AWS profile name by masking account IDs for secure logging.
|
49
|
-
|
49
|
+
|
50
50
|
Replaces 12-digit AWS account IDs with masked values to prevent account enumeration
|
51
51
|
while preserving profile identification capabilities for audit purposes.
|
52
|
-
|
52
|
+
|
53
53
|
Args:
|
54
54
|
profile_name: Original AWS profile name
|
55
55
|
mask_style: Masking pattern for account IDs (default: ***masked***)
|
56
|
-
|
56
|
+
|
57
57
|
Returns:
|
58
58
|
Sanitized profile name with masked account IDs
|
59
|
-
|
59
|
+
|
60
60
|
Example:
|
61
61
|
'my-billing-profile-123456789012' → 'my-billing-profile-***masked***'
|
62
62
|
"""
|
63
63
|
if not profile_name:
|
64
64
|
return profile_name
|
65
|
-
|
65
|
+
|
66
66
|
# Check for enterprise pattern first (more specific)
|
67
67
|
if cls.ENTERPRISE_PROFILE_PATTERN.match(profile_name):
|
68
|
-
return cls.ENTERPRISE_PROFILE_PATTERN.sub(r
|
69
|
-
|
68
|
+
return cls.ENTERPRISE_PROFILE_PATTERN.sub(r"\1-masked-ReadOnlyAccess-***masked***", profile_name)
|
69
|
+
|
70
70
|
# General account ID masking
|
71
71
|
return cls.ACCOUNT_ID_PATTERN.sub(mask_style, profile_name)
|
72
|
-
|
72
|
+
|
73
73
|
@classmethod
|
74
74
|
def sanitize_profile_list(cls, profiles: List[str]) -> List[str]:
|
75
75
|
"""
|
76
76
|
Sanitize a list of AWS profile names for secure logging.
|
77
|
-
|
77
|
+
|
78
78
|
Args:
|
79
79
|
profiles: List of AWS profile names
|
80
|
-
|
80
|
+
|
81
81
|
Returns:
|
82
82
|
List of sanitized profile names
|
83
83
|
"""
|
84
84
|
return [cls.sanitize_profile_name(profile) for profile in profiles]
|
85
|
-
|
85
|
+
|
86
86
|
@classmethod
|
87
87
|
def create_secure_log_context(cls, profile: str, operation: str) -> Dict[str, str]:
|
88
88
|
"""
|
89
89
|
Create secure logging context with sanitized profile information.
|
90
|
-
|
90
|
+
|
91
91
|
Args:
|
92
92
|
profile: AWS profile name
|
93
93
|
operation: Operation being performed
|
94
|
-
|
94
|
+
|
95
95
|
Returns:
|
96
96
|
Dictionary with sanitized context for secure logging
|
97
97
|
"""
|
@@ -99,14 +99,14 @@ class AWSProfileSanitizer:
|
|
99
99
|
"operation": operation,
|
100
100
|
"profile_sanitized": cls.sanitize_profile_name(profile),
|
101
101
|
"profile_type": cls._classify_profile_type(profile),
|
102
|
-
"timestamp": datetime.utcnow().isoformat()
|
102
|
+
"timestamp": datetime.utcnow().isoformat(),
|
103
103
|
}
|
104
|
-
|
104
|
+
|
105
105
|
@classmethod
|
106
106
|
def _classify_profile_type(cls, profile_name: str) -> str:
|
107
107
|
"""Classify profile type for enhanced logging context."""
|
108
108
|
profile_lower = profile_name.lower()
|
109
|
-
|
109
|
+
|
110
110
|
if "billing" in profile_lower:
|
111
111
|
return "billing"
|
112
112
|
elif "management" in profile_lower:
|
@@ -122,109 +122,104 @@ class AWSProfileSanitizer:
|
|
122
122
|
class AWSTokenManager:
|
123
123
|
"""
|
124
124
|
Proactive AWS token management with security-focused error handling.
|
125
|
-
|
125
|
+
|
126
126
|
Implements proactive token refresh, retry logic, and enhanced error messaging
|
127
127
|
to reduce authentication timing exposure and improve operational security.
|
128
128
|
"""
|
129
|
-
|
129
|
+
|
130
130
|
# Token refresh thresholds
|
131
131
|
TOKEN_REFRESH_THRESHOLD_MINUTES = 15 # Refresh if expires within 15 minutes
|
132
132
|
MAX_RETRY_ATTEMPTS = 3
|
133
133
|
RETRY_BACKOFF_BASE = 2 # Exponential backoff base (seconds)
|
134
|
-
|
134
|
+
|
135
135
|
def __init__(self, profile_name: str):
|
136
136
|
"""Initialize token manager for specific AWS profile."""
|
137
137
|
self.profile_name = profile_name
|
138
138
|
self.sanitized_profile = AWSProfileSanitizer.sanitize_profile_name(profile_name)
|
139
139
|
self._session = None
|
140
140
|
self._last_refresh_check = None
|
141
|
-
|
141
|
+
|
142
142
|
def get_secure_session(self, force_refresh: bool = False) -> boto3.Session:
|
143
143
|
"""
|
144
144
|
Get AWS session with proactive token refresh and security enhancements.
|
145
|
-
|
145
|
+
|
146
146
|
Implements:
|
147
147
|
- Proactive token expiration checking
|
148
148
|
- Silent token refresh before expiration
|
149
149
|
- Exponential backoff retry logic
|
150
150
|
- Security-aware error messages
|
151
|
-
|
151
|
+
|
152
152
|
Args:
|
153
153
|
force_refresh: Force token refresh regardless of expiration status
|
154
|
-
|
154
|
+
|
155
155
|
Returns:
|
156
156
|
Boto3 session with valid credentials
|
157
|
-
|
157
|
+
|
158
158
|
Raises:
|
159
159
|
SecurityError: For authentication security issues
|
160
160
|
TokenRefreshError: For token refresh failures
|
161
161
|
"""
|
162
162
|
current_time = datetime.utcnow()
|
163
|
-
|
163
|
+
|
164
164
|
# Check if proactive refresh is needed
|
165
|
-
if
|
166
|
-
self._session is None or
|
167
|
-
self._needs_token_refresh(current_time)):
|
168
|
-
|
165
|
+
if force_refresh or self._session is None or self._needs_token_refresh(current_time):
|
169
166
|
self._session = self._refresh_session_with_retry()
|
170
167
|
self._last_refresh_check = current_time
|
171
|
-
|
168
|
+
|
172
169
|
# Log secure refresh event
|
173
|
-
console.log(
|
174
|
-
|
175
|
-
)
|
176
|
-
|
170
|
+
console.log(f"[dim green]✅ Token refresh completed for profile: {self.sanitized_profile}[/]")
|
171
|
+
|
177
172
|
return self._session
|
178
|
-
|
173
|
+
|
179
174
|
def _needs_token_refresh(self, current_time: datetime) -> bool:
|
180
175
|
"""Check if proactive token refresh is needed."""
|
181
176
|
if self._last_refresh_check is None:
|
182
177
|
return True
|
183
|
-
|
178
|
+
|
184
179
|
# Check every 5 minutes to avoid excessive API calls
|
185
180
|
if (current_time - self._last_refresh_check) < timedelta(minutes=5):
|
186
181
|
return False
|
187
|
-
|
182
|
+
|
188
183
|
try:
|
189
184
|
# Test session validity with STS call
|
190
185
|
if self._session:
|
191
|
-
sts_client = self._session.client(
|
186
|
+
sts_client = self._session.client("sts")
|
192
187
|
sts_client.get_caller_identity()
|
193
188
|
return False # Session still valid
|
194
189
|
except ClientError as e:
|
195
|
-
error_code = e.response.get(
|
196
|
-
if error_code in [
|
190
|
+
error_code = e.response.get("Error", {}).get("Code", "")
|
191
|
+
if error_code in ["ExpiredToken", "InvalidToken", "TokenRefreshRequired"]:
|
197
192
|
return True
|
198
|
-
|
193
|
+
|
199
194
|
return False
|
200
|
-
|
195
|
+
|
201
196
|
def _refresh_session_with_retry(self) -> boto3.Session:
|
202
197
|
"""Refresh session with exponential backoff retry logic."""
|
203
198
|
last_exception = None
|
204
|
-
|
199
|
+
|
205
200
|
for attempt in range(self.MAX_RETRY_ATTEMPTS):
|
206
201
|
try:
|
207
202
|
# Create new session
|
208
203
|
session = boto3.Session(profile_name=self.profile_name)
|
209
|
-
|
204
|
+
|
210
205
|
# Validate session with STS call
|
211
|
-
sts_client = session.client(
|
206
|
+
sts_client = session.client("sts")
|
212
207
|
caller_identity = sts_client.get_caller_identity()
|
213
|
-
|
208
|
+
|
214
209
|
# Log successful refresh (with sanitized profile)
|
215
210
|
console.log(
|
216
211
|
f"[dim cyan]🔄 Session validated for {self.sanitized_profile} "
|
217
212
|
f"(attempt {attempt + 1}/{self.MAX_RETRY_ATTEMPTS})[/]"
|
218
213
|
)
|
219
|
-
|
214
|
+
|
220
215
|
return session
|
221
|
-
|
216
|
+
|
222
217
|
except (ClientError, NoCredentialsError, TokenRetrievalError) as e:
|
223
218
|
last_exception = e
|
224
|
-
|
219
|
+
|
225
220
|
if attempt < self.MAX_RETRY_ATTEMPTS - 1:
|
226
221
|
# Wait with exponential backoff
|
227
|
-
wait_time = self.RETRY_BACKOFF_BASE
|
222
|
+
wait_time = self.RETRY_BACKOFF_BASE**attempt
|
228
223
|
console.log(
|
229
224
|
f"[yellow]⏳ Token refresh attempt {attempt + 1} failed, "
|
230
225
|
f"retrying in {wait_time}s for {self.sanitized_profile}[/]"
|
@@ -233,17 +228,17 @@ class AWSTokenManager:
|
|
233
228
|
else:
|
234
229
|
# Final attempt failed, provide enhanced guidance
|
235
230
|
self._handle_token_refresh_failure(last_exception)
|
236
|
-
|
231
|
+
|
237
232
|
# If we get here, all attempts failed
|
238
233
|
raise TokenRefreshError(
|
239
234
|
f"Failed to refresh AWS session for profile {self.sanitized_profile} "
|
240
235
|
f"after {self.MAX_RETRY_ATTEMPTS} attempts"
|
241
236
|
)
|
242
|
-
|
237
|
+
|
243
238
|
def _handle_token_refresh_failure(self, error: Exception) -> None:
|
244
239
|
"""Provide enhanced guidance for token refresh failures."""
|
245
240
|
error_str = str(error)
|
246
|
-
|
241
|
+
|
247
242
|
# Determine error type for appropriate guidance
|
248
243
|
if "ExpiredToken" in error_str or "InvalidToken" in error_str:
|
249
244
|
console.log(
|
@@ -275,59 +270,59 @@ class AWSTokenManager:
|
|
275
270
|
|
276
271
|
class SecurityError(Exception):
|
277
272
|
"""Raised for AWS security-related errors."""
|
273
|
+
|
278
274
|
pass
|
279
275
|
|
280
276
|
|
281
277
|
class TokenRefreshError(Exception):
|
282
278
|
"""Raised for AWS token refresh failures."""
|
279
|
+
|
283
280
|
pass
|
284
281
|
|
285
282
|
|
286
283
|
def create_secure_aws_session(profile_name: str, operation_context: str = "aws_operation") -> boto3.Session:
|
287
284
|
"""
|
288
285
|
Create secure AWS session with enterprise security enhancements.
|
289
|
-
|
286
|
+
|
290
287
|
This is the primary entry point for secure AWS session creation across
|
291
288
|
all CloudOps modules. Implements:
|
292
|
-
|
289
|
+
|
293
290
|
- Profile name sanitization for secure logging
|
294
291
|
- Proactive token refresh
|
295
292
|
- Enhanced error handling
|
296
293
|
- Security audit trail
|
297
|
-
|
294
|
+
|
298
295
|
Args:
|
299
296
|
profile_name: AWS profile name
|
300
297
|
operation_context: Description of the operation for audit logging
|
301
|
-
|
298
|
+
|
302
299
|
Returns:
|
303
300
|
Secure boto3 session with valid credentials
|
304
|
-
|
301
|
+
|
305
302
|
Raises:
|
306
303
|
SecurityError: For security-related authentication issues
|
307
304
|
TokenRefreshError: For token refresh failures
|
308
|
-
|
305
|
+
|
309
306
|
Example:
|
310
307
|
session = create_secure_aws_session("my-billing-profile-123456789012", "cost_analysis")
|
311
308
|
"""
|
312
309
|
# Create secure logging context
|
313
310
|
log_context = AWSProfileSanitizer.create_secure_log_context(profile_name, operation_context)
|
314
|
-
|
311
|
+
|
315
312
|
console.log(
|
316
313
|
f"[dim cyan]🔐 Initiating secure AWS session for {log_context['profile_sanitized']} "
|
317
314
|
f"({log_context['profile_type']} profile)[/]"
|
318
315
|
)
|
319
|
-
|
316
|
+
|
320
317
|
try:
|
321
318
|
# Initialize token manager and get secure session
|
322
319
|
token_manager = AWSTokenManager(profile_name)
|
323
320
|
session = token_manager.get_secure_session()
|
324
|
-
|
325
|
-
console.log(
|
326
|
-
|
327
|
-
)
|
328
|
-
|
321
|
+
|
322
|
+
console.log(f"[dim green]✅ Secure session established for {log_context['profile_sanitized']}[/]")
|
323
|
+
|
329
324
|
return session
|
330
|
-
|
325
|
+
|
331
326
|
except Exception as e:
|
332
327
|
console.log(
|
333
328
|
f"[red]❌ Failed to create secure session for {log_context['profile_sanitized']}: {str(e)[:100]}[/]"
|
@@ -338,10 +333,10 @@ def create_secure_aws_session(profile_name: str, operation_context: str = "aws_o
|
|
338
333
|
def sanitize_aws_error_message(error_message: str) -> str:
|
339
334
|
"""
|
340
335
|
Sanitize AWS error messages to remove sensitive account information.
|
341
|
-
|
336
|
+
|
342
337
|
Args:
|
343
338
|
error_message: Original AWS error message
|
344
|
-
|
339
|
+
|
345
340
|
Returns:
|
346
341
|
Sanitized error message with account IDs masked
|
347
342
|
"""
|
@@ -351,10 +346,10 @@ def sanitize_aws_error_message(error_message: str) -> str:
|
|
351
346
|
def get_profile_classification(profile_name: str) -> Dict[str, str]:
|
352
347
|
"""
|
353
348
|
Get security classification information for AWS profile.
|
354
|
-
|
349
|
+
|
355
350
|
Args:
|
356
351
|
profile_name: AWS profile name
|
357
|
-
|
352
|
+
|
358
353
|
Returns:
|
359
354
|
Dictionary with profile security classification
|
360
355
|
"""
|
@@ -363,5 +358,5 @@ def get_profile_classification(profile_name: str) -> Dict[str, str]:
|
|
363
358
|
"original": profile_name,
|
364
359
|
"sanitized": sanitizer.sanitize_profile_name(profile_name),
|
365
360
|
"type": sanitizer._classify_profile_type(profile_name),
|
366
|
-
"risk_level": "high" if "admin" in profile_name.lower() else "medium"
|
367
|
-
}
|
361
|
+
"risk_level": "high" if "admin" in profile_name.lower() else "medium",
|
362
|
+
}
|