今回は以前ご紹介した記事「HRMOS」の従業員データベースシステムのAWSリージョンをオレゴンから東京に移行した話の中でも少し触れていた、 「Amazon Cognito user poolsユーザーの別アカウントへの移行」 について、具体的にどのように行ったかをご紹介します。
Amazon Cognito user poolsのアカウント間移行は、我々にとって初めての経験の連続でした。世の中で発信されている情報も少なく、社内にも知見が無いため、AWSサポートの方と連携を取りながら進める必要のあるものでした(対応可不可の判断の多くでAWSサポートへの確認も行っています)。この貴重な経験がお役に立てると思い、今回の取り組みで得た知見を共有させていただきます。
パスワード再設定などのユーザーアクションを極力なくすためにどのような移行方法をとれるかを検討
要件
移行に関係する仕様と移行前の利用状況は以下の通りです(Amazon Cognito user poolsの細かい設定は割愛します)。
- 「HRMOSタレントマネジメント」を契約いただいている全企業の全ユーザーを対象とする
- 「HRMOSタレントマネジメント」が対応しているすべての認証方法に対応する
- メールアドレスとパスワードによる認証(9割以上のユーザーがこちら)
- 多要素認証( MFA )
- SMSによるコード認証( Amazon Cognito user poolsの機能を利用)
- アプリによるコード認証( Authenticatorなどのアプリを利用)
今回オレゴンリージョンで構築していたAmazon Cognito user poolsの全ユーザーを、 上記の要件を満たしつつ 東京リージョンに構築したAmazon Cognito user poolsに移行する必要がありました。 その際、当然要件にはパスワードの再設定など ユーザーに対して特別なアクションを極力発生させないこと も含まれていました。
方針検討
AWSではAmazon Cognito user poolsのユーザーを別のAmazon Cognito user poolsに移行する方法として以下の2つを提供しており、今回は 「ユーザー移行Lambdaトリガーを使用したユーザーインポート」 の方法を採用しました。
「 CSVファイルからのユーザーインポート」は移行元のuser poolsから パスワード以外の すべてのユーザー情報をCSVファイルにエクスポートし、それを移行先のuser poolsにインポートすることで移行を実現します。移行方法自体はシンプルなのですが、この方法の場合は移行後に すべてのユーザーにパスワードを再設定してもらう必要があります。 これは要件の 「ユーザーに対して特別なアクションを極力発生させないこと」 に反しているので採用しませんでした。
ユーザー移行Lambdaトリガーの利用
この方法はAmazon Cognito user poolsでいくつか用意されているLambdaトリガーのうち、「ユーザー移行のLambdaトリガー」を利用した移行方法で、 Amazon Cognito user poolsのサインアップフローで動作します。 具体的には、 パスワードによるサインイン時 または パスワード再設定時 にユーザーが移行先のuser poolsに存在しない場合に、設定したLambda関数が動作する仕組みです。 関数が適切なレスポンスを返却すると、移行先のuser poolsに パスワードを引き継いだ状態 で対象のユーザーが移行されます。
関数のリクエスト・レスポンスパラメータとしては色々あるのですが、今回利用したものは以下になります。
リクエスト
| パラメータ名 | 値 |
|---|---|
| TriggerSource | UserMigration_Authentication (サインイン時) または UserMigration_ForgotPassword (パスワードリセット時) |
| UserName | 入力されたユーザー名 |
| Password | 入力されたパスワード(パスワードリセット時は設定されない) |
レスポンス
| パラメータ名 | 値 |
|---|---|
| UserAttributes | Amazon Cognito user poolsのユーザー属性として記録される値 |
| FinalUserStatus | 移行後のユーザーの状態。 CONFIRMED または RESET_REQUIRED |
| MessageAction | 移行後にウェルカムメッセージを送信するかどうか。SUPPRESS を設定するとウェルカムメッセージを送信をしない |
処理内容
サインインイベント( TriggerSourceが UserMigration_Authentication)時のみ、リクエストされたユーザー名とパスワードで移行元のuser poolsに認証できることを確認する必要があります。
パスワードリセットイベント( TriggerSourceが UserMigration_ForgotPassword)時はリクエストにパスワードが含まれていないため、移行元のuser poolsにはユーザーが存在することの確認のみを行います。
- 移行元のuser poolsに AdminInitiateAuth をリクエストし、認証できるかチェックします。(サインインイベント時のみ)
- ユーザー属性をそのまま移行したいので、移行元のuser poolsに AdminGetUser をリクエストして該当ユーザーの情報を取得します。
- 関数のレスポンスとして以下をセットして処理を終了します。
- UserAttributes
- username : 移行先のuser pools用に新規発行したUUIDをセット
- email : ユーザーのメールアドレスをセット
- email_verified : メールアドレスの確認は済んでいる前提なので
true固定でセット - phone_number :
AdminGetUserで取得した情報をそのままセット(存在しない場合もあり) - phone_number_verified :
AdminGetUserで取得した情報をそのままセット(存在しない場合もあり)
- FinalUserStatus : パスワードをそのまま利用したいので
CONFIRMEDをセット - MessageAction : ウェルカムメッセージは不要なので
SUPPRESSをセット
- UserAttributes
注意点
ユーザー移行Lambda関数で利用するパスワードを クライアントから送信してもらう必要がある ため、Amazon Cognito user poolsの認証フローとして USER_PASSWORD_AUTH を設定しなければなりません。
一般的には、パスワードを送信しないことから、よりセキュアな認証フローである USER_SRP_AUTH を設定していることが多いと思いますので、移行時のみ USER_PASSWORD_AUTH を設定し、移行が終わり次第 USER_SRP_AUTH に戻すと良いでしょう。
MFA(SMS)の対応
ここまででユーザー移行Lambdaトリガーを利用して、移行元のuser poolsからユーザーを順次移行していく仕組みができましたが、このままでは MFAを利用しているユーザーがログイン時に2段階目の認証をパスすることができませんでした。 調査の結果、ユーザー移行Lambdaトリガー内でMFA(SMS) を有効化することはできないことが判明したため、シームレスな移行を実現するために 認証前Lambdaトリガー を利用することにしました。
認証前Lambdaトリガー
認証前Lambdaトリガーとは、Amazon Cognito user poolsのサインイン処理の前にLambda関数が実行されるトリガーです。ここでMFS(SMS) を有効化することができました。
補足として、ユーザー移行Lambdaトリガーと同時に利用している場合は ユーザー移行Lambdaトリガーが先に実行され、その後 認証前Lambdaトリガーが実行されます。 ただし、ユーザー移行Lambdaトリガーは基本的に 1ユーザーにつき1度しか実行されない のに対し、認証前Lambdaトリガーは 毎回実行される 点は考慮が必要です。
今回利用した関数のリクエスト・レスポンスパラメータは以下になります。
リクエスト
| パラメータ名 | 値 |
|---|---|
| UserAttributes | ユーザー属性 |
レスポンス
なし
処理内容
ここでやるべきことはMFA(SMS) を有効にすることです。 MFA(SMS) を有効にすべきユーザーを判定して、 AdminSetUserMFAPreference をリクエストします。
MFA(SMS) を有効にすべきユーザーの判定方法は、先行して実行されるユーザー移行Lambda関数の中でカスタム属性を設定することで解決しました。
- ユーザー移行Lambda関数で、取得したユーザー情報を精査し
PreferredMfaSetting == "SMS_MFA"が設定されている場合、UserAttributesにカスタム属性としてcustom:mfa_setting: SMS_MFAをセットします。 - 認証前Lambda関数で、
UserAttributesにカスタム属性custom:mfa_setting: SMS_MFAが見つかった場合のみAdminSetUserMFAPreferenceをリクエストします。 - カスタム属性
custom:mfa_setting: SMS_MFAはもう不要なので削除しておきます。
注意点
Amazon Cognito user poolsにおける属性変更などの処理は結果整合性的に反映が行われる動作となっているため、ごく稀に反映遅延によりMFAコードの入力を求められない可能性があります。
その時々の状況によって発生頻度が変わる可能性はありますが、我々の開発環境で何度かテストした結果ではほぼ100% MFAコード入力が求められ、入力後正常にログインすることができていました。 そのため、可能性はゼロではないと知りつつも今回の移行ではMFAを利用しているユーザーがそれほど多くないこと、再度ログインを試みることで解消できることからこの件は容認しました。
やむを得ずユーザー負担がかかってしまったところ
MFA(Application) はユーザーに再設定を求めた
移行要件として、 ユーザーに対して特別なアクションを極力発生させないこと としていたのですが、 MFA(Application) はAmazon Cognito user poolsとは関係のない機能ということもあり、 移行後にユーザーに再設定を求める ことになりました。
MFA(SMS) 利用ユーザーは認証コードが2回届く
MFA(SMS) を利用しているユーザーがこの移行プロセスを通過する際、以下のタイミングで認証コードが送信されます。
- ユーザー移行Lambda関数で実行している移行元のuser poolsへの認証処理
- 移行先のuser poolsに対するMFA(SMS) の有効化後に実行される認証処理
これによって、 ユーザーは2回認証コードを受け取ることになり、2回目に届いたコードを入力してもらう 必要がありました。 ユーザーからするとややわかりにくい挙動となってしまいましたが、 移行後の初回のみかつ、 MFA(SMS) 利用ユーザー数は少ない ということから容認することにしました。
まとめ
やむを得ず容認せざるを得ないこともいくつかあったものの、ユーザー移行Lambdaトリガーと認証前Lambdaトリガーを利用することで、 MFA(SMS) にも対応した極力ユーザーアクションを発生させないAmazon Cognito user poolsのユーザー移行が実現できました。
Amazon Cognito user poolsには途中で変更できない設定項目もいくつか存在し、サービスの運用途中でその設定を変更したいという要件が出てきた場合はAmazon Cognito user pools自体を再作成する必要があります。そのため、今回のようなAWSリージョン移行に限らずAmazon Cognito user poolsのユーザー移行が必要な場面もあるのではないかと思います。
なるべくならそのような変更が起きないようにしっかり設計をしておくべきではあるのですが、プロダクトを取り巻く様々な状況の変化によって移行せざるを得ないケースに直面することもあるでしょう。 その際にはぜひこの記事を思い出し、シームレスに移行させる手段としてユーザー移行Lambdaトリガーを利用できることやどのように利用すればいいのかなどを考える際の参考として、少しでも役立てていただけると嬉しいです。