概要
Cognitoのユーザープールを作成するのに、AWS マネジメントコンソールを利用するのが面倒になり、AWS SDK for Pythonを利用して面倒さを解消したのですが、AWSにはCloudFormation(CFn)という素敵サービスがありますので、それを利用してさらに手間を省けないか検証してみました。
CFnでCognitoはすでに対応されていますが、MFA有効化してTOTPを選択するのに少し手間がかかりました。そのうちにもっと簡単に作成されるといいですね。(2018/11/09時点)
CloudFormation で Cognito
https://qiita.com/y13i/items/1923b47079bdf7c44eec
Amazon Cognito Now Supported by AWS CloudFormation
https://aws.amazon.com/jp/about-aws/whats-new/2017/04/amazon-cognito-now-supported-by-aws-cloudformation/
CognitoやMFA、TOTPってなんぞ?という方は下記をご参考ください。
Amazon Cognitoのワンタイムパスワード(TOTP)認証をNode.jsで試してみた
https://cloudpack.media/44521
PythonでAmazon Cognitoのユーザープールを作成してみる
https://cloudpack.media/44563
AWS CloudFormation(Cfn)とは
AWS CloudFormation (設定管理とオーケストレーション) | AWS
https://www.google.co.jp/search?q=CloudFormation
AWS CloudFormation は、クラウド環境内のすべてのインフラストラクチャリソースを記述してプロビジョニングするための共通言語を提供します。CloudFormation では、シンプルなテキストファイルを使用して、あらゆるリージョンとアカウントでアプリケーションに必要とされるすべてのリソースを、自動化された安全な方法でモデル化し、プロビジョニングできます。このファイルは、クラウド環境における真の単一ソースとして機能します。
CloudFormation超入門
https://dev.classmethod.jp/beginners/chonyumon-cloudformation/
誤解を恐れつつ一言で言えば、「自動的にAWS上で作りたいものを作ってくれる」サービスです。というか、そういう環境を用意してくれるサービスです。
CFnのテンプレート
今回作成したCFnのテンプレートです。
CFnのテンプレートはAWS マネジメントコンソールにあるデザイナーでも作成できますが、今回は利用せずに作成しました。フォーマットはJSON、YAMLが利用できますが、YAMLで作成しています。
検証用のユーザーをあわせて作成していますが、ユーザー名、パスワードはべた書きなので、あしからず。
ソースはGitHubにもアップしています。
https://github.com/kai-kou/create-cognito-user-pool-at-cloudformation
create-user-pool-template.yaml
Resources: # ユーザープールの作成 UserPool: Type: "AWS::Cognito::UserPool" Properties: Policies: PasswordPolicy: MinimumLength: 8 RequireUppercase: true RequireLowercase: true RequireNumbers: true RequireSymbols: true UserPoolName: Ref: AWS::StackName MfaConfiguration: 'OFF' AdminCreateUserConfig: AllowAdminCreateUserOnly: false UnusedAccountValidityDays: 7 # ユーザープールにアプリクライアントを作成 UserPoolClient: Type: "AWS::Cognito::UserPoolClient" Properties: UserPoolId: Ref: UserPool ClientName: Ref: AWS::StackName RefreshTokenValidity: 30 # ユーザープールでMFA(TOTP)有効化 UserPoolMfaConfig: Type: Custom::CustomResource Properties: ServiceToken: !GetAtt CognitoSetUserPoolMfaConfigFunction.Arn UserPoolId: Ref: UserPool # 検証用のユーザーを追加 AdminCreateUser: Type: Custom::CustomResource Properties: ServiceToken: !GetAtt CognitoAdminCreateUserFunction.Arn UserPoolId: Ref: UserPool UserName: hoge Password: hogeHoge7! # 検証用のユーザーを追加 AdminCreateUser2: Type: Custom::CustomResource Properties: ServiceToken: !GetAtt CognitoAdminCreateUserFunction.Arn UserPoolId: Ref: UserPool UserName: hoge2 Password: hogeHoge7! # ユーザープールでMFA(TOTP)有効化するLambda関数 CognitoSetUserPoolMfaConfigFunction: Type: AWS::Lambda::Function Properties: Handler: index.handler Role: !GetAtt CognitoFunctionExecutionRole.Arn Code: ZipFile: !Sub | import cfnresponse import boto3 def handler(event, context): # スタック削除時にも実行されるので、処理せずに終了させる if event['RequestType'] == 'Delete': cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) return # UserPoolIDを取得する user_pool_id = event['ResourceProperties']['UserPoolId'] print(f'user_pool_id: {user_pool_id}') # MFA有効化してTOTPを指定する response_data = {} try: client = boto3.client('cognito-idp') response_data = client.set_user_pool_mfa_config( UserPoolId=user_pool_id, SoftwareTokenMfaConfiguration={ 'Enabled': True }, MfaConfiguration='ON' ) except Exception as e: print("error: " + str(e)) response_data = {'error': str(e)} cfnresponse.send(event, context, cfnresponse.FAILED, response_data) return print(response_data) cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data) Runtime: python3.6 # ユーザープールにテスト用ユーザーを作成するLambda関数 CognitoAdminCreateUserFunction: Type: AWS::Lambda::Function Properties: Handler: index.handler Role: !GetAtt CognitoFunctionExecutionRole.Arn Code: ZipFile: !Sub | import cfnresponse import boto3 def handler(event, context): print(event['RequestType']) # スタック削除時にも実行されるので、処理せずに終了させる if event['RequestType'] == 'Delete': cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) return # UserPoolIDを取得する user_pool_id = event['ResourceProperties']['UserPoolId'] print(f'user_pool_id: {user_pool_id}') # ユーザー名、パスワードを取得する username = event['ResourceProperties']['UserName'] password = event['ResourceProperties']['Password'] # 検証用のユーザーを作成する response_data = {} try: client = boto3.client('cognito-idp') response_data = client.admin_create_user( UserPoolId=user_pool_id, Username=username, TemporaryPassword=password, MessageAction='SUPPRESS' ) # Datetime型のままなので文字列に変換する response_data['User']['UserCreateDate'] = \ response_data['User']['UserCreateDate'].strftime('%c') response_data['User']['UserLastModifiedDate'] = \ response_data['User']['UserLastModifiedDate'].strftime('%c') except Exception as e: print("error: " + str(e)) response_data = {'error': str(e)} cfnresponse.send(event, context, cfnresponse.FAILED, response_data) return print(response_data) cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data) Runtime: python3.6 # Lambda関数実行用のロール CognitoFunctionExecutionRole: Type: AWS::IAM::Role Properties: 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:*:*:*" # Cognitoの操作権限を付与する - Effect: Allow Action: - cognito-idp:* Resource: "arn:aws:cognito-idp:*:*:userpool/*"
はい。
ざっくりとポイントだけ。
ユーザープール作成時にTOTPを利用したMFA有効化はできない
MfaConfiguration
というパラメータでMFA有効化できるのですが、いまのところ有効化時にTOTPを指定するパラメータがありませんでしたので、とりあえず、OFF
で作成しています。(2018/11/09時点)
パラメータについては公式のドキュメントが参考になります。
AWS::Cognito::UserPool – AWS CloudFormation
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html
ユーザープール名UserPoolName
の指定はRef: AWS::StackName
とすることでCFnのスタック名としています。
UserPool: Type: "AWS::Cognito::UserPool" Properties: Policies: PasswordPolicy: MinimumLength: 8 RequireUppercase: true RequireLowercase: true RequireNumbers: true RequireSymbols: true UserPoolName: Ref: AWS::StackName MfaConfiguration: 'OFF' AdminCreateUserConfig: AllowAdminCreateUserOnly: false UnusedAccountValidityDays: 7
AWS Lambda-backed カスタムリソースを利用してMFAを有効化する
CFnにはカスタムリソースというものが用意されており、CFnが対応していないリソースを自前で管理することができます。さらにカスタムリソースを定義する際にLambda関数を利用することができます。便利ですね^^
AWS Lambda-backed カスタムリソース – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html
Lambda 関数とカスタム リソースを関連付けた場合、この関数はカスタム リソースが作成、更新、または削除されるたびに呼び出されます。
こちらを利用して、CognitoのユーザープールでMFA有効化します。
カスタムリソースの定義
UserPoolMfaConfig: Type: Custom::CustomResource Properties: ServiceToken: !GetAtt CognitoSetUserPoolMfaConfigFunction.Arn UserPoolId: Ref: UserPool
UserPoolMfaConfig
はカスタムリソースの名称となり任意で指定できます。Type: Custom::CustomResource
とすることでカスタムリソースと定義します。ServiceToken
に実行するLambda関数のArnを指定、UserPoolId
はLambda関数に引き渡すパラメータになります。ここではRef: UserPool
として、作成されたユーザープールのIDを渡しています。
Lambda関数の定義
CognitoSetUserPoolMfaConfigFunction: Type: AWS::Lambda::Function Properties: Handler: index.handler Role: !GetAtt CognitoFunctionExecutionRole.Arn Code: ZipFile: !Sub | (略) Runtime: python3.6
実行するLambda関数は作成済みの関数も指定できますが、同一テンプレート(スタック)内で作成することもできます。(すごい!便利!
今回はAWS SDKを利用するだけでよかったので、ソースも含めて定義していますが、もし他のライブラリをインポートしたい場合は、ソースをZIP圧縮してS3へアップしたものを指定することができます。通常のAWS Lambda関数をデプロイする方法と同じです。
Lambda関数の実行に必要なロールも同一テンプレート(スタック)内で作成して指定ができます。
Handler: index.handler
としていますが、index
に関してはFCnで関数を作成する場合、ファイル名がindex.py
となるので、変更不可のようです。
Lambda関数の詳細については下記が参考になります。
AWS Lambda 関数コード – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html
Lambda-backedカスタムリソースの雛形
https://www.weblog-beta.com/posts/custom-resource/
Lamnda関数の実装
import cfnresponse import boto3 def handler(event, context): # スタック削除時にも実行されるので、処理せずに終了させる if event['RequestType'] == 'Delete': cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) return # UserPoolIDを取得する user_pool_id = event['ResourceProperties']['UserPoolId'] print(f'user_pool_id: {user_pool_id}') # MFA有効化してTOTPを指定する response_data = {} try: client = boto3.client('cognito-idp') response_data = client.set_user_pool_mfa_config( UserPoolId=user_pool_id, SoftwareTokenMfaConfiguration={ 'Enabled': True }, MfaConfiguration='ON' ) except Exception as e: print("error: " + str(e)) response_data = {'error': str(e)} cfnresponse.send(event, context, cfnresponse.FAILED, response_data) return print(response_data) cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
Lambda関数はCFnのスタック作成時だけでなく、更新や削除時にも実行されます。
今回は削除するケースに対応するのに、if event['RequestType'] == 'Delete':
で、削除時には処理をスキップするようにしています。
例外のハンドリングなど他にも考慮すべき点がありますが、下記が参考になります。
CloudFormationでLambdaを実行する
https://prgrmmbl.com/2018/07/01/Lambda-With-CloudFormation.html
Lambda-backed Custom Resourceのcfn-responseモジュールを利用する上での注意点
https://dev.classmethod.jp/cloud/aws/note-about-using-delete-response-with-cfn-response-module-in-lambda-backed-custom-resource/
Cognitoのユーザープールの操作にはPythonで提供されているAWS SDKのboto3
を利用しています。SDKの利用に関しては下記をご参考ください。
PythonでAmazon Cognitoのユーザープールを作成してみる
https://cloudpack.media/44563
作成する
CFnで上記テンプレートからスタックを作成してみます。
AWS CLIで作成する
AWS CLIを利用して、CFnのスタックを作成してみます。
上記のテンプレートを適当なディレクトリに保存して実行します。
AWS CLIのインストールについては下記が参考になります。
AWS CLIのインストール
https://qiita.com/yuyj109/items/3163a84480da4c8f402c
> aws --version aws-cli/1.16.27 Python/3.6.6 Darwin/17.7.0 botocore/1.12.17 > mkdir 任意のディレクトリ > cd 任意のディレクトリ > touch create-user-pool-template.yaml > vi create-user-pool-template.yaml
create-stack – AWS CLI 1.16.51 Command Reference
https://docs.aws.amazon.com/cli/latest/reference/cloudformation/create-stack.html
AWS CloudformationをAWS CLIから使ってみる
https://techte.co/2018/01/25/cloudformation/
> aws cloudformation create-stack \ --region ap-northeast-1 \ --stack-name cognito-totp-mfa-user-pool \ --template-body file://create-user-pool-template.yaml \ --capabilities CAPABILITY_IAM { "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxx:stack/cognito-totp-mfa-user-pool/3d3591e0-e3ca-11e8-bdc2-503a6ff78e2a" }
create-stack
でスタックを作成します。
ロールを作成する場合、--capabilities CAPABILITY_IAM
と指定しておかないと、Requires capabilities : [CAPABILITY_IAM]
と怒られるので、ご注意ください。
Requires capabilities : [CAPABILITY_IAM]
https://github.com/awslabs/serverless-application-model/issues/51
describe-stacks
でスタックの情報が確認できます。
> aws cloudformation describe-stacks \ --stack-name cognito-totp-mfa-user-pool { "Stacks": [ { "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxx:stack/cognito-totp-mfa-user-pool/752fbbe0-e3d2-11e8-8ab5-50a686699882", "StackName": "cognito-totp-mfa-user-pool", "CreationTime": "2018-11-09T03:49:28.365Z", "RollbackConfiguration": {}, "StackStatus": "CREATE_IN_PROGRESS", "DisableRollback": false, "NotificationARNs": [], "Capabilities": [ "CAPABILITY_IAM" ], "Tags": [], "EnableTerminationProtection": false } ] }
AWS マネジメントコンソールでもスタックが作成ことが確認できます。
AWS マネジメントコンソールで作成する
最近CloudFormationの操作UIが新しくなりプレビュー版が利用できるようになったみたいです。LambdaのUIっぽくて良い感じです。せっかくなので新しいUIで試してみます。
ローカルからファイルを指定するとS3にアップロードされます。
最後に、テンプレート内でロール作成の定義があるから気をつけてねと確認がでてきます。
AWS CLIで作成したときの--capabilities CAPABILITY_IAM
と同じ確認ですね。
ユーザープールの確認
Cognitoでユーザープールが作成されたか確認してみます。
はい。
ちゃんとMFA有効化されて、ユーザーも作成されています。
やったぜ。
最後に、スタックを削除して、関連するリソースが削除されることを確認しておきます。
注意点としては、テンプレートで定義した各リソースは削除されますが、S3にアップロードされたテンプレートファイルや、Lambda関数のログは残ったままになりますので、そちらは手動で削除することになります。(未確認)
> aws cloudformation delete-stack \ --stack-name cognito-totp-mfa-user-pool > aws cloudformation describe-stacks \ --stack-name cognito-totp-mfa-user-pool An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id cognito-totp-mfa-user-pool does not exist
まとめ
CFnを利用することで、AWS マネジメントコンソールやスクリプトを書くことなくAWSのリソースを作成・管理できることがわかりました。カスタムリソースを利用することで、より柔軟なリソース管理もできるので、応用がとても効きます。
いままで使ったことがなかったのですが、環境構築の再現性も非常に高いので、AWSを利用するのに必須といっていいかもしれません。
参考
CloudFormation で Cognito
https://qiita.com/y13i/items/1923b47079bdf7c44eec
Amazon Cognito Now Supported by AWS CloudFormation
https://aws.amazon.com/jp/about-aws/whats-new/2017/04/amazon-cognito-now-supported-by-aws-cloudformation/
Amazon Cognito Resource Types Reference
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-reference-cognito.html
Lambda-backedカスタムリソースの雛形
https://www.weblog-beta.com/posts/custom-resource/
Amazon Cognitoのワンタイムパスワード(TOTP)認証をNode.jsで試してみた
https://cloudpack.media/44521
PythonでAmazon Cognitoのユーザープールを作成してみる
https://cloudpack.media/44563
AWS CloudFormation (設定管理とオーケストレーション) | AWS
https://www.google.co.jp/search?q=CloudFormation
CloudFormation超入門
https://dev.classmethod.jp/beginners/chonyumon-cloudformation/
AWS::Cognito::UserPool – AWS CloudFormation
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html
AWS Lambda-backed カスタムリソース – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html
AWS Lambda 関数コード – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html
Lambda-backedカスタムリソースの雛形
https://www.weblog-beta.com/posts/custom-resource/
CloudFormationでLambdaを実行する
https://prgrmmbl.com/2018/07/01/Lambda-With-CloudFormation.html
Lambda-backed Custom Resourceのcfn-responseモジュールを利用する上での注意点
https://dev.classmethod.jp/cloud/aws/note-about-using-delete-response-with-cfn-response-module-in-lambda-backed-custom-resource/
AWS CLIのインストール
https://qiita.com/yuyj109/items/3163a84480da4c8f402c
Requires capabilities : [CAPABILITY_IAM]
https://github.com/awslabs/serverless-application-model/issues/51
create-stack – AWS CLI 1.16.51 Command Reference
https://docs.aws.amazon.com/cli/latest/reference/cloudformation/create-stack.html