概要
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://qiita.com/kai_kou/items/aa7dc0042b44649f576a
PythonでAmazon Cognitoのユーザープールを作成してみる
https://qiita.com/kai_kou/items/f1623ac1f3d7aaab23fe
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
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://qiita.com/kai_kou/items/f1623ac1f3d7aaab23fe
作成する
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://qiita.com/kai_kou/items/aa7dc0042b44649f576a
PythonでAmazon Cognitoのユーザープールを作成してみる
https://qiita.com/kai_kou/items/f1623ac1f3d7aaab23fe
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