はじめに
こんにちは、アプリケーションサービス部 ディベロップメントサービス1課の北出です。
Cognitoでユーザーにアクセス権限を付与するにあたって、あるユーザーが他のユーザーの情報を見れないようにしないといけないといった要件があると思います。 今回は、Cognitoでユーザーごとにアクセス権限を付与する手順を紹介します。
前提条件
前回の記事からの続きとなりますので、前回の記事のリソースが作成されている前提とします。
システム構成
本記事では、この図のように、user-0001の人は自分のS3プレフィックスのオブジェクトにしかアクセスできず、DynamoDBも自分のパーティションキーのレコードしかアクセスできないようにします。 ユーザーごとに異なるIAMロールをつくるのは運用負荷が高いので、一つのIAMロールでユーザーごとに動的にアクセス権限が変わるようにします。
やってみる
S3
基本的にはこちらのドキュメントを参考にしています。 IDプールに割り当てるIAMロールのポリシーを以下のように設定します。
{ "Version": "2012-10-17", "Statement": [ { "Condition": { "StringLike": { "s3:prefix": [ "${cognito-identity.amazonaws.com:sub}/*" ] } }, "Action": [ "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::${bucket-name}" ], "Effect": "Allow" }, { "Action": [ "s3:GetObject", "s3:PutObject",, "s3:DeleteObject", ], "Resource": [ "arn:aws:s3:::${bucket-name}/${cognito-identity.amazonaws.com:sub}/*" ], "Effect": "Allow" } ] }
`${bucket-name}はアクセス先のS3バケット名を指定してください。
ユーザーごとのアクセス制御の肝となるのは、${cognito-identity.amazonaws.com:sub}
の部分です。
この部分がユーザーIDに応じて動的に変化して、自分のユーザーIDのプレフィックスのオブジェクトにのみアクセスできます。
これを実装するには、${cognito-identity.amazonaws.com:sub}
の値を取得する必要があります。この値の取得方法は後述します。
このJSONポリシーはCloudFormationテンプレートでは以下のように記述します。
CognitoAuthRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${CognitoRoleName} 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 CognitoIdentityPool ForAnyValue:StringLike: cognito-identity.amazonaws.com:amr: authenticated Policies: - PolicyName: !Sub ${PolicyName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "s3:ListBucket" Resource: - !Sub "arn:aws:s3:::${bucket-name}" Condition: StringLike: "s3:prefix": - !Join - "" - - "${cognito-identity.amazonaws.com:sub}/*" - Effect: "Allow" Action: - "s3:GetObject" - "s3:PutObject" - "s3:DeleteObject" Resource: - !Join - "" - - "arn:aws:s3:::" - !Sub "${bucket-name}/" - "${cognito-identity.amazonaws.com:sub}/*"
DynamoDB
次にDynamoDBへのアクセス制御を実装します。
これはこちらのドキュメントを参考にします。
IDプールに割り当てるIAMロールのポリシーを以下のように設定します。
{ "Version": "2012-10-17", "Statement": [ { "Condition": { "ForAllValues:StringEquals": { "dynamodb:LeadingKeys": [ "${cognito-identity.amazonaws.com:sub}" ] } }, "Action": [ "dynamodb:GetItem", "dynamodb:BatchGetItem", "dynamodb:Scan", "dynamodb:Query" ], "Resource": "arn:aws:dynamodb:ap-northeast-1:${account-id}:table/${table-name}", "Effect": "Allow" } ] }
S3と同様に、${cognito-identity.amazonaws.com:sub}を使って動的にアクセス権限を制御します。
上記のように設定することで、ユーザーIDがパーティションキーと一致しているレコードのみにアクセスでき、ほかのユーザーの情報は見れないようにできます。
このJSONポリシーはCloudFormationテンプレートでは以下のように記述します。
CognitoAuthRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${CognitoRoleName} 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 CognitoIdentityPool ForAnyValue:StringLike: cognito-identity.amazonaws.com:amr: authenticated Policies: - PolicyName: !Sub ${PolicyName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "dynamodb:GetItem" - "dynamodb:BatchGetItem" - "dynamodb:Scan" - "dynamodb:Query" Resource: !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${table-name}" Condition: ForAllValues:StringEquals: "dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"]
ユーザーIDの取得方法(Boto3)
最後に、クライアントアプリで実装するために、${cognito-identity.amazonaws.com:sub}
を取得する方法を記述します。
前回の記事で実装した内容を使用します。
前回の記事の libs/session_maneger.py
の返り値の identity_id
が ${cognito-identity.amazonaws.com:sub}
となります。
libs/session_maneger.py
import boto3 import configparser import libs.get_access_token as get_access_token from libs.logger_config import setup_logger # ロガーの設定 logger = setup_logger("session_manager") # 設定ファイルの読み込み config = configparser.ConfigParser() config.read("config.ini") AWS_REGION = config["DEFAULT"]["AWS_REGION"] USER_POOL_ID = config["DEFAULT"]["USER_POOL_ID"] IDPOOL_ID = config["DEFAULT"]["IDPOOL_ID"] def create_session(): """Cognito認証を行い、AWSの一時セッションを作成する""" # ユーザープールで認証 accessToken, refreshToken, id_token = get_access_token.authenticate_user() # Identity Poolを使って一時認証情報を取得 identity_id, id_provider = get_access_token.get_identity_id(id_token, IDPOOL_ID) # 一時認証情報を取得 response = id_provider.get_credentials_for_identity( IdentityId=identity_id, Logins={f"cognito-idp.{AWS_REGION}.amazonaws.com/{USER_POOL_ID}": id_token}, ) credentials = response["Credentials"] # Boto3セッション作成 session = boto3.Session( aws_access_key_id=credentials["AccessKeyId"], aws_secret_access_key=credentials["SecretKey"], aws_session_token=credentials["SessionToken"], ) return session, identity_id
このidentity_idを使って、試しにS3バケットにアップロードするプログラムは以下になります。
import configparser import libs.session_manager as session_manager from libs.logger_config import setup_logger # ロガーの設定 logger = setup_logger("s3_upload") """ S3アップロードするプログラム """ # 設定ファイルの読み込み config = configparser.ConfigParser() config.read("config.ini") S3_REQUEST_BUCKET = config["DEFAULT"]["S3_REQUEST_BUCKET"] LOCAL_FILE_PATH = config["DEFAULT"]["LOCAL_FILE_PATH"] # セッションの取得 session, identity_id = session_manager.create_session() # S3クライアントの作成 s3 = session.client("s3") # 異なるユーザーのオブジェクトにアクセスを試みる場合 # identity_id = "another-user-id" # S3アップロード情報 s3_bucket = S3_REQUEST_BUCKET s3_key = f"{identity_id}/input.txt" local_file_path = LOCAL_FILE_PATH # S3にファイルをアップロード response = s3.upload_file(local_file_path, s3_bucket, s3_key) logger.info(f"Uploaded {local_file_path} to s3://{s3_bucket}/{s3_key}")
こちらも前回の記事と同様のプログラムになります。
s3_key
に取得したidentity_idをプレフィックスに設定することで割り当てられたアクセス権限に従った実装ができます。
異なるユーザーのオブジェクトにアクセスできないことを確かめたい場合は、プレフィックスのidentity_idを変えてみてください
おわりに
前回から続けてCognitoの実装についての記事を作成しました。 今回の記事はAWSの公式ドキュメントもわかりやすいものだったのでそれほど苦労せずに実装することができました。