runbooks 0.7.0__py3-none-any.whl → 0.7.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 +87 -37
- runbooks/cfat/README.md +300 -49
- runbooks/cfat/__init__.py +2 -2
- runbooks/finops/__init__.py +1 -1
- runbooks/finops/cli.py +1 -1
- runbooks/inventory/collectors/__init__.py +8 -0
- runbooks/inventory/collectors/aws_management.py +791 -0
- runbooks/inventory/collectors/aws_networking.py +3 -3
- runbooks/main.py +3389 -782
- runbooks/operate/__init__.py +207 -0
- runbooks/operate/base.py +311 -0
- runbooks/operate/cloudformation_operations.py +619 -0
- runbooks/operate/cloudwatch_operations.py +496 -0
- runbooks/operate/dynamodb_operations.py +812 -0
- runbooks/operate/ec2_operations.py +926 -0
- runbooks/operate/iam_operations.py +569 -0
- runbooks/operate/s3_operations.py +1211 -0
- runbooks/operate/tagging_operations.py +655 -0
- runbooks/remediation/CLAUDE.md +100 -0
- runbooks/remediation/DOME9.md +218 -0
- runbooks/remediation/README.md +26 -0
- runbooks/remediation/Tests/__init__.py +0 -0
- runbooks/remediation/Tests/update_policy.py +74 -0
- runbooks/remediation/__init__.py +95 -0
- runbooks/remediation/acm_cert_expired_unused.py +98 -0
- runbooks/remediation/acm_remediation.py +875 -0
- runbooks/remediation/api_gateway_list.py +167 -0
- runbooks/remediation/base.py +643 -0
- runbooks/remediation/cloudtrail_remediation.py +908 -0
- runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
- runbooks/remediation/cognito_active_users.py +78 -0
- runbooks/remediation/cognito_remediation.py +856 -0
- runbooks/remediation/cognito_user_password_reset.py +163 -0
- runbooks/remediation/commons.py +455 -0
- runbooks/remediation/dynamodb_optimize.py +155 -0
- runbooks/remediation/dynamodb_remediation.py +744 -0
- runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
- runbooks/remediation/ec2_public_ips.py +134 -0
- runbooks/remediation/ec2_remediation.py +892 -0
- runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
- runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
- runbooks/remediation/ec2_unused_security_groups.py +202 -0
- runbooks/remediation/kms_enable_key_rotation.py +651 -0
- runbooks/remediation/kms_remediation.py +717 -0
- runbooks/remediation/lambda_list.py +243 -0
- runbooks/remediation/lambda_remediation.py +971 -0
- runbooks/remediation/multi_account.py +569 -0
- runbooks/remediation/rds_instance_list.py +199 -0
- runbooks/remediation/rds_remediation.py +873 -0
- runbooks/remediation/rds_snapshot_list.py +192 -0
- runbooks/remediation/requirements.txt +118 -0
- runbooks/remediation/s3_block_public_access.py +159 -0
- runbooks/remediation/s3_bucket_public_access.py +143 -0
- runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
- runbooks/remediation/s3_downloader.py +215 -0
- runbooks/remediation/s3_enable_access_logging.py +562 -0
- runbooks/remediation/s3_encryption.py +526 -0
- runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
- runbooks/remediation/s3_list.py +141 -0
- runbooks/remediation/s3_object_search.py +201 -0
- runbooks/remediation/s3_remediation.py +816 -0
- runbooks/remediation/scan_for_phrase.py +425 -0
- runbooks/remediation/workspaces_list.py +220 -0
- runbooks/security/__init__.py +9 -10
- runbooks/security/security_baseline_tester.py +4 -2
- runbooks-0.7.6.dist-info/METADATA +608 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/RECORD +84 -76
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/entry_points.txt +0 -1
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/top_level.txt +0 -1
- jupyter-agent/.env +0 -2
- jupyter-agent/.env.template +0 -2
- jupyter-agent/.gitattributes +0 -35
- jupyter-agent/.gradio/certificate.pem +0 -31
- jupyter-agent/README.md +0 -16
- jupyter-agent/__main__.log +0 -8
- jupyter-agent/app.py +0 -256
- jupyter-agent/cloudops-agent.png +0 -0
- jupyter-agent/ds-system-prompt.txt +0 -154
- jupyter-agent/jupyter-agent.png +0 -0
- jupyter-agent/llama3_template.jinja +0 -123
- jupyter-agent/requirements.txt +0 -9
- jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +0 -68
- jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +0 -57
- jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +0 -53
- jupyter-agent/tmp/jupyter-agent.ipynb +0 -27
- jupyter-agent/utils.py +0 -409
- runbooks/aws/__init__.py +0 -58
- runbooks/aws/dynamodb_operations.py +0 -231
- runbooks/aws/ec2_copy_image_cross-region.py +0 -195
- runbooks/aws/ec2_describe_instances.py +0 -202
- runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
- runbooks/aws/ec2_run_instances.py +0 -213
- runbooks/aws/ec2_start_stop_instances.py +0 -212
- runbooks/aws/ec2_terminate_instances.py +0 -143
- runbooks/aws/ec2_unused_eips.py +0 -196
- runbooks/aws/ec2_unused_volumes.py +0 -188
- runbooks/aws/s3_create_bucket.py +0 -142
- runbooks/aws/s3_list_buckets.py +0 -152
- runbooks/aws/s3_list_objects.py +0 -156
- runbooks/aws/s3_object_operations.py +0 -183
- runbooks/aws/tagging_lambda_handler.py +0 -183
- runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
- runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
- runbooks/inventory/aws_organization.png +0 -0
- runbooks/inventory/cfn_move_stack_instances.py +0 -1526
- runbooks/inventory/delete_s3_buckets_objects.py +0 -169
- runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
- runbooks/inventory/update_aws_actions.py +0 -173
- runbooks/inventory/update_cfn_stacksets.py +0 -1215
- runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
- runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
- runbooks/inventory/update_s3_public_access_block.py +0 -539
- runbooks/organizations/__init__.py +0 -12
- runbooks/organizations/manager.py +0 -374
- runbooks-0.7.0.dist-info/METADATA +0 -375
- /runbooks/inventory/{tests → Tests}/common_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/common_test_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/script_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/setup.py +0 -0
- /runbooks/inventory/{tests → Tests}/src.py +0 -0
- /runbooks/inventory/{tests/test_inventory_modules.py → Tests/test_Inventory_Modules.py} +0 -0
- /runbooks/inventory/{tests → Tests}/test_cfn_describe_stacks.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_ec2_describe_instances.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_lambda_list_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_moto_integration_example.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_org_list_accounts.py +0 -0
- /runbooks/inventory/{Inventory_Modules.py → inventory_modules.py} +0 -0
- /runbooks/{aws → operate}/tags.json +0 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/WHEEL +0 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,643 @@
|
|
1
|
+
"""
|
2
|
+
Enterprise Remediation Base Classes - Production-Ready Security & Compliance Automation
|
3
|
+
|
4
|
+
This module provides the foundational architecture for AWS security and compliance
|
5
|
+
remediation operations, designed to integrate seamlessly with assessment findings
|
6
|
+
from security and CFAT modules.
|
7
|
+
|
8
|
+
## Design Principles
|
9
|
+
|
10
|
+
**Safety First**: All operations include dry-run, backup, and rollback capabilities
|
11
|
+
**Enterprise Ready**: Multi-account, multi-region support with SSO integration
|
12
|
+
**Audit Compliant**: Complete operation tracking and compliance mapping
|
13
|
+
**Assessment Integration**: Direct integration with security/CFAT findings
|
14
|
+
|
15
|
+
## Architecture
|
16
|
+
|
17
|
+
The remediation framework follows proven enterprise patterns from the operate module:
|
18
|
+
|
19
|
+
- **BaseRemediation**: Abstract base class for all remediation operations
|
20
|
+
- **RemediationContext**: Execution context with assessment findings integration
|
21
|
+
- **RemediationResult**: Structured outcomes with compliance mapping
|
22
|
+
- **RemediationStatus**: Operation states and progress tracking
|
23
|
+
|
24
|
+
## Example Usage
|
25
|
+
|
26
|
+
```python
|
27
|
+
from runbooks.remediation import S3SecurityRemediation
|
28
|
+
from runbooks.security import SecurityBaselineTester
|
29
|
+
|
30
|
+
# 1. Run security assessment
|
31
|
+
security_findings = SecurityBaselineTester().run_assessment()
|
32
|
+
|
33
|
+
# 2. Create remediation context from findings
|
34
|
+
context = RemediationContext.from_security_findings(security_findings)
|
35
|
+
|
36
|
+
# 3. Execute remediation with safety checks
|
37
|
+
s3_remediation = S3SecurityRemediation()
|
38
|
+
results = s3_remediation.enforce_ssl(context, bucket_name="critical-data")
|
39
|
+
|
40
|
+
# 4. Verify remediation success
|
41
|
+
verification_results = security_findings.verify_remediation(results)
|
42
|
+
```
|
43
|
+
|
44
|
+
## Multi-Account Integration
|
45
|
+
|
46
|
+
All remediation operations support enterprise multi-account patterns:
|
47
|
+
|
48
|
+
```python
|
49
|
+
# Multi-account remediation execution
|
50
|
+
accounts = ["123456789012", "987654321098", "456789012345"]
|
51
|
+
results = s3_remediation.enforce_ssl_bulk(context, accounts=accounts)
|
52
|
+
```
|
53
|
+
|
54
|
+
## Compliance Mapping
|
55
|
+
|
56
|
+
Each remediation operation includes compliance framework mapping:
|
57
|
+
|
58
|
+
- **CIS AWS Foundations Benchmark**: Direct control mapping
|
59
|
+
- **NIST Cybersecurity Framework**: Category and function alignment
|
60
|
+
- **AWS Well-Architected Framework**: Pillar and principle mapping
|
61
|
+
- **CheckPoint CloudGuard/Dome9**: Rule-by-rule remediation mapping
|
62
|
+
|
63
|
+
Version: 0.7.6 - Enterprise Production Ready
|
64
|
+
Compatibility: AWS SDK v3, Python 3.8+, Multi-deployment ready
|
65
|
+
"""
|
66
|
+
|
67
|
+
import json
|
68
|
+
import logging
|
69
|
+
import os
|
70
|
+
import uuid
|
71
|
+
from abc import ABC, abstractmethod
|
72
|
+
from datetime import datetime, timedelta
|
73
|
+
from enum import Enum
|
74
|
+
from typing import Any, Dict, List, Optional, Set, Union
|
75
|
+
|
76
|
+
import boto3
|
77
|
+
import click
|
78
|
+
from botocore.exceptions import BotoCoreError, ClientError
|
79
|
+
from loguru import logger
|
80
|
+
from pydantic import BaseModel, Field
|
81
|
+
|
82
|
+
from runbooks.inventory.models.account import AWSAccount
|
83
|
+
|
84
|
+
|
85
|
+
class RemediationStatus(Enum):
|
86
|
+
"""
|
87
|
+
Enumerated remediation operation states.
|
88
|
+
|
89
|
+
Provides consistent status tracking across all remediation operations
|
90
|
+
with clear semantic meaning for enterprise reporting and monitoring.
|
91
|
+
"""
|
92
|
+
|
93
|
+
PENDING = "pending" # Remediation planned but not started
|
94
|
+
IN_PROGRESS = "in_progress" # Remediation currently executing
|
95
|
+
SUCCESS = "success" # Remediation completed successfully
|
96
|
+
FAILED = "failed" # Remediation failed with errors
|
97
|
+
DRY_RUN = "dry_run" # Dry-run mode (no actual changes)
|
98
|
+
CANCELLED = "cancelled" # User cancelled operation
|
99
|
+
ROLLED_BACK = "rolled_back" # Operation rolled back
|
100
|
+
REQUIRES_MANUAL = "requires_manual" # Manual intervention required
|
101
|
+
SKIPPED = "skipped" # Operation skipped (already compliant)
|
102
|
+
|
103
|
+
|
104
|
+
class ComplianceMapping(BaseModel):
|
105
|
+
"""
|
106
|
+
Compliance framework mapping for remediation operations.
|
107
|
+
|
108
|
+
Maps each remediation to relevant compliance frameworks and controls,
|
109
|
+
enabling automated compliance reporting and audit trail generation.
|
110
|
+
"""
|
111
|
+
|
112
|
+
cis_controls: List[str] = Field(default_factory=list, description="CIS AWS Foundations controls")
|
113
|
+
nist_categories: List[str] = Field(default_factory=list, description="NIST Cybersecurity Framework categories")
|
114
|
+
well_architected_pillars: List[str] = Field(default_factory=list, description="AWS Well-Architected pillars")
|
115
|
+
dome9_rules: List[str] = Field(default_factory=list, description="CheckPoint CloudGuard/Dome9 rules")
|
116
|
+
aws_config_rules: List[str] = Field(default_factory=list, description="AWS Config compliance rules")
|
117
|
+
severity: str = Field(default="medium", description="Risk severity level")
|
118
|
+
|
119
|
+
|
120
|
+
class RemediationContext(BaseModel):
|
121
|
+
"""
|
122
|
+
Comprehensive execution context for remediation operations.
|
123
|
+
|
124
|
+
Provides all necessary context for safe, auditable remediation execution
|
125
|
+
including account information, safety settings, and assessment integration.
|
126
|
+
|
127
|
+
Attributes:
|
128
|
+
account: AWS account information
|
129
|
+
region: Target AWS region
|
130
|
+
operation_type: Type of remediation operation
|
131
|
+
resource_types: AWS resource types affected
|
132
|
+
dry_run: Safety flag for testing operations
|
133
|
+
force: Override confirmation prompts (for automation)
|
134
|
+
backup_enabled: Enable automatic backup creation
|
135
|
+
compliance_mapping: Compliance framework mapping
|
136
|
+
assessment_findings: Related assessment findings
|
137
|
+
rollback_plan: Automatic rollback configuration
|
138
|
+
change_ticket: Change management integration
|
139
|
+
"""
|
140
|
+
|
141
|
+
account: AWSAccount
|
142
|
+
region: str = Field(default="us-east-1", description="AWS region")
|
143
|
+
operation_type: str = Field(description="Remediation operation type")
|
144
|
+
resource_types: List[str] = Field(default_factory=list, description="AWS resource types")
|
145
|
+
|
146
|
+
# Safety and control flags
|
147
|
+
dry_run: bool = Field(default=True, description="Enable dry-run mode")
|
148
|
+
force: bool = Field(default=False, description="Skip confirmation prompts")
|
149
|
+
backup_enabled: bool = Field(default=True, description="Enable automatic backups")
|
150
|
+
|
151
|
+
# Compliance and audit
|
152
|
+
compliance_mapping: ComplianceMapping = Field(default_factory=ComplianceMapping)
|
153
|
+
assessment_findings: Dict[str, Any] = Field(default_factory=dict, description="Related assessment findings")
|
154
|
+
|
155
|
+
# Enterprise features
|
156
|
+
rollback_plan: Dict[str, Any] = Field(default_factory=dict, description="Rollback configuration")
|
157
|
+
change_ticket: Optional[str] = Field(default=None, description="Change management ticket")
|
158
|
+
notification_targets: List[str] = Field(default_factory=list, description="SNS notification targets")
|
159
|
+
|
160
|
+
@classmethod
|
161
|
+
def from_security_findings(cls, findings: Dict[str, Any], **kwargs) -> "RemediationContext":
|
162
|
+
"""
|
163
|
+
Create remediation context from security assessment findings.
|
164
|
+
|
165
|
+
Args:
|
166
|
+
findings: Security assessment findings from security module
|
167
|
+
**kwargs: Additional context parameters
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
RemediationContext with populated assessment data
|
171
|
+
|
172
|
+
Example:
|
173
|
+
```python
|
174
|
+
security_findings = SecurityBaselineTester().run_assessment()
|
175
|
+
context = RemediationContext.from_security_findings(security_findings)
|
176
|
+
```
|
177
|
+
"""
|
178
|
+
# Extract account information from findings
|
179
|
+
account_id = findings.get("account_id", "unknown")
|
180
|
+
account_name = findings.get("account_name", "unknown")
|
181
|
+
account = AWSAccount(account_id=account_id, account_name=account_name)
|
182
|
+
|
183
|
+
# Map findings to compliance frameworks
|
184
|
+
compliance_mapping = ComplianceMapping()
|
185
|
+
if "cis_controls" in findings:
|
186
|
+
compliance_mapping.cis_controls = findings["cis_controls"]
|
187
|
+
if "severity" in findings:
|
188
|
+
compliance_mapping.severity = findings["severity"]
|
189
|
+
|
190
|
+
return cls(
|
191
|
+
account=account,
|
192
|
+
operation_type="security_remediation",
|
193
|
+
assessment_findings=findings,
|
194
|
+
compliance_mapping=compliance_mapping,
|
195
|
+
**kwargs,
|
196
|
+
)
|
197
|
+
|
198
|
+
@classmethod
|
199
|
+
def from_cfat_findings(cls, findings: Dict[str, Any], **kwargs) -> "RemediationContext":
|
200
|
+
"""
|
201
|
+
Create remediation context from CFAT assessment findings.
|
202
|
+
|
203
|
+
Args:
|
204
|
+
findings: CFAT assessment findings from cfat module
|
205
|
+
**kwargs: Additional context parameters
|
206
|
+
|
207
|
+
Returns:
|
208
|
+
RemediationContext with populated assessment data
|
209
|
+
"""
|
210
|
+
account_id = findings.get("account_id", "unknown")
|
211
|
+
account_name = findings.get("account_name", "unknown")
|
212
|
+
account = AWSAccount(account_id=account_id, account_name=account_name)
|
213
|
+
|
214
|
+
# Map CFAT findings to Well-Architected pillars
|
215
|
+
compliance_mapping = ComplianceMapping()
|
216
|
+
if "well_architected_pillars" in findings:
|
217
|
+
compliance_mapping.well_architected_pillars = findings["well_architected_pillars"]
|
218
|
+
|
219
|
+
return cls(
|
220
|
+
account=account,
|
221
|
+
operation_type="cfat_remediation",
|
222
|
+
assessment_findings=findings,
|
223
|
+
compliance_mapping=compliance_mapping,
|
224
|
+
**kwargs,
|
225
|
+
)
|
226
|
+
|
227
|
+
|
228
|
+
class RemediationResult(BaseModel):
|
229
|
+
"""
|
230
|
+
Structured result from remediation operations.
|
231
|
+
|
232
|
+
Provides comprehensive outcome tracking with compliance mapping,
|
233
|
+
backup information, and rollback capabilities for enterprise audit trails.
|
234
|
+
|
235
|
+
Attributes:
|
236
|
+
operation_id: Unique operation identifier
|
237
|
+
context: Original operation context
|
238
|
+
status: Current operation status
|
239
|
+
start_time: Operation start timestamp
|
240
|
+
end_time: Operation completion timestamp
|
241
|
+
affected_resources: List of resources modified
|
242
|
+
backup_locations: Backup storage locations
|
243
|
+
rollback_instructions: Manual rollback procedures
|
244
|
+
compliance_evidence: Compliance verification data
|
245
|
+
error_message: Error details if operation failed
|
246
|
+
response_data: Raw AWS API response data
|
247
|
+
"""
|
248
|
+
|
249
|
+
operation_id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique operation ID")
|
250
|
+
context: RemediationContext
|
251
|
+
status: RemediationStatus = Field(default=RemediationStatus.PENDING)
|
252
|
+
|
253
|
+
# Timing information
|
254
|
+
start_time: datetime = Field(default_factory=datetime.utcnow)
|
255
|
+
end_time: Optional[datetime] = Field(default=None)
|
256
|
+
duration_seconds: Optional[float] = Field(default=None)
|
257
|
+
|
258
|
+
# Resource tracking
|
259
|
+
affected_resources: List[str] = Field(default_factory=list, description="Resources modified")
|
260
|
+
backup_locations: Dict[str, str] = Field(default_factory=dict, description="Backup storage locations")
|
261
|
+
|
262
|
+
# Enterprise features
|
263
|
+
rollback_instructions: List[str] = Field(default_factory=list, description="Rollback procedures")
|
264
|
+
compliance_evidence: Dict[str, Any] = Field(default_factory=dict, description="Compliance verification")
|
265
|
+
|
266
|
+
# Operation outcomes
|
267
|
+
error_message: Optional[str] = Field(default=None)
|
268
|
+
response_data: Dict[str, Any] = Field(default_factory=dict)
|
269
|
+
|
270
|
+
@property
|
271
|
+
def success(self) -> bool:
|
272
|
+
"""Check if remediation was successful."""
|
273
|
+
return self.status == RemediationStatus.SUCCESS
|
274
|
+
|
275
|
+
@property
|
276
|
+
def failed(self) -> bool:
|
277
|
+
"""Check if remediation failed."""
|
278
|
+
return self.status == RemediationStatus.FAILED
|
279
|
+
|
280
|
+
def mark_completed(self, status: RemediationStatus, error_message: str = None) -> None:
|
281
|
+
"""
|
282
|
+
Mark remediation as completed with final status.
|
283
|
+
|
284
|
+
Args:
|
285
|
+
status: Final operation status
|
286
|
+
error_message: Error details if operation failed
|
287
|
+
"""
|
288
|
+
self.status = status
|
289
|
+
self.end_time = datetime.utcnow()
|
290
|
+
self.duration_seconds = (self.end_time - self.start_time).total_seconds()
|
291
|
+
|
292
|
+
if error_message:
|
293
|
+
self.error_message = error_message
|
294
|
+
|
295
|
+
logger.info(f"Remediation {self.operation_id} completed with status: {status.value}")
|
296
|
+
|
297
|
+
def create_rollback_plan(self, instructions: List[str]) -> None:
|
298
|
+
"""
|
299
|
+
Create rollback plan with manual instructions.
|
300
|
+
|
301
|
+
Args:
|
302
|
+
instructions: Step-by-step rollback procedures
|
303
|
+
"""
|
304
|
+
self.rollback_instructions = instructions
|
305
|
+
logger.info(f"Rollback plan created for operation {self.operation_id}")
|
306
|
+
|
307
|
+
def add_compliance_evidence(self, framework: str, evidence: Dict[str, Any]) -> None:
|
308
|
+
"""
|
309
|
+
Add compliance verification evidence.
|
310
|
+
|
311
|
+
Args:
|
312
|
+
framework: Compliance framework name (CIS, NIST, etc.)
|
313
|
+
evidence: Verification evidence data
|
314
|
+
"""
|
315
|
+
self.compliance_evidence[framework] = evidence
|
316
|
+
logger.debug(f"Compliance evidence added for {framework}")
|
317
|
+
|
318
|
+
|
319
|
+
class BaseRemediation(ABC):
|
320
|
+
"""
|
321
|
+
Abstract base class for all AWS remediation operations.
|
322
|
+
|
323
|
+
Provides consistent enterprise patterns for safety, auditing, and compliance
|
324
|
+
across all remediation implementations. Follows the proven architecture
|
325
|
+
from the operate module with enhanced safety and compliance features.
|
326
|
+
|
327
|
+
Key Features:
|
328
|
+
- Automatic backup creation before changes
|
329
|
+
- Comprehensive dry-run capabilities
|
330
|
+
- Multi-account and multi-region support
|
331
|
+
- Compliance framework mapping
|
332
|
+
- Integration with assessment findings
|
333
|
+
- Rollback and recovery procedures
|
334
|
+
|
335
|
+
Example Implementation:
|
336
|
+
```python
|
337
|
+
class S3SecurityRemediation(BaseRemediation):
|
338
|
+
supported_operations = ["enforce_ssl", "block_public_access", "enable_encryption"]
|
339
|
+
|
340
|
+
def enforce_ssl(self, context: RemediationContext, bucket_name: str) -> List[RemediationResult]:
|
341
|
+
result = self.create_remediation_result(context, "enforce_ssl", "s3:bucket", bucket_name)
|
342
|
+
|
343
|
+
try:
|
344
|
+
# Create backup if enabled
|
345
|
+
if context.backup_enabled:
|
346
|
+
self.create_backup(context, bucket_name)
|
347
|
+
|
348
|
+
# Execute remediation
|
349
|
+
if not context.dry_run:
|
350
|
+
self.apply_ssl_policy(bucket_name)
|
351
|
+
|
352
|
+
result.mark_completed(RemediationStatus.SUCCESS)
|
353
|
+
|
354
|
+
except Exception as e:
|
355
|
+
result.mark_completed(RemediationStatus.FAILED, str(e))
|
356
|
+
|
357
|
+
return [result]
|
358
|
+
```
|
359
|
+
"""
|
360
|
+
|
361
|
+
supported_operations: List[str] = []
|
362
|
+
|
363
|
+
def __init__(self, profile: str = None, region: str = None, **kwargs):
|
364
|
+
"""
|
365
|
+
Initialize remediation base with AWS configuration.
|
366
|
+
|
367
|
+
Args:
|
368
|
+
profile: AWS profile name (uses environment if not specified)
|
369
|
+
region: AWS region (uses environment if not specified)
|
370
|
+
**kwargs: Additional configuration parameters
|
371
|
+
"""
|
372
|
+
self.profile = profile or os.getenv("AWS_PROFILE", "default")
|
373
|
+
self.region = region or os.getenv("AWS_REGION", "us-east-1")
|
374
|
+
|
375
|
+
# Enterprise configuration
|
376
|
+
self.backup_enabled = kwargs.get("backup_enabled", True)
|
377
|
+
self.notification_enabled = kwargs.get("notification_enabled", False)
|
378
|
+
self.sns_topic_arn = kwargs.get("sns_topic_arn", os.getenv("REMEDIATION_SNS_TOPIC_ARN"))
|
379
|
+
|
380
|
+
# Initialize AWS clients lazily
|
381
|
+
self._session = None
|
382
|
+
self._clients = {}
|
383
|
+
|
384
|
+
logger.info(f"Initialized remediation base for profile: {self.profile}, region: {self.region}")
|
385
|
+
|
386
|
+
@property
|
387
|
+
def session(self) -> boto3.Session:
|
388
|
+
"""Get or create AWS session with profile configuration."""
|
389
|
+
if self._session is None:
|
390
|
+
try:
|
391
|
+
self._session = boto3.Session(profile_name=self.profile, region_name=self.region)
|
392
|
+
except Exception as e:
|
393
|
+
logger.warning(f"Failed to create session with profile {self.profile}: {e}")
|
394
|
+
self._session = boto3.Session(region_name=self.region)
|
395
|
+
return self._session
|
396
|
+
|
397
|
+
def get_client(self, service_name: str, region: str = None) -> Any:
|
398
|
+
"""
|
399
|
+
Get or create AWS service client with caching.
|
400
|
+
|
401
|
+
Args:
|
402
|
+
service_name: AWS service name (e.g., 's3', 'ec2', 'iam')
|
403
|
+
region: Override region for client
|
404
|
+
|
405
|
+
Returns:
|
406
|
+
Configured AWS service client
|
407
|
+
"""
|
408
|
+
region = region or self.region
|
409
|
+
client_key = f"{service_name}_{region}"
|
410
|
+
|
411
|
+
if client_key not in self._clients:
|
412
|
+
try:
|
413
|
+
self._clients[client_key] = self.session.client(service_name, region_name=region)
|
414
|
+
logger.debug(f"Created AWS client for {service_name} in {region}")
|
415
|
+
except Exception as e:
|
416
|
+
logger.error(f"Failed to create {service_name} client: {e}")
|
417
|
+
raise
|
418
|
+
|
419
|
+
return self._clients[client_key]
|
420
|
+
|
421
|
+
def create_remediation_result(
|
422
|
+
self, context: RemediationContext, operation_type: str, resource_type: str, resource_id: str
|
423
|
+
) -> RemediationResult:
|
424
|
+
"""
|
425
|
+
Create standardized remediation result object.
|
426
|
+
|
427
|
+
Args:
|
428
|
+
context: Remediation execution context
|
429
|
+
operation_type: Type of remediation operation
|
430
|
+
resource_type: AWS resource type (e.g., 's3:bucket', 'ec2:instance')
|
431
|
+
resource_id: Unique resource identifier
|
432
|
+
|
433
|
+
Returns:
|
434
|
+
Initialized RemediationResult object
|
435
|
+
"""
|
436
|
+
# Update context with operation details
|
437
|
+
context.operation_type = operation_type
|
438
|
+
if resource_type not in context.resource_types:
|
439
|
+
context.resource_types.append(resource_type)
|
440
|
+
|
441
|
+
result = RemediationResult(context=context, affected_resources=[f"{resource_type}:{resource_id}"])
|
442
|
+
|
443
|
+
logger.info(f"Created remediation result {result.operation_id} for {resource_type}:{resource_id}")
|
444
|
+
return result
|
445
|
+
|
446
|
+
def execute_aws_call(self, client: Any, method_name: str, **kwargs) -> Dict[str, Any]:
|
447
|
+
"""
|
448
|
+
Execute AWS API call with enterprise error handling.
|
449
|
+
|
450
|
+
Args:
|
451
|
+
client: AWS service client
|
452
|
+
method_name: API method name
|
453
|
+
**kwargs: Method parameters
|
454
|
+
|
455
|
+
Returns:
|
456
|
+
AWS API response data
|
457
|
+
|
458
|
+
Raises:
|
459
|
+
ClientError: AWS service errors
|
460
|
+
BotoCoreError: Boto3 core errors
|
461
|
+
"""
|
462
|
+
try:
|
463
|
+
method = getattr(client, method_name)
|
464
|
+
response = method(**kwargs)
|
465
|
+
logger.debug(f"AWS API call successful: {method_name}")
|
466
|
+
return response
|
467
|
+
|
468
|
+
except ClientError as e:
|
469
|
+
error_code = e.response.get("Error", {}).get("Code", "Unknown")
|
470
|
+
error_message = e.response.get("Error", {}).get("Message", str(e))
|
471
|
+
logger.error(f"AWS ClientError in {method_name}: {error_code} - {error_message}")
|
472
|
+
raise
|
473
|
+
|
474
|
+
except BotoCoreError as e:
|
475
|
+
logger.error(f"AWS BotoCoreError in {method_name}: {e}")
|
476
|
+
raise
|
477
|
+
|
478
|
+
except Exception as e:
|
479
|
+
logger.error(f"Unexpected error in {method_name}: {e}")
|
480
|
+
raise
|
481
|
+
|
482
|
+
def create_backup(self, context: RemediationContext, resource_id: str, backup_type: str = "configuration") -> str:
|
483
|
+
"""
|
484
|
+
Create backup of resource configuration before remediation.
|
485
|
+
|
486
|
+
Args:
|
487
|
+
context: Remediation execution context
|
488
|
+
resource_id: Resource identifier
|
489
|
+
backup_type: Type of backup (configuration, snapshot, etc.)
|
490
|
+
|
491
|
+
Returns:
|
492
|
+
Backup location or identifier
|
493
|
+
"""
|
494
|
+
if not context.backup_enabled:
|
495
|
+
logger.info("Backup disabled, skipping backup creation")
|
496
|
+
return ""
|
497
|
+
|
498
|
+
timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
|
499
|
+
backup_key = f"remediation_backup_{resource_id}_{timestamp}"
|
500
|
+
|
501
|
+
try:
|
502
|
+
# Implementation depends on resource type
|
503
|
+
backup_location = self._create_resource_backup(resource_id, backup_key, backup_type)
|
504
|
+
logger.info(f"Backup created for {resource_id} at {backup_location}")
|
505
|
+
return backup_location
|
506
|
+
|
507
|
+
except Exception as e:
|
508
|
+
logger.error(f"Failed to create backup for {resource_id}: {e}")
|
509
|
+
raise
|
510
|
+
|
511
|
+
@abstractmethod
|
512
|
+
def _create_resource_backup(self, resource_id: str, backup_key: str, backup_type: str) -> str:
|
513
|
+
"""
|
514
|
+
Implementation-specific backup creation.
|
515
|
+
|
516
|
+
Must be implemented by each remediation class to handle
|
517
|
+
resource-specific backup procedures.
|
518
|
+
"""
|
519
|
+
pass
|
520
|
+
|
521
|
+
def confirm_operation(self, context: RemediationContext, resource_id: str, operation_type: str) -> bool:
|
522
|
+
"""
|
523
|
+
Confirm destructive operations with user interaction.
|
524
|
+
|
525
|
+
Args:
|
526
|
+
context: Remediation execution context
|
527
|
+
resource_id: Resource identifier
|
528
|
+
operation_type: Operation description
|
529
|
+
|
530
|
+
Returns:
|
531
|
+
True if operation confirmed, False otherwise
|
532
|
+
"""
|
533
|
+
if context.force:
|
534
|
+
logger.info(f"Force mode enabled, skipping confirmation for {operation_type}")
|
535
|
+
return True
|
536
|
+
|
537
|
+
if context.dry_run:
|
538
|
+
logger.info(f"Dry-run mode, confirmation not required for {operation_type}")
|
539
|
+
return True
|
540
|
+
|
541
|
+
try:
|
542
|
+
confirmation = click.confirm(
|
543
|
+
f"⚠️ DESTRUCTIVE OPERATION: {operation_type} on {resource_id}. Continue?", default=False
|
544
|
+
)
|
545
|
+
if confirmation:
|
546
|
+
logger.info(f"User confirmed {operation_type} on {resource_id}")
|
547
|
+
else:
|
548
|
+
logger.info(f"User cancelled {operation_type} on {resource_id}")
|
549
|
+
return confirmation
|
550
|
+
|
551
|
+
except Exception as e:
|
552
|
+
logger.error(f"Confirmation prompt failed: {e}")
|
553
|
+
return False
|
554
|
+
|
555
|
+
def send_notification(self, context: RemediationContext, result: RemediationResult) -> None:
|
556
|
+
"""
|
557
|
+
Send operation notification via SNS.
|
558
|
+
|
559
|
+
Args:
|
560
|
+
context: Remediation execution context
|
561
|
+
result: Remediation operation result
|
562
|
+
"""
|
563
|
+
if not self.notification_enabled or not self.sns_topic_arn:
|
564
|
+
return
|
565
|
+
|
566
|
+
try:
|
567
|
+
sns_client = self.get_client("sns")
|
568
|
+
|
569
|
+
message = {
|
570
|
+
"operation_id": result.operation_id,
|
571
|
+
"operation_type": context.operation_type,
|
572
|
+
"status": result.status.value,
|
573
|
+
"account": context.account.account_id,
|
574
|
+
"region": context.region,
|
575
|
+
"affected_resources": result.affected_resources,
|
576
|
+
"duration_seconds": result.duration_seconds,
|
577
|
+
}
|
578
|
+
|
579
|
+
subject = f"Remediation {result.status.value}: {context.operation_type}"
|
580
|
+
|
581
|
+
self.execute_aws_call(
|
582
|
+
sns_client,
|
583
|
+
"publish",
|
584
|
+
TopicArn=self.sns_topic_arn,
|
585
|
+
Message=json.dumps(message, default=str),
|
586
|
+
Subject=subject,
|
587
|
+
)
|
588
|
+
|
589
|
+
logger.info(f"Notification sent for operation {result.operation_id}")
|
590
|
+
|
591
|
+
except Exception as e:
|
592
|
+
logger.error(f"Failed to send notification: {e}")
|
593
|
+
|
594
|
+
@abstractmethod
|
595
|
+
def execute_remediation(self, context: RemediationContext, **kwargs) -> List[RemediationResult]:
|
596
|
+
"""
|
597
|
+
Execute specific remediation operation.
|
598
|
+
|
599
|
+
Must be implemented by each remediation class to provide
|
600
|
+
the actual remediation logic for their service area.
|
601
|
+
|
602
|
+
Args:
|
603
|
+
context: Remediation execution context
|
604
|
+
**kwargs: Operation-specific parameters
|
605
|
+
|
606
|
+
Returns:
|
607
|
+
List of remediation results
|
608
|
+
"""
|
609
|
+
pass
|
610
|
+
|
611
|
+
def bulk_execute(self, contexts: List[RemediationContext], **kwargs) -> List[RemediationResult]:
|
612
|
+
"""
|
613
|
+
Execute remediation across multiple accounts/resources.
|
614
|
+
|
615
|
+
Args:
|
616
|
+
contexts: List of remediation contexts
|
617
|
+
**kwargs: Operation-specific parameters
|
618
|
+
|
619
|
+
Returns:
|
620
|
+
Consolidated list of remediation results
|
621
|
+
"""
|
622
|
+
all_results = []
|
623
|
+
|
624
|
+
for context in contexts:
|
625
|
+
try:
|
626
|
+
results = self.execute_remediation(context, **kwargs)
|
627
|
+
all_results.extend(results)
|
628
|
+
|
629
|
+
# Send notifications for each result
|
630
|
+
for result in results:
|
631
|
+
self.send_notification(context, result)
|
632
|
+
|
633
|
+
except Exception as e:
|
634
|
+
logger.error(f"Bulk execution failed for context {context.account.account_id}: {e}")
|
635
|
+
# Create failure result
|
636
|
+
error_result = self.create_remediation_result(
|
637
|
+
context, "bulk_execution", "account", context.account.account_id
|
638
|
+
)
|
639
|
+
error_result.mark_completed(RemediationStatus.FAILED, str(e))
|
640
|
+
all_results.append(error_result)
|
641
|
+
|
642
|
+
logger.info(f"Bulk execution completed: {len(all_results)} total results")
|
643
|
+
return all_results
|