AWS Lambda がフル IAM リソースベースポリシーをサポートしたのでクロスアカウントで試してみた
はじめに
2026 年 8 月 25 日、AWS Lambda がフル IAM リソースベースポリシーに対応したことが発表されました。PutResourcePolicy を使うと、関数のリソースベースポリシーを JSON 全体として設定できます。
What's New と Lambda 開発者ガイド では、リソースベースポリシーで IAM の条件キーを利用できると説明されています。
AddPermission と PutResourcePolicy の主な違いは、設定の単位、条件の指定方法、明示的な Deny の可否です。
| 観点 | AddPermission | PutResourcePolicy |
|---|---|---|
| 設定の単位 | 1 回の呼び出しで 1 ステートメント | JSON ポリシー全体 |
| 条件の指定 | 専用パラメータのみ | JSON の Condition |
| 明示的な Deny | 書けない | 書ける |
aws lambda add-permission の SYNOPSIS のうち、条件によって呼び出し元を絞り込むために使えるのは次の 3 行です(原文では、この 3 行の間に他のパラメータが挟まっています)。
[--source-arn <value>]
[--source-account <value>]
[--principal-org-id <value>]
この 3 行以外に、任意の IAM 条件キーを指定するパラメータはありません。
本記事では、アカウント単位の許可と特定のロールへの明示的な Deny を、1 つの JSON ポリシーにまとめた場合の挙動を確認します。あわせて、否定条件を使った Deny、管理系アクションのクロスアカウント委譲、PutResourcePolicy と AddPermission を続けて実行した場合のポリシーの変化も検証します。
検証内容
別アカウントの IAM ロールから関数を操作する構成で、フル IAM リソースベースポリシーを検証しました。
検証環境
- AWS CLI:2.36.34(
put-resource-policyサブコマンドは 2.36.28 以降で使えます) - リージョン:ap-northeast-1
- 関数:rbp-demo(ランタイム:python3.13、配置先:アカウント 111122223333)
- 呼び出し側:アカウント 444455556666 の IAM ロール cross-account-invoker
クロスアカウントの呼び出しでは、リソースベースポリシーとアイデンティティポリシーの両方で許可が必要です。今回の呼び出し側ロールには AdministratorAccess がアタッチされており、アイデンティティポリシー側の要件は満たしています。
Allow と明示的な Deny の併用
まず、Deny ステートメントを含めないポリシーを適用しました。呼び出し側ロールの ARN を Principal に指定して lambda:InvokeFunction を許可し、Invoke を実行しました。
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
関数の戻り値は {"message": "invoked", "event": {"from": "caller"}} でした。
次に、アカウント単位の Allow と特定のロールへの Deny を 1 つの JSON ポリシーにまとめ、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"
}
}
}
]
}
Deny ステートメントにも Condition を付けているため、拒否の対象は aws:PrincipalArn が一致するプリンシパルに限られます。今回呼び出しに使用したプリンシパルは、この条件に一致するロールだけです。
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
末尾の with an explicit deny in a resource-based policy から、明示的な Deny が原因であることが分かります。
AddPermission は --principal が必須で、今回はアカウント ID を渡しました。一方 PutResourcePolicy では、条件を付けない全公開の指定も JSON に書けます。
Principal に "*" を指定したポリシーを 3 パターン適用しました。今回の検証環境では、いずれも RevisionId が返りました。
aws:SourceIpだけを条件に付けたもの:受け付けられたaws:PrincipalOrgIDだけを条件に付けたもの:受け付けられた- 条件を一切付けないもの:受け付けられた
管理系アクションの委譲
エイリアス作成の権限も、リソースベースポリシーを使って別アカウントへ委譲できました。呼び出し側ロールの ARN に対して、関数の呼び出し・エイリアス作成・設定取得の 3 つを 1 ステートメントで許可しました。
{
"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"
}
]
}
このポリシーを適用し、呼び出し側アカウント 444455556666 から create-alias を実行しました。
{
"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"
}
エイリアスは関数を配置したアカウント 111122223333 に作成され、バージョン 1 に紐づきました。
指定したプリンシパル以外を拒否する
Deny の Condition を否定形にすると、指定したプリンシパル以外をまとめて拒否できます。S3 のバケットポリシーでよく使う書き方です。
比較のため、まずアカウント単位の Allow だけを適用した状態で、呼び出し側アカウントの 2 つのロールから Invoke を実行したところ、どちらもステータスコード 200 になりました。
次に、同じ Allow を残したまま ArnNotEquals を付けた Deny を足しました。
{
"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"
}
}
}
]
}
このポリシーで 3 つのプリンシパルから Invoke した結果です。
| 呼び出し元 | Deny の条件が成立 | 結果 |
|---|---|---|
| 444455556666 の cross-account-invoker | いいえ | 200 |
| 444455556666 の other-invoker | はい | AccessDeniedException |
| 111122223333 の owner-admin | はい | AccessDeniedException |
3 行目は関数を所有するアカウント側のロールです。AdministratorAccess がアタッチされていますが、拒否されました。
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
Lambda は同一アカウント内であればアイデンティティポリシーだけで Invoke できます。この Deny がなければ、owner-admin からの呼び出しは通っていました。AddPermission では Deny ステートメントを追加できないため、この形は PutResourcePolicy でしか設定できません。
なお、この Deny は Action を lambda:InvokeFunction に限定しています。Deny が有効なままでも、所有アカウントからの get-resource-policy と get-function-configuration は成功しました。ポリシーを差し替える権限は残ります。
同じ形は送信元 IP でも書けます。プリンシパルの条件を外し、NotIpAddress だけを条件にした Deny です。
{
"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" }
}
}
検証時は実際の送信元 IP を /32 で指定すると 200 になり、許可する CIDR を 203.0.113.0/24 に変えると拒否されました。掲載した JSON では、送信元 IP を例示用の値に置き換えています。aws:PrincipalArn のときと同じく、所有アカウントの owner-admin も送信元 IP が外れれば拒否されました。
今回は検証していませんが、VPC エンドポイントや AWS サービスを例外として扱うこともできます。VPC エンドポイント経由は aws:SourceVpce や aws:SourceVpc を使います。AWS サービスからの呼び出しは aws:PrincipalIsAWSService で切り分けます。詳細は IAM のグローバル条件キーのリファレンスを参照してください。
PutResourcePolicy と AddPermission の連続実行
add-permission と put-resource-policy を続けて実行し、各段階のリソースベースポリシーを確認しました。以下は get-resource-policy のレスポンスに含まれるポリシー本体です。
はじめに 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"}]}
--principal にアカウント ID を渡すと、ポリシーの Principal は root ARN に展開されました。
続けて、via-put だけを含む JSON を 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"}]}
このポリシーには via-add は含まれていません。
さらに 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"}]}
via-put が残った状態で、via-add-2 が加わりました。
まとめ
PutResourcePolicy で、アカウント単位の許可と特定のロールへの明示的な Deny を、1 つの JSON ポリシーにまとめて適用できました。Invoke だけでなく、エイリアス作成のような管理系アクションもクロスアカウントで委譲できました。
PutResourcePolicy により、Lambda 関数のリソースベースポリシー全体を JSON で設定し、任意の Condition や明示的な Deny を記述できるようになりました。特に、ArnNotEquals や NotIpAddress などの否定条件を使うことで、特定のロールや送信元 IP だけを例外とし、それ以外の呼び出しをまとめて拒否できます。
この Deny は関数を所有するアカウントにも適用され、アイデンティティポリシーで許可された同一アカウント内の呼び出しも拒否できます。Lambda 関数でも S3 のバケットポリシーに近いアクセス制御が可能になりました。従来の Lambda 関数のリソースベースポリシーに制約を感じていた方は、ぜひ今回のアップデートをお試しください。








