こんにちは、広野です。
Storage Browser for Amazon S3 の記事が意図せずしてシリーズ化されてきました。使い始めると、いろいろとやりたいことができてしまい。紹介する内容はタイトルの通りなので省略します。内容が多いので、一旦前編としてアーキテクチャと Amazon Cognito 周りの実装を紹介します。後編は React アプリ側の実装です。
背景
Storage Browser for Amazon S3 は、皆さんが AWS マネジメントコンソールでバケットやオブジェクトを操作しているようなおなじみの UI を、自分が開発したアプリに組み込める画期的な UI モジュールです。
なんですが、基本的に、アクセス可能なバケットやフォルダは「固定」です。アプリ内で所定の設定を記述するのですが、ユーザーの属性によって動的にアクセス可能なバケットやフォルダを変えようとしたらどうすればいいのだろう?というのが起点でした。実際、そんなニーズが社内開発案件でありました。
実装したいと考えた仕様は、Amazon Cognito のユーザーをグループに所属させ、グループごとにアクセス可能なバケットを動的に変更したい、です。
しかし、公式ドキュメントを読んでも直接的に参考となるドキュメントはありませんでした。IAM Identity Center と S3 Access Grants を使用した構成の記述はありますが、そこまで大がかりにしたくありません。Amazon Cognito による制御にとどめたいです。また、ドキュメントにある Customer managed auth というカスタマイズ例を活かしてプログラマティックにアクセス可能なバケットを変えられると期待したのですが、どう書いてもうまくいきませんでした。(d.items is not iterable というエラーが出るだけ)
将来的には公式のコードサンプル提供や有志の方の検証により、よりスマートな方法が確立されると思いますが、今時点で私が実現できた方法を紹介します。
仕組みの概要
まず、ベースとなる AWS リソースのアーキテクチャは以下です。
- Amazon Cognito ユーザーは、グループに所属させる。本記事では、1ユーザーにつき所属するグループは1つのみとする。
- Amazon Cognito グループに、グループごとにアクセスを許可するリソースを設定した IAM ロールを関連付ける。
- それにより、アプリで Amazon Cognito の認証を受けたユーザーは、所属する Cognito グループの IAM ロールを割り当てられる。
- IAM ロールには、そのグループにアクセスを許可する Amazon S3 バケットへのアクセス権限を記述する。
- アプリ側では、Amazon Cognito の認証を済ませることにより属性情報の1つとして所属する Cognito グループ名を取得できる。(後編記事で紹介)
- Cognito グループ名から、ビジネスロジックに応じて Storage Browser for Amazon S3 でアクセスさせる S3 バケットを動的に設定するアプリコードを書く。(後編記事で紹介)
Amazon Cognito 側の実装
Amazon Cognito グループによって Cognito ユーザーに割り当てる IAM ロールを設定するには、Amazon Cognito ユーザープールに関連付けた Amazon Cognito ID プールが必要です。Cognito グループの設定はユーザープール内で設定するのですが、ID プールが存在することが前提で機能するので、注意しましょう。
設定方法は、AWS 公式ドキュメント通りです。
開発側の注意点として、Cognito グループごとに IAM ロールを作成しなければならないことです。理想は Cognito グループを変数として IAM ロールに記述できればよいのですが、散々調べましたがそのような機能は残念ながらありません。Cognito ユーザを変数にすることはできますが、今回の要件ではないので使用できません。
今回の要件では 1 ユーザーが所属するグループは 1 つのみとしていますが、実案件では複数のグループに所属することは多々あります。そのときに適用される IAM ロールはグループの優先順位の数値に従って決められたグループの IAM ロールになりますので、優先順位の設定に注意が必要です。
ここまでは Cognito ユーザープール側の設定です。
Cognito グループに IAM ロールを関連付けるときの設定として、Cognito ID プール側の設定があります。
ID プールでは、Cognito ユーザーに割り当てる IAM ロールとして標準的に 2 つの IAM ロールを設定します。認証されたロールとゲストロールです。下の画面のように作成、関連付けをしておく必要がありますが、今回の要件では Cognito グループに IAM ロールを関連付けるため、実際のところ使用する予定はないのですが、仕様的に必要になります。
さて、この使用しないと言った 2 つのロールですが、何もしなければこのロールが Cognito ユーザーに割り当てられてしまいます。Cognito グループに関連付けた IAM ロールを割り当てて欲しいので、そのような設定を明示的にする必要があります。
Cognito ID プールの設定画面で以下の設定を見つけます。ここで、トークンで preferred_role クレームを持つロールを選択する を必ず選択します。
preferred_role は何かというと、Amazon Cognito ユーザーの属性で、そのユーザーに割り当てる IAM ロールを個別に設定するものです。なので Cognito ユーザー単位に設定が可能なのですが、Cognito グループに所属し、かつ Cognito グループに IAM ロールが関連付けられている場合はその IAM ロールが preferred_role として設定されます。つまり、Cognito グループに関連付けられた IAM ロールを使用するにはこの設定を選択する必要があるということです。
そして、ロールの解決の設定は決めなのですが、もしユーザーが Cognito グループに所属していなかったらどうするか?の判断を設定することを意味しています。前述した、認証されたロールを使用させるか、IAM ロールを割り当てないか、です。プログラミングで言う、どの条件にも該当しないときにはこうする、的な処置だと思って下さい。
これら Cognito ID プールから割り当てる IAM ロールの記述は、共通して以下の AWS 公式ドキュメント通りに書きます。
具体的な設定は、次章の AWS CloudFormation テンプレートから読み取って頂けたらと思います。少々解説は入れます。
参考: AWS CloudFormation テンプレート
解説をインラインでコメントしておきます。
AWSTemplateFormatVersion: 2010-09-09
Description: The CloudFormation template that creates a Cognito user pool and a Cognito ID pool.
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
SubName:
Type: String
Description: System sub name of example. (e.g. prod or test)
Default: test
MaxLength: 10
MinLength: 1
DomainName:
Type: String
Description: Domain name for URL. xxxxx.xxx
Default: sampledomain.com
MaxLength: 40
MinLength: 5
SubDomainName:
Type: String
Description: Sub domain name for URL.
Default: subdomain
MaxLength: 20
MinLength: 1
SesId:
Type: String
Description: Amazon SES ID for sending emails. (email addreess or domain)
Default: sampledomain.com
MaxLength: 100
MinLength: 5
SesConfigurationSet:
Type: String
Description: Amazon SES configuration set for sending emails.
MaxLength: 100
MinLength: 5
CognitoAdminAlias:
Type: String
Description: The alias name of Cognito Admin email address. (e.g. Admin)
Default: Admin
MaxLength: 100
MinLength: 5
CognitoReplyTo:
Type: String
Description: Cognito Reply-to email address. (e.g. xxx@xxx.xx)
MaxLength: 100
MinLength: 5
CognitoEmailFrom:
Type: String
Description: Cognito e-mail from address. (e.g. xxx@xxx.xx)
MaxLength: 100
MinLength: 5
Resources:
# ------------------------------------------------------------#
# Cognito IdP Roles (IAM)
# ------------------------------------------------------------#
# 以下の IAM ロールは使用する想定のない「認証されたロール」なので、このサンプルでは設定が適当です。すみません。
CognitoIdPAuthRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub example-CognitoIdPAuthRole-${SubName}
Description: This role allows Cognito authenticated users to access AWS resources.
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Action:
"sts:AssumeRoleWithWebIdentity"
Condition:
StringEquals:
"cognito-identity.amazonaws.com:aud": !Ref IdPool
"ForAnyValue:StringLike":
"cognito-identity.amazonaws.com:amr": authenticated
Policies:
- PolicyName: !Sub example-CognitoIdPAuthRolePolicy-${SubName}
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "lambda:InvokeFunctionUrl"
Resource:
- Fn::ImportValue:
!Sub example-${SubName}-LambdaBedrockArn
- Fn::ImportValue:
!Sub example-${SubName}-LambdaBedrockAgentArn
Condition:
StringEquals:
"lambda:FunctionUrlAuthType": AWS_IAM
# 以下の IAM ロールは使用する想定のない「ゲストロール」なので、このサンプルでは設定が適当です。すみません。
CognitoIdPUnauthRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub example-CognitoIdPUnauthRole-${SubName}
Description: This role allows Cognito unauthenticated users to access AWS resources.
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Action:
"sts:AssumeRoleWithWebIdentity"
Condition:
StringEquals:
"cognito-identity.amazonaws.com:aud": !Ref IdPool
"ForAnyValue:StringLike":
"cognito-identity.amazonaws.com:amr": unauthenticated
Policies:
- PolicyName: !Sub example-CognitoIdPUnauthRolePolicy-${SubName}
PolicyDocument:
Version: "2012-10-17"
Statement:
Action:
- "s3:ListBucket"
Resource:
- !Sub "arn:aws:s3:::example-${SubName}-amplifystorage"
Effect: Allow
# 以下の IAM ロールが Cognito グループに割り当てるものなので意味があります。特定の S3 バケットアクセスを許可しています。
CognitoGroupBasicRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub example-CognitoGroupBasicRole-${SubName}
Description: This role allows Cognito authenticated users that belong to BASIC group to access AWS resources.
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Action:
"sts:AssumeRoleWithWebIdentity"
Condition:
StringEquals:
"cognito-identity.amazonaws.com:aud": !Ref IdPool
"ForAnyValue:StringLike":
"cognito-identity.amazonaws.com:amr": authenticated
Policies:
- PolicyName: !Sub example-CognitoGroupBasicPolicy-${SubName}
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- "s3:ListBucket"
Resource:
- !Sub "arn:aws:s3:::example-${SubName}-kbdatasource"
Effect: Allow
- Action:
- "s3:DeleteObject"
- "s3:PutObject"
Resource:
- !Sub "arn:aws:s3:::example-${SubName}-kbdatasource/*"
Effect: Allow
# ------------------------------------------------------------#
# Cognito
# ------------------------------------------------------------#
# Cognito ユーザープールについては、特定の仕様で作成しています。ここでは言及しません。
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Sub example-${SubName}
MfaConfiguration: "ON"
EnabledMfas:
- SOFTWARE_TOKEN_MFA
Policies:
PasswordPolicy:
MinimumLength: 8
RequireUppercase: true
RequireLowercase: true
RequireNumbers: true
RequireSymbols: false
TemporaryPasswordValidityDays: 180
AccountRecoverySetting:
RecoveryMechanisms:
- Name: verified_email
Priority: 1
AdminCreateUserConfig:
AllowAdminCreateUserOnly: false
AutoVerifiedAttributes:
- email
DeviceConfiguration:
ChallengeRequiredOnNewDevice: false
DeviceOnlyRememberedOnUserPrompt: false
EmailConfiguration:
ConfigurationSet: !Ref SesConfigurationSet
EmailSendingAccount: DEVELOPER
From: !Sub "${CognitoAdminAlias} <${CognitoEmailFrom}>"
ReplyToEmailAddress: !Ref CognitoReplyTo
SourceArn: !Sub arn:aws:ses:${AWS::Region}:${AWS::AccountId}:identity/${SesId}
EmailVerificationMessage: !Sub "example-${SubName} Verification code: {####}"
EmailVerificationSubject: !Sub "example-${SubName} Verification code"
UsernameAttributes:
- email
UsernameConfiguration:
CaseSensitive: false
UserPoolAddOns:
AdvancedSecurityMode: "OFF"
UserPoolTags:
Cost: !Sub example-${SubName}
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref UserPool
ClientName: !Sub example-${SubName}-appclient
GenerateSecret: false
RefreshTokenValidity: 3
AccessTokenValidity: 6
IdTokenValidity: 6
ExplicitAuthFlows:
- ALLOW_USER_SRP_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
PreventUserExistenceErrors: ENABLED
SupportedIdentityProviders:
- COGNITO
CallbackURLs:
- !Sub https://${SubDomainName}.${DomainName}/index.html
LogoutURLs:
- !Sub https://${SubDomainName}.${DomainName}/index.html
DefaultRedirectURI: !Sub https://${SubDomainName}.${DomainName}/index.html
AllowedOAuthFlows:
- implicit
AllowedOAuthFlowsUserPoolClient: true
AllowedOAuthScopes:
- email
- openid
# ここで、BASIC という名前の Cognito ユーザーグループと、割り当てる IAM ロールを指定しています。
UserPoolGroupBasic:
Type: AWS::Cognito::UserPoolGroup
Properties:
Description: example User Group which allows users able to access basic contents.
GroupName: BASIC
Precedence: 101
UserPoolId: !Ref UserPool
RoleArn: !GetAtt CognitoGroupBasicRole.Arn
UserPoolGroupAdmin:
Type: AWS::Cognito::UserPoolGroup
Properties:
Description: example User Group which allows users able to access management tools.
GroupName: ADMIN
Precedence: 1
UserPoolId: !Ref UserPool
# Cognito ID プールの設定です。
IdPool:
Type: AWS::Cognito::IdentityPool
Properties:
IdentityPoolName: !Sub example-${SubName}
# ここで、クラシックフローを無効にする設定を入れています。有効になっていると、IAM ロールの割り当てが利かないかもしれません。
AllowClassicFlow: false
AllowUnauthenticatedIdentities: false
CognitoIdentityProviders:
- ClientId: !Ref UserPoolClient
ProviderName: !GetAtt UserPool.ProviderName
ServerSideTokenCheck: true
IdentityPoolTags:
- Key: Cost
Value: !Sub example-${SubName}
IdPoolRoleAttachment:
Type: AWS::Cognito::IdentityPoolRoleAttachment
Properties:
IdentityPoolId: !Ref IdPool
Roles:
authenticated: !GetAtt CognitoIdPAuthRole.Arn
unauthenticated: !GetAtt CognitoIdPUnauthRole.Arn
# ここで、Cognito グループに応じて IAM ロールを割り当てるという設定を入れています。
RoleMappings:
# CloudFormation で設定するときには、以下の userpool: という項目を任意の名前でいいので差し込まないとエラーになります。
# 設定を複数持てるので、以下の単位で追加できます。IDプロバイダーを Cognito 以外にソーシャルプロバイダーも指定できます。
userpool:
IdentityProvider: !Sub cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}:${UserPoolClient}
Type: Token
AmbiguousRoleResolution: AuthenticatedRole
以上で、前編の Amazon Cognito の実装を終了します。後編では React のコード実装例を紹介します。
まとめ
いかがでしたでしょうか。
本記事では、Storage Browser for Amazon S3 の利用で必要となる Amazon Cognito ID プールの実装を中心に説明しました。Storage Browser 特有の内容ではないので、他の要件での参考にもなると思います。
本記事が皆様のお役に立てれば幸いです。
関連記事



