意外と盲点?Lambda関数にトリガーを追加するときはリソースベースポリシーを意識しよう

Lambda関数にはリソースベースポリシーが設定できます。マネジメントコンソールから作業する際には自動的に設定されますが、そうでない場合には意識して設定が必要です。

コンバンハ、千葉(幸)です。

Lambda関数には、トリガーをセットすることができます。付随してリソースベースのポリシーの設定が必要となるのですが、マネジメントコンソールからセットする際には裏でよしなにやってくれるので、意識する機会は減りがちです。CloudFormationなど非コンソール操作で作成する際には、明示的にリソースベースポリシーの指定が必要なので注意しましょう。

目次

先に結論

  • Lambda関数にはリソースベースポリシーがある
  • Lambda関数にトリガーをセットする場合、トリガーからのInvocateを許可するポリシーを定義する必要がある
  • CloudFormationでリソースベースポリシーを設定するときはタイプAWS::Lambda::Permissionを用いる

ちなみに

このリソースに、リソースベースのポリシーってあったっけ?とわからなくなったときは、このページから一覧で確認できるのでブックマークオススメです。

IAM と連携する AWS のサービス

リソースベース

LambdaをCloudWatch イベントで定期実行させる設定をマネジメントコンソールで実施する

トリガーのセットをマネジメントコンソールで作業する際を考えてみましょう。

前回こんな記事を書きました。Lambda + 実行用のIAMロールまでCloudFormation化して作成してあるので、これをCloudWatch イベントで定期実行させてみます。

なお、ここで作成されるLambdaの名称は「LogGroupPutRetentionPolicy」としています。

CloudWatch イベントルールの作成

Lambdaコンソール側からの操作でも追加可能ですが、今回はCloudWatch イベントのコンソールから作成していきます。

「CloudWatch」→「イベント」→「ルール」の画面から「ルールの作成」を押下し、以下を入力していきます。

  • イベントソース:今回はスケジュールベースで5分ごとに実行させます
  • ターゲット:作成済みのLambda関数を指定します

ルールの名称を決めて作成します。今回はtest-ruleとしました。

ここでこのようなメッセージが表示されます。これがまさにターゲットのLambda関数のリソースベースのポリシーを編集することを指しています。皆さんは意識できていましたか?

CloudWatch Events はターゲットに必要な権限を追加し、このルールがトリガーされたときに呼び出せるようにします。

Lambda側での確認

ターゲットに指定したLambdaの詳細画面から確認すると、トリガーに先ほどのCloudWatch Eventルールが表示されていることがわかります。 (この画面から「トリガーを追加」を選択してCloudWatch イベントルールを作成することもできます。)

ここの表示は何を基に描画されているかというと、Lambda関数の[アクセス権限]タブの...

[リソースベースのポリシー]という部分と連動しています。

先ほどCloudWatch イベントルールをマネジメントコンソールから作成した際に、自動的にポリシーが編集されています。アクションはlambda:InvokeFunctionで、リソースとコンディションを絞ることで必要最小限の権限付与で納まるようになっています。

{
  "Version": "2012-10-17",
  "Id": "default",
  "Statement": [
    {
      "Sid": "AWSEvents_test-rule_Id2091584616251",
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:LogGroupPutRetentionPolicy",
      "Condition": {
        "ArnLike": {
          "AWS:SourceArn": "arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:rule/test-rule"
        }
      }
    }
  ]
}

このリソースベースのポリシーはマネジメントコンソールからは編集できません。

CloudFormationで作成するパターンを考える

上記の構成をCloudFormationで作成する場合を考えましょう。

コードは前回記事時点で作成済みの以下に追記する形で考えます。

折り畳み
AWSTemplateFormatVersion: '2010-09-09'
Description: "Create Lambda"
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
        Parameters:
          - LambdaMemorySize
          - LambdaTimeout
          - LogGroupPutRetentionDays

Parameters:
  LambdaMemorySize:
    Description: 128 ~ 3008. The value must be a multiple of 64.
    Type: String
    Default: 128
  LambdaTimeout:
    Description: The maximum allowed value is 900 seconds.
    Type: String
    Default: 60
  LogGroupPutRetentionDays:
    Description: Possible values are 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653.
    Type: String
    Default: 60

Resources:
# ------------------------------------------------------------#
# IAM Role
# ------------------------------------------------------------#
  LambdaFunctionRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: "Lambda-LogGroupPutRetentionPolicy-Role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "lambda.amazonaws.com"
            Action: "sts:AssumeRole"
      Path: "/"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess

# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
  LambdaFunction:
    Type: "AWS::Lambda::Function"
    Properties:
      FunctionName: "LogGroupPutRetentionPolicy"
      Handler: "index.lambda_handler"
      Role: !GetAtt [ LambdaFunctionRole, Arn ]
      Environment:
        Variables:
         RetentionDays : !Ref LogGroupPutRetentionDays
      MemorySize: !Ref LambdaMemorySize
      Timeout: !Ref LambdaTimeout
      Code:
        ZipFile: |
          import boto3
          import os

          Days=os.environ['RetentionDays']
          # Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653.

          def lambda_handler(event, context):

              logs_client = boto3.client('logs')
              response = logs_client.describe_log_groups()

              group_list = response['logGroups']

              while 'nextToken' in response:
                  response = logs_client.describe_log_groups(nextToken=response['nextToken'])
                  add_groups = response['logGroups']
                  group_list.extend(add_groups)

              for log_group in group_list:
                  print(log_group['logGroupName'])
                  result = logs_client.put_retention_policy(
                      logGroupName=log_group['logGroupName'],
                      retentionInDays=int(Days)
                  )
                  code = result['ResponseMetadata']['HTTPStatusCode']
                  print('HTTPStatusCode is ' + str(code))
      Runtime: "python3.7"

Lambdaは論理IDLambdaFunctionを持つリソースとして定義しています。

CloudWatch Eventルールの追加

リファレンスを参考に、以下のセクションを追加しました。

# ------------------------------------------------------------#
# CloudWatchEvent
# ------------------------------------------------------------#
  LambdaScheduleEvent:
    Type: AWS::Events::Rule
    Properties:
        Description: schedule event for lambda
        ScheduleExpression: 'rate(5 minutes)'
        Name: test-rule
        State: ENABLED
        Targets:
        -
          Arn: !GetAtt LambdaFunction.Arn
          Id: !Ref LambdaFunction

当初は上記のリソースだけ追加すればトリガーの設定が完了していると勘違いしていました。この状態だとLambda関数は一向に実行されず、CloudWatch EventルールのメトリクスでFailedInvocationsが記録され続けることになりました。

CloudWatch Eventルール側では定期的に呼び出しを実行するものの、Lambda関数のリソースベースポリシーで許可されていないために、そこでブロックされているという状態です。

AWS::Lambda::Permissionでのリソースベースポリシーの追加

LambdaのリソースベースポリシーもCloudFormationのテンプレートで定義してあげる必要があるので、こちらもリファレンスを参考にコードを追加します。上で確認したリソースベースポリシーを再現するだけであれば、以下のような書き方で充足しています。

# ------------------------------------------------------------#
# LambdaPermission
# ------------------------------------------------------------#
  LambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt LambdaFunction.Arn
      Principal: events.amazonaws.com
      SourceArn: !GetAtt LambdaScheduleEvent.Arn

こちらを追記することで、マネジメントコンソールからの作業で実施したものと同等の構成を作成することができます。("SID"のセクションは省略されていますが。)

{
  "Version": "2012-10-17",
  "Id": "default",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:LogGroupPutRetentionPolicy",
      "Condition": {
        "ArnLike": {
          "AWS:SourceArn": "arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:rule/test-rule"
        }
      }
    }
  ]
}

上記のコードの組み合わせによって、マネジメントコンソールでセットした場合と同一の構成を実現できます。

Ref関数とGetAtt関数が返してくれるもの

コードを試行錯誤する中でようやく調べ方を理解したので書いておきます。

CloudFormationのリファレンスの中で、リソースタイプごとに戻り値が記載されています。(戻り値の記載が無いリソースタイプもあるので、それはRefやGetAttの対象に指定できないということです。)

例えばタイプAWS::Lambda::Functionのリソースであれば、以下で確認できます。

Ref関数(参照番号と訳されていますが)ではLambda関数のリソース名を、GettAtt関数ではArn属性を指定することでLambda関数のARNを返してくれることが分かります。

終わりに

Lambdaのリソースベースポリシーについて意識が必要であることを学びました。

リソースベースポリシーの有名どころではS3のバケットポリシーやIAMロールの信頼関係ポリシーがありますが、アクションが正しく実行されるためにはIAMポリシー(アイデンティティベースポリシー)との組み合わせを意識してあげる必要があります。いい感じに設定してくれるマネジメントコンソールはとても便利ですが、裏側の仕組みも抑えておきましょう。

以上、足先が冷えがちな千葉(幸)がお届けしました。