Fix AccessDeniedException: User s3.amazonaws.com is Not Authorized to Perform lambda:InvokeFunction

intermediate☁️ AWS2026-06-17| AWS Lambda, Amazon S3, AWS CLI, Terraform, CloudFormation

Error Message

An error occurred (AccessDeniedException) when calling the Invoke operation: User: s3.amazonaws.com is not authorized to perform: lambda:InvokeFunction on resource
#aws#lambda#s3#iam-policy#devops

The Problem

You have wired up an Amazon S3 bucket to trigger a Lambda function every time a user drops a new file into a folder. On paper, everything looks correct. But in reality, the function never fires. If you try to test the trigger manually via the CLI, you hit this wall:

An error occurred (AccessDeniedException) when calling the Invoke operation: User: s3.amazonaws.com is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:us-east-1:123456789012:function:my-processor-func

The Root Cause: The Guest List Problem

This failure stems from a missing resource-based policy. Most developers focus on IAM execution roles, which tell the Lambda what it is allowed to touch. However, S3 uses a "push" model. It needs explicit permission to reach out and wake up your function.

Think of an IAM Role as the Lambda's ID card—it shows what the function can do. A resource-based policy is the guest list at the door. If the S3 service principal (s3.amazonaws.com) isn't on that guest list, the request is rejected immediately.

How to Fix It

Approach 1: The Quick Fix (AWS CLI)

You can solve this in seconds by running the add-permission command. This appends a statement to the Lambda’s internal policy without requiring you to manually edit JSON files.

aws lambda add-permission \
    --function-name my-processor-func \
    --statement-id AllowS3Invocation \
    --action lambda:InvokeFunction \
    --principal s3.amazonaws.com \
    --source-arn arn:aws:s3:::my-app-uploads-123 \
    --source-account 123456789012
- **--statement-id**: A unique name for this rule (e.g., `ProductionS3Trigger`).
- **--source-arn**: This is critical for security. It ensures *only* your specific bucket can trigger the function, preventing "confused deputy" attacks from other accounts.

Approach 2: The Visual Way (AWS Console)

If you prefer the web interface, use the Lambda dashboard rather than the S3 dashboard. The Lambda UI handles the background "permission handshake" much more reliably than the S3 notification pane.

- Open your function in the **Lambda Console**.
- Select the **Configuration** tab, then click **Permissions**.
- Look at the **Resource-based policy statements** section.
- If it's empty, click **Add trigger** at the top of the page.
- Select **S3**, choose your bucket, and click **Add**. AWS will now automatically inject the required policy statement.

Approach 3: The Permanent Fix (Terraform)

Manually clicking buttons leads to "configuration drift" where your code no longer matches your actual infrastructure. Use the aws_lambda_permission resource to make this change permanent and repeatable.

resource "aws_lambda_permission" "allow_s3" {
  statement_id  = "AllowS3Invoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.processor.function_name
  principal     = "s3.amazonaws.com"
  source_arn    = "arn:aws:s3:::${var.bucket_name}"
}

Verification

Double-check the policy is actually active by peeking at the raw JSON:

aws lambda get-policy --function-name my-processor-func

Search for "Service": "s3.amazonaws.com" in the output. Once you see it, upload a 1KB test file to your bucket. Check your CloudWatch Logs immediately—you should see a fresh log stream appearing within 5 to 10 seconds.

Pro-Tips for Prevention

- **Lock Down ARNs**: Never leave `--source-arn` empty. If you do, any S3 bucket in the world that knows your function ARN could potentially trigger it.
- **Mind the Regions**: Ensure your bucket and Lambda live in the same region (e.g., `us-east-1`). While cross-region triggers exist, they often fail silently if permissions aren't perfect.
- **Check for Circular Dependencies**: If you use CloudFormation, creating the bucket notification and the Lambda permission at the same time can sometimes cause deployment loops. Always create the permission first.

Related Error Notes