Lambda's two policies: execution role and resource-based policy

A Lambda function has an execution role (what the function may do) and a resource-based policy (who may invoke or manage it). How each works, the August 2026 change to full IAM resource-based policies, why PutResourcePolicy overwrites add-permission grants, and how to scope service principals.

markdownreferencev4EE5Q-7WR9stage: referencetrust: officialservice: lambdaservice: iaminterface: anyLambda execution roleLambda resource-based policyfunction policyadd-permissionput-resource-policyPutResourcePolicyget-policyremove-permissionRevisionIdlambda.amazonaws.comAWSLambdaBasicExecutionRoleAWSLambdaVPCAccessExecutionRoleaws:SourceArnaws:SourceAccountcross-account invokefull IAM resource-based policies

Created 18 Oct 2026 · Modified 23 Oct 2026 · 4 versions

Lambda's two policies

The execution role says what the function can do. The resource-based policy says who can invoke or manage the function. They are separate documents, attached in different places, and a missing permission on either one produces a different error.

Execution roleResource-based (function) policy
Attached toAn IAM role, set as the function's RoleThe function, a version or an alias
ControlsThe function's calls to AWS: S3, DynamoDB, logs, KMSCallers of lambda:InvokeFunction and other Lambda actions
Principallambda.amazonaws.com in the role's trust policyServices (s3.amazonaws.com, events.amazonaws.com, …), accounts, organizations
Missing permission looks likeAccessDenied… / …not authorized to perform: s3:GetObject inside the function's logs…not authorized to perform: lambda:InvokeFunction on resource… for the caller, or Unable to validate the following destination configurations when S3 saves a notification
Inspectaws iam list-attached-role-policies --role-name <role>aws lambda get-policy --function-name <fn>

Execution role

  • The trust policy allows lambda.amazonaws.com to sts:AssumeRole. Lambda assumes it on every invocation and hands the code temporary credentials through environment variables, which the SDK picks up. Don't call sts:AssumeRole on your own execution role. If you really need the role to assume itself, it must trust itself explicitly.
  • AWS managed policies to start from:
  • AWSLambdaBasicExecutionRole: CloudWatch Logs.
  • AWSLambdaVPCAccessExecutionRole: creating and deleting the network interfaces a VPC function needs. It does not grant network access; that comes from routes (patterns/lambda-in-vpc.md).
  • Service-specific ones, such as AWSLambdaDynamoDBExecutionRole for stream event sources.
  • Trim before production. IAM Access Analyzer can generate a policy from the CloudTrail activity of the role over a date range.
  • The console creates a role with basic permissions by default, or attaches one via IAM role manager when that is enabled in the account.
  • Whoever creates or updates the function needs iam:PassRole on this role (guides/iam/create-service-role.md).

Resource-based policy

Before and after August 2026

Until 2026-08-25, the function policy could only be built one grant at a time with add-permission: one principal and one action per statement. Since then, Lambda supports full IAM resource-based policies in all commercial Regions. A single document can name several principals and actions and use any IAM condition key, such as source IP or principal tags. You can write it in the console JSON editor, the CLI, the SDKs, CloudFormation or SAM.

The overwrite trap

PutResourcePolicy replaces the entire policy on the function, version or alias. Every statement added earlier with add-permission, including the ones the console added for triggers, is gone unless your document repeats it. Before you put a policy:

  1. aws lambda get-policy --function-name <fn>: copy the existing statements and the RevisionId.
  2. Merge your change into that document.
  3. Put it with the RevisionId. If someone changed the policy in between, the call fails with PreconditionFailedException (412) instead of silently overwriting their change. The API page names both GetResourcePolicy and GetFunction/GetAlias as the place to read the current revision; it contradicts itself.
import { LambdaClient, PutResourcePolicyCommand } from "@aws-sdk/client-lambda";

const lambda = new LambdaClient({ region: "<region>" });
const fn = "arn:aws:lambda:<region>:<account>:function:<name>";

await lambda.send(new PutResourcePolicyCommand({
  ResourceArn: fn,
  // Replaces the WHOLE policy: include every grant you want to keep.
  Policy: JSON.stringify({
    Version: "2012-10-17",
    Statement: [{
      Sid: "S3AndEventBridgeInvoke",
      Effect: "Allow",
      Principal: { Service: ["s3.amazonaws.com", "events.amazonaws.com"] },
      Action: "lambda:InvokeFunction",
      Resource: fn,
      Condition: { StringEquals: { "aws:SourceAccount": "<account>" } },
    }],
  }),
  // RevisionId: "<current revision>", // fail with 412 instead of overwriting a concurrent change
}));

ResourceArn takes the function ARN, with or without a version or alias qualifier, and no wildcards. The policy is limited to 20,480 characters. A policy that would make the function public is rejected with PublicPolicyException. The CLI form is aws lambda put-resource-policy --resource-arn … --policy file://policy.json (UNVERIFIED command spelling; derived from the API name). Calling it needs three IAM actions: lambda:PutResourcePolicy plus its dependent actions lambda:AddPermission and lambda:RemovePermission (Service Authorization Reference). Likewise lambda:GetResourcePolicy depends on lambda:GetPolicy, and lambda:DeleteResourcePolicy on lambda:RemovePermission. A deploy role that only had lambda:AddPermission before August 2026 fails with AccessDenied on the new call.

Use one style per function. Either manage the whole document with put-resource-policy (or IaC), or add grants with add-permission. Mixing them is how grants disappear.

add-permission (still the per-grant form)

aws lambda add-permission --function-name <fn> --statement-id <unique-id> \
  --action lambda:InvokeFunction --principal s3.amazonaws.com \
  --source-arn arn:aws:s3:::<bucket> --source-account <bucket-owner-account>

A duplicate --statement-id fails with ResourceConflictException. Remove a grant with aws lambda remove-permission --function-name <fn> --statement-id <id>.

Scoping service principals

When a service such as S3, SNS or EventBridge invokes the function, condition the grant on aws:SourceArn (the specific bucket, topic or rule) and aws:SourceAccount. Without them, any resource of that service in any account that knows the function ARN can invoke it (the confused-deputy problem: patterns/iam/trust-policies.md).

Cross-account

Account B invoking a function in account A needs both: B's identity policy allows lambda:InvokeFunction on the function ARN, and A's function policy allows B (an account principal, a role ARN, or aws:PrincipalOrgID for an organization).

See also

  • patterns/s3-to-lambda.md: the most common grant.
  • reference/lambda/errors.md: PolicyLengthExceededException, PublicPolicyException, PreconditionFailedException.

Read this document as markdown — the same bytes an agent receives.