SREチームの橋本です。SRE連載の11月号になります。
AWSの多くのリソースはIAMでアクセスを一元管理されていますが、Lambdaではユーザーが実行したり他のAWSサービスから実行されたりする都合上、様々なポリシーが絡んでいます。 特に「Lambdaを呼び出す許可」についてはID(アイデンティティ)ベースのポリシーとリソースベースのポリシーで内容が被るため、どちらで設定するか混乱しているケースも見られます。 本記事ではこうしたポリシー事情をterraformの例と共に整理し、権限設定のベストプラクティスも検討します。
そもそもIAMのポリシーについて
ドキュメントによればAWSのポリシーは実に6種類ものタイプがありますが、「使用頻度の高いものから」とあるように最初のIDベースが非常に多くのサービスで共通して使われており、次いで2番目のリソースベースが一部サービスで必要になるでしょう。
IDベースのポリシーはIAMユーザー、グループ、ロールにアタッチ(付与)されるもので、「アクセスする側」の権限設定と言えます。 リソースベースのポリシーは一部サービスのリソース(下記ドキュメントではS3バケット、SQSキュー、VPCエンドポイント、KMSキーが挙げられています)にアタッチされ、「アクセスされる側」の権限設定となるでしょう。
両者の関係についてのドキュメントによれば、基本的にIDベースとリソースベースはORで処理され、拒否が絶対的である一方許可はどらちかだけで効力を発揮します。 (ただしクロスアカウントの場合は両方が要求されます。) 詳細な評価論理はドキュメントにあるように複雑ですが、ここでは本題でないので割愛します。
Lambdaのポリシー概要
AWS Lambdaを実行する際、権限周りでは主に4種類の要素が登場します。
- Lambdaを実行するユーザー
- Lambdaを実行するサービス(リソース)
- Lambda関数自身
- Lambdaがアクセスするリソース
図の矢印のような呼び出しそれぞれについて見ていきましょう。
ユーザーがLambdaを実行する
1→3の場合、ユーザーはIAMロールを持つ人間やAWS外のアプリケーションで、IAMポリシーでlambda:InvokeFunction
を許可されることでLambda関数を実行できます。もっともPowerUserAccess
といったマネージドポリシーが一括で付与され、個別のアクションとして意識することは少ないかもしれません。
# IAMロールにLambda関数の実行権限を与える resource "aws_iam_role_policy_attachment" "foo_developer" { role = aws_iam_role.***.name # 対象ロール policy_arn = aws_iam_policy.developer.arn } resource "aws_iam_policy" "developer" { name = "developer" path = "/" description = "developer policy" policy = data.aws_iam_policy_document.developer.json } data "aws_iam_policy_document" "developer" { statement { actions = [ "lambda:InvokeFunction", ] resources = ["*"] effect = "Allow" } }
Lambdaがリソースにアクセスする
3→4については1と同様で、Lambda関数に持たせるIAMロールで許可することが基本的な条件になります。 ユーザーもLambda関数もID(IAMユーザーやロール)の使用者という点では同じことなので、ある意味当たり前ですね。
ただイベントソースマッピングは一見すると他のサービスがLambda関数を呼び出す、つまり2→3に見えますが、内部的にはLambda側が各サービス側へレコードを読みに行って処理しており、実はこの3→4に当たります。(いわゆるPullモデル。) よってKinesis Streams、SQS、DynamoDBなどイベントソースマッピングで連携を設定するサービスではLambda側にアクセス許可が必要になります。このドキュメントにおける「Lambda ポーリング」がこれに当たるようです。
# イベントソースマッピングの設定 data "aws_lambda_alias" "log_filter_current" { function_name = "log_filter" name = "current" } resource "aws_lambda_event_source_mapping" "kinesis_log_filter" { batch_size = 100 event_source_arn = aws_kinesis_stream.***.arn # イベントソース enabled = true function_name = data.aws_lambda_alias.log_filter_current.arn starting_position = "LATEST" tumbling_window_in_seconds = 60 } # Kinesisへのアクセス許可 data "aws_iam_policy" "AWSLambdaKinesisExecutionRole" { arn = " arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole" } resource "aws_iam_role_policy_attachment" "log_filter_kinesis" { role = aws_iam_role.***.name # Lambda用ロール policy_arn = aws_iam_policy.AWSLambdaKinesisExecutionRole.arn }
なおcurrent
というエイリアスはlambrollが自動で付与してくれるものです。自分の関わるプロジェクトでは、ECSにおけるecspresso利用と同様terraformでLambda関数をデプロイするということは基本していないため、このような例としました。
サービスがLambdaにアクセスする
2→3の場合が最も問題になるところです。 例えばECSのタスクロールのように、そのサービス(のリソース)がIAMロールを持つことができれば、これまでと同じくIDベースのポリシーでアクセス許可を制御できます。
しかし、Lambda関数を起動するのはそうしたサービスばかりではありません。
例えばS3バケットのイベント通知(terraformで言うaws_s3_bucket_notification
)は通知対象にLambda関数を指定できますが、IAMロールは持ちません。こうしたサービスに対してはリソースベースで許可するしかなくなります。
# S3にLambda関数へのアクセス許可を与える data "aws_lambda_alias" "notifier_current" { function_name = "notifier" name = "current" } resource "aws_lambda_permission" "notify_s3_logs" { statement_id = "AllowExecutionFromS3Bucket" action = "lambda:InvokeFunction" function_name = data.aws_lambda_alias.notifier_current.function_name qualifier = data.aws_lambda_alias.notifier_current.name principal = "s3.amazonaws.com" source_arn = aws_s3_bucket.***.arn # イベントソースとなるS3バケット }
マネジメントコンソールではConfiguration→Permissions→Resource-based policy statements、AWS CLIではaws lambda add-permission
に当たります。
ただ更なる注意点として、ID(IAMロールなど)を持たないサービスは多数ありますが、その中にはそもそもアクセス許可が要らないサービスも多く存在します。(Lambda Function URLs、Kinesis Data Firehose、SQS、SNSなど)
つまりLambdaを呼び出す許可については
- IAMのIDベースポリシーでは対応できないサービスが存在する
- そもそも許可が要らないサービスが存在する
- Lambda側のリソースベースポリシーでは全て設定できる
という状況があり、特に最初の点からIAMによる一元管理が崩れざるを得ないものとなっています。
具体的には先のS3以外にも、ALB(targetとして)、CloudWatch EventBridgeといったサービスがIDを持たずアクセス許可が必要な実行者となります。
ベストプラクティスは何か
改めて状況を整理すると以下のようになります。
呼び出し側のタイプ | 必要な許可 | サービス(リソース)の例 |
---|---|---|
IDを持たない物の一部? | 許可がいらない | Lambda Function URLs、Kinesis Data Firehose、SQS、SNS |
IDを持たない | リソースベースのみ | S3イベント通知、ALBターゲット、CloudWatch EventBridge |
IDを持つ | ID/リソースベースどちらでも可 | ECSタスク、Lambda関数 |
イベントソースマッピング | (Lambdaからのアクセス) | Kinesis Streams、SQS、DynamoDB |
まず許可が必要ない場合、当然ポリシーを書かない方が良いでしょう。 許可が要るかどうか、はっきりとした基準は分からなかったのですが、ドキュメントに明記されているので個別には判断できます。
例1:Amazon S3 での AWS Lambda の使用
関数を呼び出すには、Amazon S3 には、関数のリソースベースのポリシーによるアクセス許可が必要です。
例2:Application Load Balancer で AWS Lambda を使用する
Application Load Balancer を関数トリガーとして設定するには、まず、関数を実行するアクセス許可を Elastic Load Balancing に付与します。
「IDを持つ」タイプ、つまりIDベースでもリソースベースでも許可できるものについては考える必要があります。
IDベースになるべく統一することを重視するか、Lambdaに関するアクセス許可が分散しないことを重視するかは現場やチーム次第となるでしょう。 ただ通常はアクセス許可と言えばIDベースのポリシーというイメージがあるので、なるべくIDベースにまとめた方が把握は容易になるのではないでしょうか。
終わりに
今回はAWS Lambdaのアクセス許可について見ていきました。 一度設定してしまうとなかなか権限は外しにくいものなので、最初になるべく絞ることを意識したいですね。