AWS CDK で Lambda 作成時に自動生成される IAM ロールへ自前の IAM ポリシーをアタッチしたいときの書き方

IAM ポリシーの生成はできる限りAWS CDKに任せたい所存。
2024.01.09

AWS CDK(TypeScript)で AWS Lambda の関数をデプロイする際、CDK で自動作成された IAM ロールに自前で作成する IAM ポリシー(インラインポリシー)を付け足したいときどうやってコードを書けばよいのでしょうか?

調べてもどう書いたら良いのか上手く見つけられなかったのでピンポイントで紹介します。背景が長いのでお急ぎの方はスキップしてください。

背景

Lambda をデプロイする方法は様々考えられます。よく知られたところでは CloudFormation, AWS SAM,Severless Framework ではないでしょうか。私はもともと Lambda をデプロイするなら AWS SAM を利用していました。最近は Lambda の関数以外はよしなにやってください、お願いしますの一心で AWS CDK を利用しています。

ちょっとした検証のために Lambda 関数 1 つだけをデプロイする機会がありました。Lambda 関数 1 つだけを作成する CDK のコードはこれだけで済みます。別ファイルになっている Lambda 関数のコードは本件と直接関係ないため省略します。

import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';

export class TestLambdaBasicRoleStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Lambda
    const ssmRunCmdLambda = new lambda.Function(this, 'ssmRunCmdHandler', {
      runtime: lambda.Runtime.PYTHON_3_12,
      code: lambda.Code.fromAsset('lambda'),
      handler: 'ssm-runcmd.handler',
      architecture: lambda.Architecture.ARM_64, // or lambda.Architecture.X86_64
      timeout: cdk.Duration.seconds(60),
      logRetention: 90,
      environment: {
        INSTANCE_ID: 'i-0e7dec03d739b8301',
      }
    });

  }
}

Lambda 関数から EC2 に対して SSM Run Command で経由でコマンドを実行して試したいことがあって書いていました。

Lambda 関数の IAM ロールと IAM ポリシーは自動作成

AWS CDK で Lambda をデプロイすると、Lambda の実行ログを保存するのに必要なリソース、設定もよしなにやってくれます。ここでは CloudWatch logs へアクセスするための権限をもった IAM ロール、IAM ポリシーを自動で作成してくれます。

自動作成の IAM ロールにアタッチされている IAM ポリシーは AWS 管理ポリシーのAWSLambdaBasicExecutionRoleでした。今までポリシー内容を確認したことはなかったのですけど、AWS 管理ポリシーなのでリソース指定はゆるゆるになりますよね。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}

少し気になったのでマネージメントコンソールから Lambda 関数を作成して比較してみました。自動的に作成されている IAM ロールにアタッチされていた IAM ポリシーは以下のインラインポリシーでした。新規のインラインポリシーが作成され、ロググループ名を指定してガチガチに制限されていました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:ap-northeast-1:12345679012:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-northeast-1:123456789012:log-group:/aws/lambda/hoge-lambda:*"
            ]
        }
    ]
}

少し脱線しましたが、CDK では Lambda から CloudWatch Logs へログを保存するために必要なリソースを作成するコードを自分で書かなくて済みます。

自動作成の IAM ロールに自前の IAM ポリシーを追加したい

自動作成してくれる IAM ロールに自前の IAM ポリシーを追加する必要がありました。Lambda から SSM Run Command を実行するための権限が与えたかったからです。

IAM ロールを新規作成し、CloudWatch Logs への許可ポリシーを作成し、その上で自前のポリシーを作成し、IAM ロールへアタッチする内容の記事は検索するとよくヒットしました。

私は必須の CloudWatch Logs への許可ポリシーは CDK に任せて、自分で作成・管理するのは避けたかったです。愚直に CloudFormation で書くのと変わりなく、CDK のメリットを教授できていないのではないか、そう考えました。

CDK が作成してくれる IAM ロールに自分で作成した IAM ポリシーをアタッチする CDK(TypeScript)の書き方を試してみました。

一番最初に考えたこと

grant メソッドを利用して IAM ポリシーを生成できないのかと考えました。Lambda の関数内で SSM を使ってるので自動生成は無理がありそうだなと思い必要最低限の IAM ポリシーは自分で書くことにしました。

許可 - AWS Cloud Development Kit (AWS CDK) v2

こうやって書けばよかった

自前の IAM ポリシー作成し、自動作成の IAM ロールへのアタッチする記述は以下となりました。

import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class TestLambdaBasicRoleStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Lambda
    const ssmRunCmdLambda = new lambda.Function(this, 'ssmRunCmdHandler', {
      runtime: lambda.Runtime.PYTHON_3_12,
      code: lambda.Code.fromAsset('lambda'),
      handler: 'ssm-runcmd.handler',
      architecture: lambda.Architecture.ARM_64, // or lambda.Architecture.X86_64
      timeout: cdk.Duration.seconds(60),
      logRetention: 90,
      environment: {
        INSTANCE_ID: 'i-0e7dec03d739b8301',
      }
    });

    // 自前の IAM ポリシー作成
    const ssmRunCmdPolicy = new iam.PolicyStatement({
      actions: ['ssm:SendCommand'],
      effect: iam.Effect.ALLOW,
      resources: [
        'arn:aws:ec2:*:*:instance/*',
        "arn:aws:ssm:*:*:document/*"
      ],
    });

    // IAM ロールにアタッチ
    const lambdaRole = ssmRunCmdLambda.role as iam.Role;
    lambdaRole.addToPolicy(ssmRunCmdPolicy);

  }
}

IAM ポリシーの作成

IAM ポリシーの書き方については至って普通に必要な許可ポリシーを書くことになります。effect: iam.Effect.ALLOWは省略しても、Allow 設定してくれます。ですが、Cloudformation を使うことが多いせいか、私はパッと見不安になったのでわざわざ足しています。

    // 自前の IAM ポリシー作成
    const ssmRunCmdPolicy = new iam.PolicyStatement({
      actions: ['ssm:SendCommand'],
      effect: iam.Effect.ALLOW,
      resources: [
        'arn:aws:ec2:*:*:instance/*',
        "arn:aws:ssm:*:*:document/*"
      ],
    });

自動作成の IAM ロールにアタッチ

CDK が自動作成してくれるデフォルトの IAM ロールは Lambda 関数を定義した名前ssmRunCmdLambda.roleプロパティを付けて取得できました。 あとは、自分で作成した IAM ポリシー(ssmRunCmdPolicy)をアタッチしてあげればよかったです。

    // IAM ロールにアタッチ
    const lambdaRole = ssmRunCmdLambda.role as iam.Role;
    lambdaRole.addToPolicy(ssmRunCmdPolicy);

デプロイ結果

IAM ポリシーの記述を追加したコードで改めてcdk deployし、IAM ロールの内容を確認しました。

自動作成される IAM ロール内にデフォルトでアタッチされていた AWS 管理ポリシーと、自前の IAM ポリシーをアタッチされていました。

基本的な IAM ポリシーは CDK に任せて、Lambda に必要だった権限は自分で作成し付与するといったやりたかったことが実現できました。

おわりに

私は AWS CDK 初心者なので基本的なところが気になったのですが、検索ですぐに見つけられずに困りました。検索に引っかかるようにと備忘録がてら書き残しました。