本連載では、以下のイメージのようにOAuth2 Loginをベースとしたアーキテクチャを想定した環境構築を進めています。

構成図

前回は、Cognitoへユーザー登録し、サインアップステータスを変更するLambdaファンクションを実装しました。今回は引き続き、作成したLambdaファンクションをカスタムリソースとして登録し、実行するCloudFormationの実装方法を解説していきます。

なお、実際のソースコードはGitHub上にコミットしています。以降のソースコードでは、本質的でない記述を省略している部分があるので、実行コードを作成する際は、必要に応じて適宜GitHubにあるソースコードも参照してください。

LambdaファンクションのCloudFormationテンプレートの実装

では早速、CloudFormationテンプレートを作成していきましょう。作成の要領は、連載「AWSで作るクラウドネイティブアプリケーションの応用」の第8回と同様なので、コードの説明は割愛します。

AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  VPCName:
    Description: Target VPC Stack Name
    Type: String
    MinLength: 1
    MaxLength: 255
    AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$
    Default: mynavi-sample-microservice-vpc

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: debugroom-mynavi-microservice-cfn-lambda-bucket
      AccessControl: "Private"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

Outputs:
  S3Bucket:
    Description: Lambda deploy S3 bucket name
    Value: !Ref S3Bucket
    Export:
      Name: !Sub ${VPCName}-Lambda-S3Bucket

  S3BucketArn:
    Description: S3 for Lambda bucket arn
    Value: !GetAtt S3Bucket.Arn
    Export:
      Name: !Sub ${VPCName}-Lambda-S3Bucket-Arn

次に、作成したテンプレートを実行し、LambdaファンクションをビルドしてS3に配置します。スクリプトは以下の通りです。

#!/usr/bin/env bash

export JAVA_HOME=/usr/lib/jvm/java-11-amazon-corretto.x86_64
bucket_name="debugroom-mynavi-microservice-cfn-lambda-bucket"
stack_name="mynavi-microservice-s3-lambda"
template_path="cloudformation/2-s3-for-lambda-deploy-cfn.yml"
s3_objectkey="cognito-init-lambda-0.0.1-SNAPSHOT-aws.jar"

if [ "" == "`aws s3 ls | grep $bucket_name`" ]; then
    aws cloudformation deploy --stack-name ${stack_name} --template-file ${template_path} --capabilities CAPABILITY_IAM
fi

cd cognito-init-lambda
./mvnw clean package
aws s3 cp target/${s3_objectkey} s3://${bucket_name}/
続いて、Lambdaファンクションを定義するCloudFormationテンプレートを作成します。
AWSTemplateFormatVersion: '2010-09-09'

# omit

Resources:
  LambdaForChangeCognitoUserStatusFunction: #(A)
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket:
          Fn::ImportValue: !Sub ${VPCName}-Lambda-S3Bucket #(B)
        S3Key: cognito-init-lambda-0.0.1-SNAPSHOT-aws.jar #(C)
      Handler: org.debugroom.mynavi.sample.aws.microservice.lambda.app.handler.CloudFormationTriggerHandler::handleRequest #(D)
      FunctionName: mynavi-microservice-cfn-cognito-user-status-change-function #(E)
      Environment:
        Variables:
          SPRING_CLOUD_FUNCTION_DEFINITION: changeCognitoUserStatusFunction #(F)
      MemorySize: 1024
      Runtime: java11
      Timeout: 120
      Role: !GetAtt LambdaRole.Arn

  LambdaForAddClientSecretToParameterStoreFunction: #(G)
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket:
          Fn::ImportValue: !Sub ${VPCName}-Lambda-S3Bucket
        S3Key: cognito-init-lambda-0.0.1-SNAPSHOT-aws.jar
      Handler: org.debugroom.mynavi.sample.aws.microservice.lambda.app.handler.CloudFormationTriggerHandler::handleRequest
      FunctionName: mynavi-microservice-cfn-cognito-add-client-secret-function
      Environment:
        Variables:
          SPRING_CLOUD_FUNCTION_DEFINITION: addClientSecretToParameterStoreFunction
      MemorySize: 1024
      Runtime: java11
      Timeout: 120
      Role: !GetAtt LambdaRole.Arn

  LambdaForAddCognitoUserFunction: #(H)
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket:
          Fn::ImportValue: !Sub ${VPCName}-Lambda-S3Bucket
        S3Key: cognito-init-lambda-0.0.1-SNAPSHOT-aws.jar
      Handler: org.debugroom.mynavi.sample.aws.microservice.lambda.app.handler.CloudFormationTriggerHandler::handleRequest
      FunctionName: mynavi-microservice-cfn-cognito-add-cognito-user-function
      Environment:
        Variables:
          SPRING_CLOUD_FUNCTION_DEFINITION: addCognitoUserFunction
      MemorySize: 1024
      Runtime: java11
      Timeout: 120
      Role: !GetAtt LambdaRole.Arn

  LambdaRole: #(I)
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole

  CloudFormationAccessPolicy: #(J)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: mynavi-microservice-cfn-cognito-lambda-CloudFormationAccessPolicy
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "cloudformation:*"
            Resource: "*"
      Roles:
        - !Ref LambdaRole

  SSMAccessPolicy: #(K)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: mynavi-microservice-cfn-cognito-lambda-SSMAccessPolicy
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
          # omit
      Roles:
        - !Ref LambdaRole

  CognitoPowerUserPolicy: #(L)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: mynavi-microservice-cfn-cognito-lambda-CognitoccessPolicy
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "cognito-identity:*"
            # omit
      Roles:
        - !Ref LambdaRole

Outputs: (M)
  LambdaForChangeCognitoUserStatusFunction:
    Description: Lambda function for changing cognito user status function.
    Value: !GetAtt LambdaForChangeCognitoUserStatusFunction.Arn
    Export:
      Name: !Sub ${VPCName}-LamdaForChangeCognitoUserStatusFunctionArn

  LambdaForAddClientSecretToParameterStoreFunction:
    Description: Lambda function for addtion of client sercret to parameter store function.
    Value: !GetAtt LambdaForAddClientSecretToParameterStoreFunction.Arn
    Export:
      Name: !Sub ${VPCName}-LamdaForAddClientSecretToParameterStoreFunctionArn

  LambdaForAddCognitoUserFunction:
    Description: Lambda function for addtion of cognito user function.
    Value: !GetAtt LambdaForAddCognitoUserFunction.Arn
    Export:
      Name: !Sub ${VPCName}-LamdaForAddCognitoUserFunctionArn

コードのポイントは下表の通りです。

項番 説明
A ユーザーのサインアップステータスを変更するLambdaファンクションを定義します
B 上記で作成したS3バケット名をクロススタックリファレンスで参照します
C ビルドしたLambdaファンクションのファイル名を指定します
D 前々回実装したハンドラクラスを完全修飾クラス名で指定します
E ファンクション名を半角英数字64文字以内で指定します
F 環境変数SPRING_CLOUD_FUNCTION_DEFINITIONに、実行するファンクションクラスのBean名を指定します
G アプリクライアントのクライアントシークレットをParameter Storeに設定するLambdaファンクションを定義します。ファンクション名と環境変数SPRING_CLOUD_FUNCTION_DEFINITION以外はAと同一の定義で問題ありません。環境変数で実行するファンクションクラスを切り替えることができます
H Cognitoユーザープールにユーザーを登録するLambdaファンクションを定義します。ファンクション名と環境変数SPRING_CLOUD_FUNCTION_DEFINITION以外はAと同一の定義で問題ありません。環境変数で実行するファンクションクラスを切り替えることができます
I Lambdaファンクションにアタッチするロールを定義します
J Iのロールにアタッチするポリシーを定義します。LambdaファンクションでCloudFormationのスタック情報にアクセスするため権限を付与しておきます
K Iのロールにアタッチするポリシーを定義します。LambdaファンクションでSystems Manager Parameter Storeにアクセスするため権限を付与しておきます
L Iのロールにアタッチするポリシーを定義します。LambdaファンクションでCognitoにアクセスするため権限を付与しておきます
M CloudFormationのカスタムリソースで参照するため、ARNをOutput要素で出力しておきます

最後に、このLambdaファンクションを実行するためのカスタムリソースをテンプレートに定義します。このテンプレートを実行すると、Lambdaファンクションが実行されます。なお、先に実行する必要があるファンクションは、DependsOn属性を指定しておきます。

AWSTemplateFormatVersion: '2010-09-09'

# omit

Resources:
  LambdaForChangeCognitoUserStatusFunctionTrigger:
    Type: Custom::LambdaTrigger
    DependsOn: LambdaForAddCognitoUserFunctionTrigger
    Properties:
      ServiceToken:
        Fn::ImportValue: !Sub ${VPCName}-LamdaForChangeCognitoUserStatusFunctionArn
      Region: !Ref "AWS::Region"


  LambdaForAddCognitoUserFunctionTrigger:
    Type: Custom::LambdaTrigger
    DependsOn: LambdaForAddClientSecretToParameterStoreFunctionTrigger
    Properties:
      ServiceToken:
        Fn::ImportValue: !Sub ${VPCName}-LamdaForAddCognitoUserFunctionArn
      Region: !Ref "AWS::Region"

  LambdaForAddClientSecretToParameterStoreFunctionTrigger:
    Type: Custom::LambdaTrigger
    Properties:
      ServiceToken:
        Fn::ImportValue: !Sub ${VPCName}-LamdaForAddClientSecretToParameterStoreFunctionArn
      Region: !Ref "AWS::Region"

テンプレートを実行すると、ユーザープールにステータスが「CONFIRMED」となるユーザーが作成された状態となります。

ユーザーが作成される

* * *

今回は、Lambdaファンクションをカスタムリソースとして実行するCloudFormationテンプレートを作成し、実行してみました。次回以降は、いよいよSpring Securityを使ってOAuth2 Loginを行うアプリケーション実装の解説を進めていきます。

著者紹介


川畑 光平(KAWABATA Kohei) - NTTデータ
エグゼクティブ ITスペシャリスト ソフトウェアアーキテクト・デジタルテクノロジーストラテジスト(クラウド)

金融機関システム業務アプリケーション開発・システム基盤担当、ソフトウェア開発自動化関連の研究開発を経て、デジタル技術関連の研究開発・推進に従事。

Red Hat Certified Engineer、Pivotal Certified Spring Professional、AWS Certified Solutions Architect Professional等の資格を持ち、アプリケーション基盤・クラウドなど様々な開発プロジェクト支援にも携わる。AWS Top Engineers & Ambassadors選出。

本連載の内容に対するご意見・ご質問は Facebook まで。