こんにちは、DX推進室の安齋です。
今回はAWSサーバーレスリソース構築でハマった・工夫した箇所の紹介の第3回目となります。
第3回はCognitoユーザープールでグループを作り、グループ毎にAWSリソースへの制御を行う方法を紹介します。
CognitoユーザープールのユーザーごとにAWSリソースへの制御を行う場合、良く検索結果に出てくる方法は、ユーザー属性を使用する方法であり、CloudFormationドキュメントも同様でした。そのため、グループ毎に制御を行いたい人がいた時のために書いておこうと思います。
今回のテーマ
上記構成図の赤枠内のCognitoユーザープールにおいて、一般ユーザーと管理者ユーザーをグループに所属させ、管理者用API Gatewayに対して、管理者グループのみがアクセス出来るようにします。
なぜユーザー属性ではなくグループを使ったのか
Cognitoユーザープールにユーザーを登録する際、標準属性とカスタム属性を付与することができます。標準属性はOpenID Connectに沿った項目で、主な項目は email
, phone
, address
などです。属性の主な用途は、ログイン時のエイリアス、ちょっとしたDBの代わりを担うもの、属性に応じた権限管理などです。今回は、OpenID Connectに沿う必要もなく、複雑な属性も付与しないため、分かりやすくユーザーをグループに所属させ、グループに応じた権限管理を構築することにしました。ユーザーのグループ変更はGUIから可能なので、Cognito学習コストも低くて済むことも採用の理由でした。
参考資料
- ユーザープールの属性の設定
- https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/user-pool-settings-attributes.html
ステップ1:Cognitoユーザープールを作る
Cognitoユーザープールを作ります。こちらは特にハマった箇所はなく、公式ドキュメントの例を参考に作ることができました。今回はユーザー登録をIAMポリシーのAdministrator権限等を持つIAMエンティティのみが出来るようにしています。
以下、CloudFormationの記述です。
Resources:
#===============================
# UserPool
#===============================
UserPool:
Type: AWS::Cognito::UserPool
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
Properties:
AccountRecoverySetting:
RecoveryMechanisms:
- Name: verified_email
Priority: 1
AdminCreateUserConfig:
AllowAdminCreateUserOnly: true
AutoVerifiedAttributes:
- email
EmailConfiguration:
EmailSendingAccount: COGNITO_DEFAULT
MfaConfiguration: 'OFF'
Policies:
PasswordPolicy:
MinimumLength: 20
RequireLowercase: true
RequireNumbers: true
RequireSymbols: true
RequireUppercase: true
TemporaryPasswordValidityDays: 7
UsernameAttributes:
- email
UsernameConfiguration:
CaseSensitive: true
UserPoolName: !Sub ${ServiceName}-${Env}-user-pool
ステップ:Cognitoアプリクライアントを作る
Cognito認証前ユーザーがログインやパスワード変更をできるように、Cognitoアプリクライアントを作ります。こちらも特にハマった箇所はなく、公式ドキュメントの例を参考に作ることができました。今回はユーザー登録を特定のIAMエンティティが行うため、初回ログイン時のパスワード変更も同エンティティが行えるように ExplicitAuthFlows
に ALLOW_ADMIN_USER_PASSWORD_AUTH
も設定しています。
アプリクライアントとは?
アプリは、認証されていない API 操作 (認証されたユーザーがない操作) を呼び出すための許可を持つユーザープール内のエンティティです。この例には、登録、サインイン、忘れたパスワードの処理などの操作があります。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/user-pool-settings-client-apps.html
以下、CloudFormationの記述です。
#===============================
# UserPool Client
#===============================
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
UpdateReplacePolicy: Retain
DependsOn: UserPool
Properties:
AccessTokenValidity: 60
ClientName: !Sub ${ServiceName}-${Env}-app-pool-client
EnableTokenRevocation: false
ExplicitAuthFlows:
- ALLOW_ADMIN_USER_PASSWORD_AUTH
- ALLOW_CUSTOM_AUTH
- ALLOW_USER_SRP_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
GenerateSecret: false
IdTokenValidity: 60
PreventUserExistenceErrors: ENABLED
RefreshTokenValidity: 1
SupportedIdentityProviders:
- COGNITO
TokenValidityUnits:
AccessToken: minutes
IdToken: minutes
RefreshToken: days
UserPoolId: !Ref UserPool
ステップ3:Cognito IDプールを作る
AWSリソース制御を行うため、Cognito IDプールを作ります。こちらも特にハマった箇所はなく、公式ドキュメントの例を参考に作ることができました。
以下、CloudFormationの記述です。
#===============================
# IdentityPool
#===============================
IdentityPool:
Type: AWS::Cognito::IdentityPool
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
DependsOn:
- UserPoolClient
- UserPool
Properties:
AllowClassicFlow: false
AllowUnauthenticatedIdentities: false
CognitoIdentityProviders:
- ClientId: !Ref UserPoolClient
ProviderName: !GetAtt UserPool.ProviderName
IdentityPoolName: !Sub ${ServiceName}-${Env}-identity-pool
ステップ4:Cognitoユーザープールのグループを作る
一般グループと管理者グループを作ります。登録したユーザーをグループに所属させることは、柔軟で簡易な運用のため、GUIで行っています。CloudFormationでユーザーの所属グループを管理する場合、ユーザーの情報がローカルPCに残る可能性があることも懸念としてありました。こちらも特にハマった箇所はなく、公式ドキュメントの例を参考に作ることができました。
以下、CloudFormationの記述です。
#===============================
# Admin User Pool Group
#===============================
AdminUserPoolGroup:
Type: AWS::Cognito::UserPoolGroup
Properties:
GroupName: Admin
Precedence: 1
RoleArn: !GetAtt CognitoAuthenticatedAdminRole.Arn
UserPoolId: !Ref UserPool
#===============================
# General User Pool Group
#===============================
GeneralUserPoolGroup:
Type: AWS::Cognito::UserPoolGroup
Properties:
GroupName: General
Precedence: 2
RoleArn: !GetAtt CognitoAuthenticatedGeneralRole.Arn
UserPoolId: !Ref UserPool
ステップ5:グループ用のIAMロールを作る
一般グループと管理者グループそれぞれのIAMロールを作り、管理者用API Gatewayに管理者グループのみがアクセスできるようにしました。管理者グループのIAMロールは第1回に掲載済みなので割愛します。
以下、CloudFormationの記述です。
#===============================
# Cognito Authenticated General IAM Role
#===============================
CognitoAuthenticatedGeneralRole:
Type: AWS::IAM::Role
UpdateReplacePolicy: Retain
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action: sts:AssumeRoleWithWebIdentity
Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Condition:
StringEquals:
cognito-identity.amazonaws.com:aud: !Ref IdentityPool
ForAnyValue:StringLike:
cognito-identity.amazonaws.com:amr: authenticated
RoleName: !Sub ${ServiceName}-${Env}-cognito-authenticated-general-role
Policies:
- PolicyName: !Sub ${ServiceName}-${Env}-cognito-authenticated-general-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: Cognito
Effect: Allow
Action:
- cognito-identity:GetCredentialsForIdentity
- cognito-identity:GetId
Resource: "*"
これでイケると思った!
一般ユーザーと管理者ユーザーそれぞれでCognitoユーザープールにログインしたところ、同じエラーがCognito IDプールから返ってきており、認証後のIAMロールが設定されていないことが判明しました。IAMロールで cognito-identity.amazonaws.com:amr: authenticated
を設定しているので、ユーザーが所属しているグループのIAMロールが割り当てられていると思ったのですが、明示的にCognito IDプールに認証後のIAMロールを割り当てる必要があることが判明しました。
{"__type":"InvalidIdentityPoolConfigurationException","message":"Invalid identity pool configuration. Check assigned IAM roles for this pool."}
ステップ6:Cognito IDプールにIAMロールをアタッチする
Cognitoユーザープール認証前にはIAMロールをアタッチせず、認証後にはデフォルトのIAMロールとして一般グループのIAMロールをアタッチすることにしました。問題はここからで、IAMロールのマッピングを定義する方法が分かりませんでした。というのも、CloudFormationドキュメントには、ユーザー属性を元にしたマッピングルールの例はあっても、IAMロールを元にした例はなく、調査をしてもヒットしなかったのです。Cognito公式ドキュメントを読み漁り、ようやく参考になる箇所を見つけました。グループに応じたIAMロールをアタッチする場合、Type: Token
を指定します。
Amazon Cognito ユーザープールのグループにロールを設定する場合、これらのロールはユーザーの ID トークンを通じて渡されます。これらのロールを使用するには、ID プールの認証ロールの選択に対して [トークンからロールを選択する] を設定する必要があります
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/role-based-access-control.html#best-practices-for-role-based-access-control
以下、CloudFormationの記述です。今回のポイントとなる箇所に ★★★ポイント★★★
を付けています。
#===============================
# IdentityPool Role Attachement
#===============================
RoleAttachment:
Type: AWS::Cognito::IdentityPoolRoleAttachment
DependsOn:
- IdentityPool
- UserPool
- UserPoolClient
Properties:
IdentityPoolId: !Ref IdentityPool
Roles:
authenticated: !GetAtt CognitoAuthenticatedGeneralRole.Arn
RoleMappings:
AdminRole:
AmbiguousRoleResolution: AuthenticatedRole
IdentityProvider: !Sub cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}:${UserPoolClient} # ★★★ポイント★★★
Type: Token # ★★★ポイント★★★
参考資料
- AWS::Cognito::IdentityPoolRoleAttachment RoleMapping
- https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cognito-identitypoolroleattachment-rolemapping.html
- AWSサーバーレスで認証認可を最短・最速で実装!サーバーレスエキスパートが、CognitoによるAWSの認可実装を解説します
- https://www.ragate.co.jp/blog/articles/6254
出来上がり!!
無事にCognitoユーザープールのグループ別にAWSリソース制御をすることが出来ました。
今回、一般ユーザーが権限のない管理者用APIを呼び出すと、以下のエラーがAWSから返ってきます。
{"message":"User: arn:aws:sts::<アカウントID>:assumed-role/tech4all-stg-cognito-authenticated-general-role/CognitoIdentityCredentials is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:ap-northeast-1:<アカウントID>:<管理者用API Gateway ID>/api/GET/tech4all"}