Cognitoユーザープールでグループ毎にリソース制御を行う

Pocket

こんにちは、DX推進室の安齋です。

今回はAWSサーバーレスリソース構築でハマった・工夫した箇所の紹介の第3回目となります。

第3回はCognitoユーザープールでグループを作り、グループ毎にAWSリソースへの制御を行う方法を紹介します。

CognitoユーザープールのユーザーごとにAWSリソースへの制御を行う場合、良く検索結果に出てくる方法は、ユーザー属性を使用する方法であり、CloudFormationドキュメントも同様でした。そのため、グループ毎に制御を行いたい人がいた時のために書いておこうと思います。

今回のテーマ

serverless-3-1

上記構成図の赤枠内の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エンティティが行うため、初回ログイン時のパスワード変更も同エンティティが行えるように ExplicitAuthFlowsALLOW_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"}
Pocket

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です