AWS Lambda now supports full IAM resource-based policies, so I tried it out cross-account

AWS Lambda now supports full IAM resource-based policies, so I tried it out cross-account

AWS Lambda now supports full resource-based policies for IAM. Following the introduction of the ability to configure an entire JSON policy via PutResourcePolicy, we verified the behavior when combining account-level permissions and an explicit Deny for a specific role into a single JSON policy, the behavior when denying principals or source IPs other than those specified using a negation condition in a Deny statement, and the behavior when delegating non-Invoke administrative actions cross-account.
2026.09.01

This page has been translated by machine translation. View original

Introduction

On August 25, 2026, it was announced that AWS Lambda now supports full IAM resource-based policies. Using PutResourcePolicy, you can set a function's resource-based policy as a complete JSON document.

https://aws.amazon.com/about-aws/whats-new/2026/08/aws-lambda-full-iam-resource-based-policies/

The What's New post and the Lambda Developer Guide explain that IAM condition keys can be used in resource-based policies.

The main differences between AddPermission and PutResourcePolicy are the unit of configuration, how conditions are specified, and whether explicit Deny is supported.

Aspect AddPermission PutResourcePolicy
Unit of configuration One statement per call Entire JSON policy
Condition specification Dedicated parameters only JSON Condition
Explicit Deny Cannot be written Can be written

Of the aws lambda add-permission SYNOPSIS, the following 3 lines can be used to restrict callers by condition (in the original, other parameters are interspersed between these 3 lines).

          [--source-arn <value>]
          [--source-account <value>]
          [--principal-org-id <value>]

Apart from these 3 lines, there are no parameters for specifying arbitrary IAM condition keys.

In this article, we verify the behavior when combining account-level Allow and explicit Deny for a specific role into a single JSON policy. We also test Deny using negation conditions, cross-account delegation of management actions, and how the policy changes when PutResourcePolicy and AddPermission are executed in sequence.

What We Tested

We tested full IAM resource-based policies in a configuration where IAM roles from a separate account operate on the function.

Test Environment

  • AWS CLI: 2.36.34 (the put-resource-policy subcommand is available from 2.36.28 onwards)
  • Region: ap-northeast-1
  • Function: rbp-demo (runtime: python3.13, deployed in account 111122223333)
  • Caller: IAM role cross-account-invoker in account 444455556666

For cross-account invocations, both the resource-based policy and the identity policy must allow access. The caller role in this test has AdministratorAccess attached, satisfying the identity policy requirement.

Combining Allow and Explicit Deny

First, we applied a policy without a Deny statement. We specified the caller role's ARN as Principal, allowed lambda:InvokeFunction, and executed Invoke.

{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

The function's return value was {"message": "invoked", "event": {"from": "caller"}}.

Next, we combined an account-level Allow and an explicit Deny for a specific role into a single JSON policy and applied it with put-resource-policy.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "allow-account",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::444455556666:root" },
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo"
    },
    {
      "Sid": "deny-specific-role",
      "Effect": "Deny",
      "Principal": { "AWS": "arn:aws:iam::444455556666:root" },
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo",
      "Condition": {
        "ArnEquals": {
          "aws:PrincipalArn": "arn:aws:iam::444455556666:role/cross-account-invoker"
        }
      }
    }
  ]
}

Since the Deny statement also has a Condition, the denial is limited to principals whose aws:PrincipalArn matches. The only principal used for invocation in this test is the role that matches this condition.

An error occurred (AccessDeniedException) when calling the Invoke operation: User: arn:aws:sts::444455556666:assumed-role/cross-account-invoker/botocore-session-xxxxxxxxxx is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo with an explicit deny in a resource-based policy

The with an explicit deny in a resource-based policy at the end confirms that the explicit Deny is the cause.

AddPermission requires --principal, and in this case we passed an account ID. PutResourcePolicy, on the other hand, allows you to write a publicly accessible specification without conditions directly in the JSON.

We applied three patterns of policies specifying "*" for Principal. In our test environment, all three returned a RevisionId.

  • With only aws:SourceIp as a condition: accepted
  • With only aws:PrincipalOrgID as a condition: accepted
  • With no conditions at all: accepted

Delegating Management Actions

The permission to create aliases could also be delegated to another account using resource-based policies. We allowed function invocation, alias creation, and configuration retrieval in a single statement for the caller role's ARN.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "allow-invoke-and-alias-mgmt",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::444455556666:role/cross-account-invoker" },
      "Action": ["lambda:InvokeFunction", "lambda:CreateAlias", "lambda:GetFunctionConfiguration"],
      "Resource": "arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo"
    }
  ]
}

After applying this policy, we ran create-alias from the caller account 444455556666.

{
    "AliasArn": "arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo:prod-from-caller",
    "Name": "prod-from-caller",
    "FunctionVersion": "1",
    "Description": "",
    "RevisionId": "a1b2c3d4-5678-90ab-cdef-EXAMPLE11111"
}

The alias was created in account 111122223333, which owns the function, and was linked to version 1.

Denying All Principals Except Specified Ones

By using a negation condition in Deny, you can deny all principals except the specified one. This is a pattern commonly used in S3 bucket policies.

For comparison, we first applied only an account-level Allow and ran Invoke from two roles in the caller account, both of which returned status code 200.

Next, while keeping the same Allow, we added a Deny with ArnNotEquals.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "allow-account",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::444455556666:root" },
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo"
    },
    {
      "Sid": "deny-unless-allowed-role",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo",
      "Condition": {
        "ArnNotEquals": {
          "aws:PrincipalArn": "arn:aws:iam::444455556666:role/cross-account-invoker"
        }
      }
    }
  ]
}

Here are the results of invoking with three principals under this policy.

Caller Deny condition met Result
cross-account-invoker in 444455556666 No 200
other-invoker in 444455556666 Yes AccessDeniedException
owner-admin in 111122223333 Yes AccessDeniedException

The third row is a role from the account that owns the function. Although AdministratorAccess is attached, it was denied.

An error occurred (AccessDeniedException) when calling the Invoke operation: User: arn:aws:sts::111122223333:assumed-role/owner-admin/botocore-session-xxxxxxxxxx is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo with an explicit deny in a resource-based policy

With Lambda, Invoke within the same account is possible with only an identity policy. Without this Deny, the invocation from owner-admin would have succeeded. Since AddPermission cannot add Deny statements, this configuration is only possible with PutResourcePolicy.

Note that this Deny is limited to lambda:InvokeFunction in Action. Even with the Deny in effect, get-resource-policy and get-function-configuration from the owner account succeeded. The permission to replace the policy remains intact.

The same pattern can be written using source IP. This is a Deny with only NotIpAddress as the condition, removing the principal condition.

{
  "Sid": "deny-unless-from-allowed-ip",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "lambda:InvokeFunction",
  "Resource": "arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo",
  "Condition": {
    "NotIpAddress": { "aws:SourceIp": "198.51.100.10/32" }
  }
}

In testing, specifying the actual source IP with /32 returned 200, and changing the allowed CIDR to 203.0.113.0/24 resulted in denial. In the JSON shown here, the source IP has been replaced with example values. Just as with aws:PrincipalArn, owner-admin from the owner account was also denied when the source IP did not match.

We did not test this time, but VPC endpoints and AWS services can also be treated as exceptions. For VPC endpoint traffic, use aws:SourceVpce or aws:SourceVpc. To distinguish calls from AWS services, use aws:PrincipalIsAWSService. For details, refer to the IAM global condition keys reference.

https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html

Running PutResourcePolicy and AddPermission in Sequence

We ran add-permission and put-resource-policy in sequence and checked the resource-based policy at each step. The following is the policy body included in the get-resource-policy response.

First, we ran add-permission --statement-id via-add --principal 444455556666.

{"Version":"2012-10-17","Id":"default","Statement":[{"Sid":"via-add","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::444455556666:root"},"Action":"lambda:InvokeFunction","Resource":"arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo"}]}

When an account ID is passed to --principal, the Principal in the policy was expanded to the root ARN.

Next, we passed a JSON containing only via-put to put-resource-policy.

{"Version":"2012-10-17","Statement":[{"Sid":"via-put","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::444455556666:role/cross-account-invoker"},"Action":"lambda:InvokeFunction","Resource":"arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo"}]}

This policy does not include via-add.

We then ran add-permission --statement-id via-add-2 --principal s3.amazonaws.com.

{"Version":"2012-10-17","Statement":[{"Sid":"via-put","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::444455556666:role/cross-account-invoker"},"Action":"lambda:InvokeFunction","Resource":"arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo"},{"Sid":"via-add-2","Effect":"Allow","Principal":{"Service":"s3.amazonaws.com"},"Action":"lambda:InvokeFunction","Resource":"arn:aws:lambda:ap-northeast-1:111122223333:function:rbp-demo"}]}

With via-put remaining, via-add-2 was added.

Summary

Using PutResourcePolicy, we were able to apply an account-level Allow and an explicit Deny for a specific role in a single JSON policy. Not only Invoke, but management actions such as alias creation could also be delegated cross-account.

PutResourcePolicy allows you to configure the entire resource-based policy of a Lambda function as JSON, enabling arbitrary Conditions and explicit Deny statements. In particular, by using negation conditions such as ArnNotEquals and NotIpAddress, you can make exceptions for specific roles or source IPs and deny all other invocations collectively.

This Deny also applies to the account that owns the function, allowing you to deny same-account invocations that are permitted by identity policies. Lambda functions can now support access control similar to S3 bucket policies. If you have felt constrained by the traditional Lambda function resource-based policy, please give this update a try.

Share this article

AWSのお困り事はクラスメソッドへ