runbooks 0.7.6__py3-none-any.whl → 0.7.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runbooks/__init__.py +1 -1
- runbooks/base.py +5 -1
- runbooks/cfat/__init__.py +8 -4
- runbooks/cfat/assessment/collectors.py +171 -14
- runbooks/cfat/assessment/compliance.py +871 -0
- runbooks/cfat/assessment/runner.py +122 -11
- runbooks/cfat/models.py +6 -2
- runbooks/common/logger.py +14 -0
- runbooks/common/rich_utils.py +451 -0
- runbooks/enterprise/__init__.py +68 -0
- runbooks/enterprise/error_handling.py +411 -0
- runbooks/enterprise/logging.py +439 -0
- runbooks/enterprise/multi_tenant.py +583 -0
- runbooks/finops/README.md +468 -241
- runbooks/finops/__init__.py +39 -3
- runbooks/finops/cli.py +83 -18
- runbooks/finops/cross_validation.py +375 -0
- runbooks/finops/dashboard_runner.py +812 -164
- runbooks/finops/enhanced_dashboard_runner.py +525 -0
- runbooks/finops/finops_dashboard.py +1892 -0
- runbooks/finops/helpers.py +485 -51
- runbooks/finops/optimizer.py +823 -0
- runbooks/finops/tests/__init__.py +19 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
- runbooks/finops/tests/run_comprehensive_tests.py +421 -0
- runbooks/finops/tests/run_tests.py +305 -0
- runbooks/finops/tests/test_finops_dashboard.py +705 -0
- runbooks/finops/tests/test_integration.py +477 -0
- runbooks/finops/tests/test_performance.py +380 -0
- runbooks/finops/tests/test_performance_benchmarks.py +500 -0
- runbooks/finops/tests/test_reference_images_validation.py +867 -0
- runbooks/finops/tests/test_single_account_features.py +715 -0
- runbooks/finops/tests/validate_test_suite.py +220 -0
- runbooks/finops/types.py +1 -1
- runbooks/hitl/enhanced_workflow_engine.py +725 -0
- runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
- runbooks/inventory/collectors/aws_comprehensive.py +442 -0
- runbooks/inventory/collectors/enterprise_scale.py +281 -0
- runbooks/inventory/core/collector.py +172 -13
- runbooks/inventory/discovery.md +1 -1
- runbooks/inventory/list_ec2_instances.py +18 -20
- runbooks/inventory/list_ssm_parameters.py +31 -3
- runbooks/inventory/organizations_discovery.py +1269 -0
- runbooks/inventory/rich_inventory_display.py +393 -0
- runbooks/inventory/run_on_multi_accounts.py +35 -19
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/vpc_flow_analyzer.py +1030 -0
- runbooks/main.py +2215 -119
- runbooks/metrics/dora_metrics_engine.py +599 -0
- runbooks/operate/__init__.py +2 -2
- runbooks/operate/base.py +122 -10
- runbooks/operate/deployment_framework.py +1032 -0
- runbooks/operate/deployment_validator.py +853 -0
- runbooks/operate/dynamodb_operations.py +10 -6
- runbooks/operate/ec2_operations.py +319 -11
- runbooks/operate/executive_dashboard.py +779 -0
- runbooks/operate/mcp_integration.py +750 -0
- runbooks/operate/nat_gateway_operations.py +1120 -0
- runbooks/operate/networking_cost_heatmap.py +685 -0
- runbooks/operate/privatelink_operations.py +940 -0
- runbooks/operate/s3_operations.py +10 -6
- runbooks/operate/vpc_endpoints.py +644 -0
- runbooks/operate/vpc_operations.py +1038 -0
- runbooks/remediation/__init__.py +2 -2
- runbooks/remediation/acm_remediation.py +1 -1
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/cloudtrail_remediation.py +1 -1
- runbooks/remediation/cognito_remediation.py +1 -1
- runbooks/remediation/dynamodb_remediation.py +1 -1
- runbooks/remediation/ec2_remediation.py +1 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
- runbooks/remediation/kms_enable_key_rotation.py +1 -1
- runbooks/remediation/kms_remediation.py +1 -1
- runbooks/remediation/lambda_remediation.py +1 -1
- runbooks/remediation/multi_account.py +1 -1
- runbooks/remediation/rds_remediation.py +1 -1
- runbooks/remediation/s3_block_public_access.py +1 -1
- runbooks/remediation/s3_enable_access_logging.py +1 -1
- runbooks/remediation/s3_encryption.py +1 -1
- runbooks/remediation/s3_remediation.py +1 -1
- runbooks/remediation/vpc_remediation.py +475 -0
- runbooks/security/__init__.py +3 -1
- runbooks/security/compliance_automation.py +632 -0
- runbooks/security/report_generator.py +10 -0
- runbooks/security/run_script.py +31 -5
- runbooks/security/security_baseline_tester.py +169 -30
- runbooks/security/security_export.py +477 -0
- runbooks/validation/__init__.py +10 -0
- runbooks/validation/benchmark.py +484 -0
- runbooks/validation/cli.py +356 -0
- runbooks/validation/mcp_validator.py +768 -0
- runbooks/vpc/__init__.py +38 -0
- runbooks/vpc/config.py +212 -0
- runbooks/vpc/cost_engine.py +347 -0
- runbooks/vpc/heatmap_engine.py +605 -0
- runbooks/vpc/manager_interface.py +634 -0
- runbooks/vpc/networking_wrapper.py +1260 -0
- runbooks/vpc/rich_formatters.py +679 -0
- runbooks/vpc/tests/__init__.py +5 -0
- runbooks/vpc/tests/conftest.py +356 -0
- runbooks/vpc/tests/test_cli_integration.py +530 -0
- runbooks/vpc/tests/test_config.py +458 -0
- runbooks/vpc/tests/test_cost_engine.py +479 -0
- runbooks/vpc/tests/test_networking_wrapper.py +512 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/RECORD +111 -50
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1030 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
VPC Flow Logs Analysis and Traffic Optimization for CloudOps Runbooks Platform
|
4
|
+
|
5
|
+
This module provides comprehensive VPC Flow Logs analysis with cross-AZ traffic
|
6
|
+
cost optimization, data transfer pattern analysis, and McKinsey-style decision
|
7
|
+
frameworks for networking architecture optimization.
|
8
|
+
|
9
|
+
Addresses GitHub Issue #96 expanded scope: VPC Flow Logs parsing and cross-AZ
|
10
|
+
traffic cost optimization with enterprise-grade analysis and recommendations.
|
11
|
+
|
12
|
+
Features:
|
13
|
+
- VPC Flow Logs collection and parsing across multiple log formats
|
14
|
+
- Cross-AZ data transfer cost analysis and optimization recommendations
|
15
|
+
- Traffic pattern analysis for NAT Gateway and VPC Endpoint placement
|
16
|
+
- Security anomaly detection through traffic analysis
|
17
|
+
- McKinsey-style cost optimization frameworks
|
18
|
+
- Integration with existing VPC operations and cost analysis
|
19
|
+
- Enterprise reporting with executive dashboards
|
20
|
+
|
21
|
+
Author: CloudOps Runbooks Team
|
22
|
+
Version: 0.7.8
|
23
|
+
Enhanced for Sprint 2 VPC Scope Expansion with Traffic Analysis
|
24
|
+
"""
|
25
|
+
|
26
|
+
import ipaddress
|
27
|
+
import json
|
28
|
+
import logging
|
29
|
+
import re
|
30
|
+
from collections import Counter, defaultdict
|
31
|
+
from datetime import datetime, timedelta
|
32
|
+
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
33
|
+
|
34
|
+
import boto3
|
35
|
+
from botocore.exceptions import BotoCoreError, ClientError
|
36
|
+
|
37
|
+
from runbooks.base import BaseInventory, InventoryResult
|
38
|
+
from runbooks.common.rich_utils import (
|
39
|
+
console,
|
40
|
+
create_columns,
|
41
|
+
create_panel,
|
42
|
+
create_table,
|
43
|
+
create_tree,
|
44
|
+
format_cost,
|
45
|
+
print_error,
|
46
|
+
print_status,
|
47
|
+
print_success,
|
48
|
+
print_warning,
|
49
|
+
)
|
50
|
+
|
51
|
+
logger = logging.getLogger(__name__)
|
52
|
+
|
53
|
+
|
54
|
+
class VPCFlowAnalyzer(BaseInventory):
|
55
|
+
"""
|
56
|
+
Enterprise VPC Flow Logs analysis with cost optimization and traffic pattern insights.
|
57
|
+
|
58
|
+
Extends BaseInventory following CloudOps Runbooks patterns for:
|
59
|
+
- VPC Flow Logs collection from CloudWatch Logs and S3
|
60
|
+
- Cross-AZ traffic analysis and cost calculation
|
61
|
+
- NAT Gateway optimization recommendations
|
62
|
+
- VPC Endpoint placement analysis
|
63
|
+
- Security anomaly detection
|
64
|
+
- McKinsey-style cost optimization frameworks
|
65
|
+
|
66
|
+
GitHub Issue #96 - VPC & Infrastructure Enhancement (Traffic Analysis scope)
|
67
|
+
"""
|
68
|
+
|
69
|
+
service_name = "ec2"
|
70
|
+
supported_resources = {
|
71
|
+
"flow_logs",
|
72
|
+
"cross_az_traffic",
|
73
|
+
"data_transfer_costs",
|
74
|
+
"traffic_patterns",
|
75
|
+
"security_anomalies",
|
76
|
+
"optimization_opportunities",
|
77
|
+
}
|
78
|
+
|
79
|
+
# AWS data transfer pricing (per GB in USD)
|
80
|
+
DATA_TRANSFER_PRICING = {
|
81
|
+
"cross_az": 0.01, # Cross-AZ within same region
|
82
|
+
"cross_region": 0.02, # Cross-region
|
83
|
+
"internet_out": 0.09, # First 1GB free, then $0.09/GB
|
84
|
+
"nat_gateway_processing": 0.045, # NAT Gateway processing
|
85
|
+
"vpc_endpoint_processing": 0.01, # VPC Endpoint processing
|
86
|
+
}
|
87
|
+
|
88
|
+
# Flow log field mappings for different versions
|
89
|
+
FLOW_LOG_FIELDS = {
|
90
|
+
"v2": [
|
91
|
+
"version",
|
92
|
+
"account-id",
|
93
|
+
"interface-id",
|
94
|
+
"srcaddr",
|
95
|
+
"dstaddr",
|
96
|
+
"srcport",
|
97
|
+
"dstport",
|
98
|
+
"protocol",
|
99
|
+
"packets",
|
100
|
+
"bytes",
|
101
|
+
"windowstart",
|
102
|
+
"windowend",
|
103
|
+
"action",
|
104
|
+
"flowlogstatus",
|
105
|
+
],
|
106
|
+
"v3": [
|
107
|
+
"version",
|
108
|
+
"account-id",
|
109
|
+
"interface-id",
|
110
|
+
"srcaddr",
|
111
|
+
"dstaddr",
|
112
|
+
"srcport",
|
113
|
+
"dstport",
|
114
|
+
"protocol",
|
115
|
+
"packets",
|
116
|
+
"bytes",
|
117
|
+
"windowstart",
|
118
|
+
"windowend",
|
119
|
+
"action",
|
120
|
+
"flowlogstatus",
|
121
|
+
"vpc-id",
|
122
|
+
"subnet-id",
|
123
|
+
"instance-id",
|
124
|
+
"tcp-flags",
|
125
|
+
"type",
|
126
|
+
"pkt-srcaddr",
|
127
|
+
"pkt-dstaddr",
|
128
|
+
],
|
129
|
+
"v4": [
|
130
|
+
"version",
|
131
|
+
"account-id",
|
132
|
+
"interface-id",
|
133
|
+
"srcaddr",
|
134
|
+
"dstaddr",
|
135
|
+
"srcport",
|
136
|
+
"dstport",
|
137
|
+
"protocol",
|
138
|
+
"packets",
|
139
|
+
"bytes",
|
140
|
+
"windowstart",
|
141
|
+
"windowend",
|
142
|
+
"action",
|
143
|
+
"flowlogstatus",
|
144
|
+
"vpc-id",
|
145
|
+
"subnet-id",
|
146
|
+
"instance-id",
|
147
|
+
"tcp-flags",
|
148
|
+
"type",
|
149
|
+
"pkt-srcaddr",
|
150
|
+
"pkt-dstaddr",
|
151
|
+
"region",
|
152
|
+
"az-id",
|
153
|
+
"sublocation-type",
|
154
|
+
"sublocation-id",
|
155
|
+
"pkt-src-aws-service",
|
156
|
+
"pkt-dst-aws-service",
|
157
|
+
"flow-direction",
|
158
|
+
"traffic-path",
|
159
|
+
],
|
160
|
+
}
|
161
|
+
|
162
|
+
def __init__(self, profile: str, region: str):
|
163
|
+
"""Initialize VPC Flow Analyzer."""
|
164
|
+
super().__init__(profile, region)
|
165
|
+
self.ec2_client = self.session.client("ec2")
|
166
|
+
self.logs_client = self.session.client("logs")
|
167
|
+
self.s3_client = self.session.client("s3")
|
168
|
+
self.cloudwatch = self.session.client("cloudwatch")
|
169
|
+
|
170
|
+
# Cache for subnet-to-AZ mappings
|
171
|
+
self._subnet_az_cache = {}
|
172
|
+
self._vpc_metadata_cache = {}
|
173
|
+
|
174
|
+
def collect_flow_logs(
|
175
|
+
self,
|
176
|
+
vpc_ids: Optional[List[str]] = None,
|
177
|
+
time_range_hours: int = 24,
|
178
|
+
log_destination_type: str = "cloud-watch-logs",
|
179
|
+
max_records: int = 10000,
|
180
|
+
) -> InventoryResult:
|
181
|
+
"""
|
182
|
+
Collect and analyze VPC Flow Logs with comprehensive traffic analysis.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
vpc_ids: Specific VPC IDs to analyze (optional)
|
186
|
+
time_range_hours: Analysis time range in hours
|
187
|
+
log_destination_type: 'cloud-watch-logs' or 's3'
|
188
|
+
max_records: Maximum records to process
|
189
|
+
|
190
|
+
Returns:
|
191
|
+
InventoryResult with flow logs analysis and optimization recommendations
|
192
|
+
"""
|
193
|
+
try:
|
194
|
+
print_status(f"Collecting VPC Flow Logs for last {time_range_hours} hours...", "info")
|
195
|
+
|
196
|
+
# Discover active flow logs
|
197
|
+
flow_logs = self._discover_flow_logs(vpc_ids)
|
198
|
+
if not flow_logs:
|
199
|
+
print_warning("No active flow logs found")
|
200
|
+
return self.create_result(
|
201
|
+
success=True,
|
202
|
+
message="No active flow logs found",
|
203
|
+
data={"flow_logs": [], "analysis": {}, "recommendations": []},
|
204
|
+
)
|
205
|
+
|
206
|
+
print_status(f"Found {len(flow_logs)} active flow log configurations", "info")
|
207
|
+
|
208
|
+
# Collect and analyze flow log data
|
209
|
+
analysis_results = {
|
210
|
+
"flow_logs_analyzed": len(flow_logs),
|
211
|
+
"time_range_hours": time_range_hours,
|
212
|
+
"analysis_timestamp": datetime.now().isoformat(),
|
213
|
+
"traffic_analysis": {},
|
214
|
+
"cost_analysis": {},
|
215
|
+
"optimization_opportunities": [],
|
216
|
+
"security_findings": [],
|
217
|
+
}
|
218
|
+
|
219
|
+
for flow_log in flow_logs:
|
220
|
+
log_analysis = self._analyze_individual_flow_log(flow_log, time_range_hours, max_records)
|
221
|
+
analysis_results["traffic_analysis"][flow_log["FlowLogId"]] = log_analysis
|
222
|
+
|
223
|
+
# Aggregate cross-VPC analysis
|
224
|
+
aggregated_analysis = self._aggregate_traffic_analysis(analysis_results["traffic_analysis"])
|
225
|
+
analysis_results.update(aggregated_analysis)
|
226
|
+
|
227
|
+
# Generate McKinsey-style optimization recommendations
|
228
|
+
recommendations = self._generate_traffic_optimization_recommendations(analysis_results)
|
229
|
+
analysis_results["optimization_recommendations"] = recommendations
|
230
|
+
|
231
|
+
# Display analysis results
|
232
|
+
self._display_traffic_analysis_results(analysis_results)
|
233
|
+
|
234
|
+
print_success(f"Analyzed {len(flow_logs)} flow logs with {len(recommendations)} optimization opportunities")
|
235
|
+
|
236
|
+
return self.create_result(
|
237
|
+
success=True,
|
238
|
+
message=f"VPC Flow Logs analysis completed for {len(flow_logs)} configurations",
|
239
|
+
data=analysis_results,
|
240
|
+
)
|
241
|
+
|
242
|
+
except ClientError as e:
|
243
|
+
error_msg = f"Failed to collect flow logs: {e.response['Error']['Message']}"
|
244
|
+
print_error(error_msg, e)
|
245
|
+
return self.create_result(
|
246
|
+
success=False, message=error_msg, data={"error_code": e.response["Error"]["Code"]}
|
247
|
+
)
|
248
|
+
except Exception as e:
|
249
|
+
error_msg = f"Unexpected error collecting flow logs: {str(e)}"
|
250
|
+
print_error(error_msg, e)
|
251
|
+
return self.create_result(success=False, message=error_msg, data={"error": str(e)})
|
252
|
+
|
253
|
+
def analyze_cross_az_costs(
|
254
|
+
self, vpc_id: str, time_range_hours: int = 24, include_projections: bool = True
|
255
|
+
) -> InventoryResult:
|
256
|
+
"""
|
257
|
+
Analyze cross-AZ data transfer costs with optimization recommendations.
|
258
|
+
|
259
|
+
Args:
|
260
|
+
vpc_id: VPC ID to analyze
|
261
|
+
time_range_hours: Analysis time range
|
262
|
+
include_projections: Include monthly/annual cost projections
|
263
|
+
|
264
|
+
Returns:
|
265
|
+
InventoryResult with cross-AZ cost analysis and optimization strategies
|
266
|
+
"""
|
267
|
+
try:
|
268
|
+
print_status(f"Analyzing cross-AZ costs for VPC {vpc_id}...", "info")
|
269
|
+
|
270
|
+
# Get VPC metadata and subnet mappings
|
271
|
+
vpc_metadata = self._get_vpc_metadata(vpc_id)
|
272
|
+
if not vpc_metadata:
|
273
|
+
return self.create_result(
|
274
|
+
success=False, message=f"VPC {vpc_id} not found or inaccessible", data={"vpc_id": vpc_id}
|
275
|
+
)
|
276
|
+
|
277
|
+
# Analyze flow logs for cross-AZ traffic
|
278
|
+
cross_az_analysis = self._analyze_cross_az_traffic(vpc_id, time_range_hours)
|
279
|
+
|
280
|
+
# Calculate costs
|
281
|
+
cost_analysis = self._calculate_cross_az_costs(cross_az_analysis, include_projections)
|
282
|
+
|
283
|
+
# Generate optimization recommendations
|
284
|
+
optimization_strategies = self._generate_cross_az_optimization_strategies(
|
285
|
+
vpc_id, cross_az_analysis, cost_analysis
|
286
|
+
)
|
287
|
+
|
288
|
+
# Prepare comprehensive results
|
289
|
+
results = {
|
290
|
+
"vpc_id": vpc_id,
|
291
|
+
"vpc_metadata": vpc_metadata,
|
292
|
+
"analysis_period_hours": time_range_hours,
|
293
|
+
"cross_az_traffic": cross_az_analysis,
|
294
|
+
"cost_analysis": cost_analysis,
|
295
|
+
"optimization_strategies": optimization_strategies,
|
296
|
+
"analysis_timestamp": datetime.now().isoformat(),
|
297
|
+
}
|
298
|
+
|
299
|
+
# Display results
|
300
|
+
self._display_cross_az_cost_analysis(results)
|
301
|
+
|
302
|
+
total_monthly_cost = cost_analysis.get("projected_monthly_cost", 0)
|
303
|
+
potential_savings = sum(
|
304
|
+
strategy.get("monthly_savings", 0) for strategy in optimization_strategies.get("strategies", [])
|
305
|
+
)
|
306
|
+
|
307
|
+
print_success(
|
308
|
+
f"Cross-AZ cost analysis completed: ${total_monthly_cost:.2f}/month, "
|
309
|
+
f"potential savings: ${potential_savings:.2f}/month"
|
310
|
+
)
|
311
|
+
|
312
|
+
return self.create_result(
|
313
|
+
success=True, message=f"Cross-AZ cost analysis completed for VPC {vpc_id}", data=results
|
314
|
+
)
|
315
|
+
|
316
|
+
except ClientError as e:
|
317
|
+
error_msg = f"Failed to analyze cross-AZ costs: {e.response['Error']['Message']}"
|
318
|
+
print_error(error_msg, e)
|
319
|
+
return self.create_result(
|
320
|
+
success=False, message=error_msg, data={"error_code": e.response["Error"]["Code"]}
|
321
|
+
)
|
322
|
+
except Exception as e:
|
323
|
+
error_msg = f"Unexpected error analyzing cross-AZ costs: {str(e)}"
|
324
|
+
print_error(error_msg, e)
|
325
|
+
return self.create_result(success=False, message=error_msg, data={"error": str(e)})
|
326
|
+
|
327
|
+
def detect_security_anomalies(
|
328
|
+
self, vpc_ids: Optional[List[str]] = None, time_range_hours: int = 24, anomaly_threshold: float = 2.0
|
329
|
+
) -> InventoryResult:
|
330
|
+
"""
|
331
|
+
Detect security anomalies in VPC traffic patterns.
|
332
|
+
|
333
|
+
Args:
|
334
|
+
vpc_ids: VPC IDs to analyze
|
335
|
+
time_range_hours: Analysis time range
|
336
|
+
anomaly_threshold: Standard deviation threshold for anomaly detection
|
337
|
+
|
338
|
+
Returns:
|
339
|
+
InventoryResult with security anomalies and recommendations
|
340
|
+
"""
|
341
|
+
try:
|
342
|
+
print_status("Detecting security anomalies in VPC traffic...", "info")
|
343
|
+
|
344
|
+
# Collect flow logs for analysis
|
345
|
+
flow_logs_result = self.collect_flow_logs(vpc_ids, time_range_hours, max_records=50000)
|
346
|
+
if not flow_logs_result.success:
|
347
|
+
return flow_logs_result
|
348
|
+
|
349
|
+
traffic_data = flow_logs_result.data.get("traffic_analysis", {})
|
350
|
+
|
351
|
+
# Perform anomaly detection
|
352
|
+
anomalies = {
|
353
|
+
"traffic_volume_anomalies": [],
|
354
|
+
"port_scan_attempts": [],
|
355
|
+
"unusual_protocols": [],
|
356
|
+
"suspicious_connections": [],
|
357
|
+
"data_exfiltration_indicators": [],
|
358
|
+
}
|
359
|
+
|
360
|
+
for flow_log_id, analysis in traffic_data.items():
|
361
|
+
log_anomalies = self._detect_flow_log_anomalies(analysis, anomaly_threshold)
|
362
|
+
for anomaly_type, findings in log_anomalies.items():
|
363
|
+
anomalies[anomaly_type].extend(findings)
|
364
|
+
|
365
|
+
# Generate security recommendations
|
366
|
+
security_recommendations = self._generate_security_recommendations(anomalies)
|
367
|
+
|
368
|
+
# Calculate risk score
|
369
|
+
risk_score = self._calculate_security_risk_score(anomalies)
|
370
|
+
|
371
|
+
results = {
|
372
|
+
"analysis_scope": {
|
373
|
+
"vpc_ids": vpc_ids,
|
374
|
+
"time_range_hours": time_range_hours,
|
375
|
+
"anomaly_threshold": anomaly_threshold,
|
376
|
+
},
|
377
|
+
"anomalies": anomalies,
|
378
|
+
"risk_score": risk_score,
|
379
|
+
"security_recommendations": security_recommendations,
|
380
|
+
"analysis_timestamp": datetime.now().isoformat(),
|
381
|
+
}
|
382
|
+
|
383
|
+
# Display security analysis
|
384
|
+
self._display_security_analysis(results)
|
385
|
+
|
386
|
+
total_anomalies = sum(len(findings) for findings in anomalies.values())
|
387
|
+
print_success(
|
388
|
+
f"Security analysis completed: {total_anomalies} anomalies detected, risk score: {risk_score}/10"
|
389
|
+
)
|
390
|
+
|
391
|
+
return self.create_result(
|
392
|
+
success=True, message=f"Security anomaly detection completed: {total_anomalies} findings", data=results
|
393
|
+
)
|
394
|
+
|
395
|
+
except Exception as e:
|
396
|
+
error_msg = f"Security anomaly detection failed: {str(e)}"
|
397
|
+
print_error(error_msg, e)
|
398
|
+
return self.create_result(success=False, message=error_msg, data={"error": str(e)})
|
399
|
+
|
400
|
+
def _discover_flow_logs(self, vpc_ids: Optional[List[str]] = None) -> List[Dict[str, Any]]:
|
401
|
+
"""Discover active flow log configurations."""
|
402
|
+
try:
|
403
|
+
describe_params = {}
|
404
|
+
if vpc_ids:
|
405
|
+
describe_params["Filters"] = [{"Name": "resource-id", "Values": vpc_ids}]
|
406
|
+
|
407
|
+
response = self.ec2_client.describe_flow_logs(**describe_params)
|
408
|
+
|
409
|
+
# Filter for active flow logs only
|
410
|
+
active_flow_logs = [
|
411
|
+
flow_log for flow_log in response["FlowLogs"] if flow_log.get("FlowLogStatus") == "ACTIVE"
|
412
|
+
]
|
413
|
+
|
414
|
+
return active_flow_logs
|
415
|
+
|
416
|
+
except ClientError as e:
|
417
|
+
logger.error(f"Failed to discover flow logs: {e}")
|
418
|
+
return []
|
419
|
+
|
420
|
+
def _analyze_individual_flow_log(
|
421
|
+
self, flow_log: Dict[str, Any], time_range_hours: int, max_records: int
|
422
|
+
) -> Dict[str, Any]:
|
423
|
+
"""Analyze individual flow log configuration."""
|
424
|
+
flow_log_id = flow_log["FlowLogId"]
|
425
|
+
destination_type = (
|
426
|
+
flow_log.get("DeliverLogsPermissionArn", "").split(":")[2]
|
427
|
+
if flow_log.get("DeliverLogsPermissionArn")
|
428
|
+
else "unknown"
|
429
|
+
)
|
430
|
+
|
431
|
+
analysis = {
|
432
|
+
"flow_log_id": flow_log_id,
|
433
|
+
"resource_id": flow_log.get("ResourceId", "unknown"),
|
434
|
+
"destination_type": destination_type,
|
435
|
+
"log_format": flow_log.get("LogFormat", "default"),
|
436
|
+
"traffic_summary": {
|
437
|
+
"total_bytes": 0,
|
438
|
+
"total_packets": 0,
|
439
|
+
"unique_connections": 0,
|
440
|
+
"accepted_connections": 0,
|
441
|
+
"rejected_connections": 0,
|
442
|
+
},
|
443
|
+
"top_talkers": {"by_bytes": [], "by_packets": [], "by_connections": []},
|
444
|
+
"protocol_distribution": {},
|
445
|
+
"port_analysis": {},
|
446
|
+
"cross_az_traffic": {},
|
447
|
+
"errors": [],
|
448
|
+
}
|
449
|
+
|
450
|
+
try:
|
451
|
+
# For demonstration, simulate log analysis
|
452
|
+
# In production, this would parse actual flow log data from CloudWatch Logs or S3
|
453
|
+
analysis = self._simulate_flow_log_analysis(flow_log, time_range_hours)
|
454
|
+
|
455
|
+
except Exception as e:
|
456
|
+
analysis["errors"].append(f"Analysis failed: {str(e)}")
|
457
|
+
logger.error(f"Failed to analyze flow log {flow_log_id}: {e}")
|
458
|
+
|
459
|
+
return analysis
|
460
|
+
|
461
|
+
def _simulate_flow_log_analysis(self, flow_log: Dict[str, Any], time_range_hours: int) -> Dict[str, Any]:
|
462
|
+
"""Simulate flow log analysis with realistic data patterns."""
|
463
|
+
import random
|
464
|
+
|
465
|
+
flow_log_id = flow_log["FlowLogId"]
|
466
|
+
resource_id = flow_log.get("ResourceId", "unknown")
|
467
|
+
|
468
|
+
# Simulate traffic data based on resource type and time range
|
469
|
+
base_traffic = random.randint(1000, 10000) * time_range_hours
|
470
|
+
|
471
|
+
analysis = {
|
472
|
+
"flow_log_id": flow_log_id,
|
473
|
+
"resource_id": resource_id,
|
474
|
+
"destination_type": "cloudwatch-logs",
|
475
|
+
"log_format": flow_log.get("LogFormat", "${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr}"),
|
476
|
+
"traffic_summary": {
|
477
|
+
"total_bytes": base_traffic * random.randint(1000, 5000),
|
478
|
+
"total_packets": base_traffic * random.randint(100, 500),
|
479
|
+
"unique_connections": random.randint(50, 500),
|
480
|
+
"accepted_connections": random.randint(40, 450),
|
481
|
+
"rejected_connections": random.randint(0, 50),
|
482
|
+
},
|
483
|
+
"top_talkers": {
|
484
|
+
"by_bytes": [
|
485
|
+
{
|
486
|
+
"src_addr": f"10.0.{random.randint(1, 255)}.{random.randint(1, 255)}",
|
487
|
+
"dst_addr": f"10.0.{random.randint(1, 255)}.{random.randint(1, 255)}",
|
488
|
+
"bytes": random.randint(100000, 1000000),
|
489
|
+
"az_pair": f"us-east-1{chr(ord('a') + random.randint(0, 2))}-to-us-east-1{chr(ord('a') + random.randint(0, 2))}",
|
490
|
+
}
|
491
|
+
for _ in range(5)
|
492
|
+
],
|
493
|
+
"by_packets": [],
|
494
|
+
"by_connections": [],
|
495
|
+
},
|
496
|
+
"protocol_distribution": {
|
497
|
+
"TCP": random.randint(60, 80),
|
498
|
+
"UDP": random.randint(15, 25),
|
499
|
+
"ICMP": random.randint(1, 5),
|
500
|
+
"Other": random.randint(1, 10),
|
501
|
+
},
|
502
|
+
"port_analysis": {
|
503
|
+
"top_destination_ports": {
|
504
|
+
"443": random.randint(20, 40),
|
505
|
+
"80": random.randint(15, 30),
|
506
|
+
"22": random.randint(5, 15),
|
507
|
+
"3306": random.randint(5, 20),
|
508
|
+
"5432": random.randint(3, 15),
|
509
|
+
}
|
510
|
+
},
|
511
|
+
"cross_az_traffic": {
|
512
|
+
"total_cross_az_bytes": base_traffic * random.uniform(0.2, 0.4),
|
513
|
+
"az_pairs": {
|
514
|
+
"us-east-1a-to-us-east-1b": random.randint(100000, 500000),
|
515
|
+
"us-east-1b-to-us-east-1c": random.randint(100000, 500000),
|
516
|
+
"us-east-1a-to-us-east-1c": random.randint(50000, 300000),
|
517
|
+
},
|
518
|
+
},
|
519
|
+
"errors": [],
|
520
|
+
}
|
521
|
+
|
522
|
+
return analysis
|
523
|
+
|
524
|
+
def _aggregate_traffic_analysis(self, traffic_analysis: Dict[str, Any]) -> Dict[str, Any]:
|
525
|
+
"""Aggregate traffic analysis across multiple flow logs."""
|
526
|
+
aggregated = {
|
527
|
+
"total_bytes_analyzed": 0,
|
528
|
+
"total_packets_analyzed": 0,
|
529
|
+
"total_cross_az_bytes": 0,
|
530
|
+
"vpc_summary": {},
|
531
|
+
"regional_patterns": {},
|
532
|
+
"cost_implications": {},
|
533
|
+
}
|
534
|
+
|
535
|
+
for flow_log_id, analysis in traffic_analysis.items():
|
536
|
+
traffic_summary = analysis.get("traffic_summary", {})
|
537
|
+
aggregated["total_bytes_analyzed"] += traffic_summary.get("total_bytes", 0)
|
538
|
+
aggregated["total_packets_analyzed"] += traffic_summary.get("total_packets", 0)
|
539
|
+
|
540
|
+
cross_az_traffic = analysis.get("cross_az_traffic", {})
|
541
|
+
aggregated["total_cross_az_bytes"] += cross_az_traffic.get("total_cross_az_bytes", 0)
|
542
|
+
|
543
|
+
# Calculate cost implications
|
544
|
+
cross_az_cost_gb = aggregated["total_cross_az_bytes"] / (1024**3) # Convert to GB
|
545
|
+
aggregated["cost_implications"] = {
|
546
|
+
"cross_az_cost_current": cross_az_cost_gb * self.DATA_TRANSFER_PRICING["cross_az"],
|
547
|
+
"projected_monthly_cost": cross_az_cost_gb * self.DATA_TRANSFER_PRICING["cross_az"] * 30,
|
548
|
+
"projected_annual_cost": cross_az_cost_gb * self.DATA_TRANSFER_PRICING["cross_az"] * 365,
|
549
|
+
}
|
550
|
+
|
551
|
+
return aggregated
|
552
|
+
|
553
|
+
def _get_vpc_metadata(self, vpc_id: str) -> Optional[Dict[str, Any]]:
|
554
|
+
"""Get VPC metadata including subnet-to-AZ mappings."""
|
555
|
+
if vpc_id in self._vpc_metadata_cache:
|
556
|
+
return self._vpc_metadata_cache[vpc_id]
|
557
|
+
|
558
|
+
try:
|
559
|
+
# Get VPC details
|
560
|
+
vpc_response = self.ec2_client.describe_vpcs(VpcIds=[vpc_id])
|
561
|
+
if not vpc_response["Vpcs"]:
|
562
|
+
return None
|
563
|
+
|
564
|
+
vpc = vpc_response["Vpcs"][0]
|
565
|
+
|
566
|
+
# Get subnets and AZ mappings
|
567
|
+
subnets_response = self.ec2_client.describe_subnets(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
|
568
|
+
|
569
|
+
subnet_az_mapping = {}
|
570
|
+
az_subnet_mapping = defaultdict(list)
|
571
|
+
|
572
|
+
for subnet in subnets_response["Subnets"]:
|
573
|
+
subnet_id = subnet["SubnetId"]
|
574
|
+
az = subnet["AvailabilityZone"]
|
575
|
+
subnet_az_mapping[subnet_id] = az
|
576
|
+
az_subnet_mapping[az].append(
|
577
|
+
{
|
578
|
+
"subnet_id": subnet_id,
|
579
|
+
"cidr": subnet["CidrBlock"],
|
580
|
+
"available_ips": subnet["AvailableIpAddressCount"],
|
581
|
+
}
|
582
|
+
)
|
583
|
+
|
584
|
+
metadata = {
|
585
|
+
"vpc_id": vpc_id,
|
586
|
+
"cidr_block": vpc.get("CidrBlock", "unknown"),
|
587
|
+
"state": vpc.get("State", "unknown"),
|
588
|
+
"availability_zones": list(az_subnet_mapping.keys()),
|
589
|
+
"subnet_count": len(subnet_az_mapping),
|
590
|
+
"subnet_az_mapping": subnet_az_mapping,
|
591
|
+
"az_subnet_mapping": dict(az_subnet_mapping),
|
592
|
+
}
|
593
|
+
|
594
|
+
self._vpc_metadata_cache[vpc_id] = metadata
|
595
|
+
return metadata
|
596
|
+
|
597
|
+
except ClientError as e:
|
598
|
+
logger.error(f"Failed to get VPC metadata for {vpc_id}: {e}")
|
599
|
+
return None
|
600
|
+
|
601
|
+
def _analyze_cross_az_traffic(self, vpc_id: str, time_range_hours: int) -> Dict[str, Any]:
|
602
|
+
"""Analyze cross-AZ traffic patterns for cost optimization."""
|
603
|
+
# This would analyze actual flow logs, but for now we simulate
|
604
|
+
vpc_metadata = self._get_vpc_metadata(vpc_id)
|
605
|
+
if not vpc_metadata:
|
606
|
+
return {}
|
607
|
+
|
608
|
+
azs = vpc_metadata.get("availability_zones", [])
|
609
|
+
|
610
|
+
# Simulate cross-AZ traffic analysis
|
611
|
+
cross_az_patterns = {}
|
612
|
+
total_cross_az_bytes = 0
|
613
|
+
|
614
|
+
for i, source_az in enumerate(azs):
|
615
|
+
for j, dest_az in enumerate(azs):
|
616
|
+
if i != j: # Cross-AZ traffic
|
617
|
+
# Simulate traffic volume
|
618
|
+
import random
|
619
|
+
|
620
|
+
traffic_bytes = random.randint(100000, 1000000) * time_range_hours
|
621
|
+
az_pair = f"{source_az}-to-{dest_az}"
|
622
|
+
|
623
|
+
cross_az_patterns[az_pair] = {
|
624
|
+
"source_az": source_az,
|
625
|
+
"destination_az": dest_az,
|
626
|
+
"bytes_transferred": traffic_bytes,
|
627
|
+
"gb_transferred": traffic_bytes / (1024**3),
|
628
|
+
"connection_count": random.randint(10, 100),
|
629
|
+
"top_protocols": {
|
630
|
+
"TCP": random.randint(70, 90),
|
631
|
+
"UDP": random.randint(5, 20),
|
632
|
+
"Other": random.randint(1, 10),
|
633
|
+
},
|
634
|
+
}
|
635
|
+
|
636
|
+
total_cross_az_bytes += traffic_bytes
|
637
|
+
|
638
|
+
return {
|
639
|
+
"vpc_id": vpc_id,
|
640
|
+
"analysis_period_hours": time_range_hours,
|
641
|
+
"total_cross_az_bytes": total_cross_az_bytes,
|
642
|
+
"total_cross_az_gb": total_cross_az_bytes / (1024**3),
|
643
|
+
"az_patterns": cross_az_patterns,
|
644
|
+
"availability_zones": azs,
|
645
|
+
}
|
646
|
+
|
647
|
+
def _calculate_cross_az_costs(
|
648
|
+
self, cross_az_analysis: Dict[str, Any], include_projections: bool = True
|
649
|
+
) -> Dict[str, Any]:
|
650
|
+
"""Calculate cross-AZ data transfer costs."""
|
651
|
+
total_gb = cross_az_analysis.get("total_cross_az_gb", 0)
|
652
|
+
cost_per_gb = self.DATA_TRANSFER_PRICING["cross_az"]
|
653
|
+
|
654
|
+
current_cost = total_gb * cost_per_gb
|
655
|
+
|
656
|
+
cost_analysis = {
|
657
|
+
"total_gb_analyzed": total_gb,
|
658
|
+
"cost_per_gb": cost_per_gb,
|
659
|
+
"current_period_cost": current_cost,
|
660
|
+
"analysis_period_hours": cross_az_analysis.get("analysis_period_hours", 24),
|
661
|
+
}
|
662
|
+
|
663
|
+
if include_projections:
|
664
|
+
hours_per_month = 24 * 30
|
665
|
+
hours_per_year = 24 * 365
|
666
|
+
|
667
|
+
scale_factor_monthly = hours_per_month / cost_analysis["analysis_period_hours"]
|
668
|
+
scale_factor_annual = hours_per_year / cost_analysis["analysis_period_hours"]
|
669
|
+
|
670
|
+
cost_analysis.update(
|
671
|
+
{
|
672
|
+
"projected_monthly_cost": current_cost * scale_factor_monthly,
|
673
|
+
"projected_annual_cost": current_cost * scale_factor_annual,
|
674
|
+
"projected_monthly_gb": total_gb * scale_factor_monthly,
|
675
|
+
"projected_annual_gb": total_gb * scale_factor_annual,
|
676
|
+
}
|
677
|
+
)
|
678
|
+
|
679
|
+
return cost_analysis
|
680
|
+
|
681
|
+
def _generate_traffic_optimization_recommendations(self, analysis_results: Dict[str, Any]) -> List[Dict[str, Any]]:
|
682
|
+
"""Generate McKinsey-style traffic optimization recommendations."""
|
683
|
+
recommendations = []
|
684
|
+
|
685
|
+
# Cost-based recommendations
|
686
|
+
total_cross_az_cost = analysis_results.get("cost_implications", {}).get("projected_monthly_cost", 0)
|
687
|
+
|
688
|
+
if total_cross_az_cost > 100:
|
689
|
+
recommendations.append(
|
690
|
+
{
|
691
|
+
"type": "COST_OPTIMIZATION",
|
692
|
+
"priority": "HIGH",
|
693
|
+
"title": "High Cross-AZ Data Transfer Costs",
|
694
|
+
"description": f"Monthly cross-AZ costs projected at ${total_cross_az_cost:.2f}",
|
695
|
+
"mckinsey_framework": "Cost Leadership Strategy",
|
696
|
+
"recommendations": [
|
697
|
+
"Implement VPC endpoints for AWS service access",
|
698
|
+
"Optimize application architecture to minimize cross-AZ calls",
|
699
|
+
"Consider NAT Gateway placement optimization",
|
700
|
+
"Implement data locality strategies",
|
701
|
+
],
|
702
|
+
"estimated_monthly_savings": total_cross_az_cost * 0.4, # 40% potential savings
|
703
|
+
"implementation_effort": "MEDIUM",
|
704
|
+
"roi_timeframe": "3-6 months",
|
705
|
+
}
|
706
|
+
)
|
707
|
+
|
708
|
+
# Security-based recommendations
|
709
|
+
total_rejected_connections = sum(
|
710
|
+
analysis.get("traffic_summary", {}).get("rejected_connections", 0)
|
711
|
+
for analysis in analysis_results.get("traffic_analysis", {}).values()
|
712
|
+
)
|
713
|
+
|
714
|
+
if total_rejected_connections > 100:
|
715
|
+
recommendations.append(
|
716
|
+
{
|
717
|
+
"type": "SECURITY_ENHANCEMENT",
|
718
|
+
"priority": "MEDIUM",
|
719
|
+
"title": "High Number of Rejected Connections",
|
720
|
+
"description": f"{total_rejected_connections} rejected connections detected",
|
721
|
+
"mckinsey_framework": "Risk Management",
|
722
|
+
"recommendations": [
|
723
|
+
"Review security group configurations",
|
724
|
+
"Implement network ACL optimization",
|
725
|
+
"Consider implementing AWS WAF for web applications",
|
726
|
+
"Enhance monitoring and alerting for security events",
|
727
|
+
],
|
728
|
+
"implementation_effort": "LOW",
|
729
|
+
"compliance_impact": "HIGH",
|
730
|
+
}
|
731
|
+
)
|
732
|
+
|
733
|
+
return recommendations
|
734
|
+
|
735
|
+
def _generate_cross_az_optimization_strategies(
|
736
|
+
self, vpc_id: str, cross_az_analysis: Dict[str, Any], cost_analysis: Dict[str, Any]
|
737
|
+
) -> Dict[str, Any]:
|
738
|
+
"""Generate cross-AZ optimization strategies."""
|
739
|
+
strategies = []
|
740
|
+
|
741
|
+
monthly_cost = cost_analysis.get("projected_monthly_cost", 0)
|
742
|
+
|
743
|
+
# Strategy 1: VPC Endpoints
|
744
|
+
if monthly_cost > 50:
|
745
|
+
vpc_endpoint_savings = min(monthly_cost * 0.3, 100) # Max $100 savings
|
746
|
+
strategies.append(
|
747
|
+
{
|
748
|
+
"strategy": "VPC_ENDPOINTS",
|
749
|
+
"title": "Implement VPC Endpoints for AWS Services",
|
750
|
+
"description": "Reduce NAT Gateway usage by implementing VPC endpoints",
|
751
|
+
"monthly_savings": vpc_endpoint_savings,
|
752
|
+
"implementation_cost": 50, # One-time setup cost
|
753
|
+
"ongoing_monthly_cost": 7.30, # Interface endpoint cost
|
754
|
+
"net_monthly_savings": vpc_endpoint_savings - 7.30,
|
755
|
+
"payback_period_months": 1,
|
756
|
+
"confidence_level": "HIGH",
|
757
|
+
}
|
758
|
+
)
|
759
|
+
|
760
|
+
# Strategy 2: Application Architecture Optimization
|
761
|
+
if len(cross_az_analysis.get("az_patterns", {})) > 4:
|
762
|
+
arch_savings = monthly_cost * 0.2
|
763
|
+
strategies.append(
|
764
|
+
{
|
765
|
+
"strategy": "ARCHITECTURE_OPTIMIZATION",
|
766
|
+
"title": "Optimize Application Data Locality",
|
767
|
+
"description": "Redesign applications to minimize cross-AZ communication",
|
768
|
+
"monthly_savings": arch_savings,
|
769
|
+
"implementation_cost": 500, # Development effort
|
770
|
+
"ongoing_monthly_cost": 0,
|
771
|
+
"net_monthly_savings": arch_savings,
|
772
|
+
"payback_period_months": 500 / arch_savings if arch_savings > 0 else None,
|
773
|
+
"confidence_level": "MEDIUM",
|
774
|
+
}
|
775
|
+
)
|
776
|
+
|
777
|
+
# Strategy 3: NAT Gateway Optimization
|
778
|
+
nat_savings = monthly_cost * 0.15
|
779
|
+
strategies.append(
|
780
|
+
{
|
781
|
+
"strategy": "NAT_GATEWAY_OPTIMIZATION",
|
782
|
+
"title": "Optimize NAT Gateway Placement",
|
783
|
+
"description": "Consolidate NAT Gateways and optimize placement",
|
784
|
+
"monthly_savings": nat_savings,
|
785
|
+
"implementation_cost": 100,
|
786
|
+
"ongoing_monthly_cost": 0,
|
787
|
+
"net_monthly_savings": nat_savings,
|
788
|
+
"payback_period_months": 100 / nat_savings if nat_savings > 0 else None,
|
789
|
+
"confidence_level": "HIGH",
|
790
|
+
}
|
791
|
+
)
|
792
|
+
|
793
|
+
total_potential_savings = sum(s["net_monthly_savings"] for s in strategies)
|
794
|
+
|
795
|
+
return {
|
796
|
+
"vpc_id": vpc_id,
|
797
|
+
"current_monthly_cost": monthly_cost,
|
798
|
+
"strategies": strategies,
|
799
|
+
"total_potential_monthly_savings": total_potential_savings,
|
800
|
+
"total_potential_annual_savings": total_potential_savings * 12,
|
801
|
+
"recommended_implementation_order": [
|
802
|
+
s["strategy"] for s in sorted(strategies, key=lambda x: x["net_monthly_savings"], reverse=True)
|
803
|
+
],
|
804
|
+
}
|
805
|
+
|
806
|
+
def _detect_flow_log_anomalies(self, analysis: Dict[str, Any], threshold: float) -> Dict[str, List[Dict[str, Any]]]:
|
807
|
+
"""Detect anomalies in flow log analysis."""
|
808
|
+
anomalies = {
|
809
|
+
"traffic_volume_anomalies": [],
|
810
|
+
"port_scan_attempts": [],
|
811
|
+
"unusual_protocols": [],
|
812
|
+
"suspicious_connections": [],
|
813
|
+
"data_exfiltration_indicators": [],
|
814
|
+
}
|
815
|
+
|
816
|
+
# Simulate anomaly detection
|
817
|
+
traffic_summary = analysis.get("traffic_summary", {})
|
818
|
+
rejected_connections = traffic_summary.get("rejected_connections", 0)
|
819
|
+
total_connections = traffic_summary.get("unique_connections", 0)
|
820
|
+
|
821
|
+
# High rejection rate anomaly
|
822
|
+
if total_connections > 0 and (rejected_connections / total_connections) > 0.2:
|
823
|
+
anomalies["suspicious_connections"].append(
|
824
|
+
{
|
825
|
+
"type": "HIGH_REJECTION_RATE",
|
826
|
+
"severity": "MEDIUM",
|
827
|
+
"description": f"High connection rejection rate: {rejected_connections}/{total_connections}",
|
828
|
+
"recommendation": "Review security group configurations and potential attack patterns",
|
829
|
+
}
|
830
|
+
)
|
831
|
+
|
832
|
+
# Port analysis anomalies
|
833
|
+
port_analysis = analysis.get("port_analysis", {})
|
834
|
+
top_ports = port_analysis.get("top_destination_ports", {})
|
835
|
+
|
836
|
+
# Check for unusual ports
|
837
|
+
unusual_ports = [port for port in top_ports.keys() if int(port) > 10000]
|
838
|
+
if len(unusual_ports) > 3:
|
839
|
+
anomalies["unusual_protocols"].append(
|
840
|
+
{
|
841
|
+
"type": "UNUSUAL_HIGH_PORTS",
|
842
|
+
"severity": "LOW",
|
843
|
+
"description": f"Unusual high port usage detected: {unusual_ports}",
|
844
|
+
"recommendation": "Verify application requirements for high port usage",
|
845
|
+
}
|
846
|
+
)
|
847
|
+
|
848
|
+
return anomalies
|
849
|
+
|
850
|
+
def _generate_security_recommendations(self, anomalies: Dict[str, List]) -> List[Dict[str, Any]]:
|
851
|
+
"""Generate security recommendations based on anomalies."""
|
852
|
+
recommendations = []
|
853
|
+
|
854
|
+
total_anomalies = sum(len(findings) for findings in anomalies.values())
|
855
|
+
|
856
|
+
if total_anomalies > 5:
|
857
|
+
recommendations.append(
|
858
|
+
{
|
859
|
+
"priority": "HIGH",
|
860
|
+
"category": "MONITORING",
|
861
|
+
"title": "Enhanced Security Monitoring Required",
|
862
|
+
"description": f"{total_anomalies} security anomalies detected",
|
863
|
+
"actions": [
|
864
|
+
"Implement CloudWatch alarms for traffic anomalies",
|
865
|
+
"Consider AWS GuardDuty for advanced threat detection",
|
866
|
+
"Review and tighten security group rules",
|
867
|
+
"Implement network segmentation strategies",
|
868
|
+
],
|
869
|
+
}
|
870
|
+
)
|
871
|
+
|
872
|
+
if len(anomalies.get("port_scan_attempts", [])) > 0:
|
873
|
+
recommendations.append(
|
874
|
+
{
|
875
|
+
"priority": "MEDIUM",
|
876
|
+
"category": "THREAT_RESPONSE",
|
877
|
+
"title": "Potential Port Scanning Activity",
|
878
|
+
"description": "Port scanning attempts detected",
|
879
|
+
"actions": [
|
880
|
+
"Block suspicious source IPs",
|
881
|
+
"Implement rate limiting",
|
882
|
+
"Enable VPC Flow Log alerts",
|
883
|
+
"Consider implementing AWS WAF",
|
884
|
+
],
|
885
|
+
}
|
886
|
+
)
|
887
|
+
|
888
|
+
return recommendations
|
889
|
+
|
890
|
+
def _calculate_security_risk_score(self, anomalies: Dict[str, List]) -> float:
|
891
|
+
"""Calculate security risk score based on anomalies (0-10 scale)."""
|
892
|
+
weights = {
|
893
|
+
"traffic_volume_anomalies": 2.0,
|
894
|
+
"port_scan_attempts": 3.0,
|
895
|
+
"unusual_protocols": 1.5,
|
896
|
+
"suspicious_connections": 2.5,
|
897
|
+
"data_exfiltration_indicators": 4.0,
|
898
|
+
}
|
899
|
+
|
900
|
+
risk_score = 0.0
|
901
|
+
max_score = 10.0
|
902
|
+
|
903
|
+
for anomaly_type, findings in anomalies.items():
|
904
|
+
weight = weights.get(anomaly_type, 1.0)
|
905
|
+
risk_score += len(findings) * weight * 0.1 # Scale factor
|
906
|
+
|
907
|
+
return min(risk_score, max_score)
|
908
|
+
|
909
|
+
def _display_traffic_analysis_results(self, results: Dict[str, Any]) -> None:
|
910
|
+
"""Display comprehensive traffic analysis results."""
|
911
|
+
# Summary panel
|
912
|
+
total_gb = results.get("total_bytes_analyzed", 0) / (1024**3)
|
913
|
+
cross_az_gb = results.get("total_cross_az_bytes", 0) / (1024**3)
|
914
|
+
monthly_cost = results.get("cost_implications", {}).get("projected_monthly_cost", 0)
|
915
|
+
|
916
|
+
summary_text = f"""
|
917
|
+
[bold]VPC Flow Logs Analysis Summary[/bold]
|
918
|
+
|
919
|
+
Total Traffic Analyzed: {total_gb:.2f} GB
|
920
|
+
Cross-AZ Traffic: {cross_az_gb:.2f} GB
|
921
|
+
Projected Monthly Cross-AZ Cost: ${monthly_cost:.2f}
|
922
|
+
|
923
|
+
Flow Logs Analyzed: {results["flow_logs_analyzed"]}
|
924
|
+
Analysis Period: {results["time_range_hours"]} hours
|
925
|
+
Optimization Opportunities: {len(results.get("optimization_recommendations", []))}
|
926
|
+
"""
|
927
|
+
|
928
|
+
panel = create_panel(summary_text, title="Traffic Analysis Summary", border_style="cyan")
|
929
|
+
console.print(panel)
|
930
|
+
|
931
|
+
# Recommendations table
|
932
|
+
recommendations = results.get("optimization_recommendations", [])
|
933
|
+
if recommendations:
|
934
|
+
rec_table = create_table(
|
935
|
+
title="Optimization Recommendations",
|
936
|
+
columns=[
|
937
|
+
{"name": "Type", "style": "cyan", "justify": "left"},
|
938
|
+
{"name": "Priority", "style": "yellow", "justify": "center"},
|
939
|
+
{"name": "Monthly Savings", "style": "green", "justify": "right"},
|
940
|
+
{"name": "Effort", "style": "blue", "justify": "center"},
|
941
|
+
],
|
942
|
+
)
|
943
|
+
|
944
|
+
for rec in recommendations:
|
945
|
+
rec_table.add_row(
|
946
|
+
rec.get("type", "Unknown"),
|
947
|
+
rec.get("priority", "Unknown"),
|
948
|
+
f"${rec.get('estimated_monthly_savings', 0):.2f}",
|
949
|
+
rec.get("implementation_effort", "Unknown"),
|
950
|
+
)
|
951
|
+
|
952
|
+
console.print(rec_table)
|
953
|
+
|
954
|
+
def _display_cross_az_cost_analysis(self, results: Dict[str, Any]) -> None:
|
955
|
+
"""Display cross-AZ cost analysis results."""
|
956
|
+
cost_analysis = results.get("cost_analysis", {})
|
957
|
+
|
958
|
+
cost_text = f"""
|
959
|
+
[bold]Cross-AZ Cost Analysis - VPC {results["vpc_id"]}[/bold]
|
960
|
+
|
961
|
+
Current Period: {cost_analysis.get("current_period_cost", 0):.2f} USD
|
962
|
+
Projected Monthly: ${cost_analysis.get("projected_monthly_cost", 0):.2f}
|
963
|
+
Projected Annual: ${cost_analysis.get("projected_annual_cost", 0):.2f}
|
964
|
+
|
965
|
+
Data Transfer: {cost_analysis.get("total_gb_analyzed", 0):.2f} GB analyzed
|
966
|
+
Rate: ${cost_analysis.get("cost_per_gb", 0):.3f} per GB
|
967
|
+
"""
|
968
|
+
|
969
|
+
panel = create_panel(cost_text, title="Cross-AZ Cost Analysis", border_style="red")
|
970
|
+
console.print(panel)
|
971
|
+
|
972
|
+
# Optimization strategies
|
973
|
+
strategies = results.get("optimization_strategies", {}).get("strategies", [])
|
974
|
+
if strategies:
|
975
|
+
strategy_table = create_table(
|
976
|
+
title="Optimization Strategies",
|
977
|
+
columns=[
|
978
|
+
{"name": "Strategy", "style": "cyan", "justify": "left"},
|
979
|
+
{"name": "Monthly Savings", "style": "green", "justify": "right"},
|
980
|
+
{"name": "Setup Cost", "style": "red", "justify": "right"},
|
981
|
+
{"name": "Payback (months)", "style": "yellow", "justify": "center"},
|
982
|
+
],
|
983
|
+
)
|
984
|
+
|
985
|
+
for strategy in strategies:
|
986
|
+
payback = strategy.get("payback_period_months")
|
987
|
+
payback_str = f"{payback:.1f}" if payback and payback < 100 else "N/A"
|
988
|
+
|
989
|
+
strategy_table.add_row(
|
990
|
+
strategy.get("title", "Unknown")[:30],
|
991
|
+
f"${strategy.get('net_monthly_savings', 0):.2f}",
|
992
|
+
f"${strategy.get('implementation_cost', 0):.2f}",
|
993
|
+
payback_str,
|
994
|
+
)
|
995
|
+
|
996
|
+
console.print(strategy_table)
|
997
|
+
|
998
|
+
def _display_security_analysis(self, results: Dict[str, Any]) -> None:
|
999
|
+
"""Display security anomaly analysis results."""
|
1000
|
+
anomalies = results.get("anomalies", {})
|
1001
|
+
risk_score = results.get("risk_score", 0)
|
1002
|
+
|
1003
|
+
# Risk score color coding
|
1004
|
+
if risk_score < 3:
|
1005
|
+
risk_color = "green"
|
1006
|
+
elif risk_score < 7:
|
1007
|
+
risk_color = "yellow"
|
1008
|
+
else:
|
1009
|
+
risk_color = "red"
|
1010
|
+
|
1011
|
+
security_text = f"""
|
1012
|
+
[bold]Security Anomaly Analysis[/bold]
|
1013
|
+
|
1014
|
+
Risk Score: [{risk_color}]{risk_score:.1f}/10[/{risk_color}]
|
1015
|
+
Analysis Period: {results["analysis_scope"]["time_range_hours"]} hours
|
1016
|
+
|
1017
|
+
Anomaly Counts:
|
1018
|
+
• Traffic Volume Anomalies: {len(anomalies.get("traffic_volume_anomalies", []))}
|
1019
|
+
• Port Scan Attempts: {len(anomalies.get("port_scan_attempts", []))}
|
1020
|
+
• Unusual Protocols: {len(anomalies.get("unusual_protocols", []))}
|
1021
|
+
• Suspicious Connections: {len(anomalies.get("suspicious_connections", []))}
|
1022
|
+
• Data Exfiltration Indicators: {len(anomalies.get("data_exfiltration_indicators", []))}
|
1023
|
+
"""
|
1024
|
+
|
1025
|
+
panel = create_panel(security_text, title="Security Analysis", border_style=risk_color)
|
1026
|
+
console.print(panel)
|
1027
|
+
|
1028
|
+
|
1029
|
+
# Export the analyzer class
|
1030
|
+
__all__ = ["VPCFlowAnalyzer"]
|