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
@@ -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
|
@@ -43,14 +43,21 @@ from typing import Any, Dict, List, Optional, Set, Tuple
|
|
43
43
|
import hashlib
|
44
44
|
import boto3
|
45
45
|
from botocore.exceptions import ClientError
|
46
|
-
from rich.console import Console
|
47
46
|
from rich.table import Table
|
48
47
|
from rich.panel import Panel
|
49
|
-
from rich.progress import
|
48
|
+
from rich.progress import SpinnerColumn, TextColumn
|
49
|
+
from runbooks.common.rich_utils import Progress
|
50
50
|
|
51
51
|
from runbooks.common.rich_utils import (
|
52
|
-
console,
|
53
|
-
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
226
|
-
account_id=self.session.client(
|
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(
|
229
|
-
cidr_blocks=[block[
|
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[
|
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
|
-
|
326
|
-
)
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
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
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
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=[{
|
385
|
+
Filters=[{"Name": "attachment.vpc-id", "Values": [vpc_id]}]
|
380
386
|
)
|
381
|
-
|
382
|
-
for igw in response[
|
383
|
-
result.dependencies.append(
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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
|
-
|
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(
|
409
|
+
main_rt = any(assoc.get("Main") for assoc in rt.get("Associations", []))
|
404
410
|
if not main_rt:
|
405
|
-
result.dependencies.append(
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
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
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
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[
|
454
|
-
if attachment[
|
455
|
-
result.dependencies.append(
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
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[
|
486
|
-
|
484
|
+
|
485
|
+
all_connections = response["VpcPeeringConnections"] + response2["VpcPeeringConnections"]
|
486
|
+
|
487
487
|
for conn in all_connections:
|
488
|
-
if conn[
|
489
|
-
result.dependencies.append(
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
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[
|
506
|
-
if vpc_id in [ip[
|
507
|
-
result.dependencies.append(
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
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[
|
528
|
-
if lb[
|
529
|
-
result.dependencies.append(
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
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[
|
551
|
-
if group[
|
552
|
-
result.dependencies.append(
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
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[
|
576
|
-
if flow_log[
|
577
|
-
result.dependencies.append(
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
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
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
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
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
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(
|
665
|
-
|
666
|
-
|
667
|
-
|
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(
|
670
|
-
|
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(
|
680
|
-
|
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:** {
|
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
|
-
|
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
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
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
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
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
|
-
|
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[
|
774
|
-
|
775
|
-
|
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[
|
779
|
+
|
780
|
+
evidence_bundle["summary"]["total_vpcs_analyzed"] += 1
|
779
781
|
if result.cleanup_recommendation == "DELETE":
|
780
|
-
evidence_bundle[
|
782
|
+
evidence_bundle["summary"]["safe_to_delete"] += 1
|
781
783
|
elif result.cleanup_recommendation == "INVESTIGATE":
|
782
|
-
evidence_bundle[
|
784
|
+
evidence_bundle["summary"]["requires_investigation"] += 1
|
783
785
|
else:
|
784
|
-
evidence_bundle[
|
785
|
-
|
786
|
-
evidence_bundle[
|
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[
|
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(
|
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,
|
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}")
|