AWS Lambda のアクセス許可を紐解く

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のアクセス許可に関わる要素

  1. Lambdaを実行するユーザー
  2. Lambdaを実行するサービス(リソース)
  3. Lambda関数自身
  4. 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のアクセス許可について見ていきました。 一度設定してしまうとなかなか権限は外しにくいものなので、最初になるべく絞ることを意識したいですね。

カヤックではスマートなIaCが好きなエンジニアを募集しています!

hubspot.kayac.com