AWS CLIで動かして学ぶCognito IDプールを利用したAWSの一時クレデンシャルキー発行

「Cognito IDプールってやつはAWSリソースへのアクセスを制御する認可部分を担当しているらしいけど、いったいどういう理屈でそうなってるんだ…?」 そんな自分の疑問からAWSのドキュメントを読み実際に手を動かして得られたCognito IDプールに対する理解をまとめました。
2020.04.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Cognito IDプールってやつはAWSリソースへのアクセスを制御する認可部分を担当しているらしいけど、いったいどういう理屈でそうなってるんだ…?」

そんな自分の疑問からAWSのドキュメントを読み手を動かして得られたCognito IDプールに対する理解を、 AWS CLIで再現できる形にまとめてみました。

Cognito IDプールでAWSの一時クレデンシャルキーを発行することによって、Cognito IDプールの世界からIAMの世界へ落とし込めると、だいぶイメージが付きやすいんじゃないかと思います。

AWS CLIを動かしながらCognito IDプールの世界を学んでいきましょう。

Cognito IDプールとユーザープールの役割

いきなりCognito IDプール以外の話が出てきてしまい恐縮なのですが、Cognito IDプールを語る上でCognitoユーザープールは避けて通れないので合わせて話をさせてください。

Cognito IDプールとユーザープールの役割の違いをざっくり説明すると、次の図の様な感じです。

  • Cognitoユーザープール: ユーザーIDの管理ログイン情報の管理(IDトークン等の発行)を行い、今アクセスしてきているのは○○というユーザーであるということを管理する、いわゆる認証部分を担当。
  • Cognito IDプール: ユーザーに応じたAWSの一時クレデンシャルキーの発行を行い、どのユーザーがAWSリソースのどこにアクセスできるのかという、いわゆる認可部分を担当。

重要なのはCognito IDプールは認可だけを担当しているというところです。 しかし、「今アクセスしているユーザーが誰なのか?」ということがわからないと認可しようが無いです。 だから、認証部分は別のサービスに委任する必要があり、その認証を委任する代表格のサービスがCognitoユーザープールです。

Cognito IDプールでは他にも認証を委任できる外部IDプロバイダーはあります。詳しくは公式ドキュメントを御覧ください。

Cognito IDプールのアクセスコントロールの方法

Cognito IDプールはIAM Roleでアクセスコントーロルをしています。

ユーザーに応じて権限を付与するIAM Roleの選択が可能で、大きく分けると次の3つの方法があります。

1. 認証されたユーザーと認証されていないユーザー

1つ目は認証されたユーザーか認証されていないユーザー(ゲストユーザー)かで、IAM Roleを分けて選択させることができます。

ちなみに、IAM Roleのポリシー内でCognito IDプールのIdentity IDを${cognito-identity.amazonaws.com:sub}という変数で使用できます。 だから、IAM Roleが1つでも「ユーザーごとにS3バケットのアクセスを許可する」なんて設定はできたりします。

詳しくはこちらの弊社ブログも御覧ください。

2. ルールベースでのIAM Roleの選択

2つ目はユーザー情報からルールベースでIAM Roleを選択させることができます。

3. Cognitoユーザープールで設定したIAM Roleの利用

3つ目はCognitoユーザープールのユーザー(グループ)自体にIAM Roleを設定し、そのIAM Roleを利用させることができます。

これらの環境を実際に構築して試していきます。

Cognito IDプールを構築してみる

マネジメントコンソールのCognitoの画面にアクセスして、新しいIDプールを作成します。

IDプール名に適当な名前を入力して、認証されていないIDに対してアクセスを有効にして、IDプールを作成します。

ここで認証されたユーザーが利用できるIAM Roleと、認証されていないユーザーが利用できるIAM Roleを定義しています。 IAMロールは新しいIAMロールを作成することにして、許可します。

作成したCognito IDプールのIDは、あとで使うのでメモしておいてください。 「IDプールの編集」ボタンをクリックすると確認できます。

これで最低限の環境はできました。いったんCognito IDプールの動きを試してみます。

認証されていないユーザーの一時クレデンシャルキーを発行してみる

まずは、Cognito IDプールの一時クレデンシャルキー発行フローがどんなものかざっと見ておきましょう。

認証されていないユーザーの一時クレデンシャルキーの発行フローはこんな感じです。

Cognito IDプールから一意な Identity ID を取得して、そのIdentity IDをもとにAWSの一時的なクレデンシャルキーを取得します。

これらは、AWS CLIの aws cognito-identity get-id, aws cognito-identity get-credentials-for-identity コマンドで実施できます。

$ IDENTITY_POOL_ID=us-west-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
$ IDENTITY_ID=$(aws cognito-identity get-id \
  --identity-pool-id ${IDENTITY_POOL_ID} \
  --query "IdentityId" \
  --output text) && echo ${IDENTITY_ID}
$ OUTPUT=$(aws cognito-identity get-credentials-for-identity \
  --identity-id ${IDENTITY_ID}) && echo ${OUTPUT}

このコマンドを実行してAWSの一時的なクレデンシャルキーを取得すると、こんな形で結果が返ってきます。

結果はJSON形式ですので、 jq コマンドを使ってターミナルで使える形に変えてやります。

$ AWS_ACCESS_KEY_ID=$(echo ${OUTPUT} | jq .Credentials.AccessKeyId)
$ AWS_SECRET_ACCESS_KEY=$(echo ${OUTPUT} | jq .Credentials.SecretKey)
$ AWS_SESSION_TOKEN=$(echo ${OUTPUT} | jq .Credentials.SessionToken)
$ echo "export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID"
$ echo "export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY"
$ echo "export AWS_SESSION_TOKEN=$AWS_SESSION_TOKEN"

このコマンドを実行して結果部分をコピー&ペーストすれば、Cognito IDプールが発行した一時クレデンシャルキーを利用できます。

実際にまた別のシェルでこの一時クレデンシャルキーを使用して、 aws sts get-caller-identity コマンドを実行してみます。 そうするとこの一時クレデンシャルキーが、Cognito IDプールで設定した認証されていないユーザー用のIAM RoleからAssumeRoleされた権限であることがわかります。

$ aws sts get-caller-identity
{
    "UserId": "XXXXXXXXXXXXXXXXXXXXX:CognitoIdentityCredentials",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/Cognito_identity_pools_testUnauth_Role/CognitoIdentityCredentials"
}

認証機能をCognitoユーザープールに任せる

ここまで、まずは認証されていないユーザーの一時クレデンシャルキーを発行してみました。

次に認証されたユーザーの一時クレデンシャルキーを発行してみたいのですが、 前述の通りCognito IDプール自体は認証機能を持っていません。 今回は認証部分をCogntioユーザープールに任せます。

Cognitoユーザープールの構築については、以前書いたブログをご参照ください。このとき構築したCognitoユーザープールを流用します。

Cognito IDプールとCognitoユーザープールの関連付けは、Cognito IDプールの編集画面からできます。

編集画面の認証プロバイダーからCognitoを選んで、ユーザープールIDアプリクライアントIDを入力して変更を保存してください。

ちなみに、ユーザープールIDとアプリクライアントIDはCognitoユーザープールのこの画面で確認できます。

これで認証機能をCognitoユーザープールに委任できました。 続けて認証されたユーザーの一時クレデンシャルキーを発行してみます。

認証されたユーザーの一時クレデンシャルキーを発行してみる

認証されたユーザーの一時クレデンシャルキーの発行フローはこんな感じです。

先ほどの認証されていないユーザーのフローと異なり、Cognitoユーザープールでログイン(IDトークンを取得)する処理が増えています。

Cognito IDプールの認証フローは公式ドキュメントにも記載されています。詳細はこちらを御覧ください。

Cognitoユーザープールのログイン処理は、AWS CLIの aws cognito-idp admin-iniate-auth コマンドで実施できます。

また、get-id, get-credentials-for-identity をする際にIDトークンを渡すことで、Cognito IDプールがどのユーザーからのリクエストなのかを判別できるようにしています。

aws cognito-identity get-id コマンドの --logins パラメーターの書式(IDトークンの渡し方)は、APIリファレンスを参考にしています。

$ REGION=us-west-2
$ USER_POOL_ID=us-west-2_xxxxxxxxx
$ CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
$ USER_EMAIL=email@example.com
$ PASSWORD="Password01@"
$ IDENTITY_POOL_ID=us-west-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
$ COGNITO_USER_POOL=cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}
$ ID_TOKEN=$(aws cognito-idp admin-initiate-auth \
  --user-pool-id ${USER_POOL_ID} \
  --client-id ${CLIENT_ID} \
  --auth-flow ADMIN_NO_SRP_AUTH \
  --auth-parameters "USERNAME=${USER_EMAIL},PASSWORD=${PASSWORD}" \
  --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}

同じ様に取得できたAWS一時クレデンシャルキーを利用して、aws sts get-caller-identity コマンドを実行してみます。 そうするとこの一時クレデンシャルキーが、Cognito IDプールで設定した認証されたユーザー用のIAM RoleからAssumeRoleされた権限であることがわかります。

$ aws sts get-caller-identity
{
    "UserId": "XXXXXXXXXXXXXXXXXXXXX:CognitoIdentityCredentials",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/Cognito_identity_pools_testAuth_Role/CognitoIdentityCredentials"
}

Cognito IDプールにルールベースでIAM Roleを割り当てる設定をする

次にルールベースでIAM Roleを選択して、AWSの一時クレデンシャルキーを発行する方法を試していきます。 そのための設定をします。

まずは、Cognito IDプールで利用可能なIAM Roleをマネジメントコンソールで作ります。 信頼されたエンティティの種類としてウェブIDを選択し、IDプロバイダーにAmazon Cognitoを選択し、Identity Pool IDを入力して次へ進みます。

付与したい権限ポリシーを選択します。何でもよいので今回はAmazonS3ReadOnlyAccessを付与しておきます。

タグは必要ないので次へ進みます。

ロール名を適当につけます。今回はCognitoS3ReadOnlyRoleという名前にします。

IAM Roleが作成できたら、Cognito IDプールでIAM Roleを付与するルールを作ります。 このルールはCognito IDプールの編集画面から作成できます。

編集画面の認証プロバイダーのCognitoの認証されたロールの選択部分で、ルールを使用してロールを選択するを選ぶと、ルールベースでユーザーにIAM Roleを付与できます。

今回はemail@classmethod.jpが含まれていたら、CognitoS3ReadOnlyRoleを付与するように設定してみます。

このルールはemailの値だけではなく、ユーザークレーム(情報)を参照して柔軟に設定することが可能です。その他、利用可能なクレームについては公式ドキュメントをご参照ください。

これで設定は完了です。

ルールベースで一時クレデンシャルキーを発行してみる

ルールベースでIAM Roleを選択して、AWSの一時クレデンシャルキーを発行する方法を試してみます。

認証されたユーザーの一時クレデンシャルキーを発行した時と発行フローはほぼ変わりません。

実際にログインするユーザーには、設定したルールとあわせてemailに@classmethod.jpが含まれているものを使います。

AWS CLIで同様に一時クレデンシャルキーを取得してみます。

$ REGION=us-west-2
$ USER_POOL_ID=us-west-2_xxxxxxxxx
$ CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
$ USER_EMAIL=email@example.com
$ PASSWORD="Password01@"
$ IDENTITY_POOL_ID=us-west-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
$ COGNITO_USER_POOL=cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}
$ ID_TOKEN=$(aws cognito-idp admin-initiate-auth \
  --user-pool-id ${USER_POOL_ID} \
  --client-id ${CLIENT_ID} \
  --auth-flow ADMIN_NO_SRP_AUTH \
  --auth-parameters "USERNAME=${USER_EMAIL},PASSWORD=${PASSWORD}" \
  --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}

同様に取得できたAWS一時クレデンシャルキーを利用して、aws sts get-caller-identity コマンドを実行してみます。 そうするとこの一時クレデンシャルキーが、Cognito IDプールで設定したCognitoS3ReadOnlyRoleからAssumeRoleされた権限であることがわかります。

$ aws sts get-caller-identity
{
    "UserId": "XXXXXXXXXXXXXXXXXXXXX:CognitoIdentityCredentials",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/CognitoS3ReadOnlyRole/CognitoIdentityCredentials"
}

Cognito IDプールにCognitoユーザープールで設定したIAM Roleを割り当てる設定をする

最後にCognitoユーザープールで設定したIAM Roleを利用して、AWSの一時クレデンシャルキーを発行する方法を試していきます。 そのための設定をします。

まずは、Cognitoユーザープールで利用可能なIAM Roleをマネジメントコンソールで先ほどと同じ様に作ります。 今度はCognitoGroupS3ReadOnlyRoleというロール名にします。

IAM Roleが作成できたら、今度はCognitoユーザープールでユーザーへIAM Roleを付与するためにグループを作ります。

マネジメントコンソールのCognitoユーザープールの画面ユーザーとグループを選択し、グループを選択し、グループの作成をします。

今回は名前をS3ReadGroupにし、IAMロールにCognitoGroupS3ReadOnlyRoleを設定します。

グループが作成できたら、ログイン予定のユーザーをそのグループに所属させます。 今作ったグループをクリックします。

ユーザーを追加するをクリックします。

追加したいユーザーを選択します。

ユーザーを確認してみて、グループに所属できていればOKです。

次にCognito IDプールがCognitoユーザープールで設定したIAM Roleを割り当てるように設定します。

これはCognito IDプールの編集画面の認証プロバイダーのCognitoの認証されたロールの選択部分で、トークンからロールを選択するを選ぶことで設定できます。

トークンを使用したユーザーへのIAM Roleの割り当てについて詳しく知りたい方は、公式ドキュメントをご参照ください。

これで設定は完了です。

Cognitoユーザープールのユーザーに設定したIAM Roleで一時クレデンシャルキーを発行してみる

最後にCognitoユーザープールでユーザーに設定したIAM Roleを利用して、AWSの一時クレデンシャルキーを発行する方法を試してみます。

他のものと発行フローはほぼ変わりませんが、CognitoユーザープールはIDトークンに対してIAM Roleの情報を付与しており、それをCognito IDプールが参照しているようなイメージです。

実際にIAM Roleの情報がIDトークンへ付与されていることを確認してみます。

AWS CLIでIDトークンの取得までやってみます。

$ USER_POOL_ID=us-west-2_xxxxxxxxx
$ CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
$ USER_EMAIL=email@example.com
$ PASSWORD="Password01@"
$ ID_TOKEN=$(aws cognito-idp admin-initiate-auth \
  --user-pool-id ${USER_POOL_ID} \
  --client-id ${CLIENT_ID} \
  --auth-flow ADMIN_NO_SRP_AUTH \
  --auth-parameters "USERNAME=${USER_EMAIL},PASSWORD=${PASSWORD}" \
  --query "AuthenticationResult.IdToken" \
  --output text) && echo ${ID_TOKEN}

このIDトークンに付与されている認証されたユーザーのクレーム(情報)を次のコマンドを実行して見てみます。

$ echo ${ID_TOKEN} | cut -d'.' -f 2 | base64 -D

そうすると、たしかにIDトークンにはIAM Roleの情報が含まれていることがわかります。

IDトークンの中身の見方について詳しく知りたい方は、以前書いたブログで解説しています。こちらをご覧ください。

IDトークンにIAM Roleの情報が付与されていることはわかったので、AWS CLIで同様に一時クレデンシャルキーを取得してみます。

$ REGION=us-west-2
$ USER_POOL_ID=us-west-2_xxxxxxxxx
$ CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
$ USER_EMAIL=email@example.com
$ PASSWORD="Password01@"
$ IDENTITY_POOL_ID=us-west-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
$ COGNITO_USER_POOL=cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}
$ ID_TOKEN=$(aws cognito-idp admin-initiate-auth \
  --user-pool-id ${USER_POOL_ID} \
  --client-id ${CLIENT_ID} \
  --auth-flow ADMIN_NO_SRP_AUTH \
  --auth-parameters "USERNAME=${USER_EMAIL},PASSWORD=${PASSWORD}" \
  --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}

同様に取得できたAWS一時クレデンシャルキーを利用して、aws sts get-caller-identity コマンドを実行してみます。 そうするとこの一時クレデンシャルキーが、Cognitoユーザープールでグループに設定したCognitoGroupS3ReadOnlyRoleからAssumeRoleされた権限であることがわかります。

$ aws sts get-caller-identity
{
    "UserId": "XXXXXXXXXXXXXXXXXXXXX:CognitoIdentityCredentials",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/CognitoGroupS3ReadOnlyRole/CognitoIdentityCredentials"
}

終わりに

「Cognito IDプールがAWSリソースへのアクセスを制御する認可部分を担当しているらしいけど、いったいどういう理屈でそうなるんだ…?」という自分の疑問から、AWSのドキュメントを読み、実際に手を動かして得られた理解を再現できる形でまとめてみました。

本ブログでは説明のためにログインやIDトークンの取り回しをAWS CLIで行いましたが、AWS Amplifyのライブラリを使えばいい感じにやってくれるので、実際にCognitoを利用する際にはぜひこちらを活用してください。

CognitoユーザープールとIDプールはおもしろいサービスだと思うのですが、反面とっつきにくくて理解しにくいところもあるとも思います。 このブログを通して少しでも理解を深めていただければ幸いです。