はじめに
AWSアカウントに対する操作で、AWS CLIやSDKを使用したいという場合、その認証情報としてはIAMユーザーのアクセスキー
を発行して使用するのが一般的かと思います。
ただアクセスキーは発行すると管理する必要がありますし、強い権限のアクセスキーが漏洩した場合、悪意を持って使用される危険性があります。
なので、環境の運用の仕方によってはIAMユーザーのアクセスキーを発行すること自体を避ける場合もありますよね。そこでAWSに慣れてくると候補に上がってくるのが、一時Credentialの利用かと思います。
一時Credentialを利用する方法としては、AWS IAM Identity Centerで実装するなどいくつかあると思いますが
今回は、
- Amazon Cognito(以下単にCognito)のログインユーザーに付与されるIAMロールの一時Credentialを取得して使用する
という機会がありましたので紹介します。
前提: Cognitoの設計
Cognitoは、機能を利用することで様々な認証認可の実装が可能な機能群です。
詳細は別の機会に譲ることとしますが、Cognitoの提供する機能を組み合わせて作っていくだけでも、実装できる認証認可パターンは複数あります。
それぞれメリット・デメリット、リスクが存在し、採用する設計の決定はそこを検討した上で行う必要があります。
Cognitoユーザー(=一時Credential利用者)の要件
今回は下記のような要件でCognitoユーザーを定義します。
- 認証方法は「メールアドレス」と「パスワード」とする
-
ユーザーのMFA利用を有効(強制)とする
- MFAはGoogle Authenticatorを使用する。
-
ユーザーは自己サインアップを可能とする
- ユーザー自身でユーザーを登録できる状態。今回「パスワード確認・変更」のプロセスをスキップするため。
- ユーザーはCognitoで認証された場合、特定のAWSアカウントのリソースに対する権限を行使できる
- ユーザーは、
Cognitoユーザー登録(自己サインアップ)〜Credential取得
まで、AWS IAMの権限を特に必要としない。- ちょっとややこしいんですが、AWS CLIのコマンドで実施するものの、Cognitoのユーザー登録〜Credential取得までのコマンド実行は、AWS Credentialがなくても実行できます。
Cognito管理者(=AWSアカウント管理者)の要件
- Cognito管理者はAWSリソースを操作する権限を有していること
- Cognito関連のリソースを作成するために必要
Tips: 上記の設計の場合のリスクについて
上記の設計の場合、
- Cognito関連のリソース情報を知っている者が、自分でユーザー登録を実行し、一時Credentialを利用できてしまう可能性がある
というセキュリティ上のリスクがあります。環境での仕様を検討する際はこの点を十分に検討するようにしてください。単純な回避策としては、ユーザー登録を管理者のみにするなどで可能かと思います。
自分は今回、
- Cognito管理者とCognitoユーザーが同一人物なので、Cognito関連のリソース情報が悪用されるリスクは低い
と判断してこの設計を採用しています。
1. Amazon Cognito の設定
ここはAWSアカウントのIAM Credentialを持っている状態で実施します。
1-1. Cognito ユーザープールの作成・設定
ユーザープールを作成します。下記以外はデフォルトの設定で進めました。
-
MFA強制とする
-
ユーザーの自己登録を有効とする
-
メッセージ配信設定
-
高度なアプリケーションクライアントの設定
1-2. Cognito IDプールの作成・設定
次に、IDプールを作成します。設定で気にするところは下記です。
-
認証
-
許可を設定
-
ID プロバイダーを接続
ほかはすべてデフォルト入力とし、IDプール作成を完了します。
2. Cognitoへユーザー登録 + MFA検証
AWS CLIやコマンドでユーザー登録と、そのユーザーのMFA検証を実行していきます。途中でMFAを検証するために、シェル関連ツール(qrencode)を使用します。
ここから先はAWS CLIを使用しているため若干紛らわしいのですが、下記に示す操作は該当AWSアカウントのIAM権限なしで実行できる操作です。その事自体がリスクにもなりうるので注意が必要となります。
前提: シェルの実行環境
私の実行環境はMacBook Pro(Intel)です。他OSの場合は適宜読み替えてください。(OSに依存している部分はないかと思っています)
また、コマンドの実行に当たって下記をシェル環境に導入済みです。
- AWS CLI
- jq
- qrencode
2-1. Cognitoへのユーザー登録とユーザー検証の実施
まずユーザー登録(サインアップ)します。
# 自己登録によるユーザーサインアップ
CLIENT_ID=<Cognitoユーザープール アプリケーションクライアントIDを入力>
USER_EMAIL="istio@example.com"
PASSWORD="P@ssw0rdO123"
aws cognito-idp sign-up \
--client-id ${CLIENT_ID} \
--username ${USER_EMAIL} \
--password ${PASSWORD} \
--user-attribute "Name=email,Value=${USER_EMAIL}"
次に、登録したEメールに届く確認コードを入力し、ユーザーのサインアップを完了させます。
# 確認コードによる検証
CONFIRMATION_CODE=xxxxxx
aws cognito-idp confirm-sign-up \
--client-id ${CLIENT_ID} \
--username ${USER_EMAIL} \
--confirmation-code ${CONFIRMATION_CODE}
2-2. MFAセットアップを開始
CLIのコマンドでMFA登録に向けて進めていきます。
チャレンジの情報やセッションの情報を使用するため、シェルの変数に格納していきます。
# Cognitoのユーザー認証フローを開始
Parameters=$(aws cognito-idp initiate-auth \
--client-id ${CLIENT_ID} \
--auth-flow USER_PASSWORD_AUTH \
--auth-parameters "USERNAME=${USER_EMAIL},PASSWORD=${PASSWORD}" \
--output json) && echo ${Parameters}
# 応答から必要な情報を格納
Session=`echo ${Parameters} | jq -r '.Session'`
USER_ID_FOR_SRP=`echo ${Parameters} | jq -r '.ChallengeParameters.USER_ID_FOR_SRP'`
# Cognitoが生成する一意の秘密キーを使用して、MFAのセットアップを開始
Secret_parameters=$(aws cognito-idp associate-software-token \
--session ${Session} \
--output text) && echo ${Secret_parameters}
# 応答から必要な情報を格納
Session=`echo ${Secret_parameters} | jq -r '.Session'`
SecretCode=`echo ${Secret_parameters} | jq -r '.SecretCode'`
2-3. qrencodeでMFA登録用のQRコードを生成
- QRコード生成コマンドを実行
-
MyAWSAccountCognito
の部分は任意の文字列に変更してOKです- 詳細は割愛しますが、上記がGoogle Authenticator上の表示名になるのでわかりやすい文字列を設定するのがおすすめです。
-
qrencode -t ansi "otpauth://totp/MyAWSAccountCognito:${USER_EMAIL}?secret=${SecretCode}&issuer=MyAWSAccountCognito"
open output.png
2-4. QRコードを読み込んでGoogle Authenticatorに登録
CognitoをGoogle Authenticatorに紐づけます。
- 登録に成功すると、上記で設定した表示名で登録されます。(画像の2段目)
2-5. 登録済みMFA情報でCognitoのMFA検証を実施
- 下記コマンドを実行します
# Google Authenticatorで発行された認証コードを入力
MFA_CODE=xxxxxx
# MFA検証を実施
aws cognito-idp verify-software-token \
--user-code ${MFA_CODE} \
--session ${Session}
Tips: MFA検証の完了までにセッション切れになる場合
認証フローセッションのデフォルトの有効期限は3分です。コマンドになれるまでは、QRコードを発行〜MFA検証完了までを終える前に、認証フローの有効期限が切れることがあります。
トラブルシューティングが長引いているなどでセッションの有効期限を長くしたい場合は、アプリケーションクライアント設定の「認証フローセッション」で変更できます。ちょっとストレスを緩和できるかもしれません。
今回の方法であれば初回だけなので一時的に短くする分にはありだと思います。
3. 作成したCognitoユーザーでログインし、AWS一時Credential取得
上記までが完了したら、一時Credentialを取得できる状態になります。
ここから先の手順は、一時Credentialを取得する際に毎回行うことになります。
また、下記の操作は該当AWSアカウントのIAM権限なしで実行できる操作になります。引き続き、その事自体がリスクにもなりうるので注意が必要となります。
3-1. 必要なCognito情報を控えておく
下記3つのIDが必要になりますので、作成済みのリソースの情報を控えておきます。
- Cognito ユーザプールのID
- Cognito ユーザープール アプリケーションクライアントID
- Cognito IDプールのID
3-2. コマンドでCognitoへログインし、AWS一時Credential取得
# 変数設定
REGION=ap-northeast-1
USER_POOL_ID=<CognitoユーザプールIDを入力>
CLIENT_ID=<Cognitoユーザプール アプリケーションクライアントIDを入力>
USER_EMAIL="istio@example.com"
PASSWORD="P@ssw0rdO123"
IDENTITY_POOL_ID=<CognitoIDプールのIDを入力>
COGNITO_USER_POOL=cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}
# Cognito認証フローを開始
Parameters=$(aws cognito-idp initiate-auth \
--client-id ${CLIENT_ID} \
--auth-flow USER_PASSWORD_AUTH \
--auth-parameters "USERNAME=${USER_EMAIL},PASSWORD=${PASSWORD}" \
--output json) && echo ${Parameters}
# 必要なパラメータを格納
Session=`echo ${Parameters} | jq -r '.Session'`
USER_ID_FOR_SRP=`echo ${Parameters} | jq -r '.ChallengeParameters.USER_ID_FOR_SRP'`
# Google AuthenticatorのMFAコードを入力
MFA_CODE=xxxxxx
# あとは順番に実行
ID_TOKEN=$(aws cognito-idp respond-to-auth-challenge \
--client-id ${CLIENT_ID} \
--challenge-name SOFTWARE_TOKEN_MFA \
--challenge-responses USERNAME=${USER_ID_FOR_SRP},SOFTWARE_TOKEN_MFA_CODE=${MFA_CODE} \
--session ${Session} \
--query "AuthenticationResult.IdToken" \
--output text) && echo ${ID_TOKEN}
IDENTITY_ID=$(aws cognito-identity get-id \
--identity-pool-id ${IDENTITY_POOL_ID} \
--logins "${COGNITO_USER_POOL}=${ID_TOKEN}" \
--query "IdentityId" \
--output text) && echo ${IDENTITY_ID}
OUTPUT=$(aws cognito-identity get-credentials-for-identity \
--identity-id ${IDENTITY_ID} \
--logins "${COGNITO_USER_POOL}=${ID_TOKEN}") && echo ${OUTPUT}
上記が成功すると、変数OUTPUTに一時Credential情報が格納されます。
あとはCredential情報を各種ツールで利用できる状態にすればOKです。
- 下記に、
Credentialを環境変数に設定してAWS CLIを使用する場合
のコマンドを例示しておきます。
api_key=`echo ${OUTPUT} | jq -r '.Credentials.AccessKeyId'`
api_secret=`echo ${OUTPUT} | jq -r '.Credentials.SecretKey'`
security_token=`echo ${OUTPUT} | jq -r '.Credentials.SessionToken'`
# 環境変数に設定
export AWS_ACCESS_KEY_ID=$api_key
export AWS_SECRET_ACCESS_KEY=$api_secret
export AWS_SECURITY_TOKEN=$security_token
# Credentialが有効かどうかを確認
aws sts get-caller-identity
おわりに
IAMユーザーを発行しない場合アクセスキー管理を考える必要がなくなるので、使い所がなくはないかもな?と思いました。
個人的には、この苦闘(?)を通してAmazon Cognitoの認証フローを理解することができたことが大きかったです。
ちなみに今回はローカルのシェルでCognitoの認証フローを実行することにこだわってみましたが、これらを各プログラム言語のSDKを利用したアプリケーションコードで実装するサンプル
は、AWSドキュメントで公開されています。
AWS SDK を使用した Amazon Cognito のコード例。 - Amazon Cognito
参考文献
MFAを有効化したCognitoユーザープールよりCLIでToken取得 | クラウド・AWSのIT技術者向けブログ SKYARCH BROADCASTING
AWS SDK を使用した Amazon Cognito のコード例。 - Amazon Cognito