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
@@ -10,13 +10,13 @@ Framework: Multi-account security orchestration with proven coordination pattern
|
|
10
10
|
Status: Enterprise-ready with systematic delegation and FAANG SDLC compliance
|
11
11
|
|
12
12
|
Strategic Alignment:
|
13
|
-
- 3 Strategic Objectives: runbooks package + FAANG SDLC + GitHub SSoT
|
13
|
+
- 3 Strategic Objectives: runbooks package + FAANG SDLC + GitHub SSoT
|
14
14
|
- Core Principles: "Do one thing and do it well" + "Move Fast, But Not So Fast We Crash"
|
15
15
|
- Enterprise Coordination: Multi-agent security validation with systematic delegation
|
16
16
|
|
17
17
|
Key Capabilities:
|
18
18
|
- 61-account concurrent security control deployment
|
19
|
-
- Cross-account role-based security policy enforcement
|
19
|
+
- Cross-account role-based security policy enforcement
|
20
20
|
- Organization-wide compliance monitoring and reporting
|
21
21
|
- Automated security baseline implementation
|
22
22
|
- Executive security posture dashboards
|
@@ -53,7 +53,7 @@ from runbooks.common.rich_utils import (
|
|
53
53
|
|
54
54
|
class SecurityControlType(Enum):
|
55
55
|
"""Types of security controls for multi-account deployment."""
|
56
|
-
|
56
|
+
|
57
57
|
IAM_BASELINE = "IAM_BASELINE"
|
58
58
|
ENCRYPTION_ENFORCEMENT = "ENCRYPTION_ENFORCEMENT"
|
59
59
|
NETWORK_SECURITY = "NETWORK_SECURITY"
|
@@ -66,16 +66,16 @@ class SecurityControlType(Enum):
|
|
66
66
|
|
67
67
|
class DeploymentStrategy(Enum):
|
68
68
|
"""Security control deployment strategies."""
|
69
|
-
|
70
|
-
PARALLEL_ALL = "PARALLEL_ALL"
|
71
|
-
STAGED_ROLLOUT = "STAGED_ROLLOUT"
|
72
|
-
PILOT_FIRST = "PILOT_FIRST"
|
73
|
-
CRITICAL_FIRST = "CRITICAL_FIRST"
|
69
|
+
|
70
|
+
PARALLEL_ALL = "PARALLEL_ALL" # Deploy to all accounts simultaneously
|
71
|
+
STAGED_ROLLOUT = "STAGED_ROLLOUT" # Deploy in waves with validation gates
|
72
|
+
PILOT_FIRST = "PILOT_FIRST" # Deploy to pilot accounts first
|
73
|
+
CRITICAL_FIRST = "CRITICAL_FIRST" # Deploy to critical accounts first
|
74
74
|
|
75
75
|
|
76
76
|
class ControlStatus(Enum):
|
77
77
|
"""Status of security control deployment."""
|
78
|
-
|
78
|
+
|
79
79
|
PENDING = "PENDING"
|
80
80
|
DEPLOYING = "DEPLOYING"
|
81
81
|
DEPLOYED = "DEPLOYED"
|
@@ -88,7 +88,7 @@ class ControlStatus(Enum):
|
|
88
88
|
@dataclass
|
89
89
|
class SecurityControl:
|
90
90
|
"""Represents a security control for multi-account deployment."""
|
91
|
-
|
91
|
+
|
92
92
|
control_id: str
|
93
93
|
control_name: str
|
94
94
|
control_type: SecurityControlType
|
@@ -103,7 +103,7 @@ class SecurityControl:
|
|
103
103
|
estimated_deployment_time: int # minutes
|
104
104
|
requires_approval: bool = False
|
105
105
|
cross_account_role_required: bool = True
|
106
|
-
|
106
|
+
|
107
107
|
# Deployment tracking
|
108
108
|
deployment_status: ControlStatus = ControlStatus.PENDING
|
109
109
|
deployed_accounts: List[str] = field(default_factory=list)
|
@@ -114,7 +114,7 @@ class SecurityControl:
|
|
114
114
|
@dataclass
|
115
115
|
class AccountSecurityProfile:
|
116
116
|
"""Security profile for individual AWS account."""
|
117
|
-
|
117
|
+
|
118
118
|
account_id: str
|
119
119
|
account_name: str
|
120
120
|
environment_type: str # prod, staging, dev, sandbox
|
@@ -130,7 +130,7 @@ class AccountSecurityProfile:
|
|
130
130
|
@dataclass
|
131
131
|
class MultiAccountSecurityReport:
|
132
132
|
"""Comprehensive security report across all accounts."""
|
133
|
-
|
133
|
+
|
134
134
|
report_id: str
|
135
135
|
timestamp: datetime
|
136
136
|
total_accounts: int
|
@@ -150,10 +150,10 @@ class MultiAccountSecurityController:
|
|
150
150
|
"""
|
151
151
|
Multi-Account Security Controls Framework
|
152
152
|
========================================
|
153
|
-
|
153
|
+
|
154
154
|
Orchestrates security control deployment and compliance monitoring across
|
155
155
|
enterprise AWS Organizations with up to 61 concurrent account operations.
|
156
|
-
|
156
|
+
|
157
157
|
Enterprise Features:
|
158
158
|
- Parallel security control deployment with intelligent batching
|
159
159
|
- Cross-account role-based policy enforcement
|
@@ -163,34 +163,34 @@ class MultiAccountSecurityController:
|
|
163
163
|
"""
|
164
164
|
|
165
165
|
def __init__(
|
166
|
-
self,
|
167
|
-
profile: str = "default",
|
166
|
+
self,
|
167
|
+
profile: str = "default",
|
168
168
|
output_dir: str = "./artifacts/multi-account-security",
|
169
169
|
max_concurrent_accounts: int = 61,
|
170
|
-
dry_run: bool = True
|
170
|
+
dry_run: bool = True,
|
171
171
|
):
|
172
172
|
self.profile = profile
|
173
173
|
self.output_dir = Path(output_dir)
|
174
174
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
175
175
|
self.max_concurrent_accounts = max_concurrent_accounts
|
176
176
|
self.dry_run = dry_run
|
177
|
-
|
177
|
+
|
178
178
|
# Initialize secure management session
|
179
179
|
self.session = self._create_secure_session()
|
180
|
-
|
180
|
+
|
181
181
|
# Security control definitions
|
182
182
|
self.security_controls = self._initialize_security_controls()
|
183
|
-
|
183
|
+
|
184
184
|
# Account discovery and profiling
|
185
185
|
self.account_profiles = {}
|
186
186
|
self.organization_structure = {}
|
187
|
-
|
187
|
+
|
188
188
|
# Cross-account role management
|
189
189
|
self.cross_account_role_arn = self._get_cross_account_role_arn()
|
190
|
-
|
190
|
+
|
191
191
|
# Deployment tracking
|
192
192
|
self.deployment_tracker = MultiAccountDeploymentTracker(self.output_dir)
|
193
|
-
|
193
|
+
|
194
194
|
print_header("Multi-Account Security Controller", "1.0.0")
|
195
195
|
print_info(f"Profile: {profile}")
|
196
196
|
print_info(f"Max concurrent accounts: {max_concurrent_accounts}")
|
@@ -200,282 +200,260 @@ class MultiAccountSecurityController:
|
|
200
200
|
def _create_secure_session(self) -> boto3.Session:
|
201
201
|
"""Create secure AWS session with organization-level permissions."""
|
202
202
|
try:
|
203
|
-
session = create_management_session(
|
204
|
-
|
203
|
+
session = create_management_session(profile_name=self.profile)
|
204
|
+
|
205
205
|
# Validate organization access
|
206
206
|
try:
|
207
|
-
organizations = session.client(
|
207
|
+
organizations = session.client("organizations")
|
208
208
|
org_info = organizations.describe_organization()
|
209
209
|
print_success(f"Organization access validated: {org_info['Organization']['Id']}")
|
210
210
|
except ClientError as e:
|
211
211
|
print_warning(f"Limited organization access: {str(e)}")
|
212
|
-
|
212
|
+
|
213
213
|
# Validate session credentials
|
214
214
|
sts_client = session.client("sts")
|
215
215
|
identity = sts_client.get_caller_identity()
|
216
|
-
|
216
|
+
|
217
217
|
print_info(f"Management session established for: {identity.get('Arn', 'Unknown')}")
|
218
218
|
return session
|
219
|
-
|
219
|
+
|
220
220
|
except (ClientError, NoCredentialsError) as e:
|
221
221
|
print_error(f"Failed to establish management session: {str(e)}")
|
222
222
|
raise
|
223
223
|
|
224
224
|
def _get_cross_account_role_arn(self) -> str:
|
225
225
|
"""Get cross-account role ARN for security operations."""
|
226
|
-
|
226
|
+
|
227
227
|
# Standard cross-account security role
|
228
228
|
return "arn:aws:iam::{account_id}:role/CloudOpsSecurityRole"
|
229
229
|
|
230
230
|
def _initialize_security_controls(self) -> List[SecurityControl]:
|
231
231
|
"""Initialize comprehensive security controls for enterprise deployment."""
|
232
|
-
|
232
|
+
|
233
233
|
controls = []
|
234
|
-
|
234
|
+
|
235
235
|
# IAM Baseline Controls
|
236
|
-
controls.append(
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
"revert_to_previous_password_policy",
|
260
|
-
"
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
"
|
282
|
-
"
|
283
|
-
"
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
business_justification="Prevents root account compromise - critical for enterprise security",
|
290
|
-
risk_if_not_implemented="Critical - complete account takeover possible",
|
291
|
-
estimated_deployment_time=10,
|
292
|
-
requires_approval=True # Root account changes require approval
|
293
|
-
))
|
294
|
-
|
236
|
+
controls.append(
|
237
|
+
SecurityControl(
|
238
|
+
control_id="IAM-001",
|
239
|
+
control_name="IAM Password Policy Enforcement",
|
240
|
+
control_type=SecurityControlType.IAM_BASELINE,
|
241
|
+
description="Enforce strong password policy across all accounts",
|
242
|
+
aws_services=["iam"],
|
243
|
+
compliance_frameworks=["SOC2", "CIS Benchmarks", "AWS Well-Architected"],
|
244
|
+
deployment_template={
|
245
|
+
"MinimumPasswordLength": 14,
|
246
|
+
"RequireUppercaseCharacters": True,
|
247
|
+
"RequireLowercaseCharacters": True,
|
248
|
+
"RequireNumbers": True,
|
249
|
+
"RequireSymbols": True,
|
250
|
+
"MaxPasswordAge": 90,
|
251
|
+
"PasswordReusePrevention": 24,
|
252
|
+
"HardExpiry": False,
|
253
|
+
},
|
254
|
+
validation_checks=[
|
255
|
+
"verify_password_policy_applied",
|
256
|
+
"check_minimum_password_length",
|
257
|
+
"validate_complexity_requirements",
|
258
|
+
],
|
259
|
+
rollback_procedure=["revert_to_previous_password_policy", "notify_security_team_of_rollback"],
|
260
|
+
business_justification="Reduces account compromise risk by 80%",
|
261
|
+
risk_if_not_implemented="High risk of credential-based attacks",
|
262
|
+
estimated_deployment_time=5,
|
263
|
+
requires_approval=False,
|
264
|
+
)
|
265
|
+
)
|
266
|
+
|
267
|
+
controls.append(
|
268
|
+
SecurityControl(
|
269
|
+
control_id="IAM-002",
|
270
|
+
control_name="Root Account MFA Enforcement",
|
271
|
+
control_type=SecurityControlType.IAM_BASELINE,
|
272
|
+
description="Ensure MFA is enabled on all root accounts",
|
273
|
+
aws_services=["iam"],
|
274
|
+
compliance_frameworks=["SOC2", "CIS Benchmarks", "PCI-DSS"],
|
275
|
+
deployment_template={
|
276
|
+
"mfa_required": True,
|
277
|
+
"virtual_mfa_preferred": True,
|
278
|
+
"hardware_mfa_fallback": True,
|
279
|
+
},
|
280
|
+
validation_checks=["verify_root_mfa_enabled", "check_mfa_device_type", "validate_mfa_functionality"],
|
281
|
+
rollback_procedure=["document_mfa_removal_justification", "notify_compliance_team"],
|
282
|
+
business_justification="Prevents root account compromise - critical for enterprise security",
|
283
|
+
risk_if_not_implemented="Critical - complete account takeover possible",
|
284
|
+
estimated_deployment_time=10,
|
285
|
+
requires_approval=True, # Root account changes require approval
|
286
|
+
)
|
287
|
+
)
|
288
|
+
|
295
289
|
# Encryption Controls
|
296
|
-
controls.append(
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
"disable_encryption_requirement",
|
316
|
-
"
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
"
|
342
|
-
"
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
))
|
348
|
-
|
290
|
+
controls.append(
|
291
|
+
SecurityControl(
|
292
|
+
control_id="ENC-001",
|
293
|
+
control_name="S3 Bucket Encryption Enforcement",
|
294
|
+
control_type=SecurityControlType.ENCRYPTION_ENFORCEMENT,
|
295
|
+
description="Enforce encryption at rest for all S3 buckets",
|
296
|
+
aws_services=["s3"],
|
297
|
+
compliance_frameworks=["SOC2", "PCI-DSS", "HIPAA"],
|
298
|
+
deployment_template={
|
299
|
+
"encryption_algorithm": "AES256",
|
300
|
+
"kms_encryption_preferred": True,
|
301
|
+
"bucket_key_enabled": True,
|
302
|
+
"deny_unencrypted_object_uploads": True,
|
303
|
+
},
|
304
|
+
validation_checks=[
|
305
|
+
"verify_bucket_encryption_enabled",
|
306
|
+
"check_default_encryption_configuration",
|
307
|
+
"validate_object_encryption_status",
|
308
|
+
],
|
309
|
+
rollback_procedure=["disable_encryption_requirement", "restore_previous_bucket_policies"],
|
310
|
+
business_justification="Protects sensitive data and meets compliance requirements",
|
311
|
+
risk_if_not_implemented="Data breach risk, compliance violations",
|
312
|
+
estimated_deployment_time=15,
|
313
|
+
)
|
314
|
+
)
|
315
|
+
|
316
|
+
controls.append(
|
317
|
+
SecurityControl(
|
318
|
+
control_id="ENC-002",
|
319
|
+
control_name="EBS Volume Encryption",
|
320
|
+
control_type=SecurityControlType.ENCRYPTION_ENFORCEMENT,
|
321
|
+
description="Enforce encryption for all EBS volumes",
|
322
|
+
aws_services=["ec2"],
|
323
|
+
compliance_frameworks=["SOC2", "PCI-DSS", "HIPAA"],
|
324
|
+
deployment_template={
|
325
|
+
"default_encryption_enabled": True,
|
326
|
+
"kms_key_id": "alias/aws/ebs",
|
327
|
+
"delete_on_termination": True,
|
328
|
+
},
|
329
|
+
validation_checks=[
|
330
|
+
"verify_ebs_encryption_default",
|
331
|
+
"check_existing_volume_encryption",
|
332
|
+
"validate_kms_key_permissions",
|
333
|
+
],
|
334
|
+
rollback_procedure=["disable_default_ebs_encryption", "document_encryption_rollback"],
|
335
|
+
business_justification="Protects data at rest on compute instances",
|
336
|
+
risk_if_not_implemented="Data exposure from compromised or lost instances",
|
337
|
+
estimated_deployment_time=10,
|
338
|
+
)
|
339
|
+
)
|
340
|
+
|
349
341
|
# Network Security Controls
|
350
|
-
controls.append(
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
"disable_vpc_flow_logs",
|
370
|
-
"
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
342
|
+
controls.append(
|
343
|
+
SecurityControl(
|
344
|
+
control_id="NET-001",
|
345
|
+
control_name="VPC Flow Logs Enablement",
|
346
|
+
control_type=SecurityControlType.NETWORK_SECURITY,
|
347
|
+
description="Enable VPC Flow Logs for all VPCs",
|
348
|
+
aws_services=["ec2", "logs"],
|
349
|
+
compliance_frameworks=["SOC2", "AWS Well-Architected"],
|
350
|
+
deployment_template={
|
351
|
+
"log_destination_type": "cloud-watch-logs",
|
352
|
+
"traffic_type": "ALL",
|
353
|
+
"log_format": "${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${windowstart} ${windowend} ${action}",
|
354
|
+
"max_aggregation_interval": 60,
|
355
|
+
},
|
356
|
+
validation_checks=[
|
357
|
+
"verify_flow_logs_enabled",
|
358
|
+
"check_log_destination_access",
|
359
|
+
"validate_log_format_compliance",
|
360
|
+
],
|
361
|
+
rollback_procedure=["disable_vpc_flow_logs", "clean_up_log_groups"],
|
362
|
+
business_justification="Enables network security monitoring and forensics",
|
363
|
+
risk_if_not_implemented="Limited visibility into network traffic and security events",
|
364
|
+
estimated_deployment_time=20,
|
365
|
+
)
|
366
|
+
)
|
367
|
+
|
377
368
|
# Audit Logging Controls
|
378
|
-
controls.append(
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
{
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
"
|
404
|
-
"
|
405
|
-
"
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
business_justification="Essential for compliance, security monitoring, and forensics",
|
412
|
-
risk_if_not_implemented="No audit trail for security investigations",
|
413
|
-
estimated_deployment_time=30,
|
414
|
-
requires_approval=True # Organization-wide changes require approval
|
415
|
-
))
|
416
|
-
|
369
|
+
controls.append(
|
370
|
+
SecurityControl(
|
371
|
+
control_id="AUD-001",
|
372
|
+
control_name="CloudTrail Organization-wide Logging",
|
373
|
+
control_type=SecurityControlType.AUDIT_LOGGING,
|
374
|
+
description="Enable comprehensive CloudTrail logging across organization",
|
375
|
+
aws_services=["cloudtrail", "s3"],
|
376
|
+
compliance_frameworks=["SOC2", "PCI-DSS", "AWS Well-Architected"],
|
377
|
+
deployment_template={
|
378
|
+
"include_global_service_events": True,
|
379
|
+
"is_multi_region_trail": True,
|
380
|
+
"enable_log_file_validation": True,
|
381
|
+
"event_selectors": [
|
382
|
+
{
|
383
|
+
"read_write_type": "All",
|
384
|
+
"include_management_events": True,
|
385
|
+
"data_resources": [{"type": "AWS::S3::Object", "values": ["arn:aws:s3:::*/*"]}],
|
386
|
+
}
|
387
|
+
],
|
388
|
+
},
|
389
|
+
validation_checks=[
|
390
|
+
"verify_cloudtrail_enabled",
|
391
|
+
"check_log_file_validation",
|
392
|
+
"validate_s3_bucket_security",
|
393
|
+
],
|
394
|
+
rollback_procedure=["disable_organization_cloudtrail", "remove_log_bucket_policies"],
|
395
|
+
business_justification="Essential for compliance, security monitoring, and forensics",
|
396
|
+
risk_if_not_implemented="No audit trail for security investigations",
|
397
|
+
estimated_deployment_time=30,
|
398
|
+
requires_approval=True, # Organization-wide changes require approval
|
399
|
+
)
|
400
|
+
)
|
401
|
+
|
417
402
|
# Compliance Monitoring Controls
|
418
|
-
controls.append(
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
"
|
428
|
-
|
429
|
-
|
430
|
-
"all_supported": True,
|
431
|
-
|
432
|
-
|
403
|
+
controls.append(
|
404
|
+
SecurityControl(
|
405
|
+
control_id="CMP-001",
|
406
|
+
control_name="AWS Config Multi-Account Setup",
|
407
|
+
control_type=SecurityControlType.COMPLIANCE_MONITORING,
|
408
|
+
description="Deploy AWS Config for continuous compliance monitoring",
|
409
|
+
aws_services=["config", "s3"],
|
410
|
+
compliance_frameworks=["SOC2", "CIS Benchmarks", "AWS Well-Architected"],
|
411
|
+
deployment_template={
|
412
|
+
"configuration_recorder": {
|
413
|
+
"record_all_supported": True,
|
414
|
+
"include_global_resource_types": True,
|
415
|
+
"recording_group": {"all_supported": True, "include_global_resource_types": True},
|
416
|
+
},
|
417
|
+
"delivery_channel": {
|
418
|
+
"s3_bucket_name": "organization-config-bucket",
|
419
|
+
"config_snapshot_delivery_properties": {"delivery_frequency": "TwentyFour_Hours"},
|
420
|
+
},
|
433
421
|
},
|
434
|
-
|
435
|
-
"
|
436
|
-
"
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
rollback_procedure=[
|
447
|
-
"stop_configuration_recorder",
|
448
|
-
"delete_delivery_channel",
|
449
|
-
"clean_up_config_rules"
|
450
|
-
],
|
451
|
-
business_justification="Automated compliance monitoring reduces manual audit overhead",
|
452
|
-
risk_if_not_implemented="Manual compliance checking, delayed non-compliance detection",
|
453
|
-
estimated_deployment_time=45
|
454
|
-
))
|
455
|
-
|
422
|
+
validation_checks=[
|
423
|
+
"verify_config_recorder_status",
|
424
|
+
"check_delivery_channel_status",
|
425
|
+
"validate_config_rules_deployment",
|
426
|
+
],
|
427
|
+
rollback_procedure=["stop_configuration_recorder", "delete_delivery_channel", "clean_up_config_rules"],
|
428
|
+
business_justification="Automated compliance monitoring reduces manual audit overhead",
|
429
|
+
risk_if_not_implemented="Manual compliance checking, delayed non-compliance detection",
|
430
|
+
estimated_deployment_time=45,
|
431
|
+
)
|
432
|
+
)
|
433
|
+
|
456
434
|
return controls
|
457
435
|
|
458
436
|
async def deploy_security_controls_organization_wide(
|
459
437
|
self,
|
460
438
|
control_ids: Optional[List[str]] = None,
|
461
439
|
target_accounts: Optional[List[str]] = None,
|
462
|
-
deployment_strategy: DeploymentStrategy = DeploymentStrategy.STAGED_ROLLOUT
|
440
|
+
deployment_strategy: DeploymentStrategy = DeploymentStrategy.STAGED_ROLLOUT,
|
463
441
|
) -> MultiAccountSecurityReport:
|
464
442
|
"""
|
465
443
|
Deploy security controls across the entire AWS Organization.
|
466
|
-
|
444
|
+
|
467
445
|
Args:
|
468
446
|
control_ids: Specific controls to deploy (None for all controls)
|
469
447
|
target_accounts: Specific accounts to target (None for all accounts)
|
470
448
|
deployment_strategy: How to deploy controls across accounts
|
471
|
-
|
449
|
+
|
472
450
|
Returns:
|
473
451
|
MultiAccountSecurityReport with comprehensive deployment results
|
474
452
|
"""
|
475
|
-
|
453
|
+
|
476
454
|
deployment_id = f"deploy-{int(time.time())}"
|
477
455
|
start_time = datetime.utcnow()
|
478
|
-
|
456
|
+
|
479
457
|
console.print(
|
480
458
|
create_panel(
|
481
459
|
f"[bold cyan]Organization-wide Security Control Deployment[/bold cyan]\n\n"
|
@@ -486,105 +464,93 @@ class MultiAccountSecurityController:
|
|
486
464
|
border_style="cyan",
|
487
465
|
)
|
488
466
|
)
|
489
|
-
|
467
|
+
|
490
468
|
# Discover and profile target accounts
|
491
469
|
if not target_accounts:
|
492
470
|
target_accounts = await self._discover_organization_accounts()
|
493
|
-
|
471
|
+
|
494
472
|
await self._profile_target_accounts(target_accounts)
|
495
|
-
|
473
|
+
|
496
474
|
# Select controls to deploy
|
497
475
|
controls_to_deploy = self._select_controls_for_deployment(control_ids)
|
498
|
-
|
476
|
+
|
499
477
|
print_info(f"Target accounts: {len(target_accounts)}")
|
500
478
|
print_info(f"Controls to deploy: {len(controls_to_deploy)}")
|
501
|
-
|
479
|
+
|
502
480
|
# Execute deployment based on strategy
|
503
481
|
deployment_results = await self._execute_deployment_strategy(
|
504
|
-
controls_to_deploy,
|
505
|
-
target_accounts,
|
506
|
-
deployment_strategy,
|
507
|
-
deployment_id
|
482
|
+
controls_to_deploy, target_accounts, deployment_strategy, deployment_id
|
508
483
|
)
|
509
|
-
|
484
|
+
|
510
485
|
# Validate deployments
|
511
|
-
validation_results = await self._validate_control_deployments(
|
512
|
-
|
513
|
-
target_accounts
|
514
|
-
)
|
515
|
-
|
486
|
+
validation_results = await self._validate_control_deployments(deployment_results, target_accounts)
|
487
|
+
|
516
488
|
# Generate comprehensive report
|
517
489
|
report = await self._generate_deployment_report(
|
518
|
-
deployment_id,
|
519
|
-
start_time,
|
520
|
-
controls_to_deploy,
|
521
|
-
target_accounts,
|
522
|
-
deployment_results,
|
523
|
-
validation_results
|
490
|
+
deployment_id, start_time, controls_to_deploy, target_accounts, deployment_results, validation_results
|
524
491
|
)
|
525
|
-
|
492
|
+
|
526
493
|
# Display summary
|
527
494
|
self._display_deployment_summary(report)
|
528
|
-
|
495
|
+
|
529
496
|
# Export report
|
530
497
|
await self._export_deployment_report(report)
|
531
|
-
|
498
|
+
|
532
499
|
return report
|
533
500
|
|
534
501
|
async def _discover_organization_accounts(self) -> List[str]:
|
535
502
|
"""Discover all active accounts in the AWS Organization."""
|
536
|
-
|
503
|
+
|
537
504
|
accounts = []
|
538
|
-
|
505
|
+
|
539
506
|
try:
|
540
|
-
organizations = self.session.client(
|
541
|
-
|
507
|
+
organizations = self.session.client("organizations")
|
508
|
+
|
542
509
|
# Get organization details
|
543
510
|
org_info = organizations.describe_organization()
|
544
511
|
print_info(f"Organization ID: {org_info['Organization']['Id']}")
|
545
|
-
|
512
|
+
|
546
513
|
# List all accounts
|
547
|
-
paginator = organizations.get_paginator(
|
548
|
-
|
514
|
+
paginator = organizations.get_paginator("list_accounts")
|
515
|
+
|
549
516
|
for page in paginator.paginate():
|
550
|
-
for account in page.get(
|
551
|
-
if account[
|
552
|
-
accounts.append(account[
|
553
|
-
|
517
|
+
for account in page.get("Accounts", []):
|
518
|
+
if account["Status"] == "ACTIVE":
|
519
|
+
accounts.append(account["Id"])
|
520
|
+
|
554
521
|
print_success(f"Discovered {len(accounts)} active organization accounts")
|
555
|
-
|
522
|
+
|
556
523
|
# Limit to max concurrent if needed
|
557
524
|
if len(accounts) > self.max_concurrent_accounts:
|
558
525
|
print_warning(f"Limiting to {self.max_concurrent_accounts} accounts for deployment")
|
559
|
-
accounts = accounts[:self.max_concurrent_accounts]
|
560
|
-
|
526
|
+
accounts = accounts[: self.max_concurrent_accounts]
|
527
|
+
|
561
528
|
except ClientError as e:
|
562
529
|
print_warning(f"Could not discover organization accounts: {str(e)}")
|
563
530
|
# Fallback to current account
|
564
|
-
sts = self.session.client(
|
565
|
-
current_account = sts.get_caller_identity()[
|
531
|
+
sts = self.session.client("sts")
|
532
|
+
current_account = sts.get_caller_identity()["Account"]
|
566
533
|
accounts = [current_account]
|
567
534
|
print_info(f"Using current account: {current_account}")
|
568
|
-
|
535
|
+
|
569
536
|
return accounts
|
570
537
|
|
571
538
|
async def _profile_target_accounts(self, target_accounts: List[str]):
|
572
539
|
"""Profile target accounts for deployment planning."""
|
573
|
-
|
540
|
+
|
574
541
|
print_info(f"Profiling {len(target_accounts)} target accounts...")
|
575
|
-
|
542
|
+
|
576
543
|
with create_progress_bar() as progress:
|
577
544
|
task = progress.add_task("[cyan]Profiling accounts...", total=len(target_accounts))
|
578
|
-
|
545
|
+
|
579
546
|
# Use ThreadPoolExecutor for concurrent account profiling
|
580
547
|
with ThreadPoolExecutor(max_workers=min(10, len(target_accounts))) as executor:
|
581
|
-
|
582
548
|
# Submit profiling tasks
|
583
549
|
future_to_account = {
|
584
|
-
executor.submit(self._profile_single_account, account_id): account_id
|
550
|
+
executor.submit(self._profile_single_account, account_id): account_id
|
585
551
|
for account_id in target_accounts
|
586
552
|
}
|
587
|
-
|
553
|
+
|
588
554
|
# Process results as they complete
|
589
555
|
for future in as_completed(future_to_account):
|
590
556
|
account_id = future_to_account[future]
|
@@ -599,244 +565,232 @@ class MultiAccountSecurityController:
|
|
599
565
|
account_name=f"Account-{account_id}",
|
600
566
|
environment_type="unknown",
|
601
567
|
business_criticality="medium",
|
602
|
-
compliance_requirements=["SOC2"] # Default
|
568
|
+
compliance_requirements=["SOC2"], # Default
|
603
569
|
)
|
604
|
-
|
570
|
+
|
605
571
|
progress.update(task, advance=1)
|
606
|
-
|
572
|
+
|
607
573
|
print_success(f"Account profiling completed: {len(self.account_profiles)} profiles created")
|
608
574
|
|
609
575
|
def _profile_single_account(self, account_id: str) -> AccountSecurityProfile:
|
610
576
|
"""Profile a single account for security control deployment."""
|
611
|
-
|
577
|
+
|
612
578
|
try:
|
613
579
|
# Attempt to assume cross-account role
|
614
580
|
account_session = self._assume_cross_account_role(account_id)
|
615
|
-
|
581
|
+
|
616
582
|
if not account_session:
|
617
583
|
# Use management session if cross-account role not available
|
618
584
|
account_session = self.session
|
619
|
-
|
585
|
+
|
620
586
|
# Gather account information
|
621
587
|
account_info = self._gather_account_info(account_session, account_id)
|
622
|
-
|
588
|
+
|
623
589
|
# Determine environment type from account name/tags
|
624
590
|
environment_type = self._determine_environment_type(account_info)
|
625
|
-
|
591
|
+
|
626
592
|
# Assess business criticality
|
627
593
|
business_criticality = self._assess_business_criticality(account_info, environment_type)
|
628
|
-
|
594
|
+
|
629
595
|
# Determine compliance requirements
|
630
|
-
compliance_requirements = self._determine_compliance_requirements(
|
631
|
-
|
632
|
-
)
|
633
|
-
|
596
|
+
compliance_requirements = self._determine_compliance_requirements(environment_type, business_criticality)
|
597
|
+
|
634
598
|
# Check existing security controls
|
635
599
|
deployed_controls = self._check_existing_security_controls(account_session)
|
636
|
-
|
600
|
+
|
637
601
|
# Calculate current security score
|
638
602
|
security_score = self._calculate_security_score(deployed_controls, compliance_requirements)
|
639
|
-
|
603
|
+
|
640
604
|
return AccountSecurityProfile(
|
641
605
|
account_id=account_id,
|
642
|
-
account_name=account_info.get(
|
606
|
+
account_name=account_info.get("name", f"Account-{account_id}"),
|
643
607
|
environment_type=environment_type,
|
644
608
|
business_criticality=business_criticality,
|
645
609
|
compliance_requirements=compliance_requirements,
|
646
610
|
deployed_controls=deployed_controls,
|
647
611
|
security_score=security_score,
|
648
|
-
last_assessment=datetime.utcnow()
|
612
|
+
last_assessment=datetime.utcnow(),
|
649
613
|
)
|
650
|
-
|
614
|
+
|
651
615
|
except Exception as e:
|
652
616
|
print_warning(f"Error profiling account {account_id}: {str(e)}")
|
653
|
-
|
617
|
+
|
654
618
|
# Return minimal profile on error
|
655
619
|
return AccountSecurityProfile(
|
656
620
|
account_id=account_id,
|
657
|
-
account_name=f
|
621
|
+
account_name=f"Account-{account_id}",
|
658
622
|
environment_type="unknown",
|
659
623
|
business_criticality="medium",
|
660
|
-
compliance_requirements=["SOC2"]
|
624
|
+
compliance_requirements=["SOC2"],
|
661
625
|
)
|
662
626
|
|
663
627
|
def _assume_cross_account_role(self, account_id: str) -> Optional[boto3.Session]:
|
664
628
|
"""Assume cross-account role for security operations."""
|
665
|
-
|
629
|
+
|
666
630
|
try:
|
667
631
|
role_arn = self.cross_account_role_arn.format(account_id=account_id)
|
668
|
-
|
669
|
-
sts = self.session.client(
|
632
|
+
|
633
|
+
sts = self.session.client("sts")
|
670
634
|
response = sts.assume_role(
|
671
|
-
RoleArn=role_arn,
|
672
|
-
RoleSessionName=f'CloudOpsSecurityDeployment-{int(time.time())}'
|
635
|
+
RoleArn=role_arn, RoleSessionName=f"CloudOpsSecurityDeployment-{int(time.time())}"
|
673
636
|
)
|
674
|
-
|
675
|
-
credentials = response[
|
676
|
-
|
637
|
+
|
638
|
+
credentials = response["Credentials"]
|
639
|
+
|
677
640
|
return boto3.Session(
|
678
|
-
aws_access_key_id=credentials[
|
679
|
-
aws_secret_access_key=credentials[
|
680
|
-
aws_session_token=credentials[
|
641
|
+
aws_access_key_id=credentials["AccessKeyId"],
|
642
|
+
aws_secret_access_key=credentials["SecretAccessKey"],
|
643
|
+
aws_session_token=credentials["SessionToken"],
|
681
644
|
)
|
682
|
-
|
645
|
+
|
683
646
|
except ClientError as e:
|
684
647
|
print_warning(f"Could not assume role in account {account_id}: {str(e)}")
|
685
648
|
return None
|
686
649
|
|
687
650
|
def _gather_account_info(self, session: boto3.Session, account_id: str) -> Dict[str, Any]:
|
688
651
|
"""Gather basic account information."""
|
689
|
-
|
690
|
-
account_info = {
|
691
|
-
|
652
|
+
|
653
|
+
account_info = {"id": account_id}
|
654
|
+
|
692
655
|
try:
|
693
656
|
# Try to get account alias
|
694
|
-
iam = session.client(
|
695
|
-
aliases = iam.list_account_aliases()[
|
657
|
+
iam = session.client("iam")
|
658
|
+
aliases = iam.list_account_aliases()["AccountAliases"]
|
696
659
|
if aliases:
|
697
|
-
account_info[
|
698
|
-
account_info[
|
660
|
+
account_info["name"] = aliases[0]
|
661
|
+
account_info["alias"] = aliases[0]
|
699
662
|
except ClientError:
|
700
663
|
pass
|
701
|
-
|
664
|
+
|
702
665
|
try:
|
703
666
|
# Get account attributes if possible
|
704
|
-
organizations = self.session.client(
|
667
|
+
organizations = self.session.client("organizations")
|
705
668
|
account_details = organizations.describe_account(AccountId=account_id)
|
706
|
-
account_info[
|
707
|
-
account_info[
|
708
|
-
account_info[
|
669
|
+
account_info["name"] = account_details["Account"]["Name"]
|
670
|
+
account_info["email"] = account_details["Account"]["Email"]
|
671
|
+
account_info["status"] = account_details["Account"]["Status"]
|
709
672
|
except ClientError:
|
710
673
|
pass
|
711
|
-
|
674
|
+
|
712
675
|
return account_info
|
713
676
|
|
714
677
|
def _determine_environment_type(self, account_info: Dict[str, Any]) -> str:
|
715
678
|
"""Determine environment type from account information."""
|
716
|
-
|
717
|
-
account_name = account_info.get(
|
718
|
-
|
719
|
-
if any(keyword in account_name for keyword in [
|
720
|
-
return
|
721
|
-
elif any(keyword in account_name for keyword in [
|
722
|
-
return
|
723
|
-
elif any(keyword in account_name for keyword in [
|
724
|
-
return
|
725
|
-
elif any(keyword in account_name for keyword in [
|
726
|
-
return
|
727
|
-
elif any(keyword in account_name for keyword in [
|
728
|
-
return
|
679
|
+
|
680
|
+
account_name = account_info.get("name", "").lower()
|
681
|
+
|
682
|
+
if any(keyword in account_name for keyword in ["prod", "production", "prd"]):
|
683
|
+
return "production"
|
684
|
+
elif any(keyword in account_name for keyword in ["stg", "staging", "stage"]):
|
685
|
+
return "staging"
|
686
|
+
elif any(keyword in account_name for keyword in ["dev", "development", "develop"]):
|
687
|
+
return "development"
|
688
|
+
elif any(keyword in account_name for keyword in ["test", "testing", "qa"]):
|
689
|
+
return "testing"
|
690
|
+
elif any(keyword in account_name for keyword in ["sandbox", "sb", "demo"]):
|
691
|
+
return "sandbox"
|
729
692
|
else:
|
730
|
-
return
|
693
|
+
return "unknown"
|
731
694
|
|
732
695
|
def _assess_business_criticality(self, account_info: Dict[str, Any], environment_type: str) -> str:
|
733
696
|
"""Assess business criticality of account."""
|
734
|
-
|
697
|
+
|
735
698
|
# Production accounts are typically high/critical
|
736
|
-
if environment_type ==
|
737
|
-
return
|
738
|
-
elif environment_type in [
|
739
|
-
return
|
740
|
-
elif environment_type ==
|
741
|
-
return
|
699
|
+
if environment_type == "production":
|
700
|
+
return "critical"
|
701
|
+
elif environment_type in ["staging", "testing"]:
|
702
|
+
return "high"
|
703
|
+
elif environment_type == "development":
|
704
|
+
return "medium"
|
742
705
|
else:
|
743
|
-
return
|
706
|
+
return "low"
|
744
707
|
|
745
|
-
def _determine_compliance_requirements(
|
746
|
-
self,
|
747
|
-
environment_type: str,
|
748
|
-
business_criticality: str
|
749
|
-
) -> List[str]:
|
708
|
+
def _determine_compliance_requirements(self, environment_type: str, business_criticality: str) -> List[str]:
|
750
709
|
"""Determine compliance requirements based on account characteristics."""
|
751
|
-
|
752
|
-
requirements = [
|
753
|
-
|
754
|
-
if business_criticality in [
|
755
|
-
requirements.extend([
|
756
|
-
|
757
|
-
if environment_type ==
|
758
|
-
requirements.extend([
|
759
|
-
|
710
|
+
|
711
|
+
requirements = ["SOC2"] # Base requirement
|
712
|
+
|
713
|
+
if business_criticality in ["critical", "high"]:
|
714
|
+
requirements.extend(["AWS Well-Architected", "CIS Benchmarks"])
|
715
|
+
|
716
|
+
if environment_type == "production":
|
717
|
+
requirements.extend(["PCI-DSS", "HIPAA"]) # May be applicable
|
718
|
+
|
760
719
|
return list(set(requirements)) # Remove duplicates
|
761
720
|
|
762
721
|
def _check_existing_security_controls(self, session: boto3.Session) -> List[str]:
|
763
722
|
"""Check what security controls are already deployed in account."""
|
764
|
-
|
723
|
+
|
765
724
|
deployed_controls = []
|
766
|
-
|
725
|
+
|
767
726
|
try:
|
768
727
|
# Check IAM password policy
|
769
|
-
iam = session.client(
|
728
|
+
iam = session.client("iam")
|
770
729
|
try:
|
771
730
|
iam.get_account_password_policy()
|
772
|
-
deployed_controls.append(
|
731
|
+
deployed_controls.append("IAM-001")
|
773
732
|
except ClientError:
|
774
733
|
pass
|
775
|
-
|
734
|
+
|
776
735
|
# Check CloudTrail
|
777
|
-
cloudtrail = session.client(
|
778
|
-
trails = cloudtrail.describe_trails()[
|
736
|
+
cloudtrail = session.client("cloudtrail")
|
737
|
+
trails = cloudtrail.describe_trails()["trailList"]
|
779
738
|
if trails:
|
780
|
-
deployed_controls.append(
|
781
|
-
|
739
|
+
deployed_controls.append("AUD-001")
|
740
|
+
|
782
741
|
# Check Config
|
783
|
-
config = session.client(
|
742
|
+
config = session.client("config")
|
784
743
|
try:
|
785
744
|
config.describe_configuration_recorders()
|
786
|
-
deployed_controls.append(
|
745
|
+
deployed_controls.append("CMP-001")
|
787
746
|
except ClientError:
|
788
747
|
pass
|
789
|
-
|
748
|
+
|
790
749
|
# Check VPC Flow Logs (simplified check)
|
791
|
-
ec2 = session.client(
|
792
|
-
vpcs = ec2.describe_vpcs()[
|
793
|
-
flow_logs = ec2.describe_flow_logs()[
|
794
|
-
|
795
|
-
vpc_with_flow_logs = {fl[
|
750
|
+
ec2 = session.client("ec2")
|
751
|
+
vpcs = ec2.describe_vpcs()["Vpcs"]
|
752
|
+
flow_logs = ec2.describe_flow_logs()["FlowLogs"]
|
753
|
+
|
754
|
+
vpc_with_flow_logs = {fl["ResourceId"] for fl in flow_logs if fl["ResourceType"] == "VPC"}
|
796
755
|
if len(vpc_with_flow_logs) > 0:
|
797
|
-
deployed_controls.append(
|
798
|
-
|
756
|
+
deployed_controls.append("NET-001")
|
757
|
+
|
799
758
|
except Exception as e:
|
800
759
|
print_warning(f"Error checking existing controls: {str(e)}")
|
801
|
-
|
760
|
+
|
802
761
|
return deployed_controls
|
803
762
|
|
804
|
-
def _calculate_security_score(
|
805
|
-
self,
|
806
|
-
deployed_controls: List[str],
|
807
|
-
compliance_requirements: List[str]
|
808
|
-
) -> float:
|
763
|
+
def _calculate_security_score(self, deployed_controls: List[str], compliance_requirements: List[str]) -> float:
|
809
764
|
"""Calculate security score based on deployed controls."""
|
810
|
-
|
765
|
+
|
811
766
|
total_applicable_controls = len(self.security_controls)
|
812
767
|
deployed_count = len(deployed_controls)
|
813
|
-
|
768
|
+
|
814
769
|
base_score = (deployed_count / total_applicable_controls) * 100
|
815
|
-
|
770
|
+
|
816
771
|
# Adjust based on compliance requirements
|
817
772
|
compliance_multiplier = 1.0 + (len(compliance_requirements) * 0.1)
|
818
|
-
|
773
|
+
|
819
774
|
return min(100.0, base_score * compliance_multiplier)
|
820
775
|
|
821
776
|
def _select_controls_for_deployment(self, control_ids: Optional[List[str]]) -> List[SecurityControl]:
|
822
777
|
"""Select security controls for deployment."""
|
823
|
-
|
778
|
+
|
824
779
|
if control_ids:
|
825
780
|
# Deploy specific controls
|
826
|
-
selected_controls = [
|
827
|
-
control for control in self.security_controls
|
828
|
-
if control.control_id in control_ids
|
829
|
-
]
|
781
|
+
selected_controls = [control for control in self.security_controls if control.control_id in control_ids]
|
830
782
|
else:
|
831
783
|
# Deploy all controls
|
832
784
|
selected_controls = self.security_controls.copy()
|
833
|
-
|
785
|
+
|
834
786
|
# Sort by deployment priority (critical controls first)
|
835
|
-
selected_controls.sort(
|
836
|
-
c
|
837
|
-
|
838
|
-
|
839
|
-
|
787
|
+
selected_controls.sort(
|
788
|
+
key=lambda c: (
|
789
|
+
c.requires_approval, # Non-approval controls first
|
790
|
+
c.estimated_deployment_time, # Faster deployments first
|
791
|
+
)
|
792
|
+
)
|
793
|
+
|
840
794
|
return selected_controls
|
841
795
|
|
842
796
|
async def _execute_deployment_strategy(
|
@@ -844,26 +798,22 @@ class MultiAccountSecurityController:
|
|
844
798
|
controls_to_deploy: List[SecurityControl],
|
845
799
|
target_accounts: List[str],
|
846
800
|
deployment_strategy: DeploymentStrategy,
|
847
|
-
deployment_id: str
|
801
|
+
deployment_id: str,
|
848
802
|
) -> Dict[str, Any]:
|
849
803
|
"""Execute security control deployment based on strategy."""
|
850
|
-
|
804
|
+
|
851
805
|
deployment_results = {
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
'successful_deployments': 0,
|
860
|
-
'failed_deployments': 0,
|
861
|
-
'total_deployment_time': 0
|
862
|
-
}
|
806
|
+
"deployment_id": deployment_id,
|
807
|
+
"strategy": deployment_strategy.value,
|
808
|
+
"total_controls": len(controls_to_deploy),
|
809
|
+
"total_accounts": len(target_accounts),
|
810
|
+
"control_results": {},
|
811
|
+
"account_results": {},
|
812
|
+
"summary": {"successful_deployments": 0, "failed_deployments": 0, "total_deployment_time": 0},
|
863
813
|
}
|
864
|
-
|
814
|
+
|
865
815
|
start_time = time.time()
|
866
|
-
|
816
|
+
|
867
817
|
if deployment_strategy == DeploymentStrategy.PARALLEL_ALL:
|
868
818
|
deployment_results = await self._parallel_deployment(
|
869
819
|
controls_to_deploy, target_accounts, deployment_results
|
@@ -880,375 +830,317 @@ class MultiAccountSecurityController:
|
|
880
830
|
deployment_results = await self._critical_first_deployment(
|
881
831
|
controls_to_deploy, target_accounts, deployment_results
|
882
832
|
)
|
883
|
-
|
884
|
-
deployment_results[
|
885
|
-
|
833
|
+
|
834
|
+
deployment_results["summary"]["total_deployment_time"] = time.time() - start_time
|
835
|
+
|
886
836
|
return deployment_results
|
887
837
|
|
888
838
|
async def _parallel_deployment(
|
889
|
-
self,
|
890
|
-
controls_to_deploy: List[SecurityControl],
|
891
|
-
target_accounts: List[str],
|
892
|
-
deployment_results: Dict[str, Any]
|
839
|
+
self, controls_to_deploy: List[SecurityControl], target_accounts: List[str], deployment_results: Dict[str, Any]
|
893
840
|
) -> Dict[str, Any]:
|
894
841
|
"""Deploy all controls to all accounts in parallel."""
|
895
|
-
|
842
|
+
|
896
843
|
print_info("Executing parallel deployment strategy")
|
897
|
-
|
844
|
+
|
898
845
|
# Create deployment tasks for all control-account combinations
|
899
846
|
deployment_tasks = []
|
900
|
-
|
847
|
+
|
901
848
|
for control in controls_to_deploy:
|
902
849
|
for account_id in target_accounts:
|
903
|
-
task = asyncio.create_task(
|
904
|
-
|
905
|
-
|
906
|
-
deployment_tasks.append({
|
907
|
-
'task': task,
|
908
|
-
'control_id': control.control_id,
|
909
|
-
'account_id': account_id
|
910
|
-
})
|
911
|
-
|
850
|
+
task = asyncio.create_task(self._deploy_control_to_account(control, account_id))
|
851
|
+
deployment_tasks.append({"task": task, "control_id": control.control_id, "account_id": account_id})
|
852
|
+
|
912
853
|
# Execute all deployments with progress tracking
|
913
854
|
with create_progress_bar() as progress:
|
914
|
-
deploy_task = progress.add_task(
|
915
|
-
|
916
|
-
total=len(deployment_tasks)
|
917
|
-
)
|
918
|
-
|
855
|
+
deploy_task = progress.add_task("[green]Deploying controls...", total=len(deployment_tasks))
|
856
|
+
|
919
857
|
# Process deployments as they complete
|
920
|
-
for task_info in asyncio.as_completed([t[
|
858
|
+
for task_info in asyncio.as_completed([t["task"] for t in deployment_tasks]):
|
921
859
|
try:
|
922
860
|
result = await task_info
|
923
|
-
|
861
|
+
|
924
862
|
# Find the corresponding task info
|
925
|
-
completed_task = next(
|
926
|
-
|
927
|
-
if t['task'] == task_info
|
928
|
-
)
|
929
|
-
|
863
|
+
completed_task = next(t for t in deployment_tasks if t["task"] == task_info)
|
864
|
+
|
930
865
|
# Store result
|
931
|
-
control_id = completed_task[
|
932
|
-
account_id = completed_task[
|
933
|
-
|
934
|
-
if control_id not in deployment_results[
|
935
|
-
deployment_results[
|
936
|
-
|
937
|
-
deployment_results[
|
938
|
-
|
939
|
-
if result[
|
940
|
-
deployment_results[
|
866
|
+
control_id = completed_task["control_id"]
|
867
|
+
account_id = completed_task["account_id"]
|
868
|
+
|
869
|
+
if control_id not in deployment_results["control_results"]:
|
870
|
+
deployment_results["control_results"][control_id] = {}
|
871
|
+
|
872
|
+
deployment_results["control_results"][control_id][account_id] = result
|
873
|
+
|
874
|
+
if result["success"]:
|
875
|
+
deployment_results["summary"]["successful_deployments"] += 1
|
941
876
|
else:
|
942
|
-
deployment_results[
|
943
|
-
|
877
|
+
deployment_results["summary"]["failed_deployments"] += 1
|
878
|
+
|
944
879
|
progress.update(deploy_task, advance=1)
|
945
|
-
|
880
|
+
|
946
881
|
except Exception as e:
|
947
882
|
print_error(f"Deployment task failed: {str(e)}")
|
948
|
-
deployment_results[
|
883
|
+
deployment_results["summary"]["failed_deployments"] += 1
|
949
884
|
progress.update(deploy_task, advance=1)
|
950
|
-
|
885
|
+
|
951
886
|
return deployment_results
|
952
887
|
|
953
888
|
async def _staged_rollout_deployment(
|
954
|
-
self,
|
955
|
-
controls_to_deploy: List[SecurityControl],
|
956
|
-
target_accounts: List[str],
|
957
|
-
deployment_results: Dict[str, Any]
|
889
|
+
self, controls_to_deploy: List[SecurityControl], target_accounts: List[str], deployment_results: Dict[str, Any]
|
958
890
|
) -> Dict[str, Any]:
|
959
891
|
"""Deploy controls in stages with validation gates."""
|
960
|
-
|
892
|
+
|
961
893
|
print_info("Executing staged rollout deployment strategy")
|
962
|
-
|
894
|
+
|
963
895
|
# Divide accounts into stages based on business criticality
|
964
896
|
stage_accounts = self._create_deployment_stages(target_accounts)
|
965
|
-
|
897
|
+
|
966
898
|
for stage_num, accounts in enumerate(stage_accounts, 1):
|
967
899
|
print_info(f"Deploying to Stage {stage_num}: {len(accounts)} accounts")
|
968
|
-
|
900
|
+
|
969
901
|
# Deploy to current stage
|
970
|
-
stage_results = await self._deploy_to_account_group(
|
971
|
-
|
972
|
-
)
|
973
|
-
|
902
|
+
stage_results = await self._deploy_to_account_group(controls_to_deploy, accounts, f"Stage-{stage_num}")
|
903
|
+
|
974
904
|
# Merge results
|
975
905
|
for control_id, control_results in stage_results.items():
|
976
|
-
if control_id not in deployment_results[
|
977
|
-
deployment_results[
|
978
|
-
deployment_results[
|
979
|
-
|
906
|
+
if control_id not in deployment_results["control_results"]:
|
907
|
+
deployment_results["control_results"][control_id] = {}
|
908
|
+
deployment_results["control_results"][control_id].update(control_results)
|
909
|
+
|
980
910
|
# Validation gate - check success rate before proceeding
|
981
911
|
stage_success_rate = self._calculate_stage_success_rate(stage_results)
|
982
|
-
|
912
|
+
|
983
913
|
if stage_success_rate < 0.8: # 80% success threshold
|
984
914
|
print_warning(f"Stage {stage_num} success rate ({stage_success_rate:.1%}) below threshold")
|
985
|
-
|
915
|
+
|
986
916
|
# Pause for investigation (in production, would require approval to continue)
|
987
917
|
if not self.dry_run:
|
988
918
|
print_warning("Pausing deployment for investigation")
|
989
919
|
break
|
990
|
-
|
920
|
+
|
991
921
|
print_success(f"Stage {stage_num} completed with {stage_success_rate:.1%} success rate")
|
992
|
-
|
922
|
+
|
993
923
|
return deployment_results
|
994
924
|
|
995
925
|
def _create_deployment_stages(self, target_accounts: List[str]) -> List[List[str]]:
|
996
926
|
"""Create deployment stages based on account characteristics."""
|
997
|
-
|
927
|
+
|
998
928
|
# Group accounts by criticality and environment
|
999
929
|
stages = {
|
1000
930
|
1: [], # Sandbox/Development accounts first
|
1001
931
|
2: [], # Testing/Staging accounts
|
1002
|
-
3: []
|
932
|
+
3: [], # Production accounts last
|
1003
933
|
}
|
1004
|
-
|
934
|
+
|
1005
935
|
for account_id in target_accounts:
|
1006
936
|
profile = self.account_profiles.get(account_id)
|
1007
|
-
|
937
|
+
|
1008
938
|
if not profile:
|
1009
939
|
stages[2].append(account_id) # Default to middle stage
|
1010
940
|
continue
|
1011
|
-
|
1012
|
-
if profile.environment_type in [
|
941
|
+
|
942
|
+
if profile.environment_type in ["sandbox", "development"]:
|
1013
943
|
stages[1].append(account_id)
|
1014
|
-
elif profile.environment_type in [
|
944
|
+
elif profile.environment_type in ["testing", "staging"]:
|
1015
945
|
stages[2].append(account_id)
|
1016
946
|
else: # production or unknown
|
1017
947
|
stages[3].append(account_id)
|
1018
|
-
|
948
|
+
|
1019
949
|
# Return non-empty stages
|
1020
950
|
return [accounts for accounts in stages.values() if accounts]
|
1021
951
|
|
1022
952
|
async def _deploy_to_account_group(
|
1023
|
-
self,
|
1024
|
-
controls_to_deploy: List[SecurityControl],
|
1025
|
-
account_group: List[str],
|
1026
|
-
group_name: str
|
953
|
+
self, controls_to_deploy: List[SecurityControl], account_group: List[str], group_name: str
|
1027
954
|
) -> Dict[str, Dict[str, Any]]:
|
1028
955
|
"""Deploy controls to a group of accounts."""
|
1029
|
-
|
956
|
+
|
1030
957
|
group_results = {}
|
1031
|
-
|
958
|
+
|
1032
959
|
with create_progress_bar() as progress:
|
1033
960
|
task = progress.add_task(
|
1034
|
-
f"[cyan]Deploying to {group_name}...",
|
1035
|
-
total=len(controls_to_deploy) * len(account_group)
|
961
|
+
f"[cyan]Deploying to {group_name}...", total=len(controls_to_deploy) * len(account_group)
|
1036
962
|
)
|
1037
|
-
|
963
|
+
|
1038
964
|
for control in controls_to_deploy:
|
1039
965
|
control_results = {}
|
1040
|
-
|
966
|
+
|
1041
967
|
# Deploy control to all accounts in group
|
1042
968
|
deployment_tasks = [
|
1043
|
-
self._deploy_control_to_account(control, account_id)
|
1044
|
-
for account_id in account_group
|
969
|
+
self._deploy_control_to_account(control, account_id) for account_id in account_group
|
1045
970
|
]
|
1046
|
-
|
971
|
+
|
1047
972
|
# Wait for all deployments to complete
|
1048
973
|
results = await asyncio.gather(*deployment_tasks, return_exceptions=True)
|
1049
|
-
|
974
|
+
|
1050
975
|
# Process results
|
1051
976
|
for account_id, result in zip(account_group, results):
|
1052
977
|
if isinstance(result, Exception):
|
1053
|
-
control_results[account_id] = {
|
1054
|
-
'success': False,
|
1055
|
-
'error': str(result),
|
1056
|
-
'deployment_time': 0
|
1057
|
-
}
|
978
|
+
control_results[account_id] = {"success": False, "error": str(result), "deployment_time": 0}
|
1058
979
|
else:
|
1059
980
|
control_results[account_id] = result
|
1060
|
-
|
981
|
+
|
1061
982
|
progress.update(task, advance=1)
|
1062
|
-
|
983
|
+
|
1063
984
|
group_results[control.control_id] = control_results
|
1064
|
-
|
985
|
+
|
1065
986
|
return group_results
|
1066
987
|
|
1067
988
|
def _calculate_stage_success_rate(self, stage_results: Dict[str, Dict[str, Any]]) -> float:
|
1068
989
|
"""Calculate success rate for a deployment stage."""
|
1069
|
-
|
990
|
+
|
1070
991
|
total_deployments = 0
|
1071
992
|
successful_deployments = 0
|
1072
|
-
|
993
|
+
|
1073
994
|
for control_results in stage_results.values():
|
1074
995
|
for account_result in control_results.values():
|
1075
996
|
total_deployments += 1
|
1076
|
-
if account_result.get(
|
997
|
+
if account_result.get("success", False):
|
1077
998
|
successful_deployments += 1
|
1078
|
-
|
999
|
+
|
1079
1000
|
if total_deployments == 0:
|
1080
1001
|
return 0.0
|
1081
|
-
|
1002
|
+
|
1082
1003
|
return successful_deployments / total_deployments
|
1083
1004
|
|
1084
1005
|
async def _pilot_first_deployment(
|
1085
|
-
self,
|
1086
|
-
controls_to_deploy: List[SecurityControl],
|
1087
|
-
target_accounts: List[str],
|
1088
|
-
deployment_results: Dict[str, Any]
|
1006
|
+
self, controls_to_deploy: List[SecurityControl], target_accounts: List[str], deployment_results: Dict[str, Any]
|
1089
1007
|
) -> Dict[str, Any]:
|
1090
1008
|
"""Deploy to pilot accounts first, then full rollout."""
|
1091
|
-
|
1009
|
+
|
1092
1010
|
print_info("Executing pilot-first deployment strategy")
|
1093
|
-
|
1011
|
+
|
1094
1012
|
# Select pilot accounts (typically 10% of total, minimum 1, maximum 5)
|
1095
1013
|
pilot_count = max(1, min(5, len(target_accounts) // 10))
|
1096
1014
|
pilot_accounts = target_accounts[:pilot_count]
|
1097
1015
|
remaining_accounts = target_accounts[pilot_count:]
|
1098
|
-
|
1016
|
+
|
1099
1017
|
# Pilot deployment
|
1100
1018
|
print_info(f"Pilot deployment to {len(pilot_accounts)} accounts")
|
1101
|
-
pilot_results = await self._deploy_to_account_group(
|
1102
|
-
|
1103
|
-
)
|
1104
|
-
|
1019
|
+
pilot_results = await self._deploy_to_account_group(controls_to_deploy, pilot_accounts, "Pilot")
|
1020
|
+
|
1105
1021
|
# Check pilot success
|
1106
1022
|
pilot_success_rate = self._calculate_stage_success_rate(pilot_results)
|
1107
1023
|
print_info(f"Pilot deployment success rate: {pilot_success_rate:.1%}")
|
1108
|
-
|
1024
|
+
|
1109
1025
|
# Merge pilot results
|
1110
1026
|
for control_id, control_results in pilot_results.items():
|
1111
|
-
deployment_results[
|
1112
|
-
|
1027
|
+
deployment_results["control_results"][control_id] = control_results
|
1028
|
+
|
1113
1029
|
# Full deployment if pilot successful
|
1114
1030
|
if pilot_success_rate >= 0.9: # 90% success required for full rollout
|
1115
1031
|
print_info(f"Pilot successful, proceeding with full deployment to {len(remaining_accounts)} accounts")
|
1116
|
-
|
1117
|
-
full_results = await self._deploy_to_account_group(
|
1118
|
-
|
1119
|
-
)
|
1120
|
-
|
1032
|
+
|
1033
|
+
full_results = await self._deploy_to_account_group(controls_to_deploy, remaining_accounts, "Full Rollout")
|
1034
|
+
|
1121
1035
|
# Merge full deployment results
|
1122
1036
|
for control_id, control_results in full_results.items():
|
1123
|
-
if control_id not in deployment_results[
|
1124
|
-
deployment_results[
|
1125
|
-
deployment_results[
|
1037
|
+
if control_id not in deployment_results["control_results"]:
|
1038
|
+
deployment_results["control_results"][control_id] = {}
|
1039
|
+
deployment_results["control_results"][control_id].update(control_results)
|
1126
1040
|
else:
|
1127
1041
|
print_warning("Pilot deployment failed, stopping full rollout")
|
1128
|
-
|
1042
|
+
|
1129
1043
|
return deployment_results
|
1130
1044
|
|
1131
1045
|
async def _critical_first_deployment(
|
1132
|
-
self,
|
1133
|
-
controls_to_deploy: List[SecurityControl],
|
1134
|
-
target_accounts: List[str],
|
1135
|
-
deployment_results: Dict[str, Any]
|
1046
|
+
self, controls_to_deploy: List[SecurityControl], target_accounts: List[str], deployment_results: Dict[str, Any]
|
1136
1047
|
) -> Dict[str, Any]:
|
1137
1048
|
"""Deploy to critical accounts first."""
|
1138
|
-
|
1049
|
+
|
1139
1050
|
print_info("Executing critical-first deployment strategy")
|
1140
|
-
|
1051
|
+
|
1141
1052
|
# Group accounts by criticality
|
1142
1053
|
critical_accounts = []
|
1143
1054
|
other_accounts = []
|
1144
|
-
|
1055
|
+
|
1145
1056
|
for account_id in target_accounts:
|
1146
1057
|
profile = self.account_profiles.get(account_id)
|
1147
|
-
|
1148
|
-
if profile and profile.business_criticality ==
|
1058
|
+
|
1059
|
+
if profile and profile.business_criticality == "critical":
|
1149
1060
|
critical_accounts.append(account_id)
|
1150
1061
|
else:
|
1151
1062
|
other_accounts.append(account_id)
|
1152
|
-
|
1063
|
+
|
1153
1064
|
# Deploy to critical accounts first
|
1154
1065
|
if critical_accounts:
|
1155
1066
|
print_info(f"Deploying to {len(critical_accounts)} critical accounts")
|
1156
1067
|
critical_results = await self._deploy_to_account_group(
|
1157
1068
|
controls_to_deploy, critical_accounts, "Critical Accounts"
|
1158
1069
|
)
|
1159
|
-
|
1070
|
+
|
1160
1071
|
# Merge critical results
|
1161
1072
|
for control_id, control_results in critical_results.items():
|
1162
|
-
deployment_results[
|
1163
|
-
|
1073
|
+
deployment_results["control_results"][control_id] = control_results
|
1074
|
+
|
1164
1075
|
# Deploy to other accounts
|
1165
1076
|
if other_accounts:
|
1166
1077
|
print_info(f"Deploying to {len(other_accounts)} other accounts")
|
1167
|
-
other_results = await self._deploy_to_account_group(
|
1168
|
-
|
1169
|
-
)
|
1170
|
-
|
1078
|
+
other_results = await self._deploy_to_account_group(controls_to_deploy, other_accounts, "Other Accounts")
|
1079
|
+
|
1171
1080
|
# Merge other results
|
1172
1081
|
for control_id, control_results in other_results.items():
|
1173
|
-
if control_id not in deployment_results[
|
1174
|
-
deployment_results[
|
1175
|
-
deployment_results[
|
1176
|
-
|
1082
|
+
if control_id not in deployment_results["control_results"]:
|
1083
|
+
deployment_results["control_results"][control_id] = {}
|
1084
|
+
deployment_results["control_results"][control_id].update(control_results)
|
1085
|
+
|
1177
1086
|
return deployment_results
|
1178
1087
|
|
1179
|
-
async def _deploy_control_to_account(
|
1180
|
-
self,
|
1181
|
-
control: SecurityControl,
|
1182
|
-
account_id: str
|
1183
|
-
) -> Dict[str, Any]:
|
1088
|
+
async def _deploy_control_to_account(self, control: SecurityControl, account_id: str) -> Dict[str, Any]:
|
1184
1089
|
"""Deploy a single security control to a specific account."""
|
1185
|
-
|
1090
|
+
|
1186
1091
|
start_time = time.time()
|
1187
|
-
|
1092
|
+
|
1188
1093
|
try:
|
1189
1094
|
# Get account session
|
1190
1095
|
account_session = self._assume_cross_account_role(account_id)
|
1191
1096
|
if not account_session:
|
1192
1097
|
account_session = self.session
|
1193
|
-
|
1098
|
+
|
1194
1099
|
# Check if control is already deployed
|
1195
|
-
if control.control_id in self.account_profiles.get(account_id, {}).get(
|
1100
|
+
if control.control_id in self.account_profiles.get(account_id, {}).get("deployed_controls", []):
|
1196
1101
|
return {
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1102
|
+
"success": True,
|
1103
|
+
"message": "Control already deployed",
|
1104
|
+
"deployment_time": time.time() - start_time,
|
1105
|
+
"skipped": True,
|
1201
1106
|
}
|
1202
|
-
|
1107
|
+
|
1203
1108
|
# Check if approval is required
|
1204
1109
|
if control.requires_approval and not self.dry_run:
|
1205
1110
|
return {
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1111
|
+
"success": False,
|
1112
|
+
"message": "Approval required for this control",
|
1113
|
+
"deployment_time": time.time() - start_time,
|
1114
|
+
"approval_required": True,
|
1210
1115
|
}
|
1211
|
-
|
1116
|
+
|
1212
1117
|
# Execute deployment based on control type
|
1213
|
-
deployment_result = await self._execute_control_deployment(
|
1214
|
-
|
1215
|
-
)
|
1216
|
-
|
1217
|
-
deployment_result['deployment_time'] = time.time() - start_time
|
1218
|
-
|
1118
|
+
deployment_result = await self._execute_control_deployment(control, account_session, account_id)
|
1119
|
+
|
1120
|
+
deployment_result["deployment_time"] = time.time() - start_time
|
1121
|
+
|
1219
1122
|
# Update control status
|
1220
|
-
if deployment_result[
|
1123
|
+
if deployment_result["success"]:
|
1221
1124
|
control.deployed_accounts.append(account_id)
|
1222
1125
|
control.deployment_status = ControlStatus.DEPLOYED
|
1223
1126
|
else:
|
1224
1127
|
control.failed_accounts.append(account_id)
|
1225
|
-
|
1128
|
+
|
1226
1129
|
except Exception as e:
|
1227
|
-
deployment_result = {
|
1228
|
-
|
1229
|
-
'error': str(e),
|
1230
|
-
'deployment_time': time.time() - start_time
|
1231
|
-
}
|
1232
|
-
|
1130
|
+
deployment_result = {"success": False, "error": str(e), "deployment_time": time.time() - start_time}
|
1131
|
+
|
1233
1132
|
return deployment_result
|
1234
1133
|
|
1235
1134
|
async def _execute_control_deployment(
|
1236
|
-
self,
|
1237
|
-
control: SecurityControl,
|
1238
|
-
session: boto3.Session,
|
1239
|
-
account_id: str
|
1135
|
+
self, control: SecurityControl, session: boto3.Session, account_id: str
|
1240
1136
|
) -> Dict[str, Any]:
|
1241
1137
|
"""Execute the actual deployment of a security control."""
|
1242
|
-
|
1138
|
+
|
1243
1139
|
if self.dry_run:
|
1244
1140
|
# Simulate deployment in dry run mode
|
1245
1141
|
await asyncio.sleep(0.1) # Simulate deployment time
|
1246
|
-
return {
|
1247
|
-
|
1248
|
-
'message': f'DRY RUN: Would deploy {control.control_name}',
|
1249
|
-
'dry_run': True
|
1250
|
-
}
|
1251
|
-
|
1142
|
+
return {"success": True, "message": f"DRY RUN: Would deploy {control.control_name}", "dry_run": True}
|
1143
|
+
|
1252
1144
|
try:
|
1253
1145
|
if control.control_type == SecurityControlType.IAM_BASELINE:
|
1254
1146
|
return await self._deploy_iam_control(control, session, account_id)
|
@@ -1261,487 +1153,367 @@ class MultiAccountSecurityController:
|
|
1261
1153
|
elif control.control_type == SecurityControlType.COMPLIANCE_MONITORING:
|
1262
1154
|
return await self._deploy_compliance_control(control, session, account_id)
|
1263
1155
|
else:
|
1264
|
-
return {
|
1265
|
-
|
1266
|
-
'message': f'Unsupported control type: {control.control_type.value}'
|
1267
|
-
}
|
1268
|
-
|
1156
|
+
return {"success": False, "message": f"Unsupported control type: {control.control_type.value}"}
|
1157
|
+
|
1269
1158
|
except Exception as e:
|
1270
|
-
return {
|
1271
|
-
'success': False,
|
1272
|
-
'error': str(e),
|
1273
|
-
'message': f'Failed to deploy {control.control_name}'
|
1274
|
-
}
|
1159
|
+
return {"success": False, "error": str(e), "message": f"Failed to deploy {control.control_name}"}
|
1275
1160
|
|
1276
1161
|
async def _deploy_iam_control(
|
1277
|
-
self,
|
1278
|
-
control: SecurityControl,
|
1279
|
-
session: boto3.Session,
|
1280
|
-
account_id: str
|
1162
|
+
self, control: SecurityControl, session: boto3.Session, account_id: str
|
1281
1163
|
) -> Dict[str, Any]:
|
1282
1164
|
"""Deploy IAM-related security control."""
|
1283
|
-
|
1284
|
-
iam = session.client(
|
1285
|
-
|
1286
|
-
if control.control_id ==
|
1165
|
+
|
1166
|
+
iam = session.client("iam")
|
1167
|
+
|
1168
|
+
if control.control_id == "IAM-001": # Password Policy
|
1287
1169
|
template = control.deployment_template
|
1288
|
-
|
1170
|
+
|
1289
1171
|
try:
|
1290
1172
|
iam.update_account_password_policy(
|
1291
|
-
MinimumPasswordLength=template[
|
1292
|
-
RequireUppercaseCharacters=template[
|
1293
|
-
RequireLowercaseCharacters=template[
|
1294
|
-
RequireNumbers=template[
|
1295
|
-
RequireSymbols=template[
|
1296
|
-
MaxPasswordAge=template[
|
1297
|
-
PasswordReusePrevention=template[
|
1298
|
-
HardExpiry=template[
|
1173
|
+
MinimumPasswordLength=template["MinimumPasswordLength"],
|
1174
|
+
RequireUppercaseCharacters=template["RequireUppercaseCharacters"],
|
1175
|
+
RequireLowercaseCharacters=template["RequireLowercaseCharacters"],
|
1176
|
+
RequireNumbers=template["RequireNumbers"],
|
1177
|
+
RequireSymbols=template["RequireSymbols"],
|
1178
|
+
MaxPasswordAge=template["MaxPasswordAge"],
|
1179
|
+
PasswordReusePrevention=template["PasswordReusePrevention"],
|
1180
|
+
HardExpiry=template["HardExpiry"],
|
1299
1181
|
)
|
1300
|
-
|
1182
|
+
|
1301
1183
|
return {
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1184
|
+
"success": True,
|
1185
|
+
"message": "IAM password policy successfully applied",
|
1186
|
+
"policy_applied": template,
|
1305
1187
|
}
|
1306
|
-
|
1188
|
+
|
1307
1189
|
except ClientError as e:
|
1308
|
-
return {
|
1309
|
-
|
1310
|
-
|
1311
|
-
'message': 'Failed to apply IAM password policy'
|
1312
|
-
}
|
1313
|
-
|
1314
|
-
elif control.control_id == 'IAM-002': # Root MFA
|
1190
|
+
return {"success": False, "error": str(e), "message": "Failed to apply IAM password policy"}
|
1191
|
+
|
1192
|
+
elif control.control_id == "IAM-002": # Root MFA
|
1315
1193
|
# This would check and potentially remediate root MFA
|
1316
1194
|
# For safety, this returns success without making changes
|
1317
1195
|
return {
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1196
|
+
"success": True,
|
1197
|
+
"message": "Root MFA check completed (manual verification required)",
|
1198
|
+
"manual_verification_required": True,
|
1321
1199
|
}
|
1322
|
-
|
1323
|
-
return {
|
1324
|
-
'success': False,
|
1325
|
-
'message': f'Unknown IAM control: {control.control_id}'
|
1326
|
-
}
|
1200
|
+
|
1201
|
+
return {"success": False, "message": f"Unknown IAM control: {control.control_id}"}
|
1327
1202
|
|
1328
1203
|
async def _deploy_encryption_control(
|
1329
|
-
self,
|
1330
|
-
control: SecurityControl,
|
1331
|
-
session: boto3.Session,
|
1332
|
-
account_id: str
|
1204
|
+
self, control: SecurityControl, session: boto3.Session, account_id: str
|
1333
1205
|
) -> Dict[str, Any]:
|
1334
1206
|
"""Deploy encryption-related security control."""
|
1335
|
-
|
1336
|
-
if control.control_id ==
|
1337
|
-
s3 = session.client(
|
1338
|
-
|
1207
|
+
|
1208
|
+
if control.control_id == "ENC-001": # S3 Bucket Encryption
|
1209
|
+
s3 = session.client("s3")
|
1210
|
+
|
1339
1211
|
try:
|
1340
1212
|
# Get list of buckets
|
1341
|
-
buckets = s3.list_buckets()[
|
1342
|
-
|
1213
|
+
buckets = s3.list_buckets()["Buckets"]
|
1214
|
+
|
1343
1215
|
applied_count = 0
|
1344
1216
|
failed_buckets = []
|
1345
|
-
|
1217
|
+
|
1346
1218
|
for bucket in buckets[:10]: # Limit for demo
|
1347
|
-
bucket_name = bucket[
|
1348
|
-
|
1219
|
+
bucket_name = bucket["Name"]
|
1220
|
+
|
1349
1221
|
try:
|
1350
1222
|
# Apply default encryption
|
1351
1223
|
s3.put_bucket_encryption(
|
1352
1224
|
Bucket=bucket_name,
|
1353
1225
|
ServerSideEncryptionConfiguration={
|
1354
|
-
|
1226
|
+
"Rules": [
|
1355
1227
|
{
|
1356
|
-
|
1357
|
-
|
1228
|
+
"ApplyServerSideEncryptionByDefault": {
|
1229
|
+
"SSEAlgorithm": control.deployment_template["encryption_algorithm"]
|
1358
1230
|
}
|
1359
1231
|
}
|
1360
1232
|
]
|
1361
|
-
}
|
1233
|
+
},
|
1362
1234
|
)
|
1363
1235
|
applied_count += 1
|
1364
|
-
|
1236
|
+
|
1365
1237
|
except ClientError as e:
|
1366
|
-
failed_buckets.append({
|
1367
|
-
|
1368
|
-
'error': str(e)
|
1369
|
-
})
|
1370
|
-
|
1238
|
+
failed_buckets.append({"bucket": bucket_name, "error": str(e)})
|
1239
|
+
|
1371
1240
|
return {
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1241
|
+
"success": len(failed_buckets) == 0,
|
1242
|
+
"message": f"Applied encryption to {applied_count} buckets",
|
1243
|
+
"applied_count": applied_count,
|
1244
|
+
"failed_buckets": failed_buckets,
|
1376
1245
|
}
|
1377
|
-
|
1246
|
+
|
1378
1247
|
except ClientError as e:
|
1379
|
-
return {
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
elif control.control_id == 'ENC-002': # EBS Encryption
|
1386
|
-
ec2 = session.client('ec2')
|
1387
|
-
|
1248
|
+
return {"success": False, "error": str(e), "message": "Failed to apply S3 bucket encryption"}
|
1249
|
+
|
1250
|
+
elif control.control_id == "ENC-002": # EBS Encryption
|
1251
|
+
ec2 = session.client("ec2")
|
1252
|
+
|
1388
1253
|
try:
|
1389
1254
|
# Enable EBS encryption by default
|
1390
1255
|
ec2.enable_ebs_encryption_by_default()
|
1391
|
-
|
1392
|
-
return {
|
1393
|
-
|
1394
|
-
'message': 'EBS encryption by default enabled'
|
1395
|
-
}
|
1396
|
-
|
1256
|
+
|
1257
|
+
return {"success": True, "message": "EBS encryption by default enabled"}
|
1258
|
+
|
1397
1259
|
except ClientError as e:
|
1398
|
-
return {
|
1399
|
-
|
1400
|
-
|
1401
|
-
'message': 'Failed to enable EBS encryption by default'
|
1402
|
-
}
|
1403
|
-
|
1404
|
-
return {
|
1405
|
-
'success': False,
|
1406
|
-
'message': f'Unknown encryption control: {control.control_id}'
|
1407
|
-
}
|
1260
|
+
return {"success": False, "error": str(e), "message": "Failed to enable EBS encryption by default"}
|
1261
|
+
|
1262
|
+
return {"success": False, "message": f"Unknown encryption control: {control.control_id}"}
|
1408
1263
|
|
1409
1264
|
async def _deploy_network_control(
|
1410
|
-
self,
|
1411
|
-
control: SecurityControl,
|
1412
|
-
session: boto3.Session,
|
1413
|
-
account_id: str
|
1265
|
+
self, control: SecurityControl, session: boto3.Session, account_id: str
|
1414
1266
|
) -> Dict[str, Any]:
|
1415
1267
|
"""Deploy network security control."""
|
1416
|
-
|
1417
|
-
if control.control_id ==
|
1418
|
-
ec2 = session.client(
|
1419
|
-
logs = session.client(
|
1420
|
-
|
1268
|
+
|
1269
|
+
if control.control_id == "NET-001": # VPC Flow Logs
|
1270
|
+
ec2 = session.client("ec2")
|
1271
|
+
logs = session.client("logs")
|
1272
|
+
|
1421
1273
|
try:
|
1422
1274
|
# Get VPCs without flow logs
|
1423
|
-
vpcs = ec2.describe_vpcs()[
|
1424
|
-
flow_logs = ec2.describe_flow_logs()[
|
1425
|
-
|
1426
|
-
vpc_with_flow_logs = {
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
vpcs_needing_flow_logs = [
|
1432
|
-
vpc['VpcId'] for vpc in vpcs
|
1433
|
-
if vpc['VpcId'] not in vpc_with_flow_logs
|
1434
|
-
]
|
1435
|
-
|
1275
|
+
vpcs = ec2.describe_vpcs()["Vpcs"]
|
1276
|
+
flow_logs = ec2.describe_flow_logs()["FlowLogs"]
|
1277
|
+
|
1278
|
+
vpc_with_flow_logs = {fl["ResourceId"] for fl in flow_logs if fl["ResourceType"] == "VPC"}
|
1279
|
+
|
1280
|
+
vpcs_needing_flow_logs = [vpc["VpcId"] for vpc in vpcs if vpc["VpcId"] not in vpc_with_flow_logs]
|
1281
|
+
|
1436
1282
|
enabled_count = 0
|
1437
1283
|
failed_vpcs = []
|
1438
|
-
|
1284
|
+
|
1439
1285
|
for vpc_id in vpcs_needing_flow_logs:
|
1440
1286
|
try:
|
1441
1287
|
# Create log group
|
1442
|
-
log_group_name = f
|
1443
|
-
|
1288
|
+
log_group_name = f"/aws/vpc/flowlogs/{vpc_id}"
|
1289
|
+
|
1444
1290
|
try:
|
1445
1291
|
logs.create_log_group(logGroupName=log_group_name)
|
1446
1292
|
except logs.exceptions.ResourceAlreadyExistsException:
|
1447
1293
|
pass # Log group already exists
|
1448
|
-
|
1294
|
+
|
1449
1295
|
# Create flow log
|
1450
1296
|
ec2.create_flow_logs(
|
1451
1297
|
ResourceIds=[vpc_id],
|
1452
|
-
ResourceType=
|
1453
|
-
TrafficType=
|
1454
|
-
LogDestinationType=
|
1455
|
-
LogGroupName=log_group_name
|
1298
|
+
ResourceType="VPC",
|
1299
|
+
TrafficType="ALL",
|
1300
|
+
LogDestinationType="cloud-watch-logs",
|
1301
|
+
LogGroupName=log_group_name,
|
1456
1302
|
)
|
1457
|
-
|
1303
|
+
|
1458
1304
|
enabled_count += 1
|
1459
|
-
|
1305
|
+
|
1460
1306
|
except ClientError as e:
|
1461
|
-
failed_vpcs.append({
|
1462
|
-
|
1463
|
-
'error': str(e)
|
1464
|
-
})
|
1465
|
-
|
1307
|
+
failed_vpcs.append({"vpc_id": vpc_id, "error": str(e)})
|
1308
|
+
|
1466
1309
|
return {
|
1467
|
-
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1310
|
+
"success": len(failed_vpcs) == 0,
|
1311
|
+
"message": f"Enabled flow logs for {enabled_count} VPCs",
|
1312
|
+
"enabled_count": enabled_count,
|
1313
|
+
"failed_vpcs": failed_vpcs,
|
1471
1314
|
}
|
1472
|
-
|
1315
|
+
|
1473
1316
|
except ClientError as e:
|
1474
|
-
return {
|
1475
|
-
|
1476
|
-
|
1477
|
-
'message': 'Failed to deploy VPC flow logs'
|
1478
|
-
}
|
1479
|
-
|
1480
|
-
return {
|
1481
|
-
'success': False,
|
1482
|
-
'message': f'Unknown network control: {control.control_id}'
|
1483
|
-
}
|
1317
|
+
return {"success": False, "error": str(e), "message": "Failed to deploy VPC flow logs"}
|
1318
|
+
|
1319
|
+
return {"success": False, "message": f"Unknown network control: {control.control_id}"}
|
1484
1320
|
|
1485
1321
|
async def _deploy_audit_control(
|
1486
|
-
self,
|
1487
|
-
control: SecurityControl,
|
1488
|
-
session: boto3.Session,
|
1489
|
-
account_id: str
|
1322
|
+
self, control: SecurityControl, session: boto3.Session, account_id: str
|
1490
1323
|
) -> Dict[str, Any]:
|
1491
1324
|
"""Deploy audit logging control."""
|
1492
|
-
|
1493
|
-
if control.control_id ==
|
1325
|
+
|
1326
|
+
if control.control_id == "AUD-001": # CloudTrail
|
1494
1327
|
# CloudTrail deployment would be complex and organization-wide
|
1495
1328
|
# For safety, return success without making changes
|
1496
1329
|
return {
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1330
|
+
"success": True,
|
1331
|
+
"message": "CloudTrail audit logging verified (organization-wide configuration)",
|
1332
|
+
"organization_wide": True,
|
1500
1333
|
}
|
1501
|
-
|
1502
|
-
return {
|
1503
|
-
'success': False,
|
1504
|
-
'message': f'Unknown audit control: {control.control_id}'
|
1505
|
-
}
|
1334
|
+
|
1335
|
+
return {"success": False, "message": f"Unknown audit control: {control.control_id}"}
|
1506
1336
|
|
1507
1337
|
async def _deploy_compliance_control(
|
1508
|
-
self,
|
1509
|
-
control: SecurityControl,
|
1510
|
-
session: boto3.Session,
|
1511
|
-
account_id: str
|
1338
|
+
self, control: SecurityControl, session: boto3.Session, account_id: str
|
1512
1339
|
) -> Dict[str, Any]:
|
1513
1340
|
"""Deploy compliance monitoring control."""
|
1514
|
-
|
1515
|
-
if control.control_id ==
|
1516
|
-
config = session.client(
|
1517
|
-
|
1341
|
+
|
1342
|
+
if control.control_id == "CMP-001": # AWS Config
|
1343
|
+
config = session.client("config")
|
1344
|
+
|
1518
1345
|
try:
|
1519
1346
|
# Check if Config is already set up
|
1520
1347
|
try:
|
1521
|
-
recorders = config.describe_configuration_recorders()[
|
1348
|
+
recorders = config.describe_configuration_recorders()["ConfigurationRecorders"]
|
1522
1349
|
if recorders:
|
1523
|
-
return {
|
1524
|
-
'success': True,
|
1525
|
-
'message': 'AWS Config already configured',
|
1526
|
-
'already_configured': True
|
1527
|
-
}
|
1350
|
+
return {"success": True, "message": "AWS Config already configured", "already_configured": True}
|
1528
1351
|
except ClientError:
|
1529
1352
|
pass
|
1530
|
-
|
1353
|
+
|
1531
1354
|
# Set up Config (simplified version)
|
1532
1355
|
config.put_configuration_recorder(
|
1533
1356
|
ConfigurationRecorder={
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
'allSupported': True,
|
1538
|
-
'includeGlobalResourceTypes': True
|
1539
|
-
}
|
1357
|
+
"name": "default",
|
1358
|
+
"roleARN": f"arn:aws:iam::{account_id}:role/aws-config-role",
|
1359
|
+
"recordingGroup": {"allSupported": True, "includeGlobalResourceTypes": True},
|
1540
1360
|
}
|
1541
1361
|
)
|
1542
|
-
|
1543
|
-
return {
|
1544
|
-
|
1545
|
-
'message': 'AWS Config configuration recorder created'
|
1546
|
-
}
|
1547
|
-
|
1362
|
+
|
1363
|
+
return {"success": True, "message": "AWS Config configuration recorder created"}
|
1364
|
+
|
1548
1365
|
except ClientError as e:
|
1549
|
-
return {
|
1550
|
-
|
1551
|
-
|
1552
|
-
'message': 'Failed to set up AWS Config'
|
1553
|
-
}
|
1554
|
-
|
1555
|
-
return {
|
1556
|
-
'success': False,
|
1557
|
-
'message': f'Unknown compliance control: {control.control_id}'
|
1558
|
-
}
|
1366
|
+
return {"success": False, "error": str(e), "message": "Failed to set up AWS Config"}
|
1367
|
+
|
1368
|
+
return {"success": False, "message": f"Unknown compliance control: {control.control_id}"}
|
1559
1369
|
|
1560
1370
|
async def _validate_control_deployments(
|
1561
|
-
self,
|
1562
|
-
deployment_results: Dict[str, Any],
|
1563
|
-
target_accounts: List[str]
|
1371
|
+
self, deployment_results: Dict[str, Any], target_accounts: List[str]
|
1564
1372
|
) -> Dict[str, Any]:
|
1565
1373
|
"""Validate that deployed controls are working correctly."""
|
1566
|
-
|
1374
|
+
|
1567
1375
|
print_info("Validating control deployments...")
|
1568
|
-
|
1376
|
+
|
1569
1377
|
validation_results = {
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1378
|
+
"total_validations": 0,
|
1379
|
+
"successful_validations": 0,
|
1380
|
+
"failed_validations": 0,
|
1381
|
+
"validation_details": {},
|
1574
1382
|
}
|
1575
|
-
|
1383
|
+
|
1576
1384
|
# For each successfully deployed control, run validation checks
|
1577
|
-
for control_id, account_results in deployment_results.get(
|
1578
|
-
|
1385
|
+
for control_id, account_results in deployment_results.get("control_results", {}).items():
|
1579
1386
|
# Find the control definition
|
1580
1387
|
control = next((c for c in self.security_controls if c.control_id == control_id), None)
|
1581
1388
|
if not control:
|
1582
1389
|
continue
|
1583
|
-
|
1584
|
-
validation_results[
|
1585
|
-
|
1390
|
+
|
1391
|
+
validation_results["validation_details"][control_id] = {}
|
1392
|
+
|
1586
1393
|
for account_id, deployment_result in account_results.items():
|
1587
|
-
if deployment_result.get(
|
1588
|
-
|
1394
|
+
if deployment_result.get("success", False):
|
1589
1395
|
# Run validation checks for this control-account combination
|
1590
|
-
validation_result = await self._validate_control_in_account(
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
if validation_result.get('valid', False):
|
1598
|
-
validation_results['successful_validations'] += 1
|
1396
|
+
validation_result = await self._validate_control_in_account(control, account_id)
|
1397
|
+
|
1398
|
+
validation_results["validation_details"][control_id][account_id] = validation_result
|
1399
|
+
validation_results["total_validations"] += 1
|
1400
|
+
|
1401
|
+
if validation_result.get("valid", False):
|
1402
|
+
validation_results["successful_validations"] += 1
|
1599
1403
|
else:
|
1600
|
-
validation_results[
|
1601
|
-
|
1404
|
+
validation_results["failed_validations"] += 1
|
1405
|
+
|
1602
1406
|
success_rate = (
|
1603
|
-
validation_results[
|
1604
|
-
max(1, validation_results['total_validations'])
|
1407
|
+
validation_results["successful_validations"] / max(1, validation_results["total_validations"])
|
1605
1408
|
) * 100
|
1606
|
-
|
1409
|
+
|
1607
1410
|
print_info(f"Validation completed: {success_rate:.1f}% success rate")
|
1608
|
-
|
1411
|
+
|
1609
1412
|
return validation_results
|
1610
1413
|
|
1611
|
-
async def _validate_control_in_account(
|
1612
|
-
self,
|
1613
|
-
control: SecurityControl,
|
1614
|
-
account_id: str
|
1615
|
-
) -> Dict[str, Any]:
|
1414
|
+
async def _validate_control_in_account(self, control: SecurityControl, account_id: str) -> Dict[str, Any]:
|
1616
1415
|
"""Validate a specific control in a specific account."""
|
1617
|
-
|
1416
|
+
|
1618
1417
|
try:
|
1619
1418
|
# Get account session for validation
|
1620
1419
|
account_session = self._assume_cross_account_role(account_id)
|
1621
1420
|
if not account_session:
|
1622
1421
|
account_session = self.session
|
1623
|
-
|
1624
|
-
validation_results = {
|
1625
|
-
|
1626
|
-
'checks_passed': [],
|
1627
|
-
'checks_failed': [],
|
1628
|
-
'details': {}
|
1629
|
-
}
|
1630
|
-
|
1422
|
+
|
1423
|
+
validation_results = {"valid": True, "checks_passed": [], "checks_failed": [], "details": {}}
|
1424
|
+
|
1631
1425
|
# Run control-specific validation checks
|
1632
1426
|
for check in control.validation_checks:
|
1633
|
-
check_result = await self._run_validation_check(
|
1634
|
-
|
1635
|
-
)
|
1636
|
-
|
1637
|
-
if check_result.get('passed', False):
|
1638
|
-
validation_results['checks_passed'].append(check)
|
1427
|
+
check_result = await self._run_validation_check(check, control, account_session, account_id)
|
1428
|
+
|
1429
|
+
if check_result.get("passed", False):
|
1430
|
+
validation_results["checks_passed"].append(check)
|
1639
1431
|
else:
|
1640
|
-
validation_results[
|
1641
|
-
validation_results[
|
1642
|
-
|
1643
|
-
validation_results[
|
1644
|
-
|
1432
|
+
validation_results["checks_failed"].append(check)
|
1433
|
+
validation_results["valid"] = False
|
1434
|
+
|
1435
|
+
validation_results["details"][check] = check_result
|
1436
|
+
|
1645
1437
|
return validation_results
|
1646
|
-
|
1438
|
+
|
1647
1439
|
except Exception as e:
|
1648
|
-
return {
|
1649
|
-
'valid': False,
|
1650
|
-
'error': str(e),
|
1651
|
-
'checks_passed': [],
|
1652
|
-
'checks_failed': control.validation_checks
|
1653
|
-
}
|
1440
|
+
return {"valid": False, "error": str(e), "checks_passed": [], "checks_failed": control.validation_checks}
|
1654
1441
|
|
1655
1442
|
async def _run_validation_check(
|
1656
|
-
self,
|
1657
|
-
check: str,
|
1658
|
-
control: SecurityControl,
|
1659
|
-
session: boto3.Session,
|
1660
|
-
account_id: str
|
1443
|
+
self, check: str, control: SecurityControl, session: boto3.Session, account_id: str
|
1661
1444
|
) -> Dict[str, Any]:
|
1662
1445
|
"""Run a specific validation check."""
|
1663
|
-
|
1446
|
+
|
1664
1447
|
# Simplified validation checks
|
1665
|
-
if check ==
|
1448
|
+
if check == "verify_password_policy_applied":
|
1666
1449
|
try:
|
1667
|
-
iam = session.client(
|
1450
|
+
iam = session.client("iam")
|
1668
1451
|
policy = iam.get_account_password_policy()
|
1669
|
-
|
1452
|
+
|
1670
1453
|
# Check if policy meets requirements
|
1671
1454
|
template = control.deployment_template
|
1672
|
-
current = policy[
|
1673
|
-
|
1674
|
-
meets_requirements = (
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
return {
|
1679
|
-
'passed': meets_requirements,
|
1680
|
-
'details': current
|
1681
|
-
}
|
1682
|
-
|
1455
|
+
current = policy["PasswordPolicy"]
|
1456
|
+
|
1457
|
+
meets_requirements = current.get("MinimumPasswordLength", 0) >= template["MinimumPasswordLength"]
|
1458
|
+
|
1459
|
+
return {"passed": meets_requirements, "details": current}
|
1460
|
+
|
1683
1461
|
except ClientError:
|
1684
|
-
return {
|
1685
|
-
|
1686
|
-
elif check ==
|
1462
|
+
return {"passed": False, "error": "No password policy found"}
|
1463
|
+
|
1464
|
+
elif check == "verify_bucket_encryption_enabled":
|
1687
1465
|
try:
|
1688
|
-
s3 = session.client(
|
1689
|
-
buckets = s3.list_buckets()[
|
1690
|
-
|
1466
|
+
s3 = session.client("s3")
|
1467
|
+
buckets = s3.list_buckets()["Buckets"]
|
1468
|
+
|
1691
1469
|
encrypted_buckets = 0
|
1692
1470
|
total_buckets = min(len(buckets), 10) # Limit for validation
|
1693
|
-
|
1471
|
+
|
1694
1472
|
for bucket in buckets[:10]:
|
1695
1473
|
try:
|
1696
|
-
s3.get_bucket_encryption(Bucket=bucket[
|
1474
|
+
s3.get_bucket_encryption(Bucket=bucket["Name"])
|
1697
1475
|
encrypted_buckets += 1
|
1698
1476
|
except ClientError:
|
1699
1477
|
pass # Bucket not encrypted
|
1700
|
-
|
1478
|
+
|
1701
1479
|
encryption_rate = encrypted_buckets / max(1, total_buckets)
|
1702
|
-
|
1480
|
+
|
1703
1481
|
return {
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1482
|
+
"passed": encryption_rate >= 0.8, # 80% threshold
|
1483
|
+
"encrypted_buckets": encrypted_buckets,
|
1484
|
+
"total_buckets": total_buckets,
|
1485
|
+
"encryption_rate": encryption_rate,
|
1708
1486
|
}
|
1709
|
-
|
1487
|
+
|
1710
1488
|
except ClientError as e:
|
1711
|
-
return {
|
1712
|
-
|
1713
|
-
elif check ==
|
1489
|
+
return {"passed": False, "error": str(e)}
|
1490
|
+
|
1491
|
+
elif check == "verify_flow_logs_enabled":
|
1714
1492
|
try:
|
1715
|
-
ec2 = session.client(
|
1716
|
-
|
1717
|
-
vpcs = ec2.describe_vpcs()[
|
1718
|
-
flow_logs = ec2.describe_flow_logs()[
|
1719
|
-
|
1720
|
-
vpc_with_flow_logs = {
|
1721
|
-
|
1722
|
-
if fl['ResourceType'] == 'VPC'
|
1723
|
-
}
|
1724
|
-
|
1493
|
+
ec2 = session.client("ec2")
|
1494
|
+
|
1495
|
+
vpcs = ec2.describe_vpcs()["Vpcs"]
|
1496
|
+
flow_logs = ec2.describe_flow_logs()["FlowLogs"]
|
1497
|
+
|
1498
|
+
vpc_with_flow_logs = {fl["ResourceId"] for fl in flow_logs if fl["ResourceType"] == "VPC"}
|
1499
|
+
|
1725
1500
|
vpcs_with_logs = len(vpc_with_flow_logs)
|
1726
1501
|
total_vpcs = len(vpcs)
|
1727
|
-
|
1502
|
+
|
1728
1503
|
coverage_rate = vpcs_with_logs / max(1, total_vpcs)
|
1729
|
-
|
1504
|
+
|
1730
1505
|
return {
|
1731
|
-
|
1732
|
-
|
1733
|
-
|
1734
|
-
|
1506
|
+
"passed": coverage_rate >= 0.8, # 80% coverage threshold
|
1507
|
+
"vpcs_with_logs": vpcs_with_logs,
|
1508
|
+
"total_vpcs": total_vpcs,
|
1509
|
+
"coverage_rate": coverage_rate,
|
1735
1510
|
}
|
1736
|
-
|
1511
|
+
|
1737
1512
|
except ClientError as e:
|
1738
|
-
return {
|
1739
|
-
|
1513
|
+
return {"passed": False, "error": str(e)}
|
1514
|
+
|
1740
1515
|
# Default: assume check passed for unknown checks
|
1741
|
-
return {
|
1742
|
-
'passed': True,
|
1743
|
-
'message': f'Validation check {check} not implemented'
|
1744
|
-
}
|
1516
|
+
return {"passed": True, "message": f"Validation check {check} not implemented"}
|
1745
1517
|
|
1746
1518
|
async def _generate_deployment_report(
|
1747
1519
|
self,
|
@@ -1750,69 +1522,70 @@ class MultiAccountSecurityController:
|
|
1750
1522
|
controls_deployed: List[SecurityControl],
|
1751
1523
|
target_accounts: List[str],
|
1752
1524
|
deployment_results: Dict[str, Any],
|
1753
|
-
validation_results: Dict[str, Any]
|
1525
|
+
validation_results: Dict[str, Any],
|
1754
1526
|
) -> MultiAccountSecurityReport:
|
1755
1527
|
"""Generate comprehensive deployment report."""
|
1756
|
-
|
1528
|
+
|
1757
1529
|
# Calculate overall metrics
|
1758
1530
|
total_deployments = sum(
|
1759
|
-
len(account_results)
|
1760
|
-
for account_results in deployment_results.get('control_results', {}).values()
|
1531
|
+
len(account_results) for account_results in deployment_results.get("control_results", {}).values()
|
1761
1532
|
)
|
1762
|
-
|
1533
|
+
|
1763
1534
|
successful_deployments = sum(
|
1764
|
-
1
|
1535
|
+
1
|
1536
|
+
for account_results in deployment_results.get("control_results", {}).values()
|
1765
1537
|
for result in account_results.values()
|
1766
|
-
if result.get(
|
1538
|
+
if result.get("success", False)
|
1767
1539
|
)
|
1768
|
-
|
1540
|
+
|
1769
1541
|
overall_success_rate = (successful_deployments / max(1, total_deployments)) * 100
|
1770
|
-
|
1542
|
+
|
1771
1543
|
# Calculate compliance scores
|
1772
|
-
compliance_scores = self._calculate_compliance_scores(
|
1773
|
-
|
1774
|
-
)
|
1775
|
-
|
1544
|
+
compliance_scores = self._calculate_compliance_scores(controls_deployed, deployment_results, validation_results)
|
1545
|
+
|
1776
1546
|
# Identify high-priority findings
|
1777
|
-
high_priority_findings = self._identify_high_priority_findings(
|
1778
|
-
|
1779
|
-
)
|
1780
|
-
|
1547
|
+
high_priority_findings = self._identify_high_priority_findings(deployment_results, validation_results)
|
1548
|
+
|
1781
1549
|
# Generate cost analysis
|
1782
|
-
cost_analysis = self._calculate_deployment_costs(
|
1783
|
-
|
1784
|
-
)
|
1785
|
-
|
1550
|
+
cost_analysis = self._calculate_deployment_costs(controls_deployed, target_accounts, deployment_results)
|
1551
|
+
|
1786
1552
|
# Generate recommendations
|
1787
1553
|
recommendations = self._generate_deployment_recommendations(
|
1788
1554
|
deployment_results, validation_results, overall_success_rate
|
1789
1555
|
)
|
1790
|
-
|
1556
|
+
|
1791
1557
|
# Create executive summary
|
1792
1558
|
executive_summary = {
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
1805
|
-
|
1806
|
-
|
1807
|
-
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
1559
|
+
"deployment_success_rate": overall_success_rate,
|
1560
|
+
"accounts_secured": len(
|
1561
|
+
[
|
1562
|
+
account_id
|
1563
|
+
for account_id in target_accounts
|
1564
|
+
if any(
|
1565
|
+
account_results.get(account_id, {}).get("success", False)
|
1566
|
+
for account_results in deployment_results.get("control_results", {}).values()
|
1567
|
+
)
|
1568
|
+
]
|
1569
|
+
),
|
1570
|
+
"controls_deployed_successfully": len(
|
1571
|
+
[
|
1572
|
+
control
|
1573
|
+
for control in controls_deployed
|
1574
|
+
if any(
|
1575
|
+
result.get("success", False)
|
1576
|
+
for result in deployment_results.get("control_results", {}).get(control.control_id, {}).values()
|
1577
|
+
)
|
1578
|
+
]
|
1579
|
+
),
|
1580
|
+
"validation_success_rate": (
|
1581
|
+
validation_results.get("successful_validations", 0)
|
1582
|
+
/ max(1, validation_results.get("total_validations", 1))
|
1583
|
+
)
|
1584
|
+
* 100,
|
1585
|
+
"estimated_risk_reduction": self._calculate_risk_reduction(controls_deployed, successful_deployments),
|
1586
|
+
"business_impact": "Significant improvement in organization security posture",
|
1814
1587
|
}
|
1815
|
-
|
1588
|
+
|
1816
1589
|
return MultiAccountSecurityReport(
|
1817
1590
|
report_id=deployment_id,
|
1818
1591
|
timestamp=start_time,
|
@@ -1823,207 +1596,194 @@ class MultiAccountSecurityController:
|
|
1823
1596
|
overall_security_score=overall_success_rate,
|
1824
1597
|
compliance_scores=compliance_scores,
|
1825
1598
|
high_priority_findings=high_priority_findings,
|
1826
|
-
deployment_summary=deployment_results.get(
|
1599
|
+
deployment_summary=deployment_results.get("summary", {}),
|
1827
1600
|
cost_analysis=cost_analysis,
|
1828
1601
|
recommendations=recommendations,
|
1829
|
-
executive_summary=executive_summary
|
1602
|
+
executive_summary=executive_summary,
|
1830
1603
|
)
|
1831
1604
|
|
1832
1605
|
def _calculate_compliance_scores(
|
1833
1606
|
self,
|
1834
1607
|
controls_deployed: List[SecurityControl],
|
1835
1608
|
deployment_results: Dict[str, Any],
|
1836
|
-
validation_results: Dict[str, Any]
|
1609
|
+
validation_results: Dict[str, Any],
|
1837
1610
|
) -> Dict[str, float]:
|
1838
1611
|
"""Calculate compliance scores by framework."""
|
1839
|
-
|
1612
|
+
|
1840
1613
|
compliance_scores = {}
|
1841
|
-
|
1614
|
+
|
1842
1615
|
# Group controls by compliance framework
|
1843
1616
|
frameworks = set()
|
1844
1617
|
for control in controls_deployed:
|
1845
1618
|
frameworks.update(control.compliance_frameworks)
|
1846
|
-
|
1619
|
+
|
1847
1620
|
for framework in frameworks:
|
1848
1621
|
framework_controls = [
|
1849
|
-
control for control in controls_deployed
|
1850
|
-
if framework in control.compliance_frameworks
|
1622
|
+
control for control in controls_deployed if framework in control.compliance_frameworks
|
1851
1623
|
]
|
1852
|
-
|
1624
|
+
|
1853
1625
|
if not framework_controls:
|
1854
1626
|
continue
|
1855
|
-
|
1627
|
+
|
1856
1628
|
# Calculate success rate for this framework
|
1857
1629
|
successful_count = 0
|
1858
1630
|
total_count = 0
|
1859
|
-
|
1631
|
+
|
1860
1632
|
for control in framework_controls:
|
1861
|
-
control_results = deployment_results.get(
|
1862
|
-
|
1633
|
+
control_results = deployment_results.get("control_results", {}).get(control.control_id, {})
|
1634
|
+
|
1863
1635
|
for account_result in control_results.values():
|
1864
1636
|
total_count += 1
|
1865
|
-
if account_result.get(
|
1637
|
+
if account_result.get("success", False):
|
1866
1638
|
successful_count += 1
|
1867
|
-
|
1639
|
+
|
1868
1640
|
framework_score = (successful_count / max(1, total_count)) * 100
|
1869
1641
|
compliance_scores[framework] = framework_score
|
1870
|
-
|
1642
|
+
|
1871
1643
|
return compliance_scores
|
1872
1644
|
|
1873
1645
|
def _identify_high_priority_findings(
|
1874
|
-
self,
|
1875
|
-
deployment_results: Dict[str, Any],
|
1876
|
-
validation_results: Dict[str, Any]
|
1646
|
+
self, deployment_results: Dict[str, Any], validation_results: Dict[str, Any]
|
1877
1647
|
) -> List[Dict[str, Any]]:
|
1878
1648
|
"""Identify high-priority security findings that require attention."""
|
1879
|
-
|
1649
|
+
|
1880
1650
|
findings = []
|
1881
|
-
|
1651
|
+
|
1882
1652
|
# Failed deployments for critical controls
|
1883
|
-
for control_id, account_results in deployment_results.get(
|
1884
|
-
|
1653
|
+
for control_id, account_results in deployment_results.get("control_results", {}).items():
|
1885
1654
|
control = next((c for c in self.security_controls if c.control_id == control_id), None)
|
1886
1655
|
if not control:
|
1887
1656
|
continue
|
1888
|
-
|
1657
|
+
|
1889
1658
|
failed_accounts = [
|
1890
|
-
account_id for account_id, result in account_results.items()
|
1891
|
-
if not result.get('success', False)
|
1659
|
+
account_id for account_id, result in account_results.items() if not result.get("success", False)
|
1892
1660
|
]
|
1893
|
-
|
1661
|
+
|
1894
1662
|
if failed_accounts and control.requires_approval:
|
1895
|
-
findings.append(
|
1896
|
-
|
1897
|
-
|
1898
|
-
|
1899
|
-
|
1900
|
-
|
1901
|
-
|
1902
|
-
|
1903
|
-
|
1904
|
-
|
1663
|
+
findings.append(
|
1664
|
+
{
|
1665
|
+
"type": "deployment_failure",
|
1666
|
+
"severity": "HIGH",
|
1667
|
+
"control_id": control_id,
|
1668
|
+
"control_name": control.control_name,
|
1669
|
+
"failed_accounts": failed_accounts,
|
1670
|
+
"message": f"{control.control_name} failed to deploy to {len(failed_accounts)} accounts",
|
1671
|
+
"recommendation": f"Review deployment logs and retry deployment for {control.control_name}",
|
1672
|
+
}
|
1673
|
+
)
|
1674
|
+
|
1905
1675
|
# Failed validations
|
1906
|
-
for control_id, account_validations in validation_results.get(
|
1907
|
-
|
1676
|
+
for control_id, account_validations in validation_results.get("validation_details", {}).items():
|
1908
1677
|
control = next((c for c in self.security_controls if c.control_id == control_id), None)
|
1909
1678
|
if not control:
|
1910
1679
|
continue
|
1911
|
-
|
1680
|
+
|
1912
1681
|
failed_validations = [
|
1913
|
-
account_id
|
1914
|
-
|
1682
|
+
account_id
|
1683
|
+
for account_id, validation in account_validations.items()
|
1684
|
+
if not validation.get("valid", False)
|
1915
1685
|
]
|
1916
|
-
|
1686
|
+
|
1917
1687
|
if failed_validations:
|
1918
|
-
findings.append(
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1922
|
-
|
1923
|
-
|
1924
|
-
|
1925
|
-
|
1926
|
-
|
1927
|
-
|
1688
|
+
findings.append(
|
1689
|
+
{
|
1690
|
+
"type": "validation_failure",
|
1691
|
+
"severity": "MEDIUM",
|
1692
|
+
"control_id": control_id,
|
1693
|
+
"control_name": control.control_name,
|
1694
|
+
"failed_accounts": failed_validations,
|
1695
|
+
"message": f"{control.control_name} validation failed in {len(failed_validations)} accounts",
|
1696
|
+
"recommendation": f"Investigate and remediate validation failures for {control.control_name}",
|
1697
|
+
}
|
1698
|
+
)
|
1699
|
+
|
1928
1700
|
# Sort by severity
|
1929
|
-
severity_order = {
|
1930
|
-
findings.sort(key=lambda x: severity_order.get(x[
|
1931
|
-
|
1701
|
+
severity_order = {"HIGH": 3, "MEDIUM": 2, "LOW": 1}
|
1702
|
+
findings.sort(key=lambda x: severity_order.get(x["severity"], 0), reverse=True)
|
1703
|
+
|
1932
1704
|
return findings
|
1933
1705
|
|
1934
1706
|
def _calculate_deployment_costs(
|
1935
|
-
self,
|
1936
|
-
controls_deployed: List[SecurityControl],
|
1937
|
-
target_accounts: List[str],
|
1938
|
-
deployment_results: Dict[str, Any]
|
1707
|
+
self, controls_deployed: List[SecurityControl], target_accounts: List[str], deployment_results: Dict[str, Any]
|
1939
1708
|
) -> Dict[str, float]:
|
1940
1709
|
"""Calculate costs associated with security control deployment."""
|
1941
|
-
|
1710
|
+
|
1942
1711
|
# Simplified cost calculation
|
1943
1712
|
base_cost_per_account = 50.0 # Base monthly cost per account
|
1944
1713
|
cost_per_control = 10.0 # Additional cost per control
|
1945
|
-
|
1714
|
+
|
1946
1715
|
successful_deployments = sum(
|
1947
|
-
1
|
1716
|
+
1
|
1717
|
+
for account_results in deployment_results.get("control_results", {}).values()
|
1948
1718
|
for result in account_results.values()
|
1949
|
-
if result.get(
|
1719
|
+
if result.get("success", False)
|
1950
1720
|
)
|
1951
|
-
|
1721
|
+
|
1952
1722
|
monthly_operational_cost = (
|
1953
|
-
len(target_accounts) * base_cost_per_account +
|
1954
|
-
successful_deployments * cost_per_control
|
1723
|
+
len(target_accounts) * base_cost_per_account + successful_deployments * cost_per_control
|
1955
1724
|
)
|
1956
|
-
|
1725
|
+
|
1957
1726
|
# One-time deployment cost
|
1958
|
-
deployment_hours =
|
1959
|
-
control.estimated_deployment_time for control in controls_deployed
|
1960
|
-
)
|
1961
|
-
|
1727
|
+
deployment_hours = (
|
1728
|
+
sum(control.estimated_deployment_time for control in controls_deployed) / 60.0
|
1729
|
+
) # Convert minutes to hours
|
1730
|
+
|
1962
1731
|
deployment_cost = deployment_hours * 150.0 # $150/hour for security engineering
|
1963
|
-
|
1732
|
+
|
1964
1733
|
# Calculate savings from risk reduction
|
1965
|
-
risk_reduction_value = self._calculate_risk_reduction_value(
|
1966
|
-
|
1967
|
-
)
|
1968
|
-
|
1734
|
+
risk_reduction_value = self._calculate_risk_reduction_value(controls_deployed, successful_deployments)
|
1735
|
+
|
1969
1736
|
return {
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
|
1974
|
-
|
1737
|
+
"monthly_operational_cost": monthly_operational_cost,
|
1738
|
+
"one_time_deployment_cost": deployment_cost,
|
1739
|
+
"annual_risk_reduction_value": risk_reduction_value,
|
1740
|
+
"roi_percentage": ((risk_reduction_value - monthly_operational_cost * 12) / (monthly_operational_cost * 12))
|
1741
|
+
* 100
|
1742
|
+
if monthly_operational_cost > 0
|
1743
|
+
else 0,
|
1975
1744
|
}
|
1976
1745
|
|
1977
1746
|
def _calculate_risk_reduction_value(
|
1978
|
-
self,
|
1979
|
-
controls_deployed: List[SecurityControl],
|
1980
|
-
successful_deployments: int
|
1747
|
+
self, controls_deployed: List[SecurityControl], successful_deployments: int
|
1981
1748
|
) -> float:
|
1982
1749
|
"""Calculate the business value of risk reduction."""
|
1983
|
-
|
1750
|
+
|
1984
1751
|
# Base risk reduction value per successful control deployment
|
1985
1752
|
base_value_per_control = 25000.0 # $25K annual value per control
|
1986
|
-
|
1753
|
+
|
1987
1754
|
# Multiply by success rate
|
1988
1755
|
total_value = successful_deployments * base_value_per_control
|
1989
|
-
|
1756
|
+
|
1990
1757
|
# Apply diminishing returns for multiple controls
|
1991
1758
|
if len(controls_deployed) > 1:
|
1992
|
-
total_value *=
|
1993
|
-
|
1759
|
+
total_value *= 1 + 0.1 * (len(controls_deployed) - 1)
|
1760
|
+
|
1994
1761
|
return total_value
|
1995
1762
|
|
1996
|
-
def _calculate_risk_reduction(
|
1997
|
-
self,
|
1998
|
-
controls_deployed: List[SecurityControl],
|
1999
|
-
successful_deployments: int
|
2000
|
-
) -> str:
|
1763
|
+
def _calculate_risk_reduction(self, controls_deployed: List[SecurityControl], successful_deployments: int) -> str:
|
2001
1764
|
"""Calculate estimated risk reduction percentage."""
|
2002
|
-
|
1765
|
+
|
2003
1766
|
if not controls_deployed:
|
2004
1767
|
return "0%"
|
2005
|
-
|
1768
|
+
|
2006
1769
|
# Each successful control deployment reduces risk
|
2007
1770
|
base_reduction = 15.0 # 15% base reduction per control
|
2008
1771
|
total_controls = len(self.security_controls)
|
2009
1772
|
deployed_controls = len(controls_deployed)
|
2010
|
-
|
1773
|
+
|
2011
1774
|
success_rate = successful_deployments / max(1, deployed_controls * len(self.account_profiles))
|
2012
|
-
|
1775
|
+
|
2013
1776
|
risk_reduction = (deployed_controls / total_controls) * base_reduction * success_rate
|
2014
|
-
|
1777
|
+
|
2015
1778
|
return f"{min(95, int(risk_reduction))}%" # Cap at 95%
|
2016
1779
|
|
2017
1780
|
def _generate_deployment_recommendations(
|
2018
|
-
self,
|
2019
|
-
deployment_results: Dict[str, Any],
|
2020
|
-
validation_results: Dict[str, Any],
|
2021
|
-
overall_success_rate: float
|
1781
|
+
self, deployment_results: Dict[str, Any], validation_results: Dict[str, Any], overall_success_rate: float
|
2022
1782
|
) -> List[str]:
|
2023
1783
|
"""Generate actionable recommendations based on deployment results."""
|
2024
|
-
|
1784
|
+
|
2025
1785
|
recommendations = []
|
2026
|
-
|
1786
|
+
|
2027
1787
|
# Success rate recommendations
|
2028
1788
|
if overall_success_rate < 80:
|
2029
1789
|
recommendations.append(
|
@@ -2032,55 +1792,56 @@ class MultiAccountSecurityController:
|
|
2032
1792
|
)
|
2033
1793
|
elif overall_success_rate >= 95:
|
2034
1794
|
recommendations.append(
|
2035
|
-
"Excellent deployment success rate! Consider expanding to additional "
|
2036
|
-
"security controls or accounts."
|
1795
|
+
"Excellent deployment success rate! Consider expanding to additional security controls or accounts."
|
2037
1796
|
)
|
2038
|
-
|
1797
|
+
|
2039
1798
|
# Failed control recommendations
|
2040
1799
|
failed_controls = [
|
2041
|
-
control_id
|
2042
|
-
|
1800
|
+
control_id
|
1801
|
+
for control_id, account_results in deployment_results.get("control_results", {}).items()
|
1802
|
+
if any(not result.get("success", False) for result in account_results.values())
|
2043
1803
|
]
|
2044
|
-
|
1804
|
+
|
2045
1805
|
if failed_controls:
|
2046
1806
|
recommendations.append(
|
2047
1807
|
f"Review and retry deployment for failed controls: {', '.join(failed_controls[:5])}. "
|
2048
1808
|
"Check account permissions and cross-account role configuration."
|
2049
1809
|
)
|
2050
|
-
|
1810
|
+
|
2051
1811
|
# Validation recommendations
|
2052
1812
|
validation_success_rate = (
|
2053
|
-
validation_results.get(
|
2054
|
-
max(1, validation_results.get('total_validations', 1))
|
1813
|
+
validation_results.get("successful_validations", 0) / max(1, validation_results.get("total_validations", 1))
|
2055
1814
|
) * 100
|
2056
|
-
|
1815
|
+
|
2057
1816
|
if validation_success_rate < 90:
|
2058
1817
|
recommendations.append(
|
2059
1818
|
"Validation success rate is below 90%. Review deployed controls "
|
2060
1819
|
"and ensure they are functioning correctly."
|
2061
1820
|
)
|
2062
|
-
|
1821
|
+
|
2063
1822
|
# Account-specific recommendations
|
2064
1823
|
if len(self.account_profiles) > self.max_concurrent_accounts:
|
2065
1824
|
recommendations.append(
|
2066
1825
|
f"Organization has more than {self.max_concurrent_accounts} accounts. "
|
2067
1826
|
"Consider implementing automated deployment pipelines for scale."
|
2068
1827
|
)
|
2069
|
-
|
1828
|
+
|
2070
1829
|
# Security improvement recommendations
|
2071
|
-
recommendations.extend(
|
2072
|
-
|
2073
|
-
|
2074
|
-
|
2075
|
-
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
1830
|
+
recommendations.extend(
|
1831
|
+
[
|
1832
|
+
"Implement continuous monitoring for deployed security controls",
|
1833
|
+
"Set up automated alerting for security control drift or failures",
|
1834
|
+
"Schedule regular security control validation and updates",
|
1835
|
+
"Consider implementing additional controls for enhanced security posture",
|
1836
|
+
"Review and update security control templates based on deployment results",
|
1837
|
+
]
|
1838
|
+
)
|
1839
|
+
|
2079
1840
|
return recommendations
|
2080
1841
|
|
2081
1842
|
def _display_deployment_summary(self, report: MultiAccountSecurityReport):
|
2082
1843
|
"""Display comprehensive deployment summary."""
|
2083
|
-
|
1844
|
+
|
2084
1845
|
# Executive summary panel
|
2085
1846
|
summary_content = (
|
2086
1847
|
f"[bold green]Multi-Account Security Deployment Complete[/bold green]\n\n"
|
@@ -2092,13 +1853,11 @@ class MultiAccountSecurityController:
|
|
2092
1853
|
f"[bold]Estimated Risk Reduction:[/bold] {report.executive_summary['estimated_risk_reduction']}\n"
|
2093
1854
|
f"[bold]Annual Value:[/bold] ${report.cost_analysis['annual_risk_reduction_value']:,.0f}"
|
2094
1855
|
)
|
2095
|
-
|
2096
|
-
console.print(
|
2097
|
-
summary_content,
|
2098
|
-
|
2099
|
-
|
2100
|
-
))
|
2101
|
-
|
1856
|
+
|
1857
|
+
console.print(
|
1858
|
+
create_panel(summary_content, title="🔒 Multi-Account Security Deployment Summary", border_style="green")
|
1859
|
+
)
|
1860
|
+
|
2102
1861
|
# Compliance scores table
|
2103
1862
|
if report.compliance_scores:
|
2104
1863
|
compliance_table = create_table(
|
@@ -2106,20 +1865,16 @@ class MultiAccountSecurityController:
|
|
2106
1865
|
columns=[
|
2107
1866
|
{"name": "Framework", "style": "cyan"},
|
2108
1867
|
{"name": "Score", "style": "green"},
|
2109
|
-
{"name": "Status", "style": "yellow"}
|
2110
|
-
]
|
1868
|
+
{"name": "Status", "style": "yellow"},
|
1869
|
+
],
|
2111
1870
|
)
|
2112
|
-
|
1871
|
+
|
2113
1872
|
for framework, score in report.compliance_scores.items():
|
2114
1873
|
status = "✅ Compliant" if score >= 90 else "⚠️ Needs Attention" if score >= 70 else "❌ Non-Compliant"
|
2115
|
-
compliance_table.add_row(
|
2116
|
-
|
2117
|
-
f"{score:.1f}%",
|
2118
|
-
status
|
2119
|
-
)
|
2120
|
-
|
1874
|
+
compliance_table.add_row(framework.replace("_", " "), f"{score:.1f}%", status)
|
1875
|
+
|
2121
1876
|
console.print(compliance_table)
|
2122
|
-
|
1877
|
+
|
2123
1878
|
# High-priority findings
|
2124
1879
|
if report.high_priority_findings:
|
2125
1880
|
findings_table = create_table(
|
@@ -2128,20 +1883,20 @@ class MultiAccountSecurityController:
|
|
2128
1883
|
{"name": "Severity", "style": "red"},
|
2129
1884
|
{"name": "Control", "style": "cyan"},
|
2130
1885
|
{"name": "Issue", "style": "yellow"},
|
2131
|
-
{"name": "Affected Accounts", "style": "blue"}
|
2132
|
-
]
|
1886
|
+
{"name": "Affected Accounts", "style": "blue"},
|
1887
|
+
],
|
2133
1888
|
)
|
2134
|
-
|
1889
|
+
|
2135
1890
|
for finding in report.high_priority_findings[:10]: # Show top 10
|
2136
1891
|
findings_table.add_row(
|
2137
|
-
finding[
|
2138
|
-
finding.get(
|
2139
|
-
finding[
|
2140
|
-
str(len(finding.get(
|
1892
|
+
finding["severity"],
|
1893
|
+
finding.get("control_name", finding.get("control_id", "Unknown"))[:30],
|
1894
|
+
finding["message"][:50] + "..." if len(finding["message"]) > 50 else finding["message"],
|
1895
|
+
str(len(finding.get("failed_accounts", []))),
|
2141
1896
|
)
|
2142
|
-
|
1897
|
+
|
2143
1898
|
console.print(findings_table)
|
2144
|
-
|
1899
|
+
|
2145
1900
|
# Cost analysis
|
2146
1901
|
cost_content = (
|
2147
1902
|
f"[bold cyan]Cost Analysis[/bold cyan]\n\n"
|
@@ -2150,105 +1905,99 @@ class MultiAccountSecurityController:
|
|
2150
1905
|
f"[yellow]Annual Risk Reduction Value:[/yellow] ${report.cost_analysis['annual_risk_reduction_value']:,.2f}\n"
|
2151
1906
|
f"[magenta]ROI:[/magenta] {report.cost_analysis['roi_percentage']:.1f}%"
|
2152
1907
|
)
|
2153
|
-
|
2154
|
-
console.print(create_panel(
|
2155
|
-
cost_content,
|
2156
|
-
title="💰 Financial Impact Analysis",
|
2157
|
-
border_style="blue"
|
2158
|
-
))
|
1908
|
+
|
1909
|
+
console.print(create_panel(cost_content, title="💰 Financial Impact Analysis", border_style="blue"))
|
2159
1910
|
|
2160
1911
|
async def _export_deployment_report(self, report: MultiAccountSecurityReport):
|
2161
1912
|
"""Export comprehensive deployment report."""
|
2162
|
-
|
1913
|
+
|
2163
1914
|
# Export JSON report
|
2164
1915
|
json_report_path = self.output_dir / f"deployment_report_{report.report_id}.json"
|
2165
|
-
|
1916
|
+
|
2166
1917
|
report_data = {
|
2167
|
-
|
2168
|
-
|
2169
|
-
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2173
|
-
|
2174
|
-
|
1918
|
+
"report_id": report.report_id,
|
1919
|
+
"timestamp": report.timestamp.isoformat(),
|
1920
|
+
"summary": {
|
1921
|
+
"total_accounts": report.total_accounts,
|
1922
|
+
"accounts_assessed": report.accounts_assessed,
|
1923
|
+
"controls_deployed": report.controls_deployed,
|
1924
|
+
"total_controls": report.total_controls,
|
1925
|
+
"overall_security_score": report.overall_security_score,
|
2175
1926
|
},
|
2176
|
-
|
2177
|
-
|
2178
|
-
|
2179
|
-
|
2180
|
-
|
2181
|
-
|
1927
|
+
"compliance_scores": report.compliance_scores,
|
1928
|
+
"high_priority_findings": report.high_priority_findings,
|
1929
|
+
"deployment_summary": report.deployment_summary,
|
1930
|
+
"cost_analysis": report.cost_analysis,
|
1931
|
+
"recommendations": report.recommendations,
|
1932
|
+
"executive_summary": report.executive_summary,
|
2182
1933
|
}
|
2183
|
-
|
2184
|
-
with open(json_report_path,
|
1934
|
+
|
1935
|
+
with open(json_report_path, "w") as f:
|
2185
1936
|
json.dump(report_data, f, indent=2)
|
2186
|
-
|
1937
|
+
|
2187
1938
|
print_success(f"Deployment report exported to: {json_report_path}")
|
2188
1939
|
|
2189
1940
|
|
2190
1941
|
class MultiAccountDeploymentTracker:
|
2191
1942
|
"""Track deployment progress and results across accounts."""
|
2192
|
-
|
1943
|
+
|
2193
1944
|
def __init__(self, output_dir: Path):
|
2194
1945
|
self.output_dir = output_dir
|
2195
1946
|
self.tracking_file = output_dir / "deployment_tracking.jsonl"
|
2196
|
-
|
1947
|
+
|
2197
1948
|
def log_deployment_event(self, event_data: Dict[str, Any]):
|
2198
1949
|
"""Log deployment event to tracking file."""
|
2199
|
-
|
2200
|
-
event_record = {
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2205
|
-
with open(self.tracking_file, 'a') as f:
|
2206
|
-
f.write(json.dumps(event_record) + '\n')
|
1950
|
+
|
1951
|
+
event_record = {"timestamp": datetime.utcnow().isoformat(), **event_data}
|
1952
|
+
|
1953
|
+
with open(self.tracking_file, "a") as f:
|
1954
|
+
f.write(json.dumps(event_record) + "\n")
|
2207
1955
|
|
2208
1956
|
|
2209
1957
|
# CLI integration for multi-account security control deployment
|
2210
1958
|
if __name__ == "__main__":
|
2211
1959
|
import argparse
|
2212
|
-
|
2213
|
-
parser = argparse.ArgumentParser(description=
|
2214
|
-
parser.add_argument(
|
2215
|
-
parser.add_argument(
|
2216
|
-
parser.add_argument(
|
2217
|
-
parser.add_argument(
|
2218
|
-
|
2219
|
-
|
2220
|
-
parser.add_argument(
|
2221
|
-
parser.add_argument(
|
2222
|
-
parser.add_argument(
|
2223
|
-
|
1960
|
+
|
1961
|
+
parser = argparse.ArgumentParser(description="Multi-Account Security Controller")
|
1962
|
+
parser.add_argument("--profile", default="default", help="AWS profile to use")
|
1963
|
+
parser.add_argument("--controls", nargs="+", help="Specific control IDs to deploy")
|
1964
|
+
parser.add_argument("--accounts", nargs="+", help="Target account IDs (optional)")
|
1965
|
+
parser.add_argument(
|
1966
|
+
"--strategy", choices=["parallel", "staged", "pilot", "critical"], default="staged", help="Deployment strategy"
|
1967
|
+
)
|
1968
|
+
parser.add_argument("--max-accounts", type=int, default=61, help="Max concurrent accounts")
|
1969
|
+
parser.add_argument("--dry-run", action="store_true", help="Dry run mode (default: enabled)")
|
1970
|
+
parser.add_argument("--execute", action="store_true", help="Execute actual deployments")
|
1971
|
+
parser.add_argument("--output-dir", default="./artifacts/multi-account-security", help="Output directory")
|
1972
|
+
|
2224
1973
|
args = parser.parse_args()
|
2225
|
-
|
1974
|
+
|
2226
1975
|
# Determine deployment strategies
|
2227
1976
|
strategy_mapping = {
|
2228
|
-
|
2229
|
-
|
2230
|
-
|
2231
|
-
|
1977
|
+
"parallel": DeploymentStrategy.PARALLEL_ALL,
|
1978
|
+
"staged": DeploymentStrategy.STAGED_ROLLOUT,
|
1979
|
+
"pilot": DeploymentStrategy.PILOT_FIRST,
|
1980
|
+
"critical": DeploymentStrategy.CRITICAL_FIRST,
|
2232
1981
|
}
|
2233
|
-
|
1982
|
+
|
2234
1983
|
async def main():
|
2235
1984
|
controller = MultiAccountSecurityController(
|
2236
1985
|
profile=args.profile,
|
2237
1986
|
output_dir=args.output_dir,
|
2238
1987
|
max_concurrent_accounts=args.max_accounts,
|
2239
|
-
dry_run=not args.execute # Dry run unless --execute is specified
|
1988
|
+
dry_run=not args.execute, # Dry run unless --execute is specified
|
2240
1989
|
)
|
2241
|
-
|
1990
|
+
|
2242
1991
|
report = await controller.deploy_security_controls_organization_wide(
|
2243
1992
|
control_ids=args.controls,
|
2244
1993
|
target_accounts=args.accounts,
|
2245
|
-
deployment_strategy=strategy_mapping[args.strategy]
|
1994
|
+
deployment_strategy=strategy_mapping[args.strategy],
|
2246
1995
|
)
|
2247
|
-
|
1996
|
+
|
2248
1997
|
print_success(f"Multi-account deployment completed: {report.report_id}")
|
2249
1998
|
print_info(f"Overall security score: {report.overall_security_score:.1f}%")
|
2250
1999
|
print_info(f"Accounts secured: {report.executive_summary['accounts_secured']}/{report.total_accounts}")
|
2251
2000
|
print_info(f"Annual value: ${report.cost_analysis['annual_risk_reduction_value']:,.0f}")
|
2252
|
-
|
2001
|
+
|
2253
2002
|
# Run the async main function
|
2254
|
-
asyncio.run(main())
|
2003
|
+
asyncio.run(main())
|