islamelkadi/terraform-aws-lambda
Production-ready Terraform module for AWS Lambda with multi-framework compliance support, built on real-world enterprise experience as a Forward Deployed Engineer.
Terraform AWS Lambda Module
A reusable Terraform module for creating AWS Lambda functions with AWS Security Hub compliance (FSBP, CIS, NIST 800-53, NIST 800-171, PCI DSS), VPC integration, KMS encryption, and flexible security control overrides.
Prerequisites
This module is designed for macOS. The following must already be installed on your machine:
To install the remaining development tools, run:
make bootstrapThis will install/upgrade: tfenv, Terraform (via tfenv), tflint, terraform-docs, checkov, and pre-commit.
Security
Environment-Based Security Controls
Security controls are automatically applied based on the environment through the terraform-aws-metadata module's security profiles:
| Control | Dev | Staging | Prod |
|---|---|---|---|
| KMS customer-managed keys | Optional | Required | Required |
| VPC integration | Optional | Required | Required |
| Reserved concurrency | Optional | Required | Required |
| Dead letter queue | Optional | Required | Required |
| X-Ray tracing | Optional | Required | Required |
| Log retention | 7 days | 90 days | 365 days |
For full details on security profiles and how controls vary by environment, see the Security Profiles documentation.
Security Scan Suppressions
This module suppresses certain Checkov security checks that are either not applicable to example/demo code or represent optional features. The following checks are suppressed in .checkov.yaml:
Module Source Versioning (CKV_TF_1, CKV_TF_2)
- Suppressed because we use semantic version tags (
?ref=v1.0.0) instead of commit hashes for better maintainability and readability - Semantic versioning is a valid and widely-accepted versioning strategy for stable releases
KMS IAM Policies (CKV_AWS_111, CKV_AWS_356, CKV_AWS_109)
- Suppressed in example code where KMS modules use flexible IAM policies for demonstration purposes
- Production deployments should customize KMS policies based on specific security requirements and apply least privilege principles
Lambda Optional Features
- VPC Public Subnets (CKV_AWS_130): Public subnets are designed to auto-assign public IPs for resources that need internet access; this is intentional
- Code Signing (CKV_AWS_272): Optional security feature that adds complexity and requires additional infrastructure; enable based on security requirements
Examples Included
1. Basic Lambda Function
Minimal configuration for simple tasks with fictitious deployment package.
Features:
- No VPC integration (public Lambda)
- No KMS encryption (no sensitive data)
- X-Ray tracing enabled
- 7-day log retention
- Security control overrides for simplicity
Use Cases:
- Simple data transformations
- Scheduled tasks
- Event-driven processing without sensitive data
2. Production Lambda with Full Compliance
Full security compliance configuration with all controls enforced.
Features:
- VPC integration (private subnets)
- KMS encryption for environment variables
- Dead letter queue for error handling
- Reserved concurrency (50)
- X-Ray tracing (Active mode)
- 365-day log retention
- Custom IAM policies
Use Cases:
- Production workloads
- Processing sensitive data
- Database access from private subnets
- High-availability requirements
3. API Lambda (API Gateway Integration)
Public-facing Lambda optimized for API Gateway.
Features:
- No VPC integration (reduces cold start)
- KMS encryption enabled
- Dead letter queue
- Reserved concurrency (10)
- X-Ray tracing
- 90-day log retention
Use Cases:
- REST API endpoints
- GraphQL resolvers
- Webhook handlers
- Public-facing services
Before You Start
Before using these examples, you need:
- Lambda Deployment Packages - Create ZIP files with your Lambda code
- KMS Key - For encrypting environment variables (production/API examples)
- VPC Resources - Private subnets and security groups (production example)
- Dead Letter Queue - SQS queue for error handling (production/API examples)
Usage
Step 1: Create Lambda Deployment Packages
Create simple Lambda functions for testing:
# Create lambda-packages directory
mkdir -p lambda-packages
# Basic Lambda
cat > index.py << 'EOF'
def handler(event, context):
return {
'statusCode': 200,
'body': 'Hello from Basic Lambda!'
}
EOF
zip lambda-packages/basic.zip index.py
# Production Lambda
cat > index.py << 'EOF'
import os
def handler(event, context):
log_level = os.environ.get('LOG_LEVEL', 'INFO')
return {
'statusCode': 200,
'body': f'Production Lambda - Log Level: {log_level}'
}
EOF
zip lambda-packages/processor.zip index.py
# API Lambda
cat > index.py << 'EOF'
import json
def handler(event, context):
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'message': 'API Lambda response'})
}
EOF
zip lambda-packages/api.zip index.py
# Clean up
rm index.pyStep 2: Update Variables
Edit params/input.tfvars and replace fictitious values with your actual AWS resources:
namespace = "your-org"
environment = "dev"
region = "us-east-1"
# Replace with your actual KMS key ARN
kms_key_arn = "arn:aws:kms:us-east-1:YOUR_ACCOUNT:key/YOUR_KEY_ID"
# Replace with your actual VPC subnet IDs
vpc_subnet_ids = [
"subnet-YOUR_SUBNET_1",
"subnet-YOUR_SUBNET_2"
]
# Replace with your actual security group IDs
vpc_security_group_ids = [
"sg-YOUR_SECURITY_GROUP"
]
# Replace with your actual SQS DLQ ARN
dlq_arn = "arn:aws:sqs:us-east-1:YOUR_ACCOUNT:YOUR_DLQ_NAME"Step 3: Deploy
# Initialize Terraform
terraform init
# Review the plan
terraform plan -var-file=params/input.tfvars
# Apply the configuration
terraform apply -var-file=params/input.tfvarsStep 4: Test Lambda Functions
# Test basic Lambda
aws lambda invoke \
--function-name example-dev-basic-function \
--region us-east-1 \
response.json && cat response.json
# Test production Lambda
aws lambda invoke \
--function-name example-prod-data-processor \
--region us-east-1 \
response.json && cat response.json
# Test API Lambda
aws lambda invoke \
--function-name example-dev-public-api \
--region us-east-1 \
response.json && cat response.jsonSecurity Control Overrides
These examples demonstrate the security control override system:
Basic Example
security_control_overrides = {
disable_vpc_requirement = true
disable_kms_requirement = true
justification = "Basic Lambda function with no sensitive data or private resource access."
}Production Example
No overrides - all security controls enforced (VPC, KMS, DLQ, reserved concurrency)
API Example
security_control_overrides = {
disable_vpc_requirement = true
justification = "Public-facing Lambda invoked by API Gateway. VPC would add cold start latency."
}Cost Estimate
Lambda Pricing (US East):
- Requests: $0.20 per 1M requests
- Duration: $0.0000166667 per GB-second
- Free tier: 1M requests + 400,000 GB-seconds per month
Example Monthly Costs:
- Basic Lambda (100K requests, 512MB, 1s avg): ~$0.10
- Production Lambda (1M requests, 2GB, 5s avg): ~$18.00
- API Lambda (500K requests, 1GB, 0.5s avg): ~$4.50
Additional Costs:
- CloudWatch Logs: $0.50 per GB ingested
- X-Ray: $5 per 1M traces recorded
- KMS: $1/month per key + $0.03 per 10,000 requests
- VPC: No additional cost (NAT Gateway separate)
Common Issues
Cold Start Latency
Problem: First invocation is slow
Solutions:
- Use provisioned concurrency (adds cost)
- Avoid VPC for public-facing functions
- Reduce deployment package size
- Use ARM64 architecture (Graviton2)
VPC Connectivity Issues
Problem: Lambda can't access internet or AWS services
Solutions:
- Ensure NAT Gateway in public subnet
- Add VPC endpoints for AWS services (S3, DynamoDB, etc.)
- Check security group egress rules
- Verify route table configuration
Permission Errors
Problem: Lambda can't access AWS resources
Solutions:
- Check IAM role has required permissions
- Verify resource-based policies (S3, SQS, etc.)
- Check KMS key policy for encryption access
- Review CloudWatch Logs for specific errors
Deployment Package Too Large
Problem: Deployment package exceeds 50MB (direct upload) or 250MB (unzipped)
Solutions:
- Use Lambda layers for dependencies
- Deploy via S3 (supports up to 250MB zipped)
- Use container images (up to 10GB)
- Remove unnecessary files from package
Clean Up
# Destroy all resources
terraform destroy -var-file=params/input.tfvarsNote: CloudWatch Log Groups may be retained based on retention settings.
Advanced Configuration
Adding Custom IAM Policies
module "custom_lambda" {
source = "github.com/islamelkadi/terraform-aws-lambda"
inline_policies = {
s3_access = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["s3:GetObject", "s3:PutObject"]
Resource = "arn:aws:s3:::my-bucket/*"
}]
})
}
}Using Lambda Layers
module "lambda_with_layers" {
source = "github.com/islamelkadi/terraform-aws-lambda"
layers = [
"arn:aws:lambda:us-east-1:123456789012:layer:my-layer:1"
]
}Container Image Deployment
module "container_lambda" {
source = "github.com/islamelkadi/terraform-aws-lambda"
package_type = "Image"
image_uri = "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-lambda:latest"
}References
-
Terraform validation checks for compliance
Usage Examples
Basic Example
module "lambda" {
source = "github.com/islamelkadi/terraform-aws-lambda"
namespace = "example"
environment = "prod"
name = "event-processor"
region = "us-east-1"
runtime = "python3.13"
handler = "index.handler"
filename = "lambda.zip"
memory_size = 512
timeout = 30
environment_variables = {
LOG_LEVEL = "INFO"
}
tags = {
Project = "CorporateActions"
}
}Production Function with Security Controls
module "lambda" {
source = "github.com/islamelkadi/terraform-aws-lambda"
security_controls = module.metadata.security_controls
namespace = "example"
environment = "prod"
name = "event-processor"
region = "us-east-1"
runtime = "python3.13"
handler = "index.handler"
filename = "lambda.zip"
# VPC configuration (required by security controls)
vpc_config = {
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.lambda_sg.id]
}
# KMS encryption (required by security controls)
kms_key_arn = module.kms.key_arn
# Reserved concurrency (required by security controls)
reserved_concurrent_executions = 10
# Dead letter queue (required by security controls)
dead_letter_config = {
target_arn = module.dlq.arn
}
# CloudWatch Logs with compliance retention
log_retention_days = 365
# X-Ray tracing enabled
enable_tracing = true
memory_size = 1024
timeout = 300
environment_variables = {
DB_ENDPOINT = module.rds.endpoint
S3_BUCKET = module.s3.bucket_name
}
}Development Function with Overrides
module "lambda" {
source = "github.com/islamelkadi/terraform-aws-lambda"
security_controls = module.metadata.security_controls
# Override security controls for development
security_control_overrides = {
disable_vpc_requirement = true
disable_reserved_concurrency = true
disable_dead_letter_queue = true
justification = "Development function for testing. No VPC access needed. Unpredictable load pattern. Non-critical failures acceptable."
}
namespace = "example"
environment = "dev"
name = "test-function"
region = "us-east-1"
runtime = "python3.13"
handler = "index.handler"
filename = "lambda.zip"
memory_size = 256
timeout = 60
log_retention_days = 7
}Public API Function
module "api_lambda" {
source = "github.com/islamelkadi/terraform-aws-lambda"
security_controls = module.metadata.security_controls
# Override VPC requirement for public API
security_control_overrides = {
disable_vpc_requirement = true
justification = "Public API function invoked by API Gateway. No private resource access required. Reviewed and approved by security team."
}
namespace = "example"
environment = "prod"
name = "api-handler"
region = "us-east-1"
runtime = "nodejs20.x"
handler = "index.handler"
filename = "api-lambda.zip"
# Still use KMS encryption for environment variables
kms_key_arn = module.kms.key_arn
# Reserved concurrency for API stability
reserved_concurrent_executions = 100
memory_size = 512
timeout = 30
environment_variables = {
API_KEY_SECRET = "arn:aws:secretsmanager:..."
}
}MCP Servers
This module includes two Model Context Protocol (MCP) servers configured in .kiro/settings/mcp.json for use with Kiro:
| Server | Package | Description |
|---|---|---|
aws-docs |
awslabs.aws-documentation-mcp-server@latest |
Provides access to AWS documentation for contextual lookups of service features, API references, and best practices. |
terraform |
awslabs.terraform-mcp-server@latest |
Enables Terraform operations (init, validate, plan, fmt, tflint) directly from the IDE with auto-approved commands for common workflows. |
Both servers run via uvx and require no additional installation beyond the bootstrap step.
Usage
# Lambda Function Examples
# Demonstrates various Lambda configurations with security control overrides
# ============================================================================
# Example 1: Basic Lambda Function
# Minimal configuration with fictitious deployment package
# Override: VPC and KMS not required for simple functions
# ============================================================================
module "basic_lambda" {
source = "github.com/islamelkadi/terraform-aws-lambda"
namespace = var.namespace
environment = var.environment
name = "basic-function"
region = var.region
handler = "index.handler"
runtime = "python3.13"
# Fictitious deployment package - replace with your actual Lambda code
# Create a simple Lambda: zip index.py -r lambda.zip
filename = "${path.module}/lambda-packages/basic.zip"
description = "Basic Lambda function for simple tasks"
memory_size = 512
timeout = 60
# Security Control Overrides: Relaxed for basic function
security_control_overrides = {
disable_vpc_requirement = true
disable_kms_requirement = true
justification = "Basic Lambda function with no sensitive data or private resource access. VPC and KMS encryption not required."
}
# Basic monitoring
enable_tracing = true
log_retention_days = 90
tags = {
Example = "basic"
}
}
# ============================================================================
# Example 2: Production Lambda with Full Compliance
# All security controls enforced (VPC, KMS, DLQ, Reserved Concurrency)
# ============================================================================
module "production_lambda" {
source = "github.com/islamelkadi/terraform-aws-lambda"
namespace = var.namespace
environment = "prod"
name = "data-processor"
region = var.region
handler = "index.handler"
runtime = "python3.13"
filename = "${path.module}/lambda-packages/processor.zip"
description = "Production Lambda with full security compliance"
memory_size = 2048
timeout = 300
# VPC Configuration - replace with your actual VPC resources
vpc_config = {
subnet_ids = var.vpc_subnet_ids
security_group_ids = var.vpc_security_group_ids
}
# KMS Encryption - replace with your actual KMS key ARN
kms_key_arn = var.kms_key_arn
# Dead Letter Queue - replace with your actual SQS queue ARN
dead_letter_config = {
target_arn = var.dlq_arn
}
# Reserved Concurrency - prevent resource exhaustion
reserved_concurrent_executions = 50
# X-Ray Tracing - observability
enable_tracing = true
tracing_mode = "Active"
# CloudWatch Logs - 365-day retention for compliance
log_retention_days = 365
create_log_group = true
# Environment Variables
environment_variables = {
LOG_LEVEL = "INFO"
STAGE = "production"
}
tags = {
Environment = "Production"
Compliance = "FullyCompliant"
Example = "production"
}
}
# ============================================================================
# Example 3: API Lambda (API Gateway Integration)
# Override: VPC not required for public-facing functions
# ============================================================================
module "api_lambda" {
source = "github.com/islamelkadi/terraform-aws-lambda"
namespace = var.namespace
environment = var.environment
name = "public-api"
region = var.region
handler = "index.handler"
runtime = "python3.13"
filename = "${path.module}/lambda-packages/api.zip"
description = "Public-facing Lambda invoked by API Gateway"
memory_size = 1024
timeout = 30
# Security Control Override: VPC not required
security_control_overrides = {
disable_vpc_requirement = true
justification = "Public-facing Lambda invoked by API Gateway. VPC integration would add cold start latency without security benefit."
}
# KMS encryption for environment variables
kms_key_arn = var.kms_key_arn
# Dead letter queue
dead_letter_config = {
target_arn = var.dlq_arn
}
reserved_concurrent_executions = 10
enable_tracing = true
log_retention_days = 90
environment_variables = {
API_VERSION = "v1"
}
tags = {
UseCase = "PublicAPI"
Example = "api-gateway"
}
}
Requirements
| Name | Version |
|---|---|
| terraform | >= 1.14.3 |
| aws | >= 6.34 |
Providers
| Name | Version |
|---|---|
| aws | >= 6.34 |
Modules
| Name | Source | Version |
|---|---|---|
| metadata | github.com/islamelkadi/terraform-aws-metadata | v1.1.0 |
Resources
| Name | Type |
|---|---|
| aws_cloudwatch_log_group.this | resource |
| aws_iam_role.this | resource |
| aws_iam_role_policy.inline_policies | resource |
| aws_iam_role_policy_attachment.managed_policies | resource |
| aws_lambda_function.this | resource |
| aws_iam_policy_document.assume_role | data source |
Inputs
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| architectures | Instruction set architecture for the Lambda function (x86_64 or arm64) | list(string) |
[ |
no |
| assume_role_policy | Custom assume role policy JSON. If not provided, a default Lambda assume role policy is used | string |
null |
no |
| attributes | Additional attributes for naming | list(string) |
[] |
no |
| create_log_group | Whether to create a CloudWatch Log Group for the Lambda function | bool |
true |
no |
| create_role | Whether to create an IAM role for the Lambda function | bool |
true |
no |
| dead_letter_config | Dead letter queue configuration for failed invocations | object({ |
null |
no |
| delimiter | Delimiter to use between name components | string |
"-" |
no |
| description | Description of the Lambda function | string |
"" |
no |
| enable_tracing | Enable X-Ray tracing for the Lambda function | bool |
true |
no |
| environment | Environment name (dev, staging, prod) | string |
n/a | yes |
| environment_variables | Map of environment variables for the Lambda function | map(string) |
{} |
no |
| ephemeral_storage_size | Size of the /tmp directory in MB | number |
512 |
no |
| filename | Path to the Lambda deployment package (Zip) | string |
null |
no |
| handler | Function entrypoint in the code | string |
null |
no |
| image_uri | ECR image URI for container-based Lambda (Image package type) | string |
null |
no |
| inline_policies | Map of inline IAM policies to attach to the Lambda role (name => policy JSON) | map(string) |
{} |
no |
| kms_key_arn | ARN of KMS key for encrypting environment variables and log group | string |
null |
no |
| layers | List of Lambda layer ARNs to attach | list(string) |
[] |
no |
| log_retention_days | CloudWatch Logs retention period in days | number |
365 |
no |
| managed_policy_arns | List of IAM managed policy ARNs to attach to the Lambda role | list(string) |
[] |
no |
| memory_size | Amount of memory in MB allocated to the Lambda function | number |
128 |
no |
| name | Name of the Lambda function | string |
n/a | yes |
| namespace | Namespace (organization/team name) | string |
n/a | yes |
| package_type | Lambda deployment package type (Zip or Image) | string |
"Zip" |
no |
| region | AWS region where resources will be created | string |
n/a | yes |
| reserved_concurrent_executions | Number of reserved concurrent executions. Set to -1 for unreserved | number |
-1 |
no |
| role_arn | ARN of an existing IAM role to use. Required if create_role is false | string |
null |
no |
| runtime | Runtime environment for the Lambda function | string |
null |
no |
| s3_bucket | S3 bucket containing the Lambda deployment package | string |
null |
no |
| s3_key | S3 key of the Lambda deployment package | string |
null |
no |
| s3_object_version | S3 object version of the Lambda deployment package | string |
null |
no |
| security_control_overrides | Override specific security controls for this Lambda function. Only use when there's a documented business justification. Example use cases: - disable_vpc_requirement: Public API functions (API Gateway integration, no private resource access) - disable_kms_requirement: No environment variables or only public configuration - disable_reserved_concurrency: Development/testing functions with variable load - disable_dead_letter_queue: Synchronous-only invocations with caller-side error handling IMPORTANT: Document the reason in the 'justification' field for audit purposes. |
object({ |
{ |
no |
| security_controls | Security controls configuration from metadata module. Used to enforce security standards | object({ |
null |
no |
| tags | Additional tags to apply to resources | map(string) |
{} |
no |
| timeout | Maximum execution time in seconds | number |
3 |
no |
| tracing_mode | X-Ray tracing mode (Active or PassThrough) | string |
"Active" |
no |
| vpc_config | VPC configuration for Lambda (subnet_ids and security_group_ids) | object({ |
null |
no |
Outputs
| Name | Description |
|---|---|
| function_arn | ARN of the Lambda function |
| function_invoke_arn | ARN to be used for invoking Lambda function from API Gateway |
| function_name | Name of the Lambda function |
| function_qualified_arn | Qualified ARN of the Lambda function (includes version) |
| function_version | Latest published version of the Lambda function |
| log_group_arn | ARN of the CloudWatch Log Group |
| log_group_name | Name of the CloudWatch Log Group |
| role_arn | ARN of the IAM role used by the Lambda function |
| role_name | Name of the IAM role used by the Lambda function |
| tags | Tags applied to the Lambda function |
Examples
See example/ for a complete working example with all features.