Cognito認証によるDynamoDB FGAC #アドカレ2015
丹内です。
昨日はGoogleアカウントでCognito認証を行いました。
本日はその応用として、DynamoDBのアクセス制御をご紹介します。
前提
AndroidアプリでWebアイデンティティフェデレーションを使ったCognito認証ができていること
DynamoDB FGACとは
IAMポリシー変数でCondition節を記述したIAM PolicyをCognito認証して引き受けるAssume Roleにアタッチすることで、Cognito Identity Pool或いはCognito Identity IDごとなど様々な権限に応じて操作可能なDynamoDBの項目を細かく設定できる機能です。
以下に手順を示します。
DynamoDBテーブルを作成する
今回は例として、以下の様なテーブルを作成しました。個別の項目には、特に意味はありません。
- Table Name: projects
- Hash Key: name(String)
- Range Key: member(String)
- Attribute: description(String)
FGAC用IAMポリシーを作成し、Cognito Auth Roleにアタッチする
以下のポリシーを作成し、Cognito認証に成功した際にアクセス可能なポリシーを引き受けられるようにアタッチします。
この例では、ユーザは自分のCognito Identity IDと同じHash KeyしかQueryできません。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "dynamodb:Query" ], "Resource": [ "arn:aws:dynamodb:ap-northeast-1:<Account ID>:table/projects", "arn:aws:dynamodb:ap-northeast-1:<Account ID>:table/projects/index/*" ], "Condition": { "ForAllValues:StringEquals": { "dynamodb:LeadingKeys": [ "${cognito-identity.amazonaws.com:sub}" ] } } } ] }
cognito-identity.amazonaws.com:sub
でIdentity ID単位、cognito-identity.amazonaws.com:aud
でIdentity Pool単位で設定できます。
マッピングクラスを実装する
ここからはAndroidアプリです。まずはマッピングクラスを作成します。
@DynamoDBTable(tableName = "projects") public class Project { private String name; private String member; private String description; @DynamoDBHashKey(attributeName = "name") public String getName() { return name; } public void setName(String name) { this.name = name; } @DynamoDBRangeKey(attributeName = "member") public String getMember() { return member; } public void setMember(String member) { this.member = member; } @DynamoDBAttribute(attributeName = "description") public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
DynamoDBの処理を実装する
昨日のエントリに加えて、DynamoDBにQueryを実施する処理を追加します。
private void handleSignInResult(GoogleSignInResult result) { Log.d(TAG, "handleSignInResult:" + result.isSuccess()); if (result.isSuccess()) { GoogleSignInAccount account = result.getSignInAccount(); Log.d(TAG, "IdToken: " + account.getIdToken()); googleAuthenticationStream(account) .flatMap(this::cognitoAuthenticationStream) .flatMap(this::ddbProjectStream) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( ddbResult -> Toast.makeText(getActivity(), ddbResult.toString(), Toast.LENGTH_SHORT).show(), Throwable::printStackTrace, () -> Log.d(TAG, "onCompleted") ); } } Observable<PaginatedQueryList<Project>> ddbProjectStream(final CognitoCachingCredentialsProvider provider) { return Observable.create(subscriber -> { AmazonDynamoDBClient ddbClient = new AmazonDynamoDBClient(provider); DynamoDBMapper mapper = new DynamoDBMapper(ddbClient); ddbClient.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1)); Project projectToFind = new Project(); projectToFind.setName(provider.getCachedIdentityId()); String queryString = "tannai"; com.amazonaws.services.dynamodbv2.model.Condition rangeKeyCondition = new com.amazonaws.services.dynamodbv2.model.Condition() .withComparisonOperator(ComparisonOperator.EQ.toString()) .withAttributeValueList(new AttributeValue().withS(queryString)); DynamoDBQueryExpression<Project> queryExpression = new DynamoDBQueryExpression<Project>() .withHashKeyValues(projectToFind) .withRangeKeyCondition("member", rangeKeyCondition); PaginatedQueryList<Project> result = mapper.query(Project.class, queryExpression); subscriber.onNext(result); }); }
動作確認
projectToFind.setName(provider.getCachedIdentityId())
でHash Keyを、withAttributeValueList(new AttributeValue().withS(queryString))
でRange Keyを設定します。
事前にマネジメントコンソールからCognito Identity IDを調べておき、適当なRange Keyと共にDynamoDBにItemを作成します。
このItemに対するQueryは成功します。自分のIdentity ID以外のHash Keyでは、AccessDeniedException
で失敗します。
まとめ
Cognitoを使うことで、AWSリソースに簡単にアクセスできるのはとても良いです。
また、私は実務ではサーバサイドしかやっていませんが、サーバサイドアプリケーションを書く人こそ、クライアントからAWSを使うことで得られる知識(IAMやCognito)も大きいと思います。
明日からぜひ使ってみてください!