Lambda Function Not Publicly Accessible
Overview
This check examines AWS Lambda function resource-based policies for overly permissive access. It flags functions where the policy contains a wildcard (*) or empty Principal that allows anyone on the internet to invoke the function.
Risk
A publicly accessible Lambda function creates serious security vulnerabilities:
- Unauthorized code execution: Anyone can invoke your function, running code under your function's IAM role
- Data exposure: Attackers may access backend systems (databases, APIs, internal services) through your function
- Data manipulation: Malicious invocations could modify data or trigger unintended side effects
- Cost explosion: Attackers can flood your function with invocations, causing throttling and unexpected AWS charges
- Compliance violations: Public Lambda functions may violate regulatory requirements (PCI-DSS, HIPAA, SOC 2)
Remediation Steps
Prerequisites
- AWS account access with permissions to modify Lambda function policies
- Knowledge of which AWS services or accounts should legitimately invoke your function
Required IAM permissions
You will need the following permissions:
lambda:GetPolicy- View the function's resource-based policylambda:RemovePermission- Remove permissive policy statementslambda:AddPermission- Add properly scoped permissions
AWS Console Method
Step 1: Review the current policy
- Sign in to the AWS Management Console
- Navigate to Lambda > Functions
- Select the flagged function
- Click the Configuration tab
- Click Permissions in the left sidebar
- Scroll down to Resource-based policy statements
- Look for statements where Principal is set to
*(these are the problematic ones) - Note down the Statement ID (Sid) for each overly permissive statement
Step 2: Remove the public permission
- In the Resource-based policy statements section, find the statement with Principal
* - Click the statement to expand it
- Click Delete (or the trash icon)
- Confirm the deletion when prompted
Step 3: Add properly scoped permissions (if needed)
If a legitimate AWS service or account needs to invoke this function, add a new permission with a specific principal:
- In the Resource-based policy statements section, click Add permissions
- Choose the appropriate option:
- AWS service - For services like S3, SNS, API Gateway, etc.
- AWS account - For cross-account access
- Configure the permission:
- Service: Select the AWS service (e.g.,
sns.amazonaws.com) - Statement ID: Enter a descriptive name (e.g.,
allow-sns-invoke) - Principal: The service or account ARN
- Action:
lambda:InvokeFunction
- Service: Select the AWS service (e.g.,
- For service principals, add conditions to restrict access:
- Source ARN: The ARN of the specific resource (e.g., an SNS topic ARN)
- Source account: Your AWS account ID
- Click Save
AWS CLI (optional)
View the current policy:
aws lambda get-policy \
--function-name <your-function-name> \
--region us-east-1
This returns a JSON policy. Look for statements where Principal is "*" or {"AWS": "*"} and note the Sid value.
Remove the public permission:
aws lambda remove-permission \
--function-name <your-function-name> \
--statement-id <statement-id-from-policy> \
--region us-east-1
Replace <statement-id-from-policy> with the Sid value you noted (e.g., public-access or AllowPublicInvoke).
Add a properly scoped permission (example for SNS):
aws lambda add-permission \
--function-name <your-function-name> \
--statement-id allow-sns-invoke \
--action lambda:InvokeFunction \
--principal sns.amazonaws.com \
--source-arn arn:aws:sns:us-east-1:<account-id>:<topic-name> \
--source-account <account-id> \
--region us-east-1
Add a properly scoped permission (example for specific AWS account):
aws lambda add-permission \
--function-name <your-function-name> \
--statement-id allow-account-invoke \
--action lambda:InvokeFunction \
--principal arn:aws:iam::<trusted-account-id>:root \
--region us-east-1
CloudFormation (optional)
Use AWS::Lambda::Permission resources to define explicit, scoped permissions. Do not use * as the principal.
AWSTemplateFormatVersion: '2010-09-09'
Description: Lambda function with properly scoped invocation permissions
Parameters:
FunctionName:
Type: String
Description: Name of the Lambda function
SNSTopicArn:
Type: String
Description: ARN of the SNS topic allowed to invoke the function
Resources:
MyLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Ref FunctionName
Runtime: python3.12
Handler: index.lambda_handler
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
def lambda_handler(event, context):
return {'statusCode': 200, 'body': 'Hello'}
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Properly scoped permission for SNS
SNSInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref MyLambdaFunction
Action: lambda:InvokeFunction
Principal: sns.amazonaws.com
SourceArn: !Ref SNSTopicArn
SourceAccount: !Ref AWS::AccountId
# Example: Properly scoped permission for API Gateway
# APIGatewayInvokePermission:
# Type: AWS::Lambda::Permission
# Properties:
# FunctionName: !Ref MyLambdaFunction
# Action: lambda:InvokeFunction
# Principal: apigateway.amazonaws.com
# SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:<api-id>/*/*/*"
Outputs:
FunctionArn:
Description: ARN of the Lambda function
Value: !GetAtt MyLambdaFunction.Arn
Deploy the template:
aws cloudformation deploy \
--template-file lambda-permissions.yaml \
--stack-name lambda-secure-permissions \
--parameter-overrides \
FunctionName=my-secure-function \
SNSTopicArn=arn:aws:sns:us-east-1:123456789012:my-topic \
--capabilities CAPABILITY_IAM \
--region us-east-1
Important: Never use Principal: "*" in AWS::Lambda::Permission resources.
Terraform (optional)
Use aws_lambda_permission resources with specific principals and source constraints.
provider "aws" {
region = "us-east-1"
}
# Lambda function
resource "aws_lambda_function" "my_function" {
filename = "function.zip"
function_name = "my-secure-function"
role = aws_iam_role.lambda_role.arn
handler = "index.lambda_handler"
runtime = "python3.12"
source_code_hash = filebase64sha256("function.zip")
}
# IAM role for Lambda
resource "aws_iam_role" "lambda_role" {
name = "my-lambda-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}]
})
}
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# Properly scoped permission for SNS
resource "aws_lambda_permission" "sns_invoke" {
statement_id = "AllowSNSInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.my_function.function_name
principal = "sns.amazonaws.com"
source_arn = aws_sns_topic.my_topic.arn
source_account = data.aws_caller_identity.current.account_id
}
# Example SNS topic that can invoke the function
resource "aws_sns_topic" "my_topic" {
name = "my-lambda-trigger-topic"
}
resource "aws_sns_topic_subscription" "lambda_subscription" {
topic_arn = aws_sns_topic.my_topic.arn
protocol = "lambda"
endpoint = aws_lambda_function.my_function.arn
}
# Get current account ID for source_account constraint
data "aws_caller_identity" "current" {}
# Example: Properly scoped permission for API Gateway
# resource "aws_lambda_permission" "api_gateway_invoke" {
# statement_id = "AllowAPIGatewayInvoke"
# action = "lambda:InvokeFunction"
# function_name = aws_lambda_function.my_function.function_name
# principal = "apigateway.amazonaws.com"
# source_arn = "${aws_api_gateway_rest_api.my_api.execution_arn}/*/*/*"
# }
# Example: Properly scoped permission for cross-account access
# resource "aws_lambda_permission" "cross_account_invoke" {
# statement_id = "AllowCrossAccountInvoke"
# action = "lambda:InvokeFunction"
# function_name = aws_lambda_function.my_function.function_name
# principal = "arn:aws:iam::123456789012:root" # Specific account
# }
Important: Never use principal = "*" in aws_lambda_permission resources.
Verification
After completing the remediation:
- Go to Lambda > Functions > your function in the AWS Console
- Click Configuration > Permissions
- Review Resource-based policy statements and confirm no statement has Principal
* - Verify any remaining permissions have specific principals (service names or account ARNs)
- Test your function to ensure legitimate callers can still invoke it
- Re-run the Prowler check to confirm the issue is resolved
CLI verification commands
Verify the policy no longer has public access:
aws lambda get-policy \
--function-name <your-function-name> \
--region us-east-1 \
--query 'Policy' \
--output text | python3 -m json.tool
Look through the output and confirm:
- No statement has
"Principal": "*" - No statement has
"Principal": {"AWS": "*"} - All principals are specific service names (e.g.,
sns.amazonaws.com) or account ARNs
If the function has no resource-based policy (which is secure), you will see an error:
An error occurred (ResourceNotFoundException): The resource you requested does not exist.
This is expected and means the function has no external invocation permissions.
Additional Resources
- Lambda Resource-Based Policies
- Using Resource-Based Policies for Lambda
- Lambda Permissions Best Practices
- AWS IAM Policy Conditions
Notes
- Assess before removing: Before removing a public permission, investigate why it was added. Removing it may break integrations.
- Use source conditions: When granting access to AWS services, always use
SourceArnandSourceAccountconditions to restrict which specific resources can invoke your function. - API Gateway considerations: If your Lambda is fronted by API Gateway, the API Gateway permission should specify the API's execution ARN, not a wildcard.
- Cross-account access: For cross-account invocations, specify the exact account ID or IAM role ARN rather than using wildcards.
- Function URLs: If you need public HTTP access to your Lambda, consider using Lambda function URLs with proper authentication (IAM or NONE with your own auth logic) rather than a public resource policy.
- Audit regularly: Periodically review Lambda permissions using IAM Access Analyzer to detect unintended public access.