背景

自社で検証環境でCognitoを作成し、動作確認を行う場合があります。
今までAWSコンソール上でユーザプールを作成し、ユーザを作成する流れになっていました。検証した後、手動で削除する必要があります。

前回、1個のテンプレートでユーザプールとユーザをまとめて作成してみましたが、
今回、CognitoユーザプールとCognitoユーザを2個のテンプレートで作成してみましたので、テンプレートを共有します。これで複数のユーザを作成する場合、お役に立てればと思います。

前回の記事
CloudFormationでCognitoユーザを作成し、SecretsManagerで生成したパスワードを利用してCONFIRMEDにしてみました。

テンプレート

Cognitoユーザプールのテンプレート

CognitoUserPool.yaml

AWSTemplateFormatVersion: "2010-09-09"
Description: "Cognito User Pool."
Resources:

  UserPool:
    Type: "AWS::Cognito::UserPool"
    Properties:
      Policies:
        PasswordPolicy:
          MinimumLength: 8
          RequireUppercase: true
          RequireLowercase: true
          RequireNumbers: true
          RequireSymbols: true
      UserPoolName: CpiDev3-Cognito-UserPool-Test1
      MfaConfiguration: 'OFF'
      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: true
        UnusedAccountValidityDays: 7

  UserPoolClient:
    Type: "AWS::Cognito::UserPoolClient"
    Properties:
      ExplicitAuthFlows: 
        - ALLOW_ADMIN_USER_PASSWORD_AUTH
        - ALLOW_CUSTOM_AUTH 
        - ALLOW_USER_PASSWORD_AUTH
        - ALLOW_USER_SRP_AUTH
        - ALLOW_REFRESH_TOKEN_AUTH
      UserPoolId:
        Ref: UserPool
      ClientName: CpiDev3-Cognito-UserPool-Test1-AppClient
      RefreshTokenValidity: 30

Outputs:
  UserPoolClientId:
    Description: The UserPoolClient Id
    Value: !Ref UserPoolClient
    Export:
      Name: "CpiDev3-Cognito-UserPool-Test1-AppClient"
  UserPoolId:
    Description: The UserPool Id
    Value: !Ref UserPool
    Export:
      Name: "CpiDev3-Cognito-UserPool-Test1-UserPool"

Cognitoユーザのテンプレート

CognitoUser.yaml

AWSTemplateFormatVersion: "2010-09-09"
Description: "Cognito User."
Parameters:
  SystemId:
    Type: String
    Description: >-
      Your System Id. For example:CpiDev3
    ConstraintDescription: System Id is required
    Default: ''
  CognitoUserName:
    Type: String
    Description: >-
      Your Cognito User Name. For example:CpiDev3CognitoTestUser
    ConstraintDescription: System Id is required
    Default: ''

Resources:
  CreateSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name:
        !Join
          - ''
          - - 'secret-'
            - !Ref SystemId
            - '-'
            - !Ref CognitoUserName
      GenerateSecretString:
        PasswordLength: 8
        ExcludeCharacters: '#$%&()*+,-./:;<=>?@[\]^`{|}~"'

  AdminCreateCognitoUser:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt CognitoAdminCreateUserFunction.Arn

  CognitoAdminCreateUserFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName:
        !Join
          - ''
          - - 'lmd-'
            - !Ref SystemId
            - '-'
            - !Ref CognitoUserName
            - '-CreateCognitoUser-1'
      Handler: index.handler
      Environment: 
        Variables:
          UserPoolId : 
            !ImportValue CpiDev3-Cognito-UserPool-Test1-UserPool
          UserPoolClientId : 
            !ImportValue CpiDev3-Cognito-UserPool-Test1-AppClient
          SecretName: !Ref CreateSecret
          UserName: !Ref CognitoUserName
      Role: !GetAtt CognitoAdminCreateUserFunctionRole.Arn
      Code:
        ZipFile: !Sub |
          import cfnresponse
          import boto3
          import os

          def handler(event, context):
            print(event['RequestType'])
            client_id = os.getenv('UserPoolClientId')
            print(f'client_id: {client_id}')

            user_pool_id = os.getenv('UserPoolId')
            print(f'user_pool_id: {user_pool_id}')

            secret_name = os.getenv('SecretName')
            print(f'secret_name: {secret_name}')

            cognito_idp = boto3.client('cognito-idp')

            # ユーザー名を取得する
            username = os.getenv('UserName')
            print(f'username: {username}')
            # パスワードを取得する
            password = get_secret(secret_name)
            # print(f'password: {password}')

            # スタック削除時にユーザーを削除します。
            response_data = {}

            if event['RequestType'] == 'Delete':
              response_data = cognito_idp.admin_delete_user(
                  UserPoolId=user_pool_id,
                  Username=username
              )
              cfnresponse.send(event, context, cfnresponse.SUCCESS,
                                      {'Response': 'Success','CognitoUserName': username})   
              return

            print(response_data)

            # ユーザーを作成する
            response_data = {}
            try:
              response_data = cognito_idp.admin_create_user(
                UserPoolId=user_pool_id,
                Username=username,
                TemporaryPassword=password,
                MessageAction='SUPPRESS'
              )

              print('admin_create_user')
              print(response_data)

              # ログインを試みる。(パスワードの変更を要求される。)
              response_data = cognito_idp.admin_initiate_auth(
                  UserPoolId=user_pool_id,
                  ClientId=client_id,
                  AuthFlow='ADMIN_NO_SRP_AUTH',
                  AuthParameters={'USERNAME': username, 'PASSWORD': password},
              )

              print('admin_initiate_auth')
              print(response_data)

              session = response_data['Session']

              # パスワードを変更する。
              response_data = cognito_idp.admin_respond_to_auth_challenge(
                  UserPoolId=user_pool_id,
                  ClientId=client_id,
                  ChallengeName='NEW_PASSWORD_REQUIRED',
                  ChallengeResponses={'USERNAME': username, 'NEW_PASSWORD': password},
                  Session=session
              )          

              print('admin_respond_to_auth_challenge')
              print(response_data)

            except Exception as e:
              print("error: " + str(e))
              response_data = {'error': str(e)}
              cfnresponse.send(event, context, cfnresponse.FAILED,
                                    {'Response': 'FAILED'})
              return

            print(response_data)

            cfnresponse.send(event, context, cfnresponse.SUCCESS,
                                    {'Response': 'Success','CognitoUserName': username})          

          def get_secret(secret_name):
              """SecretsManagerから情報を取得する"""
              try:
                  rt = None
                  region_name = "ap-northeast-1"
                  # Create a Secrets Manager client
                  client = boto3.client(
                      service_name='secretsmanager',
                      region_name=region_name
                  )
                  get_secret_value_response = client.get_secret_value(
                      SecretId=secret_name
                  )
                  if 'SecretString' in get_secret_value_response:
                      rt = get_secret_value_response['SecretString']
              except Exception:
                  print ('[ConfiRule_Notification] get secret exception error.')
                  rt=None
              return rt

      Runtime: python3.9

  CognitoAdminCreateUserFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: 
        !Join
          - ''
          - - 'irl-'
            - !Ref SystemId
            - '-'
            - !Ref CognitoUserName
            - '-CreateCognitoUser-1'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: root
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource: "arn:aws:logs:*:*:*"
          - Effect: Allow
            Action:
              - cognito-idp:*
            Resource: "arn:aws:cognito-idp:*:*:userpool/*"
          - Effect: Allow
            Action:
              - secretsmanager:GetSecretValue
            Resource: "arn:aws:secretsmanager:*:*:*"

スタック間のデータ連携

ExportとImportValueを使ってスタック間のパラメータ出力と参照を実装

Export

Cognitoユーザプールのテンプレートから抜粋

構築時のCloudFormation画面が下記のように表示されます。

ImportValue
Cognitoユーザのテンプレートから抜粋

まとめ

今回、CognitoユーザプールとCognitoユーザを2個のテンプレートで作成してみました。ユーザの作成と削除に問題がないですが、ユーザ名の変更に対応していないので、変更したい場合、スタックを削除してから、新規作成でお願いいたします。

以上です。
お役に立てれば幸いです。

参考

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/walkthrough-crossstackref.html

Last modified: 2022-06-04

Author