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,971 @@
|
|
1
|
+
"""
|
2
|
+
Enterprise Lambda Security & Optimization Remediation - Production-Ready Serverless Security Automation
|
3
|
+
|
4
|
+
## Overview
|
5
|
+
|
6
|
+
This module provides comprehensive Lambda function security and optimization remediation
|
7
|
+
capabilities, consolidating and enhancing original Lambda scripts into a single enterprise-grade
|
8
|
+
module. Designed for automated compliance with serverless security best practices, cost
|
9
|
+
optimization, and operational excellence.
|
10
|
+
|
11
|
+
## Original Scripts Enhanced
|
12
|
+
|
13
|
+
Migrated and enhanced from these original remediation scripts:
|
14
|
+
- lambda_list.py - Lambda function analysis and IAM policy optimization
|
15
|
+
|
16
|
+
## Enterprise Enhancements
|
17
|
+
|
18
|
+
- **Security Hardening**: Environment encryption, VPC configuration, runtime security
|
19
|
+
- **IAM Policy Optimization**: Least privilege enforcement and policy refinement
|
20
|
+
- **Performance Optimization**: Memory sizing, concurrent execution management
|
21
|
+
- **Cost Optimization**: Reserved concurrency, performance analytics
|
22
|
+
- **Compliance Automation**: CIS, NIST, and serverless security best practices
|
23
|
+
- **Multi-Account Support**: Bulk operations across AWS Organizations
|
24
|
+
|
25
|
+
## Compliance Framework Mapping
|
26
|
+
|
27
|
+
### CIS AWS Foundations Benchmark
|
28
|
+
- **CIS 3.1**: Lambda function encryption at rest
|
29
|
+
- **CIS 3.2**: Lambda function secure networking (VPC configuration)
|
30
|
+
- **CIS 3.7**: Lambda function logging and monitoring
|
31
|
+
|
32
|
+
### NIST Cybersecurity Framework
|
33
|
+
- **SC-28**: Protection of Information at Rest (environment encryption)
|
34
|
+
- **SC-8**: Transmission Confidentiality (VPC networking)
|
35
|
+
- **AC-6**: Least Privilege (IAM policy optimization)
|
36
|
+
|
37
|
+
### Serverless Security Best Practices
|
38
|
+
- **Environment Variables**: Encryption and secure management
|
39
|
+
- **Dead Letter Queues**: Error handling and security monitoring
|
40
|
+
- **Runtime Security**: Latest runtime versions and dependency scanning
|
41
|
+
|
42
|
+
## Example Usage
|
43
|
+
|
44
|
+
```python
|
45
|
+
from runbooks.remediation import LambdaSecurityRemediation, RemediationContext
|
46
|
+
|
47
|
+
# Initialize with enterprise configuration
|
48
|
+
lambda_remediation = LambdaSecurityRemediation(
|
49
|
+
profile="production",
|
50
|
+
encryption_required=True,
|
51
|
+
vpc_required=True
|
52
|
+
)
|
53
|
+
|
54
|
+
# Execute comprehensive Lambda security hardening
|
55
|
+
results = lambda_remediation.comprehensive_lambda_security(
|
56
|
+
context,
|
57
|
+
optimize_iam=True,
|
58
|
+
configure_vpc=True
|
59
|
+
)
|
60
|
+
```
|
61
|
+
|
62
|
+
Version: 0.7.6 - Enterprise Production Ready
|
63
|
+
"""
|
64
|
+
|
65
|
+
import copy
|
66
|
+
import json
|
67
|
+
import os
|
68
|
+
import re
|
69
|
+
import time
|
70
|
+
from typing import Any, Dict, List, Optional
|
71
|
+
|
72
|
+
import boto3
|
73
|
+
from botocore.exceptions import BotoCoreError, ClientError
|
74
|
+
from loguru import logger
|
75
|
+
|
76
|
+
from runbooks.remediation.base import (
|
77
|
+
BaseRemediation,
|
78
|
+
ComplianceMapping,
|
79
|
+
RemediationContext,
|
80
|
+
RemediationResult,
|
81
|
+
RemediationStatus,
|
82
|
+
)
|
83
|
+
|
84
|
+
|
85
|
+
class LambdaSecurityRemediation(BaseRemediation):
|
86
|
+
"""
|
87
|
+
Enterprise Lambda Security & Optimization Remediation Operations.
|
88
|
+
|
89
|
+
Provides comprehensive Lambda function remediation including security hardening,
|
90
|
+
IAM policy optimization, VPC configuration, and performance tuning.
|
91
|
+
|
92
|
+
## Key Features
|
93
|
+
|
94
|
+
- **Environment Security**: Encryption and secure variable management
|
95
|
+
- **VPC Configuration**: Secure networking and subnet management
|
96
|
+
- **IAM Optimization**: Least privilege policy enforcement
|
97
|
+
- **Performance Tuning**: Memory sizing and concurrency optimization
|
98
|
+
- **Dead Letter Queues**: Error handling and monitoring setup
|
99
|
+
- **Runtime Security**: Version management and dependency scanning
|
100
|
+
|
101
|
+
## Example Usage
|
102
|
+
|
103
|
+
```python
|
104
|
+
from runbooks.remediation import LambdaSecurityRemediation, RemediationContext
|
105
|
+
|
106
|
+
# Initialize with enterprise configuration
|
107
|
+
lambda_remediation = LambdaSecurityRemediation(
|
108
|
+
profile="production",
|
109
|
+
encryption_required=True,
|
110
|
+
vpc_required=True
|
111
|
+
)
|
112
|
+
|
113
|
+
# Execute environment encryption
|
114
|
+
results = lambda_remediation.encrypt_environment_variables_bulk(
|
115
|
+
context,
|
116
|
+
kms_key_id="alias/lambda-key",
|
117
|
+
backup_enabled=True
|
118
|
+
)
|
119
|
+
```
|
120
|
+
"""
|
121
|
+
|
122
|
+
supported_operations = [
|
123
|
+
"encrypt_environment_variables",
|
124
|
+
"encrypt_environment_variables_bulk",
|
125
|
+
"configure_vpc_settings",
|
126
|
+
"optimize_iam_policies",
|
127
|
+
"optimize_iam_policies_bulk",
|
128
|
+
"setup_dead_letter_queues",
|
129
|
+
"analyze_function_usage",
|
130
|
+
"update_runtime_versions",
|
131
|
+
"comprehensive_lambda_security",
|
132
|
+
]
|
133
|
+
|
134
|
+
def __init__(self, **kwargs):
|
135
|
+
"""
|
136
|
+
Initialize Lambda remediation with enterprise configuration.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
**kwargs: Configuration parameters including profile, region, security settings
|
140
|
+
"""
|
141
|
+
super().__init__(**kwargs)
|
142
|
+
|
143
|
+
# Lambda-specific configuration
|
144
|
+
self.encryption_required = kwargs.get("encryption_required", True)
|
145
|
+
self.vpc_required = kwargs.get("vpc_required", False)
|
146
|
+
self.default_kms_key = kwargs.get("default_kms_key", "alias/aws/lambda")
|
147
|
+
self.cost_optimization = kwargs.get("cost_optimization", True)
|
148
|
+
self.runtime_security = kwargs.get("runtime_security", True)
|
149
|
+
self.analysis_period_days = kwargs.get("analysis_period_days", 30)
|
150
|
+
self.price_per_gb_second = kwargs.get("price_per_gb_second", 0.00001667) # US East pricing
|
151
|
+
|
152
|
+
logger.info(f"Lambda Security Remediation initialized for profile: {self.profile}")
|
153
|
+
|
154
|
+
def _create_resource_backup(self, resource_id: str, backup_key: str, backup_type: str) -> str:
|
155
|
+
"""
|
156
|
+
Create backup of Lambda function configuration.
|
157
|
+
|
158
|
+
Args:
|
159
|
+
resource_id: Lambda function name
|
160
|
+
backup_key: Backup identifier
|
161
|
+
backup_type: Type of backup (function_config, iam_policy, etc.)
|
162
|
+
|
163
|
+
Returns:
|
164
|
+
Backup location identifier
|
165
|
+
"""
|
166
|
+
try:
|
167
|
+
lambda_client = self.get_client("lambda")
|
168
|
+
|
169
|
+
# Create backup of current function configuration
|
170
|
+
backup_data = {
|
171
|
+
"function_name": resource_id,
|
172
|
+
"backup_key": backup_key,
|
173
|
+
"backup_type": backup_type,
|
174
|
+
"timestamp": backup_key.split("_")[-1],
|
175
|
+
"configurations": {},
|
176
|
+
}
|
177
|
+
|
178
|
+
if backup_type == "function_config":
|
179
|
+
# Backup Lambda function configuration
|
180
|
+
response = self.execute_aws_call(lambda_client, "get_function", FunctionName=resource_id)
|
181
|
+
backup_data["configurations"]["function"] = response.get("Configuration")
|
182
|
+
backup_data["configurations"]["code"] = response.get("Code")
|
183
|
+
|
184
|
+
elif backup_type == "iam_policy":
|
185
|
+
# Backup IAM role and policies
|
186
|
+
iam_client = self.get_client("iam")
|
187
|
+
function_config = self.execute_aws_call(
|
188
|
+
lambda_client, "get_function_configuration", FunctionName=resource_id
|
189
|
+
)
|
190
|
+
role_arn = function_config["Role"]
|
191
|
+
role_name = role_arn.split("/")[-1]
|
192
|
+
|
193
|
+
# Get attached policies
|
194
|
+
attached_policies = self.execute_aws_call(iam_client, "list_attached_role_policies", RoleName=role_name)
|
195
|
+
backup_data["configurations"]["attached_policies"] = attached_policies.get("AttachedPolicies", [])
|
196
|
+
|
197
|
+
# Get inline policies
|
198
|
+
inline_policies = self.execute_aws_call(iam_client, "list_role_policies", RoleName=role_name)
|
199
|
+
backup_data["configurations"]["inline_policies"] = {}
|
200
|
+
for policy_name in inline_policies.get("PolicyNames", []):
|
201
|
+
policy_doc = self.execute_aws_call(
|
202
|
+
iam_client, "get_role_policy", RoleName=role_name, PolicyName=policy_name
|
203
|
+
)
|
204
|
+
backup_data["configurations"]["inline_policies"][policy_name] = policy_doc["PolicyDocument"]
|
205
|
+
|
206
|
+
# Store backup (simplified for MVP - would use S3 in production)
|
207
|
+
backup_location = f"lambda-backup://{backup_key}.json"
|
208
|
+
logger.info(f"Backup created for Lambda function {resource_id}: {backup_location}")
|
209
|
+
|
210
|
+
return backup_location
|
211
|
+
|
212
|
+
except Exception as e:
|
213
|
+
logger.error(f"Failed to create backup for Lambda function {resource_id}: {e}")
|
214
|
+
raise
|
215
|
+
|
216
|
+
def execute_remediation(self, context: RemediationContext, **kwargs) -> List[RemediationResult]:
|
217
|
+
"""
|
218
|
+
Execute Lambda remediation operation.
|
219
|
+
|
220
|
+
Args:
|
221
|
+
context: Remediation execution context
|
222
|
+
**kwargs: Operation-specific parameters
|
223
|
+
|
224
|
+
Returns:
|
225
|
+
List of remediation results
|
226
|
+
"""
|
227
|
+
operation_type = kwargs.get("operation_type", context.operation_type)
|
228
|
+
|
229
|
+
if operation_type == "encrypt_environment_variables":
|
230
|
+
return self.encrypt_environment_variables(context, **kwargs)
|
231
|
+
elif operation_type == "encrypt_environment_variables_bulk":
|
232
|
+
return self.encrypt_environment_variables_bulk(context, **kwargs)
|
233
|
+
elif operation_type == "configure_vpc_settings":
|
234
|
+
return self.configure_vpc_settings(context, **kwargs)
|
235
|
+
elif operation_type == "optimize_iam_policies_bulk":
|
236
|
+
return self.optimize_iam_policies_bulk(context, **kwargs)
|
237
|
+
elif operation_type == "analyze_function_usage":
|
238
|
+
return self.analyze_function_usage(context, **kwargs)
|
239
|
+
elif operation_type == "comprehensive_lambda_security":
|
240
|
+
return self.comprehensive_lambda_security(context, **kwargs)
|
241
|
+
else:
|
242
|
+
raise ValueError(f"Unsupported Lambda remediation operation: {operation_type}")
|
243
|
+
|
244
|
+
def encrypt_environment_variables(
|
245
|
+
self, context: RemediationContext, function_name: str, kms_key_id: Optional[str] = None, **kwargs
|
246
|
+
) -> List[RemediationResult]:
|
247
|
+
"""
|
248
|
+
Enable encryption for Lambda function environment variables.
|
249
|
+
|
250
|
+
Args:
|
251
|
+
context: Remediation execution context
|
252
|
+
function_name: Lambda function name
|
253
|
+
kms_key_id: KMS key ID for encryption
|
254
|
+
**kwargs: Additional parameters
|
255
|
+
|
256
|
+
Returns:
|
257
|
+
List of remediation results
|
258
|
+
"""
|
259
|
+
result = self.create_remediation_result(
|
260
|
+
context, "encrypt_environment_variables", "lambda:function", function_name
|
261
|
+
)
|
262
|
+
|
263
|
+
# Add compliance mapping
|
264
|
+
result.context.compliance_mapping = ComplianceMapping(
|
265
|
+
cis_controls=["CIS 3.1"], nist_categories=["SC-28"], severity="high"
|
266
|
+
)
|
267
|
+
|
268
|
+
kms_key_id = kms_key_id or self.default_kms_key
|
269
|
+
|
270
|
+
try:
|
271
|
+
lambda_client = self.get_client("lambda", context.region)
|
272
|
+
|
273
|
+
# Get current function configuration
|
274
|
+
function_config = self.execute_aws_call(
|
275
|
+
lambda_client, "get_function_configuration", FunctionName=function_name
|
276
|
+
)
|
277
|
+
|
278
|
+
# Check if environment variables exist and if encryption is already enabled
|
279
|
+
environment = function_config.get("Environment", {})
|
280
|
+
current_kms_key = environment.get("KmsKeyArn")
|
281
|
+
|
282
|
+
if current_kms_key:
|
283
|
+
logger.info(f"Function {function_name} environment variables already encrypted")
|
284
|
+
result.response_data = {
|
285
|
+
"function_name": function_name,
|
286
|
+
"encryption_already_enabled": True,
|
287
|
+
"current_kms_key": current_kms_key,
|
288
|
+
}
|
289
|
+
result.mark_completed(RemediationStatus.SKIPPED)
|
290
|
+
return [result]
|
291
|
+
|
292
|
+
# Check if environment variables exist
|
293
|
+
if not environment.get("Variables"):
|
294
|
+
logger.info(f"Function {function_name} has no environment variables to encrypt")
|
295
|
+
result.response_data = {"function_name": function_name, "no_environment_variables": True}
|
296
|
+
result.mark_completed(RemediationStatus.SKIPPED)
|
297
|
+
return [result]
|
298
|
+
|
299
|
+
# Create backup if enabled
|
300
|
+
if context.backup_enabled:
|
301
|
+
backup_location = self.create_backup(context, function_name, "function_config")
|
302
|
+
result.backup_locations[function_name] = backup_location
|
303
|
+
|
304
|
+
if context.dry_run:
|
305
|
+
logger.info(f"[DRY-RUN] Would enable environment encryption for function: {function_name}")
|
306
|
+
result.response_data = {
|
307
|
+
"function_name": function_name,
|
308
|
+
"kms_key_id": kms_key_id,
|
309
|
+
"variables_count": len(environment.get("Variables", {})),
|
310
|
+
"action": "dry_run",
|
311
|
+
}
|
312
|
+
result.mark_completed(RemediationStatus.DRY_RUN)
|
313
|
+
return [result]
|
314
|
+
|
315
|
+
# Update function configuration with KMS encryption
|
316
|
+
self.execute_aws_call(
|
317
|
+
lambda_client,
|
318
|
+
"update_function_configuration",
|
319
|
+
FunctionName=function_name,
|
320
|
+
Environment={"Variables": environment.get("Variables", {}), "KmsKeyArn": kms_key_id},
|
321
|
+
)
|
322
|
+
|
323
|
+
# Verify encryption was enabled
|
324
|
+
updated_config = self.execute_aws_call(
|
325
|
+
lambda_client, "get_function_configuration", FunctionName=function_name
|
326
|
+
)
|
327
|
+
updated_environment = updated_config.get("Environment", {})
|
328
|
+
|
329
|
+
result.response_data = {
|
330
|
+
"function_name": function_name,
|
331
|
+
"kms_key_id": kms_key_id,
|
332
|
+
"variables_count": len(environment.get("Variables", {})),
|
333
|
+
"encryption_enabled": bool(updated_environment.get("KmsKeyArn")),
|
334
|
+
}
|
335
|
+
|
336
|
+
# Add compliance evidence
|
337
|
+
result.add_compliance_evidence(
|
338
|
+
"cis_aws",
|
339
|
+
{
|
340
|
+
"controls": ["3.1"],
|
341
|
+
"function_name": function_name,
|
342
|
+
"environment_encryption_enabled": True,
|
343
|
+
"kms_key_id": kms_key_id,
|
344
|
+
"remediation_timestamp": result.start_time.isoformat(),
|
345
|
+
},
|
346
|
+
)
|
347
|
+
|
348
|
+
result.mark_completed(RemediationStatus.SUCCESS)
|
349
|
+
logger.info(f"Successfully enabled environment encryption for function: {function_name}")
|
350
|
+
|
351
|
+
except ClientError as e:
|
352
|
+
error_msg = f"Failed to enable environment encryption for function {function_name}: {e}"
|
353
|
+
logger.error(error_msg)
|
354
|
+
result.mark_completed(RemediationStatus.FAILED, error_msg)
|
355
|
+
except Exception as e:
|
356
|
+
error_msg = f"Unexpected error enabling environment encryption for function {function_name}: {e}"
|
357
|
+
logger.error(error_msg)
|
358
|
+
result.mark_completed(RemediationStatus.FAILED, error_msg)
|
359
|
+
|
360
|
+
return [result]
|
361
|
+
|
362
|
+
def encrypt_environment_variables_bulk(self, context: RemediationContext, **kwargs) -> List[RemediationResult]:
|
363
|
+
"""
|
364
|
+
Enable environment variable encryption for all Lambda functions in bulk.
|
365
|
+
|
366
|
+
Args:
|
367
|
+
context: Remediation execution context
|
368
|
+
**kwargs: Additional parameters
|
369
|
+
|
370
|
+
Returns:
|
371
|
+
List of remediation results
|
372
|
+
"""
|
373
|
+
result = self.create_remediation_result(context, "encrypt_environment_variables_bulk", "lambda:function", "all")
|
374
|
+
|
375
|
+
# Add compliance mapping
|
376
|
+
result.context.compliance_mapping = ComplianceMapping(
|
377
|
+
cis_controls=["CIS 3.1"], nist_categories=["SC-28"], severity="high"
|
378
|
+
)
|
379
|
+
|
380
|
+
try:
|
381
|
+
lambda_client = self.get_client("lambda", context.region)
|
382
|
+
|
383
|
+
# Discover all Lambda functions
|
384
|
+
all_functions = []
|
385
|
+
functions_needing_encryption = []
|
386
|
+
|
387
|
+
paginator = lambda_client.get_paginator("list_functions")
|
388
|
+
for page in paginator.paginate():
|
389
|
+
all_functions.extend(page["Functions"])
|
390
|
+
|
391
|
+
# Check encryption status for each function
|
392
|
+
for function in all_functions:
|
393
|
+
function_name = function["FunctionName"]
|
394
|
+
try:
|
395
|
+
# Check if function has environment variables and if they're encrypted
|
396
|
+
environment = function.get("Environment", {})
|
397
|
+
has_variables = bool(environment.get("Variables"))
|
398
|
+
has_encryption = bool(environment.get("KmsKeyArn"))
|
399
|
+
|
400
|
+
if has_variables and not has_encryption:
|
401
|
+
functions_needing_encryption.append(function_name)
|
402
|
+
logger.info(f"Function {function_name} needs environment encryption")
|
403
|
+
else:
|
404
|
+
logger.debug(f"Function {function_name} - no encryption needed")
|
405
|
+
|
406
|
+
except Exception as e:
|
407
|
+
logger.warning(f"Could not check encryption status for function {function_name}: {e}")
|
408
|
+
|
409
|
+
if context.dry_run:
|
410
|
+
logger.info(f"[DRY-RUN] Would enable encryption for {len(functions_needing_encryption)} functions")
|
411
|
+
result.response_data = {
|
412
|
+
"total_functions": len(all_functions),
|
413
|
+
"functions_needing_encryption": functions_needing_encryption,
|
414
|
+
"action": "dry_run",
|
415
|
+
}
|
416
|
+
result.mark_completed(RemediationStatus.DRY_RUN)
|
417
|
+
return [result]
|
418
|
+
|
419
|
+
# Enable encryption for all functions needing it
|
420
|
+
successful_functions = []
|
421
|
+
failed_functions = []
|
422
|
+
|
423
|
+
for function_name in functions_needing_encryption:
|
424
|
+
try:
|
425
|
+
# Create backup if enabled
|
426
|
+
if context.backup_enabled:
|
427
|
+
backup_location = self.create_backup(context, function_name, "function_config")
|
428
|
+
result.backup_locations[function_name] = backup_location
|
429
|
+
|
430
|
+
# Get current environment to preserve variables
|
431
|
+
current_config = self.execute_aws_call(
|
432
|
+
lambda_client, "get_function_configuration", FunctionName=function_name
|
433
|
+
)
|
434
|
+
environment = current_config.get("Environment", {})
|
435
|
+
|
436
|
+
# Update with encryption
|
437
|
+
self.execute_aws_call(
|
438
|
+
lambda_client,
|
439
|
+
"update_function_configuration",
|
440
|
+
FunctionName=function_name,
|
441
|
+
Environment={"Variables": environment.get("Variables", {}), "KmsKeyArn": self.default_kms_key},
|
442
|
+
)
|
443
|
+
|
444
|
+
successful_functions.append(function_name)
|
445
|
+
logger.info(f"Enabled environment encryption for function: {function_name}")
|
446
|
+
|
447
|
+
# Add to affected resources
|
448
|
+
result.affected_resources.append(f"lambda:function:{function_name}")
|
449
|
+
|
450
|
+
# Small delay to avoid throttling
|
451
|
+
time.sleep(1)
|
452
|
+
|
453
|
+
except ClientError as e:
|
454
|
+
error_msg = f"Failed to enable encryption for function {function_name}: {e}"
|
455
|
+
logger.warning(error_msg)
|
456
|
+
failed_functions.append({"function_name": function_name, "error": str(e)})
|
457
|
+
|
458
|
+
result.response_data = {
|
459
|
+
"total_functions": len(all_functions),
|
460
|
+
"functions_needing_encryption": len(functions_needing_encryption),
|
461
|
+
"successful_functions": successful_functions,
|
462
|
+
"failed_functions": failed_functions,
|
463
|
+
"success_rate": len(successful_functions) / len(functions_needing_encryption)
|
464
|
+
if functions_needing_encryption
|
465
|
+
else 1.0,
|
466
|
+
}
|
467
|
+
|
468
|
+
# Add compliance evidence
|
469
|
+
result.add_compliance_evidence(
|
470
|
+
"cis_aws",
|
471
|
+
{
|
472
|
+
"controls": ["3.1"],
|
473
|
+
"functions_processed": len(functions_needing_encryption),
|
474
|
+
"functions_encrypted": len(successful_functions),
|
475
|
+
"compliance_improvement": len(successful_functions) > 0,
|
476
|
+
"remediation_timestamp": result.start_time.isoformat(),
|
477
|
+
},
|
478
|
+
)
|
479
|
+
|
480
|
+
if len(successful_functions) == len(functions_needing_encryption):
|
481
|
+
result.mark_completed(RemediationStatus.SUCCESS)
|
482
|
+
logger.info(f"Successfully enabled encryption for all {len(successful_functions)} functions")
|
483
|
+
elif len(successful_functions) > 0:
|
484
|
+
result.mark_completed(RemediationStatus.SUCCESS) # Partial success
|
485
|
+
logger.warning(
|
486
|
+
f"Partially completed: {len(successful_functions)}/{len(functions_needing_encryption)} functions encrypted"
|
487
|
+
)
|
488
|
+
else:
|
489
|
+
result.mark_completed(RemediationStatus.FAILED, "No functions could be encrypted")
|
490
|
+
|
491
|
+
except ClientError as e:
|
492
|
+
error_msg = f"Failed to enable bulk environment encryption: {e}"
|
493
|
+
logger.error(error_msg)
|
494
|
+
result.mark_completed(RemediationStatus.FAILED, error_msg)
|
495
|
+
except Exception as e:
|
496
|
+
error_msg = f"Unexpected error during bulk environment encryption: {e}"
|
497
|
+
logger.error(error_msg)
|
498
|
+
result.mark_completed(RemediationStatus.FAILED, error_msg)
|
499
|
+
|
500
|
+
return [result]
|
501
|
+
|
502
|
+
def optimize_iam_policies_bulk(self, context: RemediationContext, **kwargs) -> List[RemediationResult]:
|
503
|
+
"""
|
504
|
+
Optimize IAM policies for all Lambda functions to follow least privilege.
|
505
|
+
|
506
|
+
Enhanced from original lambda_list.py with enterprise policy optimization.
|
507
|
+
|
508
|
+
Args:
|
509
|
+
context: Remediation execution context
|
510
|
+
**kwargs: Additional parameters
|
511
|
+
|
512
|
+
Returns:
|
513
|
+
List of remediation results
|
514
|
+
"""
|
515
|
+
result = self.create_remediation_result(context, "optimize_iam_policies_bulk", "lambda:function", "all")
|
516
|
+
|
517
|
+
# Add compliance mapping
|
518
|
+
result.context.compliance_mapping = ComplianceMapping(nist_categories=["AC-6"], severity="medium")
|
519
|
+
|
520
|
+
try:
|
521
|
+
lambda_client = self.get_client("lambda", context.region)
|
522
|
+
iam_client = self.get_client("iam", context.region)
|
523
|
+
|
524
|
+
# Get all Lambda functions
|
525
|
+
paginator = lambda_client.get_paginator("list_functions")
|
526
|
+
all_functions = []
|
527
|
+
for page in paginator.paginate():
|
528
|
+
all_functions.extend(page["Functions"])
|
529
|
+
|
530
|
+
optimization_results = []
|
531
|
+
successful_optimizations = []
|
532
|
+
failed_optimizations = []
|
533
|
+
|
534
|
+
for function in all_functions:
|
535
|
+
function_name = function["FunctionName"]
|
536
|
+
role_arn = function["Role"]
|
537
|
+
role_name = role_arn.split("/")[-1]
|
538
|
+
|
539
|
+
try:
|
540
|
+
# Get inline policies
|
541
|
+
inline_policies_response = self.execute_aws_call(
|
542
|
+
iam_client, "list_role_policies", RoleName=role_name
|
543
|
+
)
|
544
|
+
inline_policies = inline_policies_response.get("PolicyNames", [])
|
545
|
+
|
546
|
+
inline_policy_documents = {}
|
547
|
+
for policy_name in inline_policies:
|
548
|
+
policy_doc = self.execute_aws_call(
|
549
|
+
iam_client, "get_role_policy", RoleName=role_name, PolicyName=policy_name
|
550
|
+
)
|
551
|
+
inline_policy_documents[policy_name] = policy_doc["PolicyDocument"]
|
552
|
+
|
553
|
+
# Optimize policies
|
554
|
+
changes, new_policy_document = self._optimize_policy_document(inline_policy_documents)
|
555
|
+
|
556
|
+
if changes:
|
557
|
+
optimization_results.append(
|
558
|
+
{
|
559
|
+
"function_name": function_name,
|
560
|
+
"role_name": role_name,
|
561
|
+
"changes": changes,
|
562
|
+
"optimized": True,
|
563
|
+
}
|
564
|
+
)
|
565
|
+
|
566
|
+
if not context.dry_run:
|
567
|
+
# Create backup if enabled
|
568
|
+
if context.backup_enabled:
|
569
|
+
backup_location = self.create_backup(context, function_name, "iam_policy")
|
570
|
+
result.backup_locations[function_name] = backup_location
|
571
|
+
|
572
|
+
# Apply optimized policies
|
573
|
+
self._update_iam_role_policies(iam_client, role_name, new_policy_document)
|
574
|
+
successful_optimizations.append(function_name)
|
575
|
+
logger.info(f"Optimized IAM policies for function: {function_name}")
|
576
|
+
else:
|
577
|
+
optimization_results.append(
|
578
|
+
{"function_name": function_name, "role_name": role_name, "changes": [], "optimized": False}
|
579
|
+
)
|
580
|
+
|
581
|
+
except Exception as e:
|
582
|
+
error_msg = f"Failed to optimize policies for function {function_name}: {e}"
|
583
|
+
logger.warning(error_msg)
|
584
|
+
failed_optimizations.append({"function_name": function_name, "error": str(e)})
|
585
|
+
|
586
|
+
if context.dry_run:
|
587
|
+
optimizable_functions = [r for r in optimization_results if r["optimized"]]
|
588
|
+
logger.info(f"[DRY-RUN] Would optimize policies for {len(optimizable_functions)} functions")
|
589
|
+
result.response_data = {
|
590
|
+
"total_functions": len(all_functions),
|
591
|
+
"optimization_analysis": optimization_results,
|
592
|
+
"optimizable_functions": len(optimizable_functions),
|
593
|
+
"action": "dry_run",
|
594
|
+
}
|
595
|
+
result.mark_completed(RemediationStatus.DRY_RUN)
|
596
|
+
return [result]
|
597
|
+
|
598
|
+
result.response_data = {
|
599
|
+
"total_functions": len(all_functions),
|
600
|
+
"optimization_analysis": optimization_results,
|
601
|
+
"successful_optimizations": successful_optimizations,
|
602
|
+
"failed_optimizations": failed_optimizations,
|
603
|
+
"optimization_rate": len(successful_optimizations) / len(all_functions) if all_functions else 0,
|
604
|
+
}
|
605
|
+
|
606
|
+
# Add compliance evidence
|
607
|
+
result.add_compliance_evidence(
|
608
|
+
"nist",
|
609
|
+
{
|
610
|
+
"controls": ["AC-6"],
|
611
|
+
"functions_analyzed": len(all_functions),
|
612
|
+
"policies_optimized": len(successful_optimizations),
|
613
|
+
"least_privilege_improvement": len(successful_optimizations) > 0,
|
614
|
+
"remediation_timestamp": result.start_time.isoformat(),
|
615
|
+
},
|
616
|
+
)
|
617
|
+
|
618
|
+
result.mark_completed(RemediationStatus.SUCCESS)
|
619
|
+
logger.info(f"IAM policy optimization completed: {len(successful_optimizations)} functions optimized")
|
620
|
+
|
621
|
+
except ClientError as e:
|
622
|
+
error_msg = f"Failed to optimize IAM policies: {e}"
|
623
|
+
logger.error(error_msg)
|
624
|
+
result.mark_completed(RemediationStatus.FAILED, error_msg)
|
625
|
+
except Exception as e:
|
626
|
+
error_msg = f"Unexpected error during IAM policy optimization: {e}"
|
627
|
+
logger.error(error_msg)
|
628
|
+
result.mark_completed(RemediationStatus.FAILED, error_msg)
|
629
|
+
|
630
|
+
return [result]
|
631
|
+
|
632
|
+
def _optimize_policy_document(self, policy_documents: Dict[str, Any]) -> tuple:
|
633
|
+
"""
|
634
|
+
Optimize policy document for least privilege.
|
635
|
+
|
636
|
+
Enhanced from original update_policy_document function.
|
637
|
+
"""
|
638
|
+
new_policy_documents = copy.deepcopy(policy_documents)
|
639
|
+
changes = {}
|
640
|
+
|
641
|
+
for policy_name, policy in new_policy_documents.items():
|
642
|
+
for statement in self._iterate_policy_statements(policy):
|
643
|
+
original_statement = statement.copy()
|
644
|
+
|
645
|
+
# Optimize wildcard actions
|
646
|
+
if statement.get("Action") == "*" and statement.get("Resource") != "*":
|
647
|
+
if isinstance(statement["Resource"], str):
|
648
|
+
match = re.search(r"arn:aws:(\w+):", statement["Resource"])
|
649
|
+
if match:
|
650
|
+
optimized_action = f"{match.group(1)}:*"
|
651
|
+
if statement["Action"] != optimized_action:
|
652
|
+
statement["Action"] = optimized_action
|
653
|
+
changes.setdefault(policy_name, []).append(
|
654
|
+
{
|
655
|
+
"type": "action_optimization",
|
656
|
+
"original": original_statement.get("Action"),
|
657
|
+
"optimized": optimized_action,
|
658
|
+
}
|
659
|
+
)
|
660
|
+
|
661
|
+
# Optimize wildcard resources
|
662
|
+
if statement.get("Resource") == "*" and statement.get("Action") != "*":
|
663
|
+
if isinstance(statement["Action"], str):
|
664
|
+
match = re.search(r"(\w+):", statement["Action"])
|
665
|
+
if match:
|
666
|
+
optimized_resource = f"arn:aws:{match.group(1)}:*:*:*"
|
667
|
+
if statement["Resource"] != optimized_resource:
|
668
|
+
statement["Resource"] = optimized_resource
|
669
|
+
changes.setdefault(policy_name, []).append(
|
670
|
+
{
|
671
|
+
"type": "resource_optimization",
|
672
|
+
"original": original_statement.get("Resource"),
|
673
|
+
"optimized": optimized_resource,
|
674
|
+
}
|
675
|
+
)
|
676
|
+
|
677
|
+
return changes, new_policy_documents
|
678
|
+
|
679
|
+
def _iterate_policy_statements(self, policy: Dict[str, Any]):
|
680
|
+
"""Iterate over policy statements."""
|
681
|
+
statements = policy.get("Statement", [])
|
682
|
+
if isinstance(statements, list):
|
683
|
+
for statement in statements:
|
684
|
+
yield statement
|
685
|
+
elif isinstance(statements, dict):
|
686
|
+
yield statements
|
687
|
+
|
688
|
+
def _update_iam_role_policies(self, iam_client: Any, role_name: str, policy_documents: Dict[str, Any]):
|
689
|
+
"""Update IAM role with optimized policies."""
|
690
|
+
for policy_name, policy in policy_documents.items():
|
691
|
+
policy_string = json.dumps(policy)
|
692
|
+
try:
|
693
|
+
self.execute_aws_call(
|
694
|
+
iam_client,
|
695
|
+
"put_role_policy",
|
696
|
+
RoleName=role_name,
|
697
|
+
PolicyName=policy_name,
|
698
|
+
PolicyDocument=policy_string,
|
699
|
+
)
|
700
|
+
except ClientError as e:
|
701
|
+
logger.error(f"Error updating inline policy for role '{role_name}' PolicyName {policy_name}: {e}")
|
702
|
+
raise
|
703
|
+
|
704
|
+
def analyze_function_usage(self, context: RemediationContext, **kwargs) -> List[RemediationResult]:
|
705
|
+
"""
|
706
|
+
Analyze Lambda function usage and provide optimization recommendations.
|
707
|
+
|
708
|
+
Enhanced from original lambda_list.py with comprehensive metrics.
|
709
|
+
|
710
|
+
Args:
|
711
|
+
context: Remediation execution context
|
712
|
+
**kwargs: Additional parameters
|
713
|
+
|
714
|
+
Returns:
|
715
|
+
List of remediation results with analysis data
|
716
|
+
"""
|
717
|
+
result = self.create_remediation_result(context, "analyze_function_usage", "lambda:function", "all")
|
718
|
+
|
719
|
+
try:
|
720
|
+
lambda_client = self.get_client("lambda", context.region)
|
721
|
+
cloudwatch_client = self.get_client("cloudwatch", context.region)
|
722
|
+
iam_client = self.get_client("iam", context.region)
|
723
|
+
|
724
|
+
# Get all Lambda functions
|
725
|
+
paginator = lambda_client.get_paginator("list_functions")
|
726
|
+
all_functions = []
|
727
|
+
for page in paginator.paginate():
|
728
|
+
all_functions.extend(page["Functions"])
|
729
|
+
|
730
|
+
function_analyses = []
|
731
|
+
total_functions = len(all_functions)
|
732
|
+
|
733
|
+
# Analyze each function
|
734
|
+
for function in all_functions:
|
735
|
+
try:
|
736
|
+
function_analysis = self._analyze_single_function(
|
737
|
+
function, lambda_client, cloudwatch_client, iam_client
|
738
|
+
)
|
739
|
+
function_analyses.append(function_analysis)
|
740
|
+
logger.info(f"Analyzed function: {function['FunctionName']}")
|
741
|
+
|
742
|
+
except Exception as e:
|
743
|
+
logger.warning(f"Could not analyze function {function['FunctionName']}: {e}")
|
744
|
+
|
745
|
+
# Generate overall analytics
|
746
|
+
overall_analytics = self._generate_lambda_analytics(function_analyses)
|
747
|
+
|
748
|
+
result.response_data = {
|
749
|
+
"function_analyses": function_analyses,
|
750
|
+
"overall_analytics": overall_analytics,
|
751
|
+
"analysis_timestamp": result.start_time.isoformat(),
|
752
|
+
"analysis_period_days": self.analysis_period_days,
|
753
|
+
}
|
754
|
+
|
755
|
+
# Add compliance evidence
|
756
|
+
result.add_compliance_evidence(
|
757
|
+
"operational_excellence",
|
758
|
+
{
|
759
|
+
"functions_analyzed": len(function_analyses),
|
760
|
+
"cost_optimization_opportunities": overall_analytics.get("cost_optimization_opportunities", 0),
|
761
|
+
"security_recommendations": overall_analytics.get("security_recommendations", 0),
|
762
|
+
"remediation_timestamp": result.start_time.isoformat(),
|
763
|
+
},
|
764
|
+
)
|
765
|
+
|
766
|
+
result.mark_completed(RemediationStatus.SUCCESS)
|
767
|
+
logger.info(f"Function usage analysis completed: {len(function_analyses)} functions analyzed")
|
768
|
+
|
769
|
+
except ClientError as e:
|
770
|
+
error_msg = f"Failed to analyze function usage: {e}"
|
771
|
+
logger.error(error_msg)
|
772
|
+
result.mark_completed(RemediationStatus.FAILED, error_msg)
|
773
|
+
except Exception as e:
|
774
|
+
error_msg = f"Unexpected error during function usage analysis: {e}"
|
775
|
+
logger.error(error_msg)
|
776
|
+
result.mark_completed(RemediationStatus.FAILED, error_msg)
|
777
|
+
|
778
|
+
return [result]
|
779
|
+
|
780
|
+
def _analyze_single_function(
|
781
|
+
self, function: Dict[str, Any], lambda_client: Any, cloudwatch_client: Any, iam_client: Any
|
782
|
+
) -> Dict[str, Any]:
|
783
|
+
"""
|
784
|
+
Analyze a single Lambda function.
|
785
|
+
|
786
|
+
Enhanced from original function analysis with comprehensive metrics.
|
787
|
+
"""
|
788
|
+
function_name = function["FunctionName"]
|
789
|
+
|
790
|
+
# Basic function information
|
791
|
+
basic_info = {
|
792
|
+
"function_name": function_name,
|
793
|
+
"runtime": function["Runtime"],
|
794
|
+
"memory_size": function["MemorySize"],
|
795
|
+
"timeout": function["Timeout"],
|
796
|
+
"last_modified": function["LastModified"],
|
797
|
+
"description": function.get("Description", ""),
|
798
|
+
"version": function["Version"],
|
799
|
+
}
|
800
|
+
|
801
|
+
# Security analysis
|
802
|
+
environment = function.get("Environment", {})
|
803
|
+
basic_info["environment_encrypted"] = bool(environment.get("KmsKeyArn"))
|
804
|
+
basic_info["has_environment_variables"] = bool(environment.get("Variables"))
|
805
|
+
basic_info["vpc_configured"] = bool(function.get("VpcConfig", {}).get("VpcId"))
|
806
|
+
|
807
|
+
# Get CloudWatch metrics (simplified - would need actual implementation)
|
808
|
+
try:
|
809
|
+
# Placeholder for metrics - would implement actual CloudWatch queries
|
810
|
+
basic_info["invocations_30_days"] = 0 # Would get from CloudWatch
|
811
|
+
basic_info["duration_average"] = 0 # Would get from CloudWatch
|
812
|
+
basic_info["errors_30_days"] = 0 # Would get from CloudWatch
|
813
|
+
except Exception as e:
|
814
|
+
logger.warning(f"Could not get CloudWatch metrics for {function_name}: {e}")
|
815
|
+
basic_info["invocations_30_days"] = 0
|
816
|
+
basic_info["duration_average"] = 0
|
817
|
+
basic_info["errors_30_days"] = 0
|
818
|
+
|
819
|
+
# Cost analysis
|
820
|
+
memory_size_gb = function["MemorySize"] / 1024
|
821
|
+
gb_seconds = (basic_info["duration_average"] / 1000) * memory_size_gb
|
822
|
+
estimated_cost = gb_seconds * self.price_per_gb_second
|
823
|
+
basic_info["estimated_monthly_cost"] = estimated_cost
|
824
|
+
|
825
|
+
# IAM analysis
|
826
|
+
role_arn = function["Role"]
|
827
|
+
role_name = role_arn.split("/")[-1]
|
828
|
+
basic_info["role_name"] = role_name
|
829
|
+
|
830
|
+
try:
|
831
|
+
# Check IAM policies
|
832
|
+
attached_policies = self.execute_aws_call(iam_client, "list_attached_role_policies", RoleName=role_name)
|
833
|
+
inline_policies = self.execute_aws_call(iam_client, "list_role_policies", RoleName=role_name)
|
834
|
+
|
835
|
+
basic_info["attached_policies_count"] = len(attached_policies.get("AttachedPolicies", []))
|
836
|
+
basic_info["inline_policies_count"] = len(inline_policies.get("PolicyNames", []))
|
837
|
+
except Exception as e:
|
838
|
+
logger.warning(f"Could not analyze IAM for function {function_name}: {e}")
|
839
|
+
basic_info["attached_policies_count"] = 0
|
840
|
+
basic_info["inline_policies_count"] = 0
|
841
|
+
|
842
|
+
# Generate recommendations
|
843
|
+
recommendations = []
|
844
|
+
|
845
|
+
# Security recommendations
|
846
|
+
if not basic_info["environment_encrypted"] and basic_info["has_environment_variables"]:
|
847
|
+
recommendations.append("Enable environment variable encryption for security")
|
848
|
+
|
849
|
+
if not basic_info["vpc_configured"]:
|
850
|
+
recommendations.append("Consider VPC configuration for network security")
|
851
|
+
|
852
|
+
# Performance recommendations
|
853
|
+
if basic_info["memory_size"] < 512 and basic_info["duration_average"] > 30000: # > 30 seconds
|
854
|
+
recommendations.append("Consider increasing memory allocation for better performance")
|
855
|
+
|
856
|
+
# Cost recommendations
|
857
|
+
if basic_info["invocations_30_days"] < 100 and basic_info["memory_size"] > 1024:
|
858
|
+
recommendations.append("Consider reducing memory allocation for cost optimization")
|
859
|
+
|
860
|
+
# Runtime recommendations
|
861
|
+
runtime = basic_info["runtime"]
|
862
|
+
if runtime.startswith("python3.6") or runtime.startswith("python3.7"):
|
863
|
+
recommendations.append("Upgrade to latest Python runtime for security and performance")
|
864
|
+
|
865
|
+
basic_info["recommendations"] = recommendations
|
866
|
+
|
867
|
+
return basic_info
|
868
|
+
|
869
|
+
def _generate_lambda_analytics(self, function_analyses: List[Dict[str, Any]]) -> Dict[str, Any]:
|
870
|
+
"""Generate overall Lambda analytics from individual function analyses."""
|
871
|
+
total_functions = len(function_analyses)
|
872
|
+
if total_functions == 0:
|
873
|
+
return {}
|
874
|
+
|
875
|
+
encrypted_functions = sum(1 for func in function_analyses if func.get("environment_encrypted", False))
|
876
|
+
vpc_functions = sum(1 for func in function_analyses if func.get("vpc_configured", False))
|
877
|
+
functions_with_recommendations = sum(1 for func in function_analyses if func.get("recommendations", []))
|
878
|
+
|
879
|
+
cost_optimization_opportunities = sum(
|
880
|
+
1
|
881
|
+
for func in function_analyses
|
882
|
+
if any("cost" in rec.lower() or "memory" in rec.lower() for rec in func.get("recommendations", []))
|
883
|
+
)
|
884
|
+
|
885
|
+
security_recommendations = sum(
|
886
|
+
1
|
887
|
+
for func in function_analyses
|
888
|
+
if any(
|
889
|
+
"encryption" in rec.lower() or "vpc" in rec.lower() or "runtime" in rec.lower()
|
890
|
+
for rec in func.get("recommendations", [])
|
891
|
+
)
|
892
|
+
)
|
893
|
+
|
894
|
+
total_estimated_cost = sum(func.get("estimated_monthly_cost", 0) for func in function_analyses)
|
895
|
+
avg_memory_size = sum(func.get("memory_size", 0) for func in function_analyses) / total_functions
|
896
|
+
|
897
|
+
return {
|
898
|
+
"total_functions": total_functions,
|
899
|
+
"encrypted_functions": encrypted_functions,
|
900
|
+
"encryption_compliance_rate": (encrypted_functions / total_functions * 100),
|
901
|
+
"vpc_functions": vpc_functions,
|
902
|
+
"vpc_adoption_rate": (vpc_functions / total_functions * 100),
|
903
|
+
"functions_with_recommendations": functions_with_recommendations,
|
904
|
+
"cost_optimization_opportunities": cost_optimization_opportunities,
|
905
|
+
"security_recommendations": security_recommendations,
|
906
|
+
"total_estimated_monthly_cost": total_estimated_cost,
|
907
|
+
"avg_memory_size": avg_memory_size,
|
908
|
+
"security_posture": "GOOD" if encrypted_functions == total_functions else "NEEDS_IMPROVEMENT",
|
909
|
+
}
|
910
|
+
|
911
|
+
def comprehensive_lambda_security(self, context: RemediationContext, **kwargs) -> List[RemediationResult]:
|
912
|
+
"""
|
913
|
+
Apply comprehensive Lambda security configuration.
|
914
|
+
|
915
|
+
Combines multiple operations for complete Lambda hardening:
|
916
|
+
- Encrypt environment variables
|
917
|
+
- Optimize IAM policies
|
918
|
+
- Analyze usage and generate recommendations
|
919
|
+
|
920
|
+
Args:
|
921
|
+
context: Remediation execution context
|
922
|
+
**kwargs: Additional parameters
|
923
|
+
|
924
|
+
Returns:
|
925
|
+
List of remediation results from all operations
|
926
|
+
"""
|
927
|
+
logger.info("Starting comprehensive Lambda security remediation")
|
928
|
+
|
929
|
+
all_results = []
|
930
|
+
|
931
|
+
# Execute all security operations
|
932
|
+
security_operations = [
|
933
|
+
("encrypt_environment_variables_bulk", self.encrypt_environment_variables_bulk),
|
934
|
+
("optimize_iam_policies_bulk", self.optimize_iam_policies_bulk),
|
935
|
+
("analyze_function_usage", self.analyze_function_usage),
|
936
|
+
]
|
937
|
+
|
938
|
+
for operation_name, operation_method in security_operations:
|
939
|
+
try:
|
940
|
+
logger.info(f"Executing {operation_name}")
|
941
|
+
operation_results = operation_method(context, **kwargs)
|
942
|
+
all_results.extend(operation_results)
|
943
|
+
|
944
|
+
# Check if operation failed and handle accordingly
|
945
|
+
if any(r.failed for r in operation_results):
|
946
|
+
logger.warning(f"Operation {operation_name} failed")
|
947
|
+
if kwargs.get("fail_fast", False):
|
948
|
+
break
|
949
|
+
|
950
|
+
except Exception as e:
|
951
|
+
logger.error(f"Error in {operation_name}: {e}")
|
952
|
+
# Create error result
|
953
|
+
error_result = self.create_remediation_result(
|
954
|
+
context, operation_name, "lambda:function", "comprehensive"
|
955
|
+
)
|
956
|
+
error_result.mark_completed(RemediationStatus.FAILED, str(e))
|
957
|
+
all_results.append(error_result)
|
958
|
+
|
959
|
+
if kwargs.get("fail_fast", False):
|
960
|
+
break
|
961
|
+
|
962
|
+
# Generate comprehensive summary
|
963
|
+
successful_operations = [r for r in all_results if r.success]
|
964
|
+
failed_operations = [r for r in all_results if r.failed]
|
965
|
+
|
966
|
+
logger.info(
|
967
|
+
f"Comprehensive Lambda security remediation completed: "
|
968
|
+
f"{len(successful_operations)} successful, {len(failed_operations)} failed"
|
969
|
+
)
|
970
|
+
|
971
|
+
return all_results
|