AWS Lambda resource policies now need 3 IAM permissions, and the docs disagree on RevisionId
Summary. AWS announced full IAM resource-based policies for Lambda functions on 25 August 2026, in all commercial Regions, at $0 additional charge. The launch adds three API actions - PutResourcePolicy, GetResourcePolicy and DeleteResourcePolicy - on a new /2026-07-09/resource-policy/ request path, while AddPermission and GetPolicy stay on /2015-03-31/. Three details in the documentation matter more than the headline. Calling PutResourcePolicy requires 3 separate IAM permissions, not 1. The policy ceiling is still 20 KB (20,480 characters), exactly what it was before. And two AWS pages give different answers for where the RevisionId in a conditional write comes from, which is the difference between a retry loop that converges and one that returns HTTP 412 forever. There is a fourth, quieter cost: the read-modify-write pair AWS now recommends draws 100% more of the shared 15 requests per second control-plane budget than the legacy pair it replaces.
What actually shipped
The legacy path dates to the 2015-03-31 Lambda API version and has carried the same 20 KB policy ceiling throughout. Before the 25 August 2026 launch, a Lambda function's resource-based policy could only be built one statement at a time through AddPermission. That path accepts only three condition keys: aws:SourceArn, aws:SourceAccount and aws:PrincipalOrgID. No explicit Deny. No aws:PrincipalOrgPaths. No source-IP condition.
The new path accepts a complete JSON policy document. AWS states the maximum size for a JSON resource-based policy is 20 KB, and the API reference pins the Policy parameter at a minimum length of 1 and a maximum length of 20480. With a full document you can write explicit Deny statements, use the full range of IAM global condition keys, and grant several services access in one statement instead of several.
The Lambda developer guide is direct about which one AWS wants you to use: "We recommend that you define complete JSON policies to add resource-based permissions to your function."
The permission arithmetic nobody put in the announcement
The feature is aimed at platform admins and security teams. Those are exactly the teams that write tightly scoped admin roles. Here is what the developer guide's required-permissions table asks for.
| API action | IAM permissions required | Count |
|---|---|---|
| PutResourcePolicy |
lambda:PutResourcePolicy, lambda:AddPermission, lambda:RemovePermission
|
3 |
| GetResourcePolicy |
lambda:GetResourcePolicy, lambda:GetPolicy
|
2 |
| DeleteResourcePolicy |
lambda:DeleteResourcePolicy, lambda:RemovePermission
|
2 |
| AddPermission (legacy) | lambda:AddPermission |
1 |
| GetPolicy (legacy) | lambda:GetPolicy |
1 |
| RemovePermission (legacy) | lambda:RemovePermission |
1 |
Read the first row again. To use the operation AWS recommends for least-privilege policy management, an identity needs the two legacy mutation permissions as well as the new one. A role holding only lambda:PutResourcePolicy gets AccessDenied. A role granted all three can also call lambda:AddPermission and lambda:RemovePermission directly, outside the JSON editor, on any function the role's Resource block reaches.
So the identity-side blast radius of a "policy admin" role goes up, not down, even though the resource-side policy it writes can now be far more precise. If your organisation gates IAM changes on a permissions diff, expect that diff to look worse on the day you adopt this. It is the same shape we covered when IAM's 20 managed policies per role cap forced teams into inline policies, and when the IAM Role Manager's PowerUserAccess default broke role reuse.
What breaks: put-resource-policy replaces everything
The developer guide carries an Important callout: using put-resource-policy replaces any existing resource-based policy on the resource, and if the resource already has permissions defined with add-permission, put-resource-policy overwrites them. The API reference repeats it.
That is the operational hazard, because almost nobody writes Lambda trigger permissions by hand. Every AWS::Lambda::Permission in a CloudFormation stack, every event source wired up through the console, every S3 bucket notification and EventBridge rule target adds a statement through AddPermission. One put-resource-policy from a platform team, applied against a function whose triggers were provisioned by three different stacks, removes all of those statements in a single call. Nothing about the function's code or configuration changes. The triggers simply stop being authorised.
The reverse direction behaves differently, and the guide says so: add-permission called after put-resource-policy appends a statement to the existing JSON policy rather than overwriting it. So a CloudFormation stack that runs after your JSON rollout repairs its own statement, and one that ran before it does not. Drift shows up on the next deploy, not at the moment of damage.
The safe sequence is a read-modify-write: call get-resource-policy, merge, then call put-resource-policy with the --revision-id you just read. Which brings us to the part that is wrong in the docs.
The RevisionId conflict
PutResourcePolicy takes an optional RevisionId. If it does not match, the call fails with PreconditionFailedException, HTTP status 412. Its purpose is to stop two concurrent writers from silently clobbering each other.
The question is where that value comes from. AWS answers it twice, differently, on the same page.
| Source | What it tells you to call for RevisionId | Correct for PutResourcePolicy? |
|---|---|---|
API_PutResourcePolicy, RevisionId parameter |
"To retrieve the current revision ID, use the GetResourcePolicy operation." | Yes |
API_GetResourcePolicy, RevisionId response |
Pass this value as the RevisionId in a PutResourcePolicy or DeleteResourcePolicy request | Yes |
| Developer guide, "Updating existing policies" |
get-resource-policy output includes a RevisionId field |
Yes |
API_PutResourcePolicy, PreconditionFailedException
|
"For all other API operations: Call GetFunction or GetAlias to retrieve the latest RevisionId" | No |
API_DeleteResourcePolicy, PreconditionFailedException
|
Same text, same routing | No |
The error block splits the world into two cases: AddPermission and RemovePermission, which should call GetPolicy; and "all other API operations", which should call GetFunction or GetAlias. PutResourcePolicy falls into the second bucket by plain reading. But GetFunction returns the function configuration's revision ID, which tracks code and configuration changes, not the policy revision that GetResourcePolicy returns.
That matters because the error block is the text an engineer reads at the worst possible moment: after a 412, mid-incident, inside a retry handler. Follow it and the retry passes a revision ID from the wrong object, the precondition fails again, and the loop never converges. The wording looks like boilerplate that predates the new operations and was carried across without editing. It appears verbatim on AddPermission, PutResourcePolicy and DeleteResourcePolicy.
The rule to encode in your tooling is simple: policy revision IDs come from GetResourcePolicy (new path) or GetPolicy (legacy path). GetFunction is never the right source for a policy precondition.
The rate limit nobody costed
Lambda's control-plane quotas have not moved, and that is the problem. From the quotas page: GetFunction is 100 requests per second. GetPolicy is 15 requests per second. Everything else is "15 requests per second across all APIs (not 15 requests per second per API)", and that bucket explicitly excludes only invocation, GetFunction and GetPolicy. None of these can be increased.
GetResourcePolicy is not named in that exclusion list. It sits in the shared 15 rps bucket. So does PutResourcePolicy.
| Workflow | Calls per function | Bucket used | Effective throughput |
|---|---|---|---|
Legacy: GetPolicy then AddPermission
|
2 | 15 rps dedicated + 15 rps shared | ~15 functions/sec |
New: GetResourcePolicy then PutResourcePolicy
|
2 | both in the same 15 rps shared bucket | ~7.5 functions/sec |
Read-only audit via GetPolicy
|
1 | 15 rps dedicated | ~15 functions/sec |
Read-only audit via GetResourcePolicy
|
1 | 15 rps shared | ~15 rps, shared with deploys |
| Any of the above during a deploy | varies | shared bucket contended | lower, throttled first |
Do the arithmetic for a real estate: 4,000 functions in one Region, read-modify-write, no concurrency beyond the quota. The legacy pair drains roughly 4,000 shared-bucket calls, about 4.5 minutes of shared budget. The new pair drains 8,000 shared-bucket calls, about 9 minutes, and it competes with every CreateFunction, UpdateFunctionConfiguration and PublishVersion your CI is running at the same time. On a large account the recommended workflow is slower than the one it replaces, and it throttles your deploys while it runs. Build the backoff in before the rollout, not after the first 429.
The 20 KB ceiling did not move
The launch pitch is that you can now define permissions for multiple principals and actions in a single policy document. The ceiling on that document is 20 KB, and AddPermission has always returned PolicyLengthExceededException against the same limit. This is not new headroom; it is the same headroom, spent differently.
A single statement with a Sid, a service principal, one action, a full function ARN and a Condition block on aws:SourceArn runs roughly 300 to 400 characters once minified. At 20,480 characters that is somewhere around 50 to 65 statements before the write starts failing with HTTP 400. Consolidating principals into one statement buys real room back, but a team that migrates a fan-in function with dozens of per-account grants should count the bytes before the cutover rather than after.
Two other 400-class errors are worth wiring into your rollout: PublicPolicyException, returned when the policy you submit would grant public access, and InvalidParameterValueException. DeleteResourcePolicy returns HTTP 204 rather than 200, and takes its RevisionId as a URI query parameter rather than a body field, which trips hand-rolled HTTP clients.
Who this affects, and how to tell if that is you
Run aws lambda get-policy across your functions and count statements. Three signals put you in the affected group.
You have functions with more than about a dozen AWS::Lambda::Permission statements, provisioned from more than one stack. Those are the functions where a single put-resource-policy does the most damage.
You have an IAM change-review process that blocks on new permissions. Adopting the recommended path adds lambda:AddPermission and lambda:RemovePermission to whatever role runs your policy tooling.
You operate outside the commercial partition. AWS scoped the launch to commercial Regions, yet the ResourceArn regex on all three new operations accepts (-gov), (-iso([a-z]?)) and a (eusc-)? prefix. A pattern that matches an ARN is not the same thing as a Region where the operation is enabled. Availability and authorization are different questions, and the pattern is not evidence for either. Test in the partition you actually run in.
Teams building fan-in event pipelines feel this first, because that is where a function accumulates the most grants. If you are designing one now, the permission model belongs in the design, not the retrofit. Our work on event-driven serverless architecture starts from the trigger inventory for exactly that reason. Related reading: Lambda microVMs and the 400 GB quota gap and Lambda durable execution versus Step Functions.
India-specific considerations
For teams in India running multi-account AWS estates, the practical cost is review time, not spend. The feature itself is free. A permissions change of this shape typically pulls a senior cloud engineer in for a day of role-diff and rollout design, which at prevailing Gurugram and Bengaluru contract rates of roughly Rs 12,000 to Rs 25,000 per engineer-day is a real line item on a 4,000-function estate spread across several accounts.
Under the Digital Personal Data Protection Act 2023, functions that process personal data sit inside the significant data fiduciary controls your organisation has to evidence. An explicit Deny statement scoped with aws:PrincipalOrgID is materially easier to show an auditor than a list of individually added Allow grants, so the new path helps the evidence story. The overwrite hazard cuts the other way: an accidental policy replacement that silently de-authorises a trigger is an availability event on a data path you are obliged to keep working.
What is still unknown
AWS has not published a per-Region enablement list, only "all commercial Regions". There is no stated migration tooling that converts an AddPermission-built policy into a consolidated JSON document; the guide's advice is to read the existing policy and merge by hand. CloudFormation and AWS SAM are named as supported update paths, but AWS has not documented what happens when a stack that owns AWS::Lambda::Permission resources runs against a function whose policy was replaced out of band. And the ResourceArn length constraint reads "Minimum length of 0" on a required field that AWS says must be a complete ARN.
FAQ
What did AWS change for Lambda resource-based policies?
On 25 August 2026 AWS added full IAM resource-based policy support to Lambda functions. Three API actions arrived: PutResourcePolicy, GetResourcePolicy and DeleteResourcePolicy. They accept a complete JSON policy document with multiple statements, explicit Deny, and the full range of IAM global condition keys, in all commercial Regions at no extra charge.
How many IAM permissions does PutResourcePolicy need?
Three. The Lambda developer guide's required-permissions table lists lambda:PutResourcePolicy, lambda:AddPermission and lambda:RemovePermission for that one action. GetResourcePolicy needs two, lambda:GetResourcePolicy plus lambda:GetPolicy. DeleteResourcePolicy also needs two, lambda:DeleteResourcePolicy plus lambda:RemovePermission. A role holding only the new permission is denied outright, so widen the role diff before you plan the rollout.
Will put-resource-policy delete my existing trigger permissions?
Yes. AWS states that put-resource-policy replaces any existing resource-based policy, and overwrites permissions previously defined with add-permission. Statements created by CloudFormation, AWS SAM, console-wired event sources and S3 notifications all go through AddPermission, so a single replacement call removes them without changing the function itself.
Where does the RevisionId for a conditional write come from?
From GetResourcePolicy on the new path, or GetPolicy on the legacy path. The PreconditionFailedException block on API_PutResourcePolicy routes "all other API operations" to GetFunction or GetAlias, which returns the function configuration revision instead of the policy revision. Following that text produces repeated HTTP 412 failures.
Did the 20 KB policy size limit change?
No. The Lambda quotas page still lists the function resource-based policy at 20 KB, and the API reference caps the Policy parameter at 20480 characters. AddPermission already returned PolicyLengthExceededException against the same ceiling. Consolidating principals into fewer statements recovers space, but the total budget is unchanged.
Does the new workflow hit rate limits harder?
Yes, on large estates. GetPolicy has a dedicated 15 requests per second quota. GetResourcePolicy is not in the quotas page exclusion list, so it shares the 15 requests per second bucket that covers all remaining control-plane calls, alongside PutResourcePolicy. The recommended read-modify-write pair therefore consumes twice the shared budget.
Is the feature available in GovCloud or the China Regions?
AWS scoped the announcement to commercial Regions. The ResourceArn regular expression on all three new operations does accept gov, iso and eusc partition ARNs, but a pattern that matches an ARN string is not confirmation that the operation is enabled in that partition. Test in your own partition before planning a migration.
How eCorpIT can help
Our senior engineering teams run AWS permission migrations as a change-review exercise first and a code exercise second: inventory every AddPermission-created statement, model the consolidated JSON policy, size it against the 20,480-character ceiling, and stage the rollout behind the 15 rps control-plane quota. eCorpIT is CMMI Level 5 and ISO 27001:2022 certified, and we design applications aligned with DPDP Act 2023 requirements. Book a Lambda permissions review and we will start from your trigger inventory.
References
- AWS Lambda functions now support full IAM resource-based policies - AWS What's New, 25 August 2026
- Working with resource-based policies in Lambda - AWS Lambda Developer Guide
- Lambda quotas - AWS Lambda Developer Guide
- PutResourcePolicy - AWS Lambda API Reference
- GetResourcePolicy - AWS Lambda API Reference
- DeleteResourcePolicy - AWS Lambda API Reference
- AddPermission - AWS Lambda API Reference
- GetPolicy - AWS Lambda API Reference
- RemovePermission - AWS Lambda API Reference
- Managing permissions in AWS Lambda - AWS Lambda Developer Guide
- Fine-tuning the Resources and Conditions sections of policies - AWS Lambda Developer Guide
- Granting Lambda function access to other accounts - AWS Lambda Developer Guide
- Granting function access to an organization - AWS Lambda Developer Guide
Last updated: 25 August 2026.
Top comments (0)