GitHunt
IS

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

Terraform Security
Terraform Lint & Validation
Terraform Docs

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 bootstrap

This 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:

  1. Lambda Deployment Packages - Create ZIP files with your Lambda code
  2. KMS Key - For encrypting environment variables (production/API examples)
  3. VPC Resources - Private subnets and security groups (production example)
  4. 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.py

Step 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.tfvars

Step 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.json

Security 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.tfvars

Note: 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

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)
[
"x86_64"
]
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({
target_arn = string
})
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({
disable_vpc_requirement = optional(bool, false)
disable_kms_requirement = optional(bool, false)
disable_cloudwatch_logs = optional(bool, false)
disable_xray_tracing = optional(bool, false)
disable_reserved_concurrency = optional(bool, false)
disable_dead_letter_queue = optional(bool, false)
disable_log_retention_validation = optional(bool, false)

# Audit trail - document why controls are disabled
justification = optional(string, "")
})
{
"disable_cloudwatch_logs": false,
"disable_dead_letter_queue": false,
"disable_kms_requirement": false,
"disable_log_retention_validation": false,
"disable_reserved_concurrency": false,
"disable_vpc_requirement": false,
"disable_xray_tracing": false,
"justification": ""
}
no
security_controls Security controls configuration from metadata module. Used to enforce security standards
object({
encryption = object({
require_kms_customer_managed = bool
require_encryption_at_rest = bool
require_encryption_in_transit = bool
enable_kms_key_rotation = bool
})
logging = object({
require_cloudwatch_logs = bool
min_log_retention_days = number
require_access_logging = bool
require_flow_logs = bool
})
monitoring = object({
enable_xray_tracing = bool
enable_enhanced_monitoring = bool
enable_performance_insights = bool
require_cloudtrail = bool
})
network = object({
require_private_subnets = bool
require_vpc_endpoints = bool
block_public_ingress = bool
require_imdsv2 = bool
})
compliance = object({
enable_point_in_time_recovery = bool
require_reserved_concurrency = bool
enable_deletion_protection = bool
})
})
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({
subnet_ids = list(string)
security_group_ids = list(string)
})
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.