Understanding Lambda Security
When you deploy code to AWS Lambda, you're handing over execution responsibility to a managed service. But that doesn't mean security becomes someone else's problem. In fact, the shared responsibility model means you must define exactly what your function can do and how it communicates. Lambda security rests on two foundational pillars: IAM policies that govern permissions, and network security that governs connectivity. Mastering both is essential to running production workloads safely.
What Is Lambda IAM Security?
Every Lambda function operates under an IAM execution role. This role is a collection of policies that declare what AWS resources the function may access and what actions it may perform. Without explicit permissions, a Lambda function can do almost nothingânot even write logs to CloudWatch. The execution role is attached at function creation and can be updated independently of the code deployment.
There are two distinct policy types you need to understand:
- Execution role (trust policy + permission policies) â The role the function assumes at runtime. The trust policy allows
lambda.amazonaws.comto assume the role, and the attached permission policies grant access to S3, DynamoDB, SQS, and other services. - Resource-based policy (function policy) â A policy attached directly to the Lambda function itself that controls who can invoke it. This is critical for cross-account access or when integrating with services like API Gateway or S3.
Why IAM Policies Matter for Lambda
A poorly scoped execution role is one of the most common vectors for privilege escalation and data leakage. If your function only needs to read from one S3 bucket, but you grant it s3:* on all buckets, a vulnerability in your code could expose sensitive data across your entire storage estate. Attackers actively scan for overly permissive Lambda roles because they provide a stepping stone from application compromise to full infrastructure access.
Beyond security, tightly scoped IAM policies also enable faster compliance audits and reduce the blast radius of misconfigurations. When every function has exactly the permissions it needsâand nothing moreâyou can trace data flows with precision and prove least-privilege access to auditors.
How to Build Secure IAM Policies for Lambda
Start by listing every AWS service call your function makes. This includes SDK calls, but also implicit actions like writing to CloudWatch Logs or reading from an event source. Then construct a policy that grants only those actions on only those resources. Let's walk through a concrete example.
Step 1: Create a Least-Privilege Execution Role
Suppose your function processes images from an S3 bucket named incoming-images-12345 and writes thumbnails to thumbnail-bucket-67890. It also needs to write logs. The minimal execution role policy looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:123456789012:*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::incoming-images-12345/*"
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": "arn:aws:s3:::thumbnail-bucket-67890/*"
}
]
}
Notice how each statement is resource-specific. The GetObject permission is locked to a single bucket path, and PutObject is restricted to the thumbnail bucket. The CloudWatch Logs permission uses a wildcard on the resource but constrains the region and accountâthis is acceptable because log groups and streams are dynamically named, and CloudWatch does not support path-based restriction beyond this pattern.
Step 2: Add the Trust Policy
The role also needs a trust policy that allows the Lambda service to assume it. This is set on the role itself, not the function:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
This trust policy is standard and should appear on every Lambda execution role. Without it, the function cannot obtain temporary credentials.
Step 3: Use Conditions to Strengthen Policies
IAM condition keys let you add contextual restrictions. For example, you can restrict a function's S3 access to objects tagged with a specific key, or prevent the function from accessing resources during non-business hours:
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::incoming-images-12345/*",
"Condition": {
"StringEquals": {
"s3:ExistingObjectTag/environment": "prod"
}
}
}
This condition ensures the function can only retrieve objects tagged environment=prod, adding a defense-in-depth layer even if the bucket path is correct.
Step 4: Define a Resource-Based Policy for Invocation Control
If your Lambda is triggered by an S3 bucket event notification or an API Gateway endpoint, you may need to grant that service permission to invoke the function. This is done via a resource-based policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:123456789012:function:image-processor",
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:apigateway:us-east-1::/restapis/abc123/*"
}
}
}
]
}
The AWS:SourceArn condition prevents other API Gateway instancesâeven in the same accountâfrom calling your function, a technique called confused deputy prevention.
What Is Lambda Network Security?
By default, Lambda functions run in an AWS-managed network space. They have internet access through AWS's NAT infrastructure and can reach public AWS endpoints, but they cannot access resources inside a VPCâsuch as an RDS database in a private subnetâunless you explicitly attach the function to that VPC.
When you enable VPC attachment, Lambda creates an elastic network interface (ENI) in your VPC subnets. The function's runtime then routes traffic through that ENI, obeying all the VPC's network controls: security groups, NACLs, route tables, and VPC endpoints.
Why Network Security Matters for Lambda
Functions that process sensitive data often need to isolate traffic away from the public internet. Consider a Lambda that queries a financial database in RDS. Exposing that database to the internetâeven indirectlyâcreates a significant breach risk. Placing both the Lambda and the database inside a VPC ensures that traffic stays within your controlled network boundary.
Network configuration also affects performance. Lambda functions attached to a VPC may experience cold start latency from ENI provisioning. Understanding this trade-off is crucial for high-throughput applications.
How to Secure Lambda's Network Connectivity
Step 1: Attach Lambda to a VPC
Specify VpcConfig in your CloudFormation, Terraform, or CLI deployment. You must provide at least one security group and subnet IDs (typically private subnets):
{
"VpcConfig": {
"SubnetIds": [
"subnet-abc12345",
"subnet-def67890"
],
"SecurityGroups": [
"sg-11111aaa"
]
}
}
The security group acts as a virtual firewall. Create a dedicated security group for your Lambda functions rather than reusing one from EC2 instances. This allows precise inbound/outbound rules.
Step 2: Define Tight Security Group Rules
A Lambda security group typically needs no inbound rules because Lambda does not listen on portsâit's invoked by the service, not by direct network connections. The only exception is if your function acts as a client receiving callbacks, but this is rare. Outbound rules should allow only the destinations your function needs:
# Security group outbound rules (example)
# Allow HTTPS to DynamoDB via VPC endpoint
Type: HTTPS (443)
Destination: vpc-endpoint-id-pl-abc123 (the VPC endpoint security group)
# Allow TCP 3306 to RDS
Type: MySQL (3306)
Destination: sg-database-67890 (the database security group)
By referencing the database's security group as the destination, you avoid hard-coding IP addresses and ensure traffic is only permitted between the Lambda SG and the database SG.
Step 3: Use VPC Endpoints to Keep Traffic Off the Internet
When a VPC-attached Lambda needs to call AWS services like S3, DynamoDB, or SQS, the traffic would normally go through a NAT gateway to the public internetâeven though both endpoints are inside AWS. VPC endpoints (Gateway or Interface) keep this traffic entirely within the AWS network:
# Example: S3 Gateway Endpoint
Type: Gateway
Service: com.amazonaws.region.s3
Route Table Association: private-subnet-route-tables
# Example: DynamoDB Interface Endpoint
Type: Interface
Service: com.amazonaws.region.dynamodb
Security Group: sg-vpc-endpoint-xyz
Subnet: private-subnets
Gateway endpoints are free and support S3 and DynamoDB. Interface endpoints cover most other services (SQS, SNS, KMS, Secrets Manager, etc.) and have an hourly cost. For production environments handling regulated data, the cost is negligible compared to the security benefit of eliminating internet traversal.
Step 4: Validate Network Isolation with Testing
After configuring VPC attachment and endpoints, test that your function can reach intended destinations and cannot reach unintended ones. A simple Lambda can validate connectivity:
import boto3
import socket
import os
def lambda_handler(event, context):
# Test 1: Can we reach DynamoDB via VPC endpoint?
try:
client = boto3.client('dynamodb')
response = client.list_tables(limit=1)
print(f"DynamoDB reachable: {response['ResponseMetadata']['HTTPStatusCode']}")
except Exception as e:
print(f"DynamoDB error: {e}")
# Test 2: Is the internet blocked? (should timeout)
try:
socket.setdefaulttimeout(3)
socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect(("example.com", 80))
print("WARNING: Internet is reachable!")
except socket.timeout:
print("Internet unreachable: expected behavior")
except Exception as e:
print(f"Internet test result: {e}")
return {"status": "network_validation_complete"}
Run this test Lambda in your VPC configuration. A timeout when connecting to example.com confirms that your private subnets lack a route to an internet gateway, which is the desired state for data-plane isolation.
Combined IAM and Network Security Architecture
The strongest security posture combines both pillars. Imagine a Lambda that processes payment transactions:
- IAM: The execution role allows only
kms:Decrypton a specific CMK,dynamodb:PutItemon a specific table, and minimal CloudWatch Logs. - Network: The function runs inside a VPC with no internet gateway, uses a VPC endpoint for DynamoDB, and has a security group that only permits outbound traffic to the KMS VPC endpoint and the DynamoDB endpoint security group.
- Resource policy: Only an SQS queue in the same account may invoke the function, enforced with an
AWS:SourceArncondition.
This layered approach means that even if an attacker compromises the function code and manages to extract credentials, they cannot exfiltrate data to the internet, nor can they access resources beyond the narrowly defined set.
Best Practices for Lambda Security
- Apply least privilege relentlessly. Start with an empty policy and add permissions one by one based on code analysis. Use IAM Access Analyzer to detect overly broad permissions.
- Use per-function execution roles. Never share a single role across multiple functions. Each function has different data access patterns and deserves its own security boundary.
- Restrict invocation with source ARN conditions. When functions are triggered by other AWS services, always constrain the
lambda:InvokeFunctionpermission withAWS:SourceArnorAWS:SourceAccount. - Run in private subnets by default. Only attach Lambda to public subnets or keep it detached from VPC if internet access is absolutely required. Otherwise, use VPC attachment with private subnets and VPC endpoints.
- Audit with AWS Config and CloudTrail. Enable Lambda-specific AWS Config rules to detect functions without VPC attachment or with overly permissive roles. CloudTrail logs every policy modificationâset up alerts for changes to production function roles.
- Monitor network egress. Use VPC Flow Logs on the subnets hosting your Lambda functions. Unexpected outbound connections indicate either misconfiguration or compromise.
- Encrypt environment variables. Use KMS-managed keys for Lambda environment variables, and restrict the execution role to only the
kms:Decryptaction on those specific keys. - Regularly rotate and review. Schedule quarterly reviews of every Lambda execution role. Remove unused permissions that accumulated during development.
Conclusion
Lambda security isn't a checkboxâit's an ongoing practice of constraining both permissions and network paths. IAM policies define what your function can touch, while VPC configuration and security groups define how it reaches those resources. Together they form a robust defense-in-depth strategy that limits blast radius, prevents data exfiltration, and satisfies compliance requirements. Start with the principle of least privilege, isolate sensitive workloads in private subnets, and continuously audit your configuration. A well-secured Lambda function is not only safer but also easier to reason about, troubleshoot, and maintain over the long term.