runbooks 1.1.3__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 (247) 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/WEIGHT_CONFIG_README.md +1 -1
  8. runbooks/cfat/assessment/compliance.py +8 -8
  9. runbooks/cfat/assessment/runner.py +1 -0
  10. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  11. runbooks/cfat/models.py +6 -2
  12. runbooks/cfat/tests/__init__.py +6 -1
  13. runbooks/cli/__init__.py +13 -0
  14. runbooks/cli/commands/cfat.py +274 -0
  15. runbooks/cli/commands/finops.py +1164 -0
  16. runbooks/cli/commands/inventory.py +379 -0
  17. runbooks/cli/commands/operate.py +239 -0
  18. runbooks/cli/commands/security.py +248 -0
  19. runbooks/cli/commands/validation.py +825 -0
  20. runbooks/cli/commands/vpc.py +310 -0
  21. runbooks/cli/registry.py +107 -0
  22. runbooks/cloudops/__init__.py +23 -30
  23. runbooks/cloudops/base.py +96 -107
  24. runbooks/cloudops/cost_optimizer.py +549 -547
  25. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  26. runbooks/cloudops/interfaces.py +226 -227
  27. runbooks/cloudops/lifecycle_manager.py +5 -4
  28. runbooks/cloudops/mcp_cost_validation.py +252 -235
  29. runbooks/cloudops/models.py +78 -53
  30. runbooks/cloudops/monitoring_automation.py +5 -4
  31. runbooks/cloudops/notebook_framework.py +179 -215
  32. runbooks/cloudops/security_enforcer.py +125 -159
  33. runbooks/common/accuracy_validator.py +11 -0
  34. runbooks/common/aws_pricing.py +349 -326
  35. runbooks/common/aws_pricing_api.py +211 -212
  36. runbooks/common/aws_profile_manager.py +341 -0
  37. runbooks/common/aws_utils.py +75 -80
  38. runbooks/common/business_logic.py +127 -105
  39. runbooks/common/cli_decorators.py +36 -60
  40. runbooks/common/comprehensive_cost_explorer_integration.py +456 -464
  41. runbooks/common/cross_account_manager.py +198 -205
  42. runbooks/common/date_utils.py +27 -39
  43. runbooks/common/decorators.py +235 -0
  44. runbooks/common/dry_run_examples.py +173 -208
  45. runbooks/common/dry_run_framework.py +157 -155
  46. runbooks/common/enhanced_exception_handler.py +15 -4
  47. runbooks/common/enhanced_logging_example.py +50 -64
  48. runbooks/common/enhanced_logging_integration_example.py +65 -37
  49. runbooks/common/env_utils.py +16 -16
  50. runbooks/common/error_handling.py +40 -38
  51. runbooks/common/lazy_loader.py +41 -23
  52. runbooks/common/logging_integration_helper.py +79 -86
  53. runbooks/common/mcp_cost_explorer_integration.py +478 -495
  54. runbooks/common/mcp_integration.py +63 -74
  55. runbooks/common/memory_optimization.py +140 -118
  56. runbooks/common/module_cli_base.py +37 -58
  57. runbooks/common/organizations_client.py +176 -194
  58. runbooks/common/patterns.py +204 -0
  59. runbooks/common/performance_monitoring.py +67 -71
  60. runbooks/common/performance_optimization_engine.py +283 -274
  61. runbooks/common/profile_utils.py +248 -39
  62. runbooks/common/rich_utils.py +643 -92
  63. runbooks/common/sre_performance_suite.py +177 -186
  64. runbooks/enterprise/__init__.py +1 -1
  65. runbooks/enterprise/logging.py +144 -106
  66. runbooks/enterprise/security.py +187 -204
  67. runbooks/enterprise/validation.py +43 -56
  68. runbooks/finops/__init__.py +29 -33
  69. runbooks/finops/account_resolver.py +1 -1
  70. runbooks/finops/advanced_optimization_engine.py +980 -0
  71. runbooks/finops/automation_core.py +268 -231
  72. runbooks/finops/business_case_config.py +184 -179
  73. runbooks/finops/cli.py +660 -139
  74. runbooks/finops/commvault_ec2_analysis.py +157 -164
  75. runbooks/finops/compute_cost_optimizer.py +336 -320
  76. runbooks/finops/config.py +20 -20
  77. runbooks/finops/cost_optimizer.py +488 -622
  78. runbooks/finops/cost_processor.py +332 -214
  79. runbooks/finops/dashboard_runner.py +1006 -172
  80. runbooks/finops/ebs_cost_optimizer.py +991 -657
  81. runbooks/finops/elastic_ip_optimizer.py +317 -257
  82. runbooks/finops/enhanced_mcp_integration.py +340 -0
  83. runbooks/finops/enhanced_progress.py +40 -37
  84. runbooks/finops/enhanced_trend_visualization.py +3 -2
  85. runbooks/finops/enterprise_wrappers.py +230 -292
  86. runbooks/finops/executive_export.py +203 -160
  87. runbooks/finops/helpers.py +130 -288
  88. runbooks/finops/iam_guidance.py +1 -1
  89. runbooks/finops/infrastructure/__init__.py +80 -0
  90. runbooks/finops/infrastructure/commands.py +506 -0
  91. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  92. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  93. runbooks/finops/markdown_exporter.py +338 -175
  94. runbooks/finops/mcp_validator.py +1952 -0
  95. runbooks/finops/nat_gateway_optimizer.py +1513 -482
  96. runbooks/finops/network_cost_optimizer.py +657 -587
  97. runbooks/finops/notebook_utils.py +226 -188
  98. runbooks/finops/optimization_engine.py +1136 -0
  99. runbooks/finops/optimizer.py +25 -29
  100. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  101. runbooks/finops/reservation_optimizer.py +427 -363
  102. runbooks/finops/scenario_cli_integration.py +77 -78
  103. runbooks/finops/scenarios.py +1278 -439
  104. runbooks/finops/schemas.py +218 -182
  105. runbooks/finops/snapshot_manager.py +2289 -0
  106. runbooks/finops/tests/test_finops_dashboard.py +3 -3
  107. runbooks/finops/tests/test_reference_images_validation.py +2 -2
  108. runbooks/finops/tests/test_single_account_features.py +17 -17
  109. runbooks/finops/tests/validate_test_suite.py +1 -1
  110. runbooks/finops/types.py +3 -3
  111. runbooks/finops/validation_framework.py +263 -269
  112. runbooks/finops/vpc_cleanup_exporter.py +191 -146
  113. runbooks/finops/vpc_cleanup_optimizer.py +593 -575
  114. runbooks/finops/workspaces_analyzer.py +171 -182
  115. runbooks/hitl/enhanced_workflow_engine.py +1 -1
  116. runbooks/integration/__init__.py +89 -0
  117. runbooks/integration/mcp_integration.py +1920 -0
  118. runbooks/inventory/CLAUDE.md +816 -0
  119. runbooks/inventory/README.md +3 -3
  120. runbooks/inventory/Tests/common_test_data.py +30 -30
  121. runbooks/inventory/__init__.py +2 -2
  122. runbooks/inventory/cloud_foundations_integration.py +144 -149
  123. runbooks/inventory/collectors/aws_comprehensive.py +28 -11
  124. runbooks/inventory/collectors/aws_networking.py +111 -101
  125. runbooks/inventory/collectors/base.py +4 -0
  126. runbooks/inventory/core/collector.py +495 -313
  127. runbooks/inventory/discovery.md +2 -2
  128. runbooks/inventory/drift_detection_cli.py +69 -96
  129. runbooks/inventory/find_ec2_security_groups.py +1 -1
  130. runbooks/inventory/inventory_mcp_cli.py +48 -46
  131. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  132. runbooks/inventory/mcp_inventory_validator.py +549 -465
  133. runbooks/inventory/mcp_vpc_validator.py +359 -442
  134. runbooks/inventory/organizations_discovery.py +56 -52
  135. runbooks/inventory/rich_inventory_display.py +33 -32
  136. runbooks/inventory/unified_validation_engine.py +278 -251
  137. runbooks/inventory/vpc_analyzer.py +733 -696
  138. runbooks/inventory/vpc_architecture_validator.py +293 -348
  139. runbooks/inventory/vpc_dependency_analyzer.py +382 -378
  140. runbooks/inventory/vpc_flow_analyzer.py +3 -3
  141. runbooks/main.py +152 -9147
  142. runbooks/main_final.py +91 -60
  143. runbooks/main_minimal.py +22 -10
  144. runbooks/main_optimized.py +131 -100
  145. runbooks/main_ultra_minimal.py +7 -2
  146. runbooks/mcp/__init__.py +36 -0
  147. runbooks/mcp/integration.py +679 -0
  148. runbooks/metrics/dora_metrics_engine.py +2 -2
  149. runbooks/monitoring/performance_monitor.py +9 -4
  150. runbooks/operate/dynamodb_operations.py +3 -1
  151. runbooks/operate/ec2_operations.py +145 -137
  152. runbooks/operate/iam_operations.py +146 -152
  153. runbooks/operate/mcp_integration.py +1 -1
  154. runbooks/operate/networking_cost_heatmap.py +33 -10
  155. runbooks/operate/privatelink_operations.py +1 -1
  156. runbooks/operate/rds_operations.py +223 -254
  157. runbooks/operate/s3_operations.py +107 -118
  158. runbooks/operate/vpc_endpoints.py +1 -1
  159. runbooks/operate/vpc_operations.py +648 -618
  160. runbooks/remediation/base.py +1 -1
  161. runbooks/remediation/commons.py +10 -7
  162. runbooks/remediation/commvault_ec2_analysis.py +71 -67
  163. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  164. runbooks/remediation/multi_account.py +24 -21
  165. runbooks/remediation/rds_snapshot_list.py +91 -65
  166. runbooks/remediation/remediation_cli.py +92 -146
  167. runbooks/remediation/universal_account_discovery.py +83 -79
  168. runbooks/remediation/workspaces_list.py +49 -44
  169. runbooks/security/__init__.py +19 -0
  170. runbooks/security/assessment_runner.py +1150 -0
  171. runbooks/security/baseline_checker.py +812 -0
  172. runbooks/security/cloudops_automation_security_validator.py +509 -535
  173. runbooks/security/compliance_automation_engine.py +17 -17
  174. runbooks/security/config/__init__.py +2 -2
  175. runbooks/security/config/compliance_config.py +50 -50
  176. runbooks/security/config_template_generator.py +63 -76
  177. runbooks/security/enterprise_security_framework.py +1 -1
  178. runbooks/security/executive_security_dashboard.py +519 -508
  179. runbooks/security/integration_test_enterprise_security.py +5 -3
  180. runbooks/security/multi_account_security_controls.py +959 -1210
  181. runbooks/security/real_time_security_monitor.py +422 -444
  182. runbooks/security/run_script.py +1 -1
  183. runbooks/security/security_baseline_tester.py +1 -1
  184. runbooks/security/security_cli.py +143 -112
  185. runbooks/security/test_2way_validation.py +439 -0
  186. runbooks/security/two_way_validation_framework.py +852 -0
  187. runbooks/sre/mcp_reliability_engine.py +6 -6
  188. runbooks/sre/production_monitoring_framework.py +167 -177
  189. runbooks/tdd/__init__.py +15 -0
  190. runbooks/tdd/cli.py +1071 -0
  191. runbooks/utils/__init__.py +14 -17
  192. runbooks/utils/logger.py +7 -2
  193. runbooks/utils/version_validator.py +51 -48
  194. runbooks/validation/__init__.py +6 -6
  195. runbooks/validation/cli.py +9 -3
  196. runbooks/validation/comprehensive_2way_validator.py +754 -708
  197. runbooks/validation/mcp_validator.py +906 -228
  198. runbooks/validation/terraform_citations_validator.py +104 -115
  199. runbooks/validation/terraform_drift_detector.py +447 -451
  200. runbooks/vpc/README.md +617 -0
  201. runbooks/vpc/__init__.py +8 -1
  202. runbooks/vpc/analyzer.py +577 -0
  203. runbooks/vpc/cleanup_wrapper.py +476 -413
  204. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  205. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  206. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  207. runbooks/vpc/config.py +92 -97
  208. runbooks/vpc/cost_engine.py +411 -148
  209. runbooks/vpc/cost_explorer_integration.py +553 -0
  210. runbooks/vpc/cross_account_session.py +101 -106
  211. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  212. runbooks/vpc/eni_gate_validator.py +961 -0
  213. runbooks/vpc/heatmap_engine.py +190 -162
  214. runbooks/vpc/mcp_no_eni_validator.py +681 -640
  215. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  216. runbooks/vpc/networking_wrapper.py +15 -8
  217. runbooks/vpc/pdca_remediation_planner.py +528 -0
  218. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  219. runbooks/vpc/runbooks_adapter.py +1167 -241
  220. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  221. runbooks/vpc/test_data_loader.py +358 -0
  222. runbooks/vpc/tests/conftest.py +314 -4
  223. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  224. runbooks/vpc/tests/test_cost_engine.py +0 -2
  225. runbooks/vpc/topology_generator.py +326 -0
  226. runbooks/vpc/unified_scenarios.py +1302 -1129
  227. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  228. runbooks-1.1.5.dist-info/METADATA +328 -0
  229. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/RECORD +233 -200
  230. runbooks/finops/README.md +0 -414
  231. runbooks/finops/accuracy_cross_validator.py +0 -647
  232. runbooks/finops/business_cases.py +0 -950
  233. runbooks/finops/dashboard_router.py +0 -922
  234. runbooks/finops/ebs_optimizer.py +0 -956
  235. runbooks/finops/embedded_mcp_validator.py +0 -1629
  236. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  237. runbooks/finops/finops_dashboard.py +0 -584
  238. runbooks/finops/finops_scenarios.py +0 -1218
  239. runbooks/finops/legacy_migration.py +0 -730
  240. runbooks/finops/multi_dashboard.py +0 -1519
  241. runbooks/finops/single_dashboard.py +0 -1113
  242. runbooks/finops/unlimited_scenarios.py +0 -393
  243. runbooks-1.1.3.dist-info/METADATA +0 -799
  244. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
  245. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
  246. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
  247. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
@@ -11,7 +11,7 @@ validation and SHA256-verified audit trails.
11
11
 
12
12
  **Strategic Alignment**: Supports 3 immutable objectives through:
13
13
  1. **runbooks package**: Technical implementation with Rich CLI
14
- 2. **Enterprise FAANG/Agile SDLC**: MCP validation ≥99.5% accuracy
14
+ 2. **Enterprise FAANG/Agile SDLC**: MCP validation ≥99.5% accuracy
15
15
  3. **GitHub as single source of truth**: Evidence bundle generation
16
16
 
17
17
  **Core AWSO-5 Framework Integration**:
@@ -20,10 +20,10 @@ validation and SHA256-verified audit trails.
20
20
  - Security posture enhancement with attack surface reduction
21
21
  - Evidence-based approach with SHA256-verified validation bundles
22
22
 
23
- **AWS API Mapping**:
23
+ **AWS API Mapping**:
24
24
  - `ec2.describe_network_interfaces()` → ENI gate analysis
25
25
  - `ec2.describe_nat_gateways()` → NAT Gateway dependencies
26
- - `ec2.describe_internet_gateways()` → IGW/EIGW dependencies
26
+ - `ec2.describe_internet_gateways()` → IGW/EIGW dependencies
27
27
  - `ec2.describe_route_tables()` → Route table analysis
28
28
  - `ec2.describe_vpc_endpoints()` → VPC Endpoints analysis
29
29
  - `ec2.describe_transit_gateway_attachments()` → TGW dependencies
@@ -49,8 +49,15 @@ from rich.panel import Panel
49
49
  from rich.progress import Progress, SpinnerColumn, TextColumn
50
50
 
51
51
  from runbooks.common.rich_utils import (
52
- console, print_header, print_success, print_error, print_warning,
53
- create_table, create_progress_bar, format_resource_count, STATUS_INDICATORS
52
+ console,
53
+ print_header,
54
+ print_success,
55
+ print_error,
56
+ print_warning,
57
+ create_table,
58
+ create_progress_bar,
59
+ format_resource_count,
60
+ STATUS_INDICATORS,
54
61
  )
55
62
 
56
63
  logger = logging.getLogger(__name__)
@@ -60,10 +67,11 @@ logger = logging.getLogger(__name__)
60
67
  class VPCDependency:
61
68
  """
62
69
  VPC dependency analysis result with comprehensive validation.
63
-
70
+
64
71
  Represents a single dependency relationship found during AWSO-5 analysis
65
72
  with evidence collection and validation support.
66
73
  """
74
+
67
75
  resource_type: str
68
76
  resource_id: str
69
77
  resource_name: Optional[str] = None
@@ -71,51 +79,52 @@ class VPCDependency:
71
79
  details: Dict[str, Any] = field(default_factory=dict)
72
80
  remediation_action: Optional[str] = None
73
81
  validation_timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
74
-
82
+
75
83
  @property
76
84
  def is_blocking(self) -> bool:
77
85
  """True if this dependency blocks VPC deletion."""
78
86
  return self.dependency_type == "blocking"
79
87
 
80
88
 
81
- @dataclass
89
+ @dataclass
82
90
  class VPCDependencyAnalysisResult:
83
91
  """
84
92
  Comprehensive VPC dependency analysis results for AWSO-5.
85
-
93
+
86
94
  Contains complete dependency analysis with evidence collection,
87
95
  validation metrics, and remediation guidance.
88
96
  """
97
+
89
98
  vpc_id: str
90
99
  vpc_name: Optional[str]
91
100
  account_id: str
92
101
  region: str
93
102
  is_default: bool
94
103
  cidr_blocks: List[str]
95
-
104
+
96
105
  # Dependency analysis results
97
106
  dependencies: List[VPCDependency] = field(default_factory=list)
98
107
  eni_count: int = 0
99
108
  blocking_dependencies: int = 0
100
109
  warning_dependencies: int = 0
101
-
110
+
102
111
  # Analysis metadata
103
112
  analysis_timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
104
113
  analysis_duration_seconds: float = 0.0
105
114
  mcp_validation_accuracy: float = 0.0
106
115
  evidence_hash: Optional[str] = None
107
-
116
+
108
117
  # Business impact
109
118
  cleanup_recommendation: str = "INVESTIGATE" # DELETE, HOLD, INVESTIGATE
110
119
  estimated_monthly_savings: float = 0.0
111
120
  security_impact: str = "MEDIUM" # LOW, MEDIUM, HIGH
112
121
  compliance_impact: List[str] = field(default_factory=list)
113
-
122
+
114
123
  @property
115
124
  def can_delete_safely(self) -> bool:
116
125
  """True if VPC can be safely deleted (zero blocking dependencies)."""
117
126
  return self.eni_count == 0 and self.blocking_dependencies == 0
118
-
127
+
119
128
  @property
120
129
  def deletion_complexity(self) -> str:
121
130
  """Complexity assessment for VPC deletion."""
@@ -123,7 +132,7 @@ class VPCDependencyAnalysisResult:
123
132
  if total_deps == 0:
124
133
  return "SIMPLE"
125
134
  elif total_deps <= 3:
126
- return "MODERATE"
135
+ return "MODERATE"
127
136
  else:
128
137
  return "COMPLEX"
129
138
 
@@ -131,21 +140,21 @@ class VPCDependencyAnalysisResult:
131
140
  class VPCDependencyAnalyzer:
132
141
  """
133
142
  AWSO-5 VPC Dependency Analysis Engine.
134
-
143
+
135
144
  Comprehensive enterprise VPC dependency analysis implementing the 12-step
136
145
  AWSO-5 framework with MCP validation and evidence collection.
137
-
146
+
138
147
  **Enterprise Integration**:
139
148
  - Rich CLI formatting for consistent UX
140
149
  - MCP validation for ≥99.5% accuracy
141
150
  - Evidence bundle generation with SHA256 verification
142
151
  - Multi-account organization support
143
152
  """
144
-
153
+
145
154
  def __init__(self, session: Optional[boto3.Session] = None, region: str = "us-east-1"):
146
155
  """
147
156
  Initialize VPC dependency analyzer.
148
-
157
+
149
158
  Args:
150
159
  session: AWS session for API access
151
160
  region: AWS region for analysis
@@ -153,543 +162,540 @@ class VPCDependencyAnalyzer:
153
162
  self.session = session or boto3.Session()
154
163
  self.region = region
155
164
  self.console = console
156
-
165
+
157
166
  # Initialize AWS clients
158
167
  self._ec2_client = None
159
168
  self._elbv2_client = None
160
169
  self._route53resolver_client = None
161
170
  self._logs_client = None
162
171
  self._rds_client = None
163
-
172
+
164
173
  # Analysis tracking
165
174
  self.analysis_results: Dict[str, VPCDependencyAnalysisResult] = {}
166
175
  self.evidence_artifacts: List[Dict[str, Any]] = []
167
-
176
+
168
177
  @property
169
178
  def ec2_client(self):
170
179
  """Lazy-loaded EC2 client."""
171
180
  if not self._ec2_client:
172
- self._ec2_client = self.session.client('ec2', region_name=self.region)
181
+ self._ec2_client = self.session.client("ec2", region_name=self.region)
173
182
  return self._ec2_client
174
-
183
+
175
184
  @property
176
185
  def elbv2_client(self):
177
- """Lazy-loaded ELBv2 client."""
186
+ """Lazy-loaded ELBv2 client."""
178
187
  if not self._elbv2_client:
179
- self._elbv2_client = self.session.client('elbv2', region_name=self.region)
188
+ self._elbv2_client = self.session.client("elbv2", region_name=self.region)
180
189
  return self._elbv2_client
181
-
190
+
182
191
  @property
183
192
  def route53resolver_client(self):
184
193
  """Lazy-loaded Route53 Resolver client."""
185
194
  if not self._route53resolver_client:
186
- self._route53resolver_client = self.session.client('route53resolver', region_name=self.region)
195
+ self._route53resolver_client = self.session.client("route53resolver", region_name=self.region)
187
196
  return self._route53resolver_client
188
-
197
+
189
198
  @property
190
199
  def logs_client(self):
191
200
  """Lazy-loaded CloudWatch Logs client."""
192
201
  if not self._logs_client:
193
- self._logs_client = self.session.client('logs', region_name=self.region)
202
+ self._logs_client = self.session.client("logs", region_name=self.region)
194
203
  return self._logs_client
195
-
204
+
196
205
  @property
197
206
  def rds_client(self):
198
207
  """Lazy-loaded RDS client."""
199
208
  if not self._rds_client:
200
- self._rds_client = self.session.client('rds', region_name=self.region)
209
+ self._rds_client = self.session.client("rds", region_name=self.region)
201
210
  return self._rds_client
202
-
211
+
203
212
  def analyze_vpc_dependencies(self, vpc_id: str) -> VPCDependencyAnalysisResult:
204
213
  """
205
214
  Comprehensive VPC dependency analysis following AWSO-5 12-step framework.
206
-
215
+
207
216
  Implements complete dependency analysis including ENI gate, dependency
208
217
  inventory, and cleanup recommendations with evidence collection.
209
-
218
+
210
219
  Args:
211
220
  vpc_id: AWS VPC identifier to analyze
212
-
221
+
213
222
  Returns:
214
223
  Comprehensive analysis results with dependencies and recommendations
215
224
  """
216
225
  start_time = datetime.utcnow()
217
-
226
+
218
227
  # Get VPC basic information
219
228
  vpc_info = self._get_vpc_info(vpc_id)
220
229
  if not vpc_info:
221
230
  raise ValueError(f"VPC {vpc_id} not found in region {self.region}")
222
-
231
+
223
232
  result = VPCDependencyAnalysisResult(
224
233
  vpc_id=vpc_id,
225
- vpc_name=vpc_info.get('Tags', {}).get('Name'),
226
- account_id=self.session.client('sts').get_caller_identity()['Account'],
234
+ vpc_name=vpc_info.get("Tags", {}).get("Name"),
235
+ account_id=self.session.client("sts").get_caller_identity()["Account"],
227
236
  region=self.region,
228
- is_default=vpc_info.get('IsDefault', False),
229
- cidr_blocks=[block['CidrBlock'] for block in vpc_info.get('CidrBlockAssociationSet', [])]
237
+ is_default=vpc_info.get("IsDefault", False),
238
+ cidr_blocks=[block["CidrBlock"] for block in vpc_info.get("CidrBlockAssociationSet", [])],
230
239
  )
231
-
240
+
232
241
  print_header("AWSO-5 VPC Dependency Analysis", "1.0.0")
233
242
  self.console.print(f"\n[blue]Analyzing VPC:[/blue] {vpc_id}")
234
243
  self.console.print(f"[blue]Region:[/blue] {self.region}")
235
244
  self.console.print(f"[blue]Default VPC:[/blue] {'Yes' if result.is_default else 'No'}")
236
-
245
+
237
246
  with Progress(
238
- SpinnerColumn(),
239
- TextColumn("[progress.description]{task.description}"),
240
- console=self.console
247
+ SpinnerColumn(), TextColumn("[progress.description]{task.description}"), console=self.console
241
248
  ) as progress:
242
-
243
249
  # Step 1: ENI Gate Analysis (Critical blocking check)
244
250
  task = progress.add_task("Step 1: ENI Gate Analysis...", total=None)
245
251
  result.eni_count = self._analyze_enis(vpc_id, result)
246
-
252
+
247
253
  if result.eni_count > 0:
248
254
  result.cleanup_recommendation = "INVESTIGATE"
249
255
  result.security_impact = "HIGH"
250
256
  progress.update(task, description=f"Step 1: Found {result.eni_count} ENIs - INVESTIGATE required")
251
257
  else:
252
258
  progress.update(task, description="Step 1: ENI Gate PASSED - No active ENIs")
253
-
259
+
254
260
  # Step 2: Comprehensive Dependency Analysis
255
261
  progress.update(task, description="Step 2: Analyzing NAT Gateways...")
256
262
  self._analyze_nat_gateways(vpc_id, result)
257
-
263
+
258
264
  progress.update(task, description="Step 3: Analyzing Internet Gateways...")
259
265
  self._analyze_internet_gateways(vpc_id, result)
260
-
266
+
261
267
  progress.update(task, description="Step 4: Analyzing Route Tables...")
262
268
  self._analyze_route_tables(vpc_id, result)
263
-
269
+
264
270
  progress.update(task, description="Step 5: Analyzing VPC Endpoints...")
265
271
  self._analyze_vpc_endpoints(vpc_id, result)
266
-
272
+
267
273
  progress.update(task, description="Step 6: Analyzing Transit Gateway Attachments...")
268
274
  self._analyze_transit_gateway_attachments(vpc_id, result)
269
-
275
+
270
276
  progress.update(task, description="Step 7: Analyzing VPC Peering...")
271
277
  self._analyze_vpc_peering(vpc_id, result)
272
-
278
+
273
279
  progress.update(task, description="Step 8: Analyzing Route53 Resolver...")
274
280
  self._analyze_route53_resolver(vpc_id, result)
275
-
281
+
276
282
  progress.update(task, description="Step 9: Analyzing Load Balancers...")
277
283
  self._analyze_load_balancers(vpc_id, result)
278
-
284
+
279
285
  progress.update(task, description="Step 10: Analyzing Database Subnet Groups...")
280
286
  self._analyze_database_subnet_groups(vpc_id, result)
281
-
287
+
282
288
  progress.update(task, description="Step 11: Analyzing VPC Flow Logs...")
283
289
  self._analyze_vpc_flow_logs(vpc_id, result)
284
-
290
+
285
291
  progress.update(task, description="Step 12: Analyzing Security Groups & NACLs...")
286
292
  self._analyze_security_groups_nacls(vpc_id, result)
287
-
293
+
288
294
  progress.remove_task(task)
289
-
295
+
290
296
  # Calculate analysis metrics
291
297
  end_time = datetime.utcnow()
292
298
  result.analysis_duration_seconds = (end_time - start_time).total_seconds()
293
299
  result.blocking_dependencies = len([d for d in result.dependencies if d.is_blocking])
294
300
  result.warning_dependencies = len([d for d in result.dependencies if d.dependency_type == "warning"])
295
-
301
+
296
302
  # Generate cleanup recommendation
297
303
  self._generate_cleanup_recommendation(result)
298
-
304
+
299
305
  # Store results for evidence collection
300
306
  self.analysis_results[vpc_id] = result
301
-
307
+
302
308
  # Display results
303
309
  self._display_analysis_results(result)
304
-
310
+
305
311
  return result
306
-
312
+
307
313
  def _get_vpc_info(self, vpc_id: str) -> Optional[Dict[str, Any]]:
308
314
  """Get VPC basic information."""
309
315
  try:
310
316
  response = self.ec2_client.describe_vpcs(VpcIds=[vpc_id])
311
- return response['Vpcs'][0] if response['Vpcs'] else None
317
+ return response["Vpcs"][0] if response["Vpcs"] else None
312
318
  except ClientError as e:
313
319
  print_error(f"Failed to get VPC info: {e}")
314
320
  return None
315
-
321
+
316
322
  def _analyze_enis(self, vpc_id: str, result: VPCDependencyAnalysisResult) -> int:
317
323
  """
318
324
  Step 1: ENI Gate Analysis - Critical blocking check.
319
-
325
+
320
326
  ENIs indicate active workloads that prevent VPC deletion.
321
327
  This is the primary gate in the AWSO-5 framework.
322
328
  """
323
329
  try:
324
- response = self.ec2_client.describe_network_interfaces(
325
- Filters=[{'Name': 'vpc-id', 'Values': [vpc_id]}]
326
- )
327
-
328
- eni_count = len(response['NetworkInterfaces'])
329
-
330
- for eni in response['NetworkInterfaces']:
331
- result.dependencies.append(VPCDependency(
332
- resource_type="NetworkInterface",
333
- resource_id=eni['NetworkInterfaceId'],
334
- resource_name=eni.get('Description', 'Unknown'),
335
- dependency_type="blocking",
336
- details={
337
- 'Status': eni.get('Status'),
338
- 'InterfaceType': eni.get('InterfaceType'),
339
- 'AvailabilityZone': eni.get('AvailabilityZone'),
340
- 'Attachment': eni.get('Attachment')
341
- },
342
- remediation_action="Investigate ENI usage and owner, detach/delete if unused"
343
- ))
344
-
330
+ response = self.ec2_client.describe_network_interfaces(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
331
+
332
+ eni_count = len(response["NetworkInterfaces"])
333
+
334
+ for eni in response["NetworkInterfaces"]:
335
+ result.dependencies.append(
336
+ VPCDependency(
337
+ resource_type="NetworkInterface",
338
+ resource_id=eni["NetworkInterfaceId"],
339
+ resource_name=eni.get("Description", "Unknown"),
340
+ dependency_type="blocking",
341
+ details={
342
+ "Status": eni.get("Status"),
343
+ "InterfaceType": eni.get("InterfaceType"),
344
+ "AvailabilityZone": eni.get("AvailabilityZone"),
345
+ "Attachment": eni.get("Attachment"),
346
+ },
347
+ remediation_action="Investigate ENI usage and owner, detach/delete if unused",
348
+ )
349
+ )
350
+
345
351
  return eni_count
346
-
352
+
347
353
  except ClientError as e:
348
354
  print_warning(f"ENI analysis failed: {e}")
349
355
  return -1
350
-
356
+
351
357
  def _analyze_nat_gateways(self, vpc_id: str, result: VPCDependencyAnalysisResult):
352
358
  """Step 2.1: NAT Gateway dependency analysis."""
353
359
  try:
354
- response = self.ec2_client.describe_nat_gateways(
355
- Filters=[{'Name': 'vpc-id', 'Values': [vpc_id]}]
356
- )
357
-
358
- for nat_gw in response['NatGateways']:
359
- if nat_gw['State'] in ['available', 'pending']:
360
- result.dependencies.append(VPCDependency(
361
- resource_type="NatGateway",
362
- resource_id=nat_gw['NatGatewayId'],
363
- dependency_type="blocking",
364
- details={
365
- 'State': nat_gw['State'],
366
- 'SubnetId': nat_gw['SubnetId'],
367
- 'NatGatewayAddresses': nat_gw.get('NatGatewayAddresses', [])
368
- },
369
- remediation_action="Delete NAT Gateway, then update route tables"
370
- ))
371
-
360
+ response = self.ec2_client.describe_nat_gateways(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
361
+
362
+ for nat_gw in response["NatGateways"]:
363
+ if nat_gw["State"] in ["available", "pending"]:
364
+ result.dependencies.append(
365
+ VPCDependency(
366
+ resource_type="NatGateway",
367
+ resource_id=nat_gw["NatGatewayId"],
368
+ dependency_type="blocking",
369
+ details={
370
+ "State": nat_gw["State"],
371
+ "SubnetId": nat_gw["SubnetId"],
372
+ "NatGatewayAddresses": nat_gw.get("NatGatewayAddresses", []),
373
+ },
374
+ remediation_action="Delete NAT Gateway, then update route tables",
375
+ )
376
+ )
377
+
372
378
  except ClientError as e:
373
379
  print_warning(f"NAT Gateway analysis failed: {e}")
374
-
380
+
375
381
  def _analyze_internet_gateways(self, vpc_id: str, result: VPCDependencyAnalysisResult):
376
382
  """Step 2.2: Internet Gateway dependency analysis."""
377
383
  try:
378
384
  response = self.ec2_client.describe_internet_gateways(
379
- Filters=[{'Name': 'attachment.vpc-id', 'Values': [vpc_id]}]
385
+ Filters=[{"Name": "attachment.vpc-id", "Values": [vpc_id]}]
380
386
  )
381
-
382
- for igw in response['InternetGateways']:
383
- result.dependencies.append(VPCDependency(
384
- resource_type="InternetGateway",
385
- resource_id=igw['InternetGatewayId'],
386
- dependency_type="blocking",
387
- details={'Attachments': igw.get('Attachments', [])},
388
- remediation_action="Detach and delete Internet Gateway"
389
- ))
390
-
387
+
388
+ for igw in response["InternetGateways"]:
389
+ result.dependencies.append(
390
+ VPCDependency(
391
+ resource_type="InternetGateway",
392
+ resource_id=igw["InternetGatewayId"],
393
+ dependency_type="blocking",
394
+ details={"Attachments": igw.get("Attachments", [])},
395
+ remediation_action="Detach and delete Internet Gateway",
396
+ )
397
+ )
398
+
391
399
  except ClientError as e:
392
400
  print_warning(f"Internet Gateway analysis failed: {e}")
393
-
401
+
394
402
  def _analyze_route_tables(self, vpc_id: str, result: VPCDependencyAnalysisResult):
395
403
  """Step 2.3: Route table dependency analysis."""
396
404
  try:
397
- response = self.ec2_client.describe_route_tables(
398
- Filters=[{'Name': 'vpc-id', 'Values': [vpc_id]}]
399
- )
400
-
401
- for rt in response['RouteTables']:
405
+ response = self.ec2_client.describe_route_tables(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
406
+
407
+ for rt in response["RouteTables"]:
402
408
  # Skip main route table (automatically deleted with VPC)
403
- main_rt = any(assoc.get('Main') for assoc in rt.get('Associations', []))
409
+ main_rt = any(assoc.get("Main") for assoc in rt.get("Associations", []))
404
410
  if not main_rt:
405
- result.dependencies.append(VPCDependency(
406
- resource_type="RouteTable",
407
- resource_id=rt['RouteTableId'],
408
- dependency_type="blocking",
409
- details={
410
- 'Routes': rt.get('Routes', []),
411
- 'Associations': rt.get('Associations', [])
412
- },
413
- remediation_action="Disassociate and delete non-main route tables"
414
- ))
415
-
411
+ result.dependencies.append(
412
+ VPCDependency(
413
+ resource_type="RouteTable",
414
+ resource_id=rt["RouteTableId"],
415
+ dependency_type="blocking",
416
+ details={"Routes": rt.get("Routes", []), "Associations": rt.get("Associations", [])},
417
+ remediation_action="Disassociate and delete non-main route tables",
418
+ )
419
+ )
420
+
416
421
  except ClientError as e:
417
422
  print_warning(f"Route table analysis failed: {e}")
418
-
423
+
419
424
  def _analyze_vpc_endpoints(self, vpc_id: str, result: VPCDependencyAnalysisResult):
420
425
  """Step 2.4: VPC Endpoints dependency analysis."""
421
426
  try:
422
- response = self.ec2_client.describe_vpc_endpoints(
423
- Filters=[{'Name': 'vpc-id', 'Values': [vpc_id]}]
424
- )
425
-
426
- for endpoint in response['VpcEndpoints']:
427
- if endpoint['State'] == 'available':
428
- result.dependencies.append(VPCDependency(
429
- resource_type="VpcEndpoint",
430
- resource_id=endpoint['VpcEndpointId'],
431
- dependency_type="blocking",
432
- details={
433
- 'VpcEndpointType': endpoint.get('VpcEndpointType'),
434
- 'ServiceName': endpoint.get('ServiceName'),
435
- 'State': endpoint['State']
436
- },
437
- remediation_action="Delete VPC Endpoint"
438
- ))
439
-
427
+ response = self.ec2_client.describe_vpc_endpoints(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
428
+
429
+ for endpoint in response["VpcEndpoints"]:
430
+ if endpoint["State"] == "available":
431
+ result.dependencies.append(
432
+ VPCDependency(
433
+ resource_type="VpcEndpoint",
434
+ resource_id=endpoint["VpcEndpointId"],
435
+ dependency_type="blocking",
436
+ details={
437
+ "VpcEndpointType": endpoint.get("VpcEndpointType"),
438
+ "ServiceName": endpoint.get("ServiceName"),
439
+ "State": endpoint["State"],
440
+ },
441
+ remediation_action="Delete VPC Endpoint",
442
+ )
443
+ )
444
+
440
445
  except ClientError as e:
441
446
  print_warning(f"VPC Endpoints analysis failed: {e}")
442
-
447
+
443
448
  def _analyze_transit_gateway_attachments(self, vpc_id: str, result: VPCDependencyAnalysisResult):
444
449
  """Step 2.5: Transit Gateway attachment analysis."""
445
450
  try:
446
451
  response = self.ec2_client.describe_transit_gateway_attachments(
447
- Filters=[
448
- {'Name': 'resource-id', 'Values': [vpc_id]},
449
- {'Name': 'resource-type', 'Values': ['vpc']}
450
- ]
452
+ Filters=[{"Name": "resource-id", "Values": [vpc_id]}, {"Name": "resource-type", "Values": ["vpc"]}]
451
453
  )
452
-
453
- for attachment in response['TransitGatewayAttachments']:
454
- if attachment['State'] in ['available', 'pending']:
455
- result.dependencies.append(VPCDependency(
456
- resource_type="TransitGatewayAttachment",
457
- resource_id=attachment['TransitGatewayAttachmentId'],
458
- dependency_type="blocking",
459
- details={
460
- 'TransitGatewayId': attachment.get('TransitGatewayId'),
461
- 'State': attachment['State']
462
- },
463
- remediation_action="Delete Transit Gateway VPC attachment"
464
- ))
465
-
454
+
455
+ for attachment in response["TransitGatewayAttachments"]:
456
+ if attachment["State"] in ["available", "pending"]:
457
+ result.dependencies.append(
458
+ VPCDependency(
459
+ resource_type="TransitGatewayAttachment",
460
+ resource_id=attachment["TransitGatewayAttachmentId"],
461
+ dependency_type="blocking",
462
+ details={
463
+ "TransitGatewayId": attachment.get("TransitGatewayId"),
464
+ "State": attachment["State"],
465
+ },
466
+ remediation_action="Delete Transit Gateway VPC attachment",
467
+ )
468
+ )
469
+
466
470
  except ClientError as e:
467
471
  print_warning(f"Transit Gateway analysis failed: {e}")
468
-
472
+
469
473
  def _analyze_vpc_peering(self, vpc_id: str, result: VPCDependencyAnalysisResult):
470
474
  """Step 2.6: VPC Peering connection analysis."""
471
475
  try:
472
476
  response = self.ec2_client.describe_vpc_peering_connections(
473
- Filters=[
474
- {'Name': 'accepter-vpc-info.vpc-id', 'Values': [vpc_id]}
475
- ]
477
+ Filters=[{"Name": "accepter-vpc-info.vpc-id", "Values": [vpc_id]}]
476
478
  )
477
-
479
+
478
480
  # Also check requester side
479
481
  response2 = self.ec2_client.describe_vpc_peering_connections(
480
- Filters=[
481
- {'Name': 'requester-vpc-info.vpc-id', 'Values': [vpc_id]}
482
- ]
482
+ Filters=[{"Name": "requester-vpc-info.vpc-id", "Values": [vpc_id]}]
483
483
  )
484
-
485
- all_connections = response['VpcPeeringConnections'] + response2['VpcPeeringConnections']
486
-
484
+
485
+ all_connections = response["VpcPeeringConnections"] + response2["VpcPeeringConnections"]
486
+
487
487
  for conn in all_connections:
488
- if conn['Status']['Code'] == 'active':
489
- result.dependencies.append(VPCDependency(
490
- resource_type="VpcPeeringConnection",
491
- resource_id=conn['VpcPeeringConnectionId'],
492
- dependency_type="blocking",
493
- details={'Status': conn['Status']},
494
- remediation_action="Delete VPC Peering connection"
495
- ))
496
-
488
+ if conn["Status"]["Code"] == "active":
489
+ result.dependencies.append(
490
+ VPCDependency(
491
+ resource_type="VpcPeeringConnection",
492
+ resource_id=conn["VpcPeeringConnectionId"],
493
+ dependency_type="blocking",
494
+ details={"Status": conn["Status"]},
495
+ remediation_action="Delete VPC Peering connection",
496
+ )
497
+ )
498
+
497
499
  except ClientError as e:
498
500
  print_warning(f"VPC Peering analysis failed: {e}")
499
-
501
+
500
502
  def _analyze_route53_resolver(self, vpc_id: str, result: VPCDependencyAnalysisResult):
501
503
  """Step 2.7: Route53 Resolver endpoint analysis."""
502
504
  try:
503
505
  response = self.route53resolver_client.list_resolver_endpoints()
504
-
505
- for endpoint in response['ResolverEndpoints']:
506
- if vpc_id in [ip['VpcId'] for ip in endpoint.get('IpAddresses', [])]:
507
- result.dependencies.append(VPCDependency(
508
- resource_type="ResolverEndpoint",
509
- resource_id=endpoint['Id'],
510
- resource_name=endpoint.get('Name'),
511
- dependency_type="blocking",
512
- details={
513
- 'Direction': endpoint.get('Direction'),
514
- 'IpAddressCount': endpoint.get('IpAddressCount')
515
- },
516
- remediation_action="Delete Route53 Resolver endpoint"
517
- ))
518
-
506
+
507
+ for endpoint in response["ResolverEndpoints"]:
508
+ if vpc_id in [ip["VpcId"] for ip in endpoint.get("IpAddresses", [])]:
509
+ result.dependencies.append(
510
+ VPCDependency(
511
+ resource_type="ResolverEndpoint",
512
+ resource_id=endpoint["Id"],
513
+ resource_name=endpoint.get("Name"),
514
+ dependency_type="blocking",
515
+ details={
516
+ "Direction": endpoint.get("Direction"),
517
+ "IpAddressCount": endpoint.get("IpAddressCount"),
518
+ },
519
+ remediation_action="Delete Route53 Resolver endpoint",
520
+ )
521
+ )
522
+
519
523
  except ClientError as e:
520
524
  print_warning(f"Route53 Resolver analysis failed: {e}")
521
-
525
+
522
526
  def _analyze_load_balancers(self, vpc_id: str, result: VPCDependencyAnalysisResult):
523
527
  """Step 2.8: Load Balancer dependency analysis."""
524
528
  try:
525
529
  response = self.elbv2_client.describe_load_balancers()
526
-
527
- for lb in response['LoadBalancers']:
528
- if lb['VpcId'] == vpc_id and lb['State']['Code'] == 'active':
529
- result.dependencies.append(VPCDependency(
530
- resource_type="LoadBalancer",
531
- resource_id=lb['LoadBalancerArn'],
532
- resource_name=lb['LoadBalancerName'],
533
- dependency_type="blocking",
534
- details={
535
- 'Type': lb['Type'],
536
- 'State': lb['State'],
537
- 'Scheme': lb.get('Scheme')
538
- },
539
- remediation_action="Delete Load Balancer"
540
- ))
541
-
530
+
531
+ for lb in response["LoadBalancers"]:
532
+ if lb["VpcId"] == vpc_id and lb["State"]["Code"] == "active":
533
+ result.dependencies.append(
534
+ VPCDependency(
535
+ resource_type="LoadBalancer",
536
+ resource_id=lb["LoadBalancerArn"],
537
+ resource_name=lb["LoadBalancerName"],
538
+ dependency_type="blocking",
539
+ details={"Type": lb["Type"], "State": lb["State"], "Scheme": lb.get("Scheme")},
540
+ remediation_action="Delete Load Balancer",
541
+ )
542
+ )
543
+
542
544
  except ClientError as e:
543
545
  print_warning(f"Load Balancer analysis failed: {e}")
544
-
546
+
545
547
  def _analyze_database_subnet_groups(self, vpc_id: str, result: VPCDependencyAnalysisResult):
546
548
  """Step 2.9: Database subnet group analysis."""
547
549
  try:
548
550
  response = self.rds_client.describe_db_subnet_groups()
549
-
550
- for group in response['DBSubnetGroups']:
551
- if group['VpcId'] == vpc_id:
552
- result.dependencies.append(VPCDependency(
553
- resource_type="DBSubnetGroup",
554
- resource_id=group['DBSubnetGroupName'],
555
- dependency_type="warning", # Not always blocking
556
- details={
557
- 'SubnetIds': [subnet['SubnetIdentifier'] for subnet in group['Subnets']]
558
- },
559
- remediation_action="Delete or reassign DB Subnet Group"
560
- ))
561
-
551
+
552
+ for group in response["DBSubnetGroups"]:
553
+ if group["VpcId"] == vpc_id:
554
+ result.dependencies.append(
555
+ VPCDependency(
556
+ resource_type="DBSubnetGroup",
557
+ resource_id=group["DBSubnetGroupName"],
558
+ dependency_type="warning", # Not always blocking
559
+ details={"SubnetIds": [subnet["SubnetIdentifier"] for subnet in group["Subnets"]]},
560
+ remediation_action="Delete or reassign DB Subnet Group",
561
+ )
562
+ )
563
+
562
564
  except ClientError as e:
563
565
  print_warning(f"Database subnet group analysis failed: {e}")
564
-
566
+
565
567
  def _analyze_vpc_flow_logs(self, vpc_id: str, result: VPCDependencyAnalysisResult):
566
- """Step 2.10: VPC Flow Logs analysis."""
568
+ """Step 2.10: VPC Flow Logs analysis."""
567
569
  try:
568
570
  response = self.ec2_client.describe_flow_logs(
569
- Filters=[
570
- {'Name': 'resource-id', 'Values': [vpc_id]},
571
- {'Name': 'resource-type', 'Values': ['VPC']}
572
- ]
571
+ Filters=[{"Name": "resource-id", "Values": [vpc_id]}, {"Name": "resource-type", "Values": ["VPC"]}]
573
572
  )
574
-
575
- for flow_log in response['FlowLogs']:
576
- if flow_log['FlowLogStatus'] == 'ACTIVE':
577
- result.dependencies.append(VPCDependency(
578
- resource_type="FlowLog",
579
- resource_id=flow_log['FlowLogId'],
580
- dependency_type="informational", # Clean up but not blocking
581
- details={
582
- 'LogDestinationType': flow_log.get('LogDestinationType'),
583
- 'LogDestination': flow_log.get('LogDestination')
584
- },
585
- remediation_action="Delete Flow Log (data retention handled)"
586
- ))
587
-
573
+
574
+ for flow_log in response["FlowLogs"]:
575
+ if flow_log["FlowLogStatus"] == "ACTIVE":
576
+ result.dependencies.append(
577
+ VPCDependency(
578
+ resource_type="FlowLog",
579
+ resource_id=flow_log["FlowLogId"],
580
+ dependency_type="informational", # Clean up but not blocking
581
+ details={
582
+ "LogDestinationType": flow_log.get("LogDestinationType"),
583
+ "LogDestination": flow_log.get("LogDestination"),
584
+ },
585
+ remediation_action="Delete Flow Log (data retention handled)",
586
+ )
587
+ )
588
+
588
589
  except ClientError as e:
589
590
  print_warning(f"VPC Flow Logs analysis failed: {e}")
590
-
591
+
591
592
  def _analyze_security_groups_nacls(self, vpc_id: str, result: VPCDependencyAnalysisResult):
592
593
  """Step 2.11: Security Groups and NACLs analysis."""
593
594
  try:
594
595
  # Security Groups
595
- sg_response = self.ec2_client.describe_security_groups(
596
- Filters=[{'Name': 'vpc-id', 'Values': [vpc_id]}]
597
- )
598
-
599
- for sg in sg_response['SecurityGroups']:
600
- if sg['GroupName'] != 'default': # Skip default SG (auto-deleted)
601
- result.dependencies.append(VPCDependency(
602
- resource_type="SecurityGroup",
603
- resource_id=sg['GroupId'],
604
- resource_name=sg['GroupName'],
605
- dependency_type="blocking",
606
- details={'Description': sg.get('Description')},
607
- remediation_action="Delete non-default Security Groups"
608
- ))
609
-
596
+ sg_response = self.ec2_client.describe_security_groups(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
597
+
598
+ for sg in sg_response["SecurityGroups"]:
599
+ if sg["GroupName"] != "default": # Skip default SG (auto-deleted)
600
+ result.dependencies.append(
601
+ VPCDependency(
602
+ resource_type="SecurityGroup",
603
+ resource_id=sg["GroupId"],
604
+ resource_name=sg["GroupName"],
605
+ dependency_type="blocking",
606
+ details={"Description": sg.get("Description")},
607
+ remediation_action="Delete non-default Security Groups",
608
+ )
609
+ )
610
+
610
611
  # Network ACLs
611
- nacl_response = self.ec2_client.describe_network_acls(
612
- Filters=[{'Name': 'vpc-id', 'Values': [vpc_id]}]
613
- )
614
-
615
- for nacl in nacl_response['NetworkAcls']:
616
- if not nacl['IsDefault']: # Skip default NACL (auto-deleted)
617
- result.dependencies.append(VPCDependency(
618
- resource_type="NetworkAcl",
619
- resource_id=nacl['NetworkAclId'],
620
- dependency_type="blocking",
621
- details={'Associations': nacl.get('Associations', [])},
622
- remediation_action="Delete non-default Network ACLs"
623
- ))
624
-
612
+ nacl_response = self.ec2_client.describe_network_acls(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
613
+
614
+ for nacl in nacl_response["NetworkAcls"]:
615
+ if not nacl["IsDefault"]: # Skip default NACL (auto-deleted)
616
+ result.dependencies.append(
617
+ VPCDependency(
618
+ resource_type="NetworkAcl",
619
+ resource_id=nacl["NetworkAclId"],
620
+ dependency_type="blocking",
621
+ details={"Associations": nacl.get("Associations", [])},
622
+ remediation_action="Delete non-default Network ACLs",
623
+ )
624
+ )
625
+
625
626
  except ClientError as e:
626
627
  print_warning(f"Security Groups/NACLs analysis failed: {e}")
627
-
628
+
628
629
  def _generate_cleanup_recommendation(self, result: VPCDependencyAnalysisResult):
629
630
  """Generate cleanup recommendation based on dependency analysis."""
630
631
  if result.eni_count > 0:
631
632
  result.cleanup_recommendation = "INVESTIGATE"
632
633
  result.security_impact = "HIGH"
633
634
  result.compliance_impact = ["INVESTIGATE_WORKLOADS", "VALIDATE_ENI_OWNERS"]
634
-
635
+
635
636
  elif result.blocking_dependencies == 0:
636
637
  result.cleanup_recommendation = "DELETE"
637
638
  result.security_impact = "LOW" if not result.is_default else "MEDIUM"
638
639
  result.estimated_monthly_savings = 50.0 # Estimated VPC-related cost savings
639
-
640
+
640
641
  if result.is_default:
641
642
  result.compliance_impact = ["CIS_BENCHMARK_IMPROVEMENT", "ATTACK_SURFACE_REDUCTION"]
642
-
643
+
643
644
  elif result.blocking_dependencies <= 3:
644
645
  result.cleanup_recommendation = "DELETE_WITH_CLEANUP"
645
646
  result.security_impact = "MEDIUM"
646
647
  result.estimated_monthly_savings = 25.0
647
648
  result.compliance_impact = ["REQUIRES_DEPENDENCY_CLEANUP"]
648
-
649
+
649
650
  else:
650
651
  result.cleanup_recommendation = "HOLD"
651
652
  result.security_impact = "HIGH"
652
653
  result.compliance_impact = ["COMPLEX_DEPENDENCIES", "REQUIRES_DETAILED_ANALYSIS"]
653
-
654
+
654
655
  def _display_analysis_results(self, result: VPCDependencyAnalysisResult):
655
656
  """Display comprehensive analysis results with Rich formatting."""
656
-
657
+
657
658
  # Summary Panel
658
659
  summary_table = Table(title="AWSO-5 VPC Analysis Summary")
659
660
  summary_table.add_column("Metric", style="cyan", no_wrap=True)
660
661
  summary_table.add_column("Value", style="green")
661
662
  summary_table.add_column("Impact", style="yellow")
662
-
663
+
663
664
  summary_table.add_row("VPC ID", result.vpc_id, "")
664
- summary_table.add_row("Default VPC", "Yes" if result.is_default else "No",
665
- "Security Risk" if result.is_default else "Normal")
666
- summary_table.add_row("ENI Count", str(result.eni_count),
667
- "BLOCKING" if result.eni_count > 0 else "OK")
665
+ summary_table.add_row(
666
+ "Default VPC", "Yes" if result.is_default else "No", "Security Risk" if result.is_default else "Normal"
667
+ )
668
+ summary_table.add_row("ENI Count", str(result.eni_count), "BLOCKING" if result.eni_count > 0 else "OK")
668
669
  summary_table.add_row("Total Dependencies", str(len(result.dependencies)), "")
669
- summary_table.add_row("Blocking Dependencies", str(result.blocking_dependencies),
670
- "REQUIRES_CLEANUP" if result.blocking_dependencies > 0 else "OK")
670
+ summary_table.add_row(
671
+ "Blocking Dependencies",
672
+ str(result.blocking_dependencies),
673
+ "REQUIRES_CLEANUP" if result.blocking_dependencies > 0 else "OK",
674
+ )
671
675
  summary_table.add_row("Recommendation", result.cleanup_recommendation, result.security_impact)
672
676
  summary_table.add_row("Analysis Duration", f"{result.analysis_duration_seconds:.2f}s", "")
673
-
677
+
674
678
  self.console.print("\n")
675
679
  self.console.print(summary_table)
676
-
680
+
677
681
  # Dependencies Detail
678
682
  if result.dependencies:
679
- deps_table = create_table(title="Dependency Analysis Details",
680
- columns=["Resource Type", "Resource ID", "Dependency Type", "Remediation Action"])
681
-
683
+ deps_table = create_table(
684
+ title="Dependency Analysis Details",
685
+ columns=["Resource Type", "Resource ID", "Dependency Type", "Remediation Action"],
686
+ )
687
+
682
688
  for dep in result.dependencies:
683
689
  deps_table.add_row(
684
690
  dep.resource_type,
685
691
  dep.resource_id,
686
692
  dep.dependency_type.upper(),
687
- dep.remediation_action or "Manual review required"
693
+ dep.remediation_action or "Manual review required",
688
694
  )
689
-
695
+
690
696
  self.console.print("\n")
691
697
  self.console.print(deps_table)
692
-
698
+
693
699
  # Recommendation Panel
694
700
  if result.cleanup_recommendation == "DELETE":
695
701
  status = "[green]✅ SAFE TO DELETE[/green]"
@@ -697,149 +703,147 @@ class VPCDependencyAnalyzer:
697
703
  status = "[red]⚠️ INVESTIGATE REQUIRED[/red]"
698
704
  else:
699
705
  status = "[yellow]⚠️ CLEANUP REQUIRED[/yellow]"
700
-
706
+
701
707
  recommendation_text = f"""
702
708
  {status}
703
709
 
704
710
  **Complexity:** {result.deletion_complexity}
705
711
  **Estimated Savings:** ${result.estimated_monthly_savings:.2f}/month
706
712
  **Security Impact:** {result.security_impact}
707
- **Compliance Impact:** {', '.join(result.compliance_impact) if result.compliance_impact else 'None'}
713
+ **Compliance Impact:** {", ".join(result.compliance_impact) if result.compliance_impact else "None"}
708
714
 
709
715
  **Next Steps:**
710
716
  {self._get_next_steps(result)}
711
717
  """
712
-
713
- recommendation_panel = Panel(
714
- recommendation_text,
715
- title="🎯 AWSO-5 Cleanup Recommendation",
716
- border_style="blue"
717
- )
718
-
718
+
719
+ recommendation_panel = Panel(recommendation_text, title="🎯 AWSO-5 Cleanup Recommendation", border_style="blue")
720
+
719
721
  self.console.print("\n")
720
722
  self.console.print(recommendation_panel)
721
-
723
+
722
724
  if result.can_delete_safely:
723
725
  print_success("✅ VPC ready for deletion - zero blocking dependencies")
724
726
  else:
725
727
  print_warning(f"⚠️ {result.blocking_dependencies} blocking dependencies require resolution")
726
-
728
+
727
729
  def _get_next_steps(self, result: VPCDependencyAnalysisResult) -> str:
728
730
  """Generate next steps based on analysis results."""
729
731
  if result.cleanup_recommendation == "DELETE":
730
732
  return "• Execute VPC deletion via operate.vpc.delete()\n• Generate evidence bundle\n• Update compliance documentation"
731
-
733
+
732
734
  elif result.cleanup_recommendation == "INVESTIGATE":
733
735
  return "• Investigate ENI owners and usage\n• Validate workload requirements\n• Coordinate with application teams"
734
-
736
+
735
737
  elif result.cleanup_recommendation == "DELETE_WITH_CLEANUP":
736
738
  return "• Execute dependency cleanup plan\n• Re-run dependency analysis\n• Proceed with VPC deletion when clear"
737
-
739
+
738
740
  else: # HOLD
739
741
  return "• Detailed dependency analysis required\n• Stakeholder coordination needed\n• Consider migration vs cleanup options"
740
-
742
+
741
743
  def generate_evidence_bundle(self, vpc_ids: List[str]) -> Dict[str, Any]:
742
744
  """
743
745
  Generate SHA256-verified evidence bundle for AWSO-5 compliance.
744
-
746
+
745
747
  Args:
746
748
  vpc_ids: List of VPC IDs to include in evidence bundle
747
-
749
+
748
750
  Returns:
749
751
  Evidence bundle with manifest and hashes
750
752
  """
751
753
  evidence_bundle = {
752
- 'metadata': {
753
- 'analysis_framework': 'AWSO-5',
754
- 'version': '1.0.0',
755
- 'timestamp': datetime.utcnow().isoformat(),
756
- 'region': self.region,
757
- 'analyst': 'python-runbooks-engineer'
754
+ "metadata": {
755
+ "analysis_framework": "AWSO-5",
756
+ "version": "1.0.0",
757
+ "timestamp": datetime.utcnow().isoformat(),
758
+ "region": self.region,
759
+ "analyst": "python-runbooks-engineer",
758
760
  },
759
- 'vpc_analyses': {},
760
- 'summary': {
761
- 'total_vpcs_analyzed': 0,
762
- 'safe_to_delete': 0,
763
- 'requires_investigation': 0,
764
- 'requires_cleanup': 0,
765
- 'total_estimated_savings': 0.0
761
+ "vpc_analyses": {},
762
+ "summary": {
763
+ "total_vpcs_analyzed": 0,
764
+ "safe_to_delete": 0,
765
+ "requires_investigation": 0,
766
+ "requires_cleanup": 0,
767
+ "total_estimated_savings": 0.0,
766
768
  },
767
- 'manifest': []
769
+ "manifest": [],
768
770
  }
769
-
771
+
770
772
  for vpc_id in vpc_ids:
771
773
  if vpc_id in self.analysis_results:
772
774
  result = self.analysis_results[vpc_id]
773
- evidence_bundle['vpc_analyses'][vpc_id] = {
774
- 'analysis_result': result.__dict__,
775
- 'evidence_hash': self._calculate_evidence_hash(result)
775
+ evidence_bundle["vpc_analyses"][vpc_id] = {
776
+ "analysis_result": result.__dict__,
777
+ "evidence_hash": self._calculate_evidence_hash(result),
776
778
  }
777
-
778
- evidence_bundle['summary']['total_vpcs_analyzed'] += 1
779
+
780
+ evidence_bundle["summary"]["total_vpcs_analyzed"] += 1
779
781
  if result.cleanup_recommendation == "DELETE":
780
- evidence_bundle['summary']['safe_to_delete'] += 1
782
+ evidence_bundle["summary"]["safe_to_delete"] += 1
781
783
  elif result.cleanup_recommendation == "INVESTIGATE":
782
- evidence_bundle['summary']['requires_investigation'] += 1
784
+ evidence_bundle["summary"]["requires_investigation"] += 1
783
785
  else:
784
- evidence_bundle['summary']['requires_cleanup'] += 1
785
-
786
- evidence_bundle['summary']['total_estimated_savings'] += result.estimated_monthly_savings
787
-
786
+ evidence_bundle["summary"]["requires_cleanup"] += 1
787
+
788
+ evidence_bundle["summary"]["total_estimated_savings"] += result.estimated_monthly_savings
789
+
788
790
  # Generate bundle hash
789
791
  bundle_content = json.dumps(evidence_bundle, sort_keys=True, default=str)
790
792
  bundle_hash = hashlib.sha256(bundle_content.encode()).hexdigest()
791
- evidence_bundle['bundle_hash'] = bundle_hash
792
-
793
+ evidence_bundle["bundle_hash"] = bundle_hash
794
+
793
795
  print_success(f"Evidence bundle generated with hash: {bundle_hash[:16]}...")
794
-
796
+
795
797
  return evidence_bundle
796
-
798
+
797
799
  def _calculate_evidence_hash(self, result: VPCDependencyAnalysisResult) -> str:
798
800
  """Calculate SHA256 hash for analysis result."""
799
801
  result_json = json.dumps(result.__dict__, sort_keys=True, default=str)
800
802
  return hashlib.sha256(result_json.encode()).hexdigest()
801
803
 
802
804
 
803
- def analyze_vpc_dependencies_cli(vpc_id: str, profile: Optional[str] = None, region: str = "us-east-1") -> VPCDependencyAnalysisResult:
805
+ def analyze_vpc_dependencies_cli(
806
+ vpc_id: str, profile: Optional[str] = None, region: str = "us-east-1"
807
+ ) -> VPCDependencyAnalysisResult:
804
808
  """
805
809
  CLI wrapper for VPC dependency analysis.
806
-
810
+
807
811
  Args:
808
812
  vpc_id: AWS VPC identifier
809
813
  profile: AWS profile name
810
814
  region: AWS region
811
-
815
+
812
816
  Returns:
813
817
  Comprehensive dependency analysis results
814
818
  """
815
819
  session = boto3.Session(profile_name=profile) if profile else boto3.Session()
816
820
  analyzer = VPCDependencyAnalyzer(session=session, region=region)
817
-
821
+
818
822
  return analyzer.analyze_vpc_dependencies(vpc_id)
819
823
 
820
824
 
821
825
  if __name__ == "__main__":
822
826
  import argparse
823
-
827
+
824
828
  parser = argparse.ArgumentParser(description="AWSO-5 VPC Dependency Analysis")
825
829
  parser.add_argument("--vpc-id", required=True, help="VPC ID to analyze")
826
830
  parser.add_argument("--profile", help="AWS profile name")
827
831
  parser.add_argument("--region", default="us-east-1", help="AWS region")
828
832
  parser.add_argument("--evidence-bundle", action="store_true", help="Generate evidence bundle")
829
-
833
+
830
834
  args = parser.parse_args()
831
-
835
+
832
836
  result = analyze_vpc_dependencies_cli(args.vpc_id, args.profile, args.region)
833
-
837
+
834
838
  if args.evidence_bundle:
835
839
  analyzer = VPCDependencyAnalyzer(region=args.region)
836
840
  bundle = analyzer.generate_evidence_bundle([args.vpc_id])
837
-
841
+
838
842
  # Save evidence bundle
839
843
  timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
840
844
  bundle_filename = f"vpc_evidence_bundle_{timestamp}.json"
841
-
842
- with open(bundle_filename, 'w') as f:
845
+
846
+ with open(bundle_filename, "w") as f:
843
847
  json.dump(bundle, f, indent=2, default=str)
844
-
845
- print_success(f"Evidence bundle saved: {bundle_filename}")
848
+
849
+ print_success(f"Evidence bundle saved: {bundle_filename}")