他のスタックからインポートしたLambdaにはaddPermissionで権限が付けられない

CDKでゴリゴリインフラを書いていくとどうしてもスタックが長くなりがちなので、適切な粒度でスタックを分割しているかと思います。 その際に、あるスタックで作成したLambdaに対して、以降の別スタックで権限を付与しようとした際にハマったので対処法を紹介します。

概要

やりたいことの概要は以下の通りです。

  • LambdaStackLambdaを作成(ここではhogeとする)
  • ApiStackApiGatewayを作成し、そこにhogeを割り当てる
  • ApiGatewayから呼ばれる(InvokeFunction)する際はLambda側にもリソースベースのポリシーが必要なのでApiStackで追加する

この3つ目のApiStackLambdaに対してポリシーを追加するのがネックでした。 LambdaStackではOutputとしてhogeARNを吐き出しています(HogeArn)。

ダメな例

import * as cdk from 'aws-cdk-lib';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';

/**
 * API Gateway Stack
 */
export class ApiStack extends cdk.Stack {

  public readonly api: apigateway.RestApi;

  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // hogeを別スタックのOutputから取得
    const hoge = lambda.Function.fromFunctionArn(this, 'HogeFunction', cdk.Fn.importValue('HogeArn'));

    // REST APIを作成
    this.api = new apigateway.RestApi(this, 'HogeApi', {
        cloudWatchRole: false,
    });

    // ルートにGETメソッドを作成しhogeを割り当てる
    this.api.root.addMethod('GET',  new apigateway.LambdaIntegration(hoge));

    // hogeのリソースベースのポリシーでApiGatewayからのInvokeFunctionを許可する
    hoge.addPermission(
      'InvokeFromApi',
      {
        principal: new iam.ServicePrincipal('apigateway.amazonaws.com'),
        action: 'lambda:InvokeFunction',
        // APIのARNを取得
        sourceArn: this.api.arnForExecuteApi('GET', '/', '*')
      }
    );
  }
}

一見これでうまくいきそうですが、肝心のリソースベースのポリシーが割り当てられていませんでした。 エラー自体は出ていないのですが、実際に作成されたAPIをテスト実行してみると権限エラーとなってしまいます。

CfnPermissionなら大丈夫らしい

以下のような記事を見つけました。 参考:DeveloperIO - AWS CDKで1つのスタックにLambdaを21個以上作る場合に起きる問題と対処方法の紹介

その中で以下のような記述があります。

(IFunction内にaddPermissionがありそちらも試したのですが、既存のLambdaをインポートした場合はCloudFormationにPermissionが出力されませんでした。 既存のLambdaをインポートした場合は、別アカウントのLambdaを参照して権限を付与しないためにaddPermissionが権限を生成せず終了するようになっています。 今回はアカウントの管理下にあるLambdaなのでCfnPermissionを使用して権限を追加しています。)

なるほど、と思いましたが同様の記事に以下のような記述も。

2020/10/27 追記 CDKのversion 1.64.0で同一のAWSアカウント内のLambdaをimportした場合Permissionが自動生成されるようになったので下記のCfnPermissionの記述は不要になりました。 詳しくはCDKのリリースノートをご参照ください。 AWS CDK v1.64.0 Release Note

手元のCDKのバージョンは2.10.0なのでとっくに解消されているはずだが、うーん・・・

何はなくともCfnPermissionを試してみます。

import * as cdk from 'aws-cdk-lib';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';

/**
 * API Gateway Stack
 */
export class ApiStack extends cdk.Stack {

  public readonly api: apigateway.RestApi;

  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // hogeを別スタックのOutputから取得
    const hoge = lambda.Function.fromFunctionArn(this, 'HogeFunction', cdk.Fn.importValue('HogeArn'));

    // REST APIを作成
    this.api = new apigateway.RestApi(this, 'HogeApi', {
        cloudWatchRole: false,
    });

    // ルートにGETメソッドを作成しhogeを割り当てる
    this.api.root.addMethod('GET',  new apigateway.LambdaIntegration(hoge));

    // hogeのリソースベースのポリシーでApiGatewayからのInvokeFunctionを許可する
    // hoge.addPermission(
    //   'InvokeFromApi',
    //   {
    //     principal: new iam.ServicePrincipal('apigateway.amazonaws.com'),
    //     action: 'lambda:InvokeFunction',
    //     // APIのARNを取得
    //     sourceArn: this.api.arnForExecuteApi('GET', '/', '*')
    //   }
    // );

    // CfnPermissionで同じことをする
    new lambda.CfnPermission(this, `InvokeFromApi`, {
        principal: 'apigateway.amazonaws.com',
        action: 'lambda:InvokeFunction',
        functionName: hello.functionName,
        sourceArn: this.api.arnForExecuteApi('GET', '/', '*')
    });
  }
}

これでhogeにリソースベースのポリシーが付与されました。 手元のCDKのバージョンなら、最初のやり方でも問題ないはずですが、またダメになったのかもしれません。 ※もしくはaddPermissionに何かしらパラメータを付けるとうまくいく??

まとめ

今回はスタックを分けた状態で、別なスタックで作成したLambdaをインポートした時に権限を割り当てられずにハマった件と、その対処法について紹介しました。 自分的には腑に落ちない解決方だったので、うまいやり方をご存知の方がいらっしゃいましたらコメントいただけると幸いです。

SNSでシェアする