![[アップデート] Amazon Quick のクロスアカウント Athena データソースアクセスを試してみた](https://images.ctfassets.net/ct0aopd36mqt/2x7muHjvW69fxuVNSKWZHp/b37e05a972fc8125e9214abd764928fa/amazon-quick.png?w=3840&fm=webp)
[アップデート] Amazon Quick のクロスアカウント Athena データソースアクセスを試してみた
クラウド事業本部の石川です。Amazon Quick から Athena データソースをクロスアカウントで参照できるようになりましたので、東京リージョンで実際に試してみました。QuickからAthenaに接続するデータソースは、これまで同一アカウント内のリソースしか参照できませんでした。データを保有するアカウントとBIツールを利用するアカウントを分けて運用しているケースでは、データをコピーしたり、複数アカウントでQuickをサブスクライブする必要があり、運用コストやコスト按分の悩みがありました。
本機能では、Quick側に作成する RunAsRole と、データソース側に作成する ConsumerAccountRoleArn の2つのIAMロールをチェーンさせることで、別アカウントのAthena/Glue/S3 リソースに直接アクセスできるようになりました。Athenaクエリ料金やGlueカタログ呼び出し、S3アクセスの料金はデータが存在するアカウント側に課金されるため、コスト按分も自動的に分離されます。
クロスアカウント Athena データソースアクセスとは
Quick の Athena データソースの AthenaParameters に、新たに ConsumerAccountRoleArn というフィールドが追加されました。Quickは以下の2段階でロールをチェーニングし、別アカウントのAthenaリソースへアクセスします。
- Quickアカウント内に作成した
RunAsRole(Role A)をquicksight.amazonaws.comサービスプリンシパルから引き受ける - Role A の権限を使って、データ所有アカウント側の
ConsumerAccountRoleArn(Role B)をクロスアカウントで引き受ける - Role B の認証情報でAccount B側のAthena/Glue/S3にアクセスする
両方のロールの信頼ポリシーには sts:ExternalId 条件としてQuickデータソースのARNを指定することで、Confused Deputy 攻撃を防ぐ仕組みが用意されています。
やってみた
システム構成

前提条件
- AWSアカウント2つ
- Account A: Amazon Quick(Enterprise Edition / 東京リージョン)
- Account B: Athenaデータソース(データ側 / 東京リージョン)
- 検証環境: ap-northeast-1
検証では、Account B 側に Athena ワークグループ・Glue カタログ・S3 バケットを用意し、Account A の Quick からそのデータをクロスアカウントで参照できるようにします。
Account B 側: データベースとテーブルの作成
productsデータの CSV を作成しデータ用バケットへアップロードします。
product_id,product_name,category,price,stock
P001,classmethod-mug,merchandise,1500,120
P002,devio-tshirt,merchandise,2800,80
P003,aws-handbook,book,3200,45
P004,quicksight-poster,merchandise,800,200
P005,athena-stickers,merchandise,300,500
CSV のスキーマを持つ Athena テーブルを作成します。
CREATE DATABASE cm-blog-cfn-db;
CREATE EXTERNAL TABLE `products`(
`product_id` string,
`product_name` string,
`category` string,
`price` int,
`stock` int)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
STORED AS INPUTFORMAT
'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
's3://cm-blog-cfn-data-<my-bucket>-ap-northeast-1/products/'
TBLPROPERTIES (
'classification'='csv',
'delimiter'=',',
'skip.header.line.count'='1')
Account B 側: 専用Athenaワークグループの作成
Athena の primary ワークグループは既定ではクエリ結果出力先が未設定で、Quick 側からアクセスするとデフォルトのバケット作成権限がなくデータソース作成に失敗します。検証用に、出力先を明示した専用ワークグループを作成しておくのが安全です。
WG_NAME="cm-blog-20260515-bc5e1f87"
aws athena create-work-group \
--name "${WG_NAME}" \
--description "Cross-account QuickSight verification workgroup" \
--configuration "ResultConfiguration={OutputLocation=s3://${QRES_BUCKET}/wg-output/},EnforceWorkGroupConfiguration=true,PublishCloudWatchMetricsEnabled=false"
aws athena get-work-group --work-group "${WG_NAME}" \
--query 'WorkGroup.{Name:Name,State:State,OutputLocation:Configuration.ResultConfiguration.OutputLocation}' \
--output table
---------------------------------------------------------------------------------------------------
| GetWorkGroup |
+----------------+--------------------------------------------------------------------+-----------+
| Name | OutputLocation | State |
+----------------+--------------------------------------------------------------------+-----------+
| cm-blog-cfn-wg| s3://cm-blog-cfn-qresults-517444948157-ap-northeast-1/wg-output/ | ENABLED |
+----------------+--------------------------------------------------------------------+-----------+
Account B 側: Role B(ConsumerAccountRoleArn)の作成
Account A の root を信頼するロールを作成します。信頼ポリシーには sts:ExternalId 条件として Quick の datasource/* ARN を指定します。
ROLE_B="qs-athena-consumer-role-20260515-bc5e1f87"
ACCOUNT_A="<ACCOUNT_A>"
cat > role_b_trust_policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::${ACCOUNT_A}:root"},
"Action": "sts:AssumeRole",
"Condition": {
"StringLike": {
"sts:ExternalId": "arn:aws:quicksight:*:${ACCOUNT_A}:datasource/*"
}
}
}
]
}
EOF
aws iam create-role \
--role-name "${ROLE_B}" \
--assume-role-policy-document file://role_b_trust_policy.json \
--description "QuickSight Athena Cross Account - Consumer Account Role"
次に権限ポリシーを付与します。athena:* / glue:Get* / s3:* に加え、Lake Formation が有効なアカウントでは lakeformation:GetDataAccess も必要です(後述)。
aws iam put-role-policy \
--role-name "${ROLE_B}" \
--policy-name "AthenaGlueS3Access" \
--policy-document file://role_b_permission_policy.json
role_b_permission_policy.json の主要部分は以下です。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AthenaAccess",
"Effect": "Allow",
"Action": [
"athena:GetWorkGroup", "athena:StartQueryExecution",
"athena:GetQueryExecution", "athena:GetQueryResults",
"athena:GetQueryResultsStream", "athena:ListDataCatalogs",
"athena:ListDatabases", "athena:GetTableMetadata"
],
"Resource": [
"arn:aws:athena:ap-northeast-1:<ACCOUNT_B>:workgroup/cm-blog-20260515-bc5e1f87",
"arn:aws:athena:ap-northeast-1:<ACCOUNT_B>:datacatalog/AwsDataCatalog"
]
},
{
"Sid": "GlueAccess",
"Effect": "Allow",
"Action": [
"glue:GetDatabase", "glue:GetDatabases", "glue:GetTable",
"glue:GetTables", "glue:GetPartition", "glue:GetPartitions",
"glue:BatchGetPartition"
],
"Resource": [
"arn:aws:glue:ap-northeast-1:<ACCOUNT_B>:catalog",
"arn:aws:glue:ap-northeast-1:<ACCOUNT_B>:database/cm_blog_db_20260515_bc5e1f87",
"arn:aws:glue:ap-northeast-1:<ACCOUNT_B>:table/cm_blog_db_20260515_bc5e1f87/*"
]
},
{
"Sid": "LakeFormationAccess",
"Effect": "Allow",
"Action": ["lakeformation:GetDataAccess"],
"Resource": "*"
},
{
"Sid": "S3DataAccess",
"Effect": "Allow",
"Action": ["s3:GetBucketLocation", "s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::cm-ishikawa-blog-data-20260515-bc5e1f87",
"arn:aws:s3:::cm-ishikawa-blog-data-20260515-bc5e1f87/*"
]
},
{
"Sid": "S3QueryResultsAccess",
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation", "s3:GetObject", "s3:PutObject",
"s3:AbortMultipartUpload", "s3:ListBucket",
"s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts"
],
"Resource": [
"arn:aws:s3:::cm-ishikawa-blog-qresults-20260515-bc5e1f87",
"arn:aws:s3:::cm-ishikawa-blog-qresults-20260515-bc5e1f87/*"
]
}
]
}
Account A 側: Role A(RunAsRole)の作成
Quickサービスプリンシパルを信頼するロールを作成します。aws:SourceAccount と aws:SourceArn 条件で呼び出し元を制限します。
ROLE_A="qs-athena-cross-account-role-a-20260515-bc5e1f87"
ACCOUNT_A="<ACCOUNT_A>"
ACCOUNT_B="<ACCOUNT_B>"
cat > role_a_trust_policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "quicksight.amazonaws.com"},
"Action": "sts:AssumeRole",
"Condition": {
"StringLike": {
"aws:SourceAccount": "${ACCOUNT_A}",
"aws:SourceArn": "arn:aws:quicksight:*:${ACCOUNT_A}:datasource/*"
}
}
}
]
}
EOF
aws iam create-role \
--role-name "${ROLE_A}" \
--assume-role-policy-document file://role_a_trust_policy.json
権限ポリシーは「Role B への AssumeRole」のみ。sts:ExternalId 条件で Quick datasource ARN にスコープします。
cat > role_a_permission_policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::${ACCOUNT_B}:role/${ROLE_B}",
"Condition": {
"StringLike": {
"sts:ExternalId": "arn:aws:quicksight:*:${ACCOUNT_A}:datasource/*"
}
}
}
]
}
EOF
aws iam put-role-policy \
--role-name "${ROLE_A}" \
--policy-name "AssumeConsumerRolePolicy" \
--policy-document file://role_a_permission_policy.json
Account A 側: クロスアカウントAthenaデータソースの作成
いよいよ、今日の主役であるクロスアカウントAthenaデータソースの作成です。執筆時点(2026/5/15)では、データソースの作成画面からクロスアカウントAthenaデータソースの作成はできないようです。そのため、下記のように AthenaParameters に ConsumerAccountRoleArn を指定します。
DS_ID="athena-cross-account-20260515-bc5e1f87"
cat > data_source_params.json << EOF
{
"AthenaParameters": {
"WorkGroup": "cm-blog-20260515-bc5e1f87",
"RoleArn": "arn:aws:iam::<ACCOUNT_A>:role/qs-athena-cross-account-role-a-20260515-bc5e1f87",
"ConsumerAccountRoleArn": "arn:aws:iam::<ACCOUNT_B>:role/qs-athena-consumer-role-20260515-bc5e1f87"
}
}
EOF
aws quicksight create-data-source \
--aws-account-id <ACCOUNT_A> \
--data-source-id "${DS_ID}" \
--name "Athena Cross Account (Account B - <ACCOUNT_B>)" \
--type ATHENA \
--data-source-parameters file://data_source_params.json \
--permissions file://data_source_permissions.json \
--region ap-northeast-1
{
"Status": 202,
"Arn": "arn:aws:quicksight:ap-northeast-1:<ACCOUNT_A>:datasource/athena-cross-account-20260515-bc5e1f87",
"DataSourceId": "athena-cross-account-20260515-bc5e1f87",
"CreationStatus": "CREATION_IN_PROGRESS",
"RequestId": "6250761e-d774-4f0e-bf88-ba33ecb5e300"
}
数秒後に describe-data-source で確認すると CREATION_SUCCESSFUL になります。
aws quicksight describe-data-source \
--aws-account-id <ACCOUNT_A> \
--data-source-id "${DS_ID}" \
--query 'DataSource.{Name:Name,Status:Status,Type:Type,RoleArn:DataSourceParameters.AthenaParameters.RoleArn,ConsumerRoleArn:DataSourceParameters.AthenaParameters.ConsumerAccountRoleArn,WorkGroup:DataSourceParameters.AthenaParameters.WorkGroup}' \
--output table
-------------------------------------------------------------------------------------------------------------------------
| DescribeDataSource |
+-----------------+-----------------------------------------------------------------------------------------------------+
| ConsumerRoleArn| arn:aws:iam::<ACCOUNT_B>:role/qs-athena-consumer-role-20260515-bc5e1f87 |
| Name | Athena Cross Account (Account B - <ACCOUNT_B>) |
| RoleArn | arn:aws:iam::<ACCOUNT_A>:role/qs-athena-cross-account-role-a-20260515-bc5e1f87 |
| Status | CREATION_SUCCESSFUL |
| Type | ATHENA |
| WorkGroup | cm-blog-20260515-bc5e1f87 |
+-----------------+-----------------------------------------------------------------------------------------------------+
Quick 側でデータソース作成時点で内部的に接続テスト(Role A → Role B チェーン)が走り、Account B 側で SHOW DATABASES 相当のクエリが実行されて成功することが確認できました。
データソースの作成画面からクロスアカウントAthenaデータソースの作成はできませんでしたが、作成されたデータソースは画面から確認できます。

補足: Account B 側: Lake Formation 権限の付与
Account B 側で Lake Formation が有効になっている場合、IAM ポリシーだけではテーブルへの SELECT が拒否されます。Role B に対して Lake Formation の権限を付与します。
ROLE_B_ARN="arn:aws:iam::<ACCOUNT_B>:role/qs-athena-consumer-role-20260515-bc5e1f87"
GLUE_DB="cm_blog_db_20260515_bc5e1f87"
DATA_BUCKET="cm-ishikawa-blog-data-20260515-bc5e1f87"
# データベースに対する DESCRIBE
aws lakeformation grant-permissions \
--principal "DataLakePrincipalIdentifier=${ROLE_B_ARN}" \
--resource "{\"Database\":{\"Name\":\"${GLUE_DB}\"}}" \
--permissions DESCRIBE
# テーブルに対する SELECT, DESCRIBE
aws lakeformation grant-permissions \
--principal "DataLakePrincipalIdentifier=${ROLE_B_ARN}" \
--resource "{\"Table\":{\"DatabaseName\":\"${GLUE_DB}\",\"Name\":\"products\"}}" \
--permissions SELECT DESCRIBE
# S3 ロケーションをLake Formationに登録 + DATA_LOCATION_ACCESS 付与
aws lakeformation register-resource \
--resource-arn "arn:aws:s3:::${DATA_BUCKET}" \
--use-service-linked-role
aws lakeformation grant-permissions \
--principal "DataLakePrincipalIdentifier=${ROLE_B_ARN}" \
--resource "{\"DataLocation\":{\"ResourceArn\":\"arn:aws:s3:::${DATA_BUCKET}\"}}" \
--permissions DATA_LOCATION_ACCESS
加えて、Role B の IAM ポリシーに lakeformation:GetDataAccess を追加する必要があります(前述の LakeFormationAccess ステートメント)。これが抜けると以下のようなエラーが出ます。
"Insufficient permissions to execute the query. User: arn:aws:sts::<ACCOUNT_B>:assumed-role/
qs-athena-consumer-role-20260515-bc5e1f87/QuickSight-RoleSession-... is not authorized to perform:
lakeformation:GetDataAccess on resource: arn:aws:glue:ap-northeast-1:<ACCOUNT_B>:table/
cm_blog_db_20260515_bc5e1f87/products"
Account A 側: Datasetを作成しSPICEに取り込む
データソースが動いていることを実データの取り込みで確認するため、Datasetを作成して SPICE Ingestion を実行します。
DATASET_ID="cm-blog-products-20260515-bc5e1f87"
aws quicksight create-data-set --cli-input-json file://dataset_input.json
dataset_input.json の主要部分です。
{
"AwsAccountId": "<ACCOUNT_A>",
"DataSetId": "cm-blog-products-20260515-bc5e1f87",
"Name": "Cross-Account Products Dataset",
"PhysicalTableMap": {
"products-physical": {
"RelationalTable": {
"DataSourceArn": "arn:aws:quicksight:ap-northeast-1:<ACCOUNT_A>:datasource/athena-cross-account-20260515-bc5e1f87",
"Catalog": "AwsDataCatalog",
"Schema": "cm_blog_db_20260515_bc5e1f87",
"Name": "products",
"InputColumns": [
{"Name": "product_id", "Type": "STRING"},
{"Name": "product_name", "Type": "STRING"},
{"Name": "category", "Type": "STRING"},
{"Name": "price", "Type": "INTEGER"},
{"Name": "stock", "Type": "INTEGER"}
]
}
}
},
"ImportMode": "SPICE"
}
Dataset作成時にSPICE Ingestionが自動でトリガーされるので、完了を待って結果を確認します。
aws quicksight describe-ingestion \
--aws-account-id <ACCOUNT_A> \
--data-set-id "${DATASET_ID}" \
--ingestion-id "01ea1ba0-1f5b-4975-bb08-b17f57e9bee3" \
--query 'Ingestion.{Status:IngestionStatus,RowsIngested:RowInfo.RowsIngested,RowsDropped:RowInfo.RowsDropped,TimeSec:IngestionTimeInSeconds,SizeBytes:IngestionSizeInBytes}' \
--output table
----------------------------------------------------------------------
| DescribeIngestion |
+-------------+---------------+------------+-------------+-----------+
| RowsDropped | RowsIngested | SizeBytes | Status | TimeSec |
+-------------+---------------+------------+-------------+-----------+
| 0 | 5 | 619 | COMPLETED | 45 |
+-------------+---------------+------------+-------------+-----------+
5行(テストデータの全件)が無事 SPICE に取り込まれました。Quickの画面からも作成したデータセットのレコードが確認できます。

Account B 側: クエリ実行履歴とコスト帰属の確認
Account B 側の Athena から、Quick が実行したクエリの履歴を確認できます。
aws athena list-query-executions --work-group "cm-blog-20260515-bc5e1f87" --max-results 1 \
--query 'QueryExecutionIds[0]' --output text
# → 944fa47c-c5ab-4990-9ea8-7023a5339ce3
aws athena get-query-execution --query-execution-id "944fa47c-c5ab-4990-9ea8-7023a5339ce3" \
--query 'QueryExecution.{Query:Query,Status:Status.State,DataScanned:Statistics.DataScannedInBytes,WorkGroup:WorkGroup,OutputLocation:ResultConfiguration.OutputLocation}' \
--output table
-----------------------------------------------------------------------------------------------------------
| GetQueryExecution |
+----------------+----------------------------------------------------------------------------------------+
| DataScanned | 240 |
| OutputLocation| s3://cm-ishikawa-blog-qresults-20260515-bc5e1f87/wg-output/944fa47c-....csv |
| Query | /* QuickSight e7b0d37c-40ea-4b3b-ab06-0945d8a1fe46 */ |
| | SELECT "product_id", "product_name", "category", "price", "stock" |
| | FROM "AwsDataCatalog"."cm_blog_db_20260515_bc5e1f87"."products" |
| Status | SUCCEEDED |
| WorkGroup | cm-blog-20260515-bc5e1f87 |
+----------------+----------------------------------------------------------------------------------------+
ポイントは次の3点です。
- クエリは Account B 側のAthenaワークグループで実行されている(
WorkGroup: cm-blog-20260515-bc5e1f87) - クエリ本文の先頭に
/* Quick <session-id> */というコメントが入っており、Quick からのクエリと識別できる - クエリ結果は Account B 側の S3 バケットに保存されている(
OutputLocationが Account B のバケット)
つまり、Athena のクエリスキャン料金、S3 PUT/GET 料金、Glue Data Catalog 呼び出し料金はすべて Account B 側に発生し、Quick セッションや SPICE ストレージ料金は Account A 側に発生する形でコストが自動的に分離されます。
考察
実際に検証してみて得られた知見と、ハマりどころを整理します。
Athenaワークグループは「primary」ではなく専用ワークグループを推奨
最初の試行では Athena の primary ワークグループを指定したところ、Unable to verify/create output bucket aws-athena-query-results-ap-northeast-1-<ACCOUNT_B> というエラーでデータソース作成に失敗しました。primary ワークグループは既定でクエリ結果出力先が未設定のため、Athena が自動的に aws-athena-query-results-<region>-<account> バケットを作ろうとして失敗するケースがあります。ResultConfiguration を明示した専用ワークグループを作成し、データソースで指定するのが安全です。
Lake Formationが有効なアカウントでは追加の権限設定が必須
公式ブログのIAMポリシー例には Lake Formation 関連の権限が含まれていません。Account B 側で Lake Formation が有効になっていると、SHOW DATABASES 相当の接続テストは通っても、実際にテーブルを読むクエリで Insufficient Lake Formation permission(s) や lakeformation:GetDataAccess on resource ... is not authorized というエラーが返ります。以下の2つを忘れずに設定する必要があります。
- Lake Formation 側で Role B に
DESCRIBE(DB / Table)、SELECT(Table)、DATA_LOCATION_ACCESS(S3)を grant - Role B の IAM ポリシーに
lakeformation:GetDataAccessを追加
ExternalIdとSourceArn条件によるConfused Deputy対策
Role A と Role B の両方の信頼ポリシーで、StringLike で arn:aws:quicksight:*:<Account-A>:datasource/* を aws:SourceArn および sts:ExternalId 条件として指定します。これにより、Role A は「Account A 配下の Quick データソースから呼び出されたとき」のみ引き受けられ、Role B は「Account A の Quick データソースを ExternalId として提示された場合」のみ引き受けられるため、別の利用者が誤って・あるいは悪意で Role を AssumeRole しても拒否されます。
コスト帰属が自動で実現される
検証で確認できたとおり、Athena のクエリ料金(スキャンしたバイト数に対する料金)、クエリ結果保存のS3料金、Glue Data Catalog 呼び出しの料金はすべて Account B 側に発生します。一方、Quick のユーザーセッションや SPICE ストレージ料金は Account A 側に発生します。データオーナーがコストを負担し、BI 利用側はサブスクリプション費用のみを負担する、というデータメッシュ的な運用が標準のAWS課金体系のまま自動で成立する点は実務的に大きな魅力です。
期待される拡張
現時点では AthenaParameters のみが ConsumerAccountRoleArn をサポートしています。Redshift や RDS など、他のデータソースタイプでも同等のクロスアカウント参照ができるようになれば、複数アカウントにまたがるデータレイク/データウェアハウスの一元 BI 利用がさらに進むと期待しています。
最後に
Amazon Quick からクロスアカウントの Athena データソースに直接アクセスできるようになりました。データを集約するためにアカウント間でコピーしたり、複数アカウントで Quick をサブスクライブする必要がなくなり、コストもデータオーナー側に自然に按分される構成が実現できます。
検証してみるとIAMロールチェーンの作りは見た目以上に綺麗で、sts:ExternalId と aws:SourceArn で Confused Deputy 対策がしっかり効いた状態で、複数アカウントのデータを安全に参照できる構成が組めることがわかりました。ハブ・アンド・スポーク構成で複数のビジネスユニットアカウントを束ねたい場合に、ぜひ採用を検討したい機能です。
ハマりどころは Athena ワークグループの出力先設定と Lake Formation 権限まわりです。本記事で示した手順がそのまま使える形になっているので、検証を始める方の参考になれば幸いです。
合わせて読みたい







