← Back to DevBytes

Lambda Security: IAM Policies and Network Security

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:

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:

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

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.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles