How to Manage AWS Lambda Permission Terraform Resources for Secure Invocation
Use the aws_lambda_permission resource to add resource-based policy statements that grant specific AWS services or accounts permission to invoke your Lambda function, always specifying a unique statement_id and scoping access with source_arn when possible.
When provisioning serverless architectures with the HashiCorp Terraform AWS provider, securing invocation access requires more than just defining the function itself. The aws_lambda_permission resource serves as the bridge between your Lambda function and its event sources, managing the resource-based policy that controls which principals can trigger execution. Understanding how to correctly configure this resource is essential for maintaining least-privilege access while avoiding common deployment errors like duplicate statement IDs.
Understanding the aws_lambda_permission Resource
The aws_lambda_permission resource is implemented in the AWS provider source code at internal/service/lambda/resource_permission.go. It creates and manages resource-based policy statements attached directly to your Lambda function, operating distinctly from identity-based IAM policies.
Unlike many AWS services that rely solely on IAM roles, Lambda employs a resource-based policy model where the function itself stores permissions listing which principals may invoke it. Terraform cannot embed these permissions directly inside the aws_lambda_function resource because multiple external services—such as API Gateway, S3, SNS, or EventBridge—might simultaneously need invocation rights. The dedicated aws_lambda_permission resource allows each caller to contribute its own policy statement modularly, avoiding configuration conflicts and ensuring Terraform can track each permission independently through its state management.
Essential Configuration Parameters
When defining an aws_lambda_permission resource, you must configure several key parameters that determine who can invoke your function and under what conditions:
-
statement_id– A unique identifier for this permission statement within the Lambda's policy. This value must be unique across all permission statements for the same function, and Terraform uses it to track the resource state. -
action– The Lambda action to allow. For invocation scenarios, this is always"lambda:InvokeFunction". -
function_name– The name or ARN of the Lambda function to which you are granting access. -
principal– The AWS service principal or account ID that will invoke the function. Common values includeapigateway.amazonaws.com,s3.amazonaws.com,sns.amazonaws.com, orevents.amazonaws.com. -
source_arn(optional but strongly recommended) – The ARN of the specific resource allowed to invoke the function. Scoping permissions to a specific source improves security posture and prevents "duplicate statement" errors when the same principal invokes multiple functions. -
qualifier– If you use Lambda versioning or aliases, set this to the specific version number or alias name to restrict invocation to that qualifier.
Practical Implementation Examples
The following examples demonstrate common patterns for connecting AWS services to Lambda functions using the aws_lambda_permission resource.
API Gateway Integration
When exposing a Lambda function via API Gateway, you must grant the API service permission to invoke your function. The source_arn should reference the API's execution ARN to ensure only that specific API can trigger the function.
resource "aws_lambda_function" "hello" {
function_name = "hello-world"
role = aws_iam_role.lambda_exec.arn
handler = "index.handler"
runtime = "nodejs20.x"
filename = "lambda.zip"
}
resource "aws_api_gateway_rest_api" "api" {
name = "hello-api"
}
resource "aws_api_gateway_resource" "proxy" {
rest_api_id = aws_api_gateway_rest_api.api.id
parent_id = aws_api_gateway_rest_api.api.root_resource_id
path_part = "{proxy+}"
}
resource "aws_api_gateway_method" "any_method" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = "ANY"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "lambda_integration" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = aws_api_gateway_method.any_method.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.hello.invoke_arn
}
# Permission allowing API Gateway to invoke the Lambda
resource "aws_lambda_permission" "api_gateway" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.hello.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.api.execution_arn}/*/*"
}
Key implementation details:
- The
source_arnuses the API Gatewayexecution_arnwith wildcards to allow any stage and method path while still restricting to the specific API. - The
statement_idmust be unique for this function; reusing "AllowAPIGatewayInvoke" for another permission on the same function causes errors.
S3 Bucket Notifications
When configuring S3 to trigger Lambda functions on object creation or deletion, you must grant S3 service permission to invoke your function. The source_arn should specify the exact bucket ARN to prevent other buckets from triggering your function.
resource "aws_lambda_function" "process_s3" {
function_name = "process-s3"
role = aws_iam_role.lambda_exec.arn
handler = "index.handler"
runtime = "python3.11"
filename = "lambda.zip"
}
resource "aws_s3_bucket" "source_bucket" {
bucket = "my-source-bucket"
}
# Permission for S3 to invoke the Lambda
resource "aws_lambda_permission" "s3_invoke" {
statement_id = "AllowS3Invoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.process_s3.function_name
principal = "s3.amazonaws.com"
source_arn = aws_s3_bucket.source_bucket.arn
}
Lambda Aliases and EventBridge
When using Lambda versioning and aliases—particularly for production deployments—you must reference the alias in the permission resource using the qualifier parameter. This ensures only the specific published version behind the alias can be invoked, not the unpublished $LATEST version.
resource "aws_lambda_function" "versioned" {
function_name = "my-versioned-fn"
role = aws_iam_role.lambda_exec.arn
handler = "index.handler"
runtime = "go1.x"
filename = "lambda.zip"
publish = true # creates a new version each apply
}
resource "aws_lambda_alias" "prod" {
name = "prod"
function_name = aws_lambda_function.versioned.function_name
function_version = aws_lambda_function.versioned.version
}
resource "aws_cloudwatch_event_rule" "schedule" {
name = "daily-trigger"
schedule_expression = "rate(1 day)"
}
resource "aws_cloudwatch_event_target" "lambda_target" {
rule = aws_cloudwatch_event_rule.schedule.name
target_id = "lambda"
arn = aws_lambda_alias.prod.arn
}
resource "aws_lambda_permission" "eventbridge" {
statement_id = "AllowEventBridgeInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.versioned.function_name
qualifier = aws_lambda_alias.prod.name # points to the alias
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.schedule.arn
}
Implementation note: The qualifier attribute points to the alias name, ensuring the permission applies only to the published version behind the alias rather than the mutable $LATEST version.
Common Pitfalls and Best Practices
When implementing aws_lambda_permission resources, avoid these frequent errors to ensure clean deployments and secure configurations:
-
Duplicate Statement IDs – The
statement_idmust be unique across all permission statements for a single Lambda function. Reusing an ID such as"AllowExecutionFromAPIGateway"for multiple permissions on the same function results in a "duplicate statement ID" error during apply, as the provider implementation atinternal/service/lambda/resource_permission.goenforces this uniqueness constraint. -
Omitting Source ARN – While optional, omitting
source_arncreates overly broad permissions that allow any resource from the specified principal service to invoke your function. For example, omitting this for S3 would allow any bucket to trigger your Lambda. This violates least-privilege principles and can cause "duplicate statement" errors when multiple functions use the same principal. -
Versioning Conflicts – When using Lambda aliases, failing to specify the
qualifierparameter results in permissions attached to the unpublished$LATESTversion rather than your production alias. This causes invocation failures when clients attempt to call the alias ARN, as the permission does not apply to the qualified ARN. Always reference the alias name in thequalifierfield when using versioning. -
Timing Dependencies – While modern AWS provider versions handle most dependencies automatically through implicit references in
source_arn, explicitly declaringdepends_onrelationships between the permission and the invoking resource ensures Terraform creates the permission only after the target resource exists, preventing race conditions during initial infrastructure creation.
Summary
Managing Lambda invocation permissions through Terraform requires understanding the resource-based policy model that AWS Lambda employs. Key takeaways include:
- The
aws_lambda_permissionresource, implemented in the AWS provider atinternal/service/lambda/resource_permission.go, creates distinct policy statements attached to your function. - Always define a unique
statement_idfor each permission to avoid duplicate statement errors during deployment. - Scope permissions using
source_arnto prevent overly broad access and ensure only specific resources can trigger your function. - When working with Lambda aliases, use the
qualifierparameter to attach permissions to the specific alias rather than the$LATESTversion. - Common invocation patterns include API Gateway REST APIs, S3 bucket notifications, and EventBridge scheduled events, each requiring specific principal and source ARN configurations according to the Terraform AWS provider documentation.
Frequently Asked Questions
What happens if I omit the source_arn parameter in aws_lambda_permission?
Omitting source_arn creates a permission that allows any resource from the specified principal service to invoke your Lambda function. For example, if you set principal = "s3.amazonaws.com" without a source_arn, any S3 bucket in your account could theoretically trigger your function. This violates the principle of least privilege and increases your attack surface. Additionally, omitting source_arn often causes "duplicate statement" errors when the same principal invokes multiple functions, as Lambda cannot distinguish between the permissions without unique source ARNs.
Why do I receive a "duplicate statement ID" error when applying Terraform?
Lambda's resource-based policy requires every permission statement to have a unique identifier within the scope of that specific function. The statement_id parameter in Terraform maps directly to the Sid (Statement ID) field in the Lambda policy. If you reuse the same statement_id value—such as "AllowExecutionFromAPIGateway"—for multiple aws_lambda_permission resources attached to the same Lambda function, AWS rejects the second creation attempt because the ID already exists in the policy. Always use descriptive, unique statement IDs such as "AllowAPIGatewayInvokeHelloWorld" or "AllowS3InvokeProcessBucket".
How do I configure permissions when using Lambda aliases or versions?
When you publish Lambda versions or create aliases—such as a prod alias pointing to version 5—you must reference the alias in the permission resource using the qualifier parameter. Set qualifier to the alias name (e.g., "prod") or version number (e.g., "5"). This attaches the permission to that specific qualifier rather than the unpublished $LATEST version. When combined with source_arn, this ensures that only your specific EventBridge rule, API Gateway stage, or other trigger can invoke the specific published version of your function, supporting immutable infrastructure patterns and safe deployment practices.
Can I use wildcards in the principal or source_arn fields?
While AWS IAM policies generally support wildcards, using them in aws_lambda_permission is strongly discouraged and often unnecessary. The principal field should specify an exact service principal (such as apigateway.amazonaws.com or s3.amazonaws.com) or a specific AWS account ID. Using wildcards in source_arn—such as arn:aws:s3:::*—defeats the purpose of resource-based scoping and can lead to security vulnerabilities where unintended resources trigger your function. Always specify the exact ARN of the API Gateway execution, S3 bucket, or EventBridge rule to maintain strict security boundaries and prevent the "duplicate statement" errors that occur when permissions are too broad.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →