runbooks 1.1.4__py3-none-any.whl → 1.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/assessment/compliance.py +1 -1
  8. runbooks/cfat/assessment/runner.py +1 -0
  9. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  10. runbooks/cli/__init__.py +1 -1
  11. runbooks/cli/commands/cfat.py +64 -23
  12. runbooks/cli/commands/finops.py +1005 -54
  13. runbooks/cli/commands/inventory.py +138 -35
  14. runbooks/cli/commands/operate.py +9 -36
  15. runbooks/cli/commands/security.py +42 -18
  16. runbooks/cli/commands/validation.py +432 -18
  17. runbooks/cli/commands/vpc.py +81 -17
  18. runbooks/cli/registry.py +22 -10
  19. runbooks/cloudops/__init__.py +20 -27
  20. runbooks/cloudops/base.py +96 -107
  21. runbooks/cloudops/cost_optimizer.py +544 -542
  22. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  23. runbooks/cloudops/interfaces.py +224 -225
  24. runbooks/cloudops/lifecycle_manager.py +5 -4
  25. runbooks/cloudops/mcp_cost_validation.py +252 -235
  26. runbooks/cloudops/models.py +78 -53
  27. runbooks/cloudops/monitoring_automation.py +5 -4
  28. runbooks/cloudops/notebook_framework.py +177 -213
  29. runbooks/cloudops/security_enforcer.py +125 -159
  30. runbooks/common/accuracy_validator.py +11 -0
  31. runbooks/common/aws_pricing.py +349 -326
  32. runbooks/common/aws_pricing_api.py +211 -212
  33. runbooks/common/aws_profile_manager.py +40 -36
  34. runbooks/common/aws_utils.py +74 -79
  35. runbooks/common/business_logic.py +126 -104
  36. runbooks/common/cli_decorators.py +36 -60
  37. runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
  38. runbooks/common/cross_account_manager.py +197 -204
  39. runbooks/common/date_utils.py +27 -39
  40. runbooks/common/decorators.py +29 -19
  41. runbooks/common/dry_run_examples.py +173 -208
  42. runbooks/common/dry_run_framework.py +157 -155
  43. runbooks/common/enhanced_exception_handler.py +15 -4
  44. runbooks/common/enhanced_logging_example.py +50 -64
  45. runbooks/common/enhanced_logging_integration_example.py +65 -37
  46. runbooks/common/env_utils.py +16 -16
  47. runbooks/common/error_handling.py +40 -38
  48. runbooks/common/lazy_loader.py +41 -23
  49. runbooks/common/logging_integration_helper.py +79 -86
  50. runbooks/common/mcp_cost_explorer_integration.py +476 -493
  51. runbooks/common/mcp_integration.py +63 -74
  52. runbooks/common/memory_optimization.py +140 -118
  53. runbooks/common/module_cli_base.py +37 -58
  54. runbooks/common/organizations_client.py +175 -193
  55. runbooks/common/patterns.py +23 -25
  56. runbooks/common/performance_monitoring.py +67 -71
  57. runbooks/common/performance_optimization_engine.py +283 -274
  58. runbooks/common/profile_utils.py +111 -37
  59. runbooks/common/rich_utils.py +201 -141
  60. runbooks/common/sre_performance_suite.py +177 -186
  61. runbooks/enterprise/__init__.py +1 -1
  62. runbooks/enterprise/logging.py +144 -106
  63. runbooks/enterprise/security.py +187 -204
  64. runbooks/enterprise/validation.py +43 -56
  65. runbooks/finops/__init__.py +26 -30
  66. runbooks/finops/account_resolver.py +1 -1
  67. runbooks/finops/advanced_optimization_engine.py +980 -0
  68. runbooks/finops/automation_core.py +268 -231
  69. runbooks/finops/business_case_config.py +184 -179
  70. runbooks/finops/cli.py +660 -139
  71. runbooks/finops/commvault_ec2_analysis.py +157 -164
  72. runbooks/finops/compute_cost_optimizer.py +336 -320
  73. runbooks/finops/config.py +20 -20
  74. runbooks/finops/cost_optimizer.py +484 -618
  75. runbooks/finops/cost_processor.py +332 -214
  76. runbooks/finops/dashboard_runner.py +1006 -172
  77. runbooks/finops/ebs_cost_optimizer.py +991 -657
  78. runbooks/finops/elastic_ip_optimizer.py +317 -257
  79. runbooks/finops/enhanced_mcp_integration.py +340 -0
  80. runbooks/finops/enhanced_progress.py +32 -29
  81. runbooks/finops/enhanced_trend_visualization.py +3 -2
  82. runbooks/finops/enterprise_wrappers.py +223 -285
  83. runbooks/finops/executive_export.py +203 -160
  84. runbooks/finops/helpers.py +130 -288
  85. runbooks/finops/iam_guidance.py +1 -1
  86. runbooks/finops/infrastructure/__init__.py +80 -0
  87. runbooks/finops/infrastructure/commands.py +506 -0
  88. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  89. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  90. runbooks/finops/markdown_exporter.py +337 -174
  91. runbooks/finops/mcp_validator.py +1952 -0
  92. runbooks/finops/nat_gateway_optimizer.py +1512 -481
  93. runbooks/finops/network_cost_optimizer.py +657 -587
  94. runbooks/finops/notebook_utils.py +226 -188
  95. runbooks/finops/optimization_engine.py +1136 -0
  96. runbooks/finops/optimizer.py +19 -23
  97. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  98. runbooks/finops/reservation_optimizer.py +427 -363
  99. runbooks/finops/scenario_cli_integration.py +64 -65
  100. runbooks/finops/scenarios.py +1277 -438
  101. runbooks/finops/schemas.py +218 -182
  102. runbooks/finops/snapshot_manager.py +2289 -0
  103. runbooks/finops/types.py +3 -3
  104. runbooks/finops/validation_framework.py +259 -265
  105. runbooks/finops/vpc_cleanup_exporter.py +189 -144
  106. runbooks/finops/vpc_cleanup_optimizer.py +591 -573
  107. runbooks/finops/workspaces_analyzer.py +171 -182
  108. runbooks/integration/__init__.py +89 -0
  109. runbooks/integration/mcp_integration.py +1920 -0
  110. runbooks/inventory/CLAUDE.md +816 -0
  111. runbooks/inventory/__init__.py +2 -2
  112. runbooks/inventory/cloud_foundations_integration.py +144 -149
  113. runbooks/inventory/collectors/aws_comprehensive.py +1 -1
  114. runbooks/inventory/collectors/aws_networking.py +109 -99
  115. runbooks/inventory/collectors/base.py +4 -0
  116. runbooks/inventory/core/collector.py +495 -313
  117. runbooks/inventory/drift_detection_cli.py +69 -96
  118. runbooks/inventory/inventory_mcp_cli.py +48 -46
  119. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  120. runbooks/inventory/mcp_inventory_validator.py +549 -465
  121. runbooks/inventory/mcp_vpc_validator.py +359 -442
  122. runbooks/inventory/organizations_discovery.py +55 -51
  123. runbooks/inventory/rich_inventory_display.py +33 -32
  124. runbooks/inventory/unified_validation_engine.py +278 -251
  125. runbooks/inventory/vpc_analyzer.py +732 -695
  126. runbooks/inventory/vpc_architecture_validator.py +293 -348
  127. runbooks/inventory/vpc_dependency_analyzer.py +382 -378
  128. runbooks/inventory/vpc_flow_analyzer.py +1 -1
  129. runbooks/main.py +49 -34
  130. runbooks/main_final.py +91 -60
  131. runbooks/main_minimal.py +22 -10
  132. runbooks/main_optimized.py +131 -100
  133. runbooks/main_ultra_minimal.py +7 -2
  134. runbooks/mcp/__init__.py +36 -0
  135. runbooks/mcp/integration.py +679 -0
  136. runbooks/monitoring/performance_monitor.py +9 -4
  137. runbooks/operate/dynamodb_operations.py +3 -1
  138. runbooks/operate/ec2_operations.py +145 -137
  139. runbooks/operate/iam_operations.py +146 -152
  140. runbooks/operate/networking_cost_heatmap.py +29 -8
  141. runbooks/operate/rds_operations.py +223 -254
  142. runbooks/operate/s3_operations.py +107 -118
  143. runbooks/operate/vpc_operations.py +646 -616
  144. runbooks/remediation/base.py +1 -1
  145. runbooks/remediation/commons.py +10 -7
  146. runbooks/remediation/commvault_ec2_analysis.py +70 -66
  147. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  148. runbooks/remediation/multi_account.py +24 -21
  149. runbooks/remediation/rds_snapshot_list.py +86 -60
  150. runbooks/remediation/remediation_cli.py +92 -146
  151. runbooks/remediation/universal_account_discovery.py +83 -79
  152. runbooks/remediation/workspaces_list.py +46 -41
  153. runbooks/security/__init__.py +19 -0
  154. runbooks/security/assessment_runner.py +1150 -0
  155. runbooks/security/baseline_checker.py +812 -0
  156. runbooks/security/cloudops_automation_security_validator.py +509 -535
  157. runbooks/security/compliance_automation_engine.py +17 -17
  158. runbooks/security/config/__init__.py +2 -2
  159. runbooks/security/config/compliance_config.py +50 -50
  160. runbooks/security/config_template_generator.py +63 -76
  161. runbooks/security/enterprise_security_framework.py +1 -1
  162. runbooks/security/executive_security_dashboard.py +519 -508
  163. runbooks/security/multi_account_security_controls.py +959 -1210
  164. runbooks/security/real_time_security_monitor.py +422 -444
  165. runbooks/security/security_baseline_tester.py +1 -1
  166. runbooks/security/security_cli.py +143 -112
  167. runbooks/security/test_2way_validation.py +439 -0
  168. runbooks/security/two_way_validation_framework.py +852 -0
  169. runbooks/sre/production_monitoring_framework.py +167 -177
  170. runbooks/tdd/__init__.py +15 -0
  171. runbooks/tdd/cli.py +1071 -0
  172. runbooks/utils/__init__.py +14 -17
  173. runbooks/utils/logger.py +7 -2
  174. runbooks/utils/version_validator.py +50 -47
  175. runbooks/validation/__init__.py +6 -6
  176. runbooks/validation/cli.py +9 -3
  177. runbooks/validation/comprehensive_2way_validator.py +745 -704
  178. runbooks/validation/mcp_validator.py +906 -228
  179. runbooks/validation/terraform_citations_validator.py +104 -115
  180. runbooks/validation/terraform_drift_detector.py +447 -451
  181. runbooks/vpc/README.md +617 -0
  182. runbooks/vpc/__init__.py +8 -1
  183. runbooks/vpc/analyzer.py +577 -0
  184. runbooks/vpc/cleanup_wrapper.py +476 -413
  185. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  186. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  187. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  188. runbooks/vpc/config.py +92 -97
  189. runbooks/vpc/cost_engine.py +411 -148
  190. runbooks/vpc/cost_explorer_integration.py +553 -0
  191. runbooks/vpc/cross_account_session.py +101 -106
  192. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  193. runbooks/vpc/eni_gate_validator.py +961 -0
  194. runbooks/vpc/heatmap_engine.py +185 -160
  195. runbooks/vpc/mcp_no_eni_validator.py +680 -639
  196. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  197. runbooks/vpc/networking_wrapper.py +15 -8
  198. runbooks/vpc/pdca_remediation_planner.py +528 -0
  199. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  200. runbooks/vpc/runbooks_adapter.py +1167 -241
  201. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  202. runbooks/vpc/test_data_loader.py +358 -0
  203. runbooks/vpc/tests/conftest.py +314 -4
  204. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  205. runbooks/vpc/tests/test_cost_engine.py +0 -2
  206. runbooks/vpc/topology_generator.py +326 -0
  207. runbooks/vpc/unified_scenarios.py +1297 -1124
  208. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  209. runbooks-1.1.5.dist-info/METADATA +328 -0
  210. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/RECORD +214 -193
  211. runbooks/finops/README.md +0 -414
  212. runbooks/finops/accuracy_cross_validator.py +0 -647
  213. runbooks/finops/business_cases.py +0 -950
  214. runbooks/finops/dashboard_router.py +0 -922
  215. runbooks/finops/ebs_optimizer.py +0 -973
  216. runbooks/finops/embedded_mcp_validator.py +0 -1629
  217. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  218. runbooks/finops/finops_dashboard.py +0 -584
  219. runbooks/finops/finops_scenarios.py +0 -1218
  220. runbooks/finops/legacy_migration.py +0 -730
  221. runbooks/finops/multi_dashboard.py +0 -1519
  222. runbooks/finops/single_dashboard.py +0 -1113
  223. runbooks/finops/unlimited_scenarios.py +0 -393
  224. runbooks-1.1.4.dist-info/METADATA +0 -800
  225. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
  226. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
  227. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
  228. {runbooks-1.1.4.dist-info → runbooks-1.1.5.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('AWS_PROFILE')
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 = 'us-east-1') -> boto3.Session:
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('sts')
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 = 'us-east-1') -> 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('sts')
127
+ sts = session.client("sts")
127
128
  identity = sts.get_caller_identity()
128
- account_id = identity['Account']
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 = 'us-east-1') -> List[Dict[str, Any]]:
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('organizations')
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('list_accounts')
164
+ paginator = org_client.get_paginator("list_accounts")
164
165
 
165
166
  for page in paginator.paginate():
166
- for account in page['Accounts']:
167
- accounts.append({
168
- 'Id': account['Id'],
169
- 'Name': account['Name'],
170
- 'Email': account['Email'],
171
- 'Status': account['Status'],
172
- 'JoinedMethod': account.get('JoinedMethod', 'UNKNOWN')
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 = ['sts', 'ce', 'ec2', 's3']
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 == 'sts':
206
+ if service == "sts":
204
207
  client.get_caller_identity()
205
- elif service == 'ce':
208
+ elif service == "ce":
206
209
  # Test Cost Explorer access
207
210
  from datetime import datetime, timedelta
208
- end_date = datetime.now().strftime('%Y-%m-%d')
209
- start_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')
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={'Start': start_date, 'End': end_date},
212
- Granularity='MONTHLY',
213
- Metrics=['UnblendedCost']
215
+ TimePeriod={"Start": start_date, "End": end_date},
216
+ Granularity="MONTHLY",
217
+ Metrics=["UnblendedCost"],
214
218
  )
215
- elif service == 'ec2':
219
+ elif service == "ec2":
216
220
  client.describe_regions(MaxResults=1)
217
- elif service == 's3':
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('~/.aws/credentials')
236
- config_path = os.path.expanduser('~/.aws/config')
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('profile '):
255
+ if section.startswith("profile "):
252
256
  profiles.add(section[8:]) # Remove 'profile ' prefix
253
- elif section == 'default':
254
- profiles.add('default')
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") -> 'AWSProfileManager':
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['mock'] = mock_account_id
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 = 'us-east-1') -> 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)
@@ -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'\b\d{12}\b')
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'(ams|aws)-.*-ReadOnlyAccess-(\d{12})')
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'\1-masked-ReadOnlyAccess-***masked***', profile_name)
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 (force_refresh or
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
- f"[dim green]✅ Token refresh completed for profile: {self.sanitized_profile}[/]"
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('sts')
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('Error', {}).get('Code', '')
196
- if error_code in ['ExpiredToken', 'InvalidToken', 'TokenRefreshRequired']:
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('sts')
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 ** attempt
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
- f"[dim green]✅ Secure session established for {log_context['profile_sanitized']}[/]"
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
+ }