【第18回】Amazon Cognito + Spring Sercurityを使ったOAuth2 Loginの実装(7)


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


../_images/oauth2-login-flow.png


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

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


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


まずは前回作成したLambdaファンクションをビルドしたパッケージを配置するS3バケットを作成するための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」となるユーザが作成された状態となります。


../_images/management-console-cognito-confirm-user.png


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


著者紹介

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

../_images/aws_361383_0752.jpeg

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

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

AWS Top Engineers & Ambassadors 選出。

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