
Amazon Quick の S3 ナレッジベースのドキュメントレベル ACL を検証してみた - ユーザーごとに機密文書を出し分ける
クラウド事業統括本部の石川です。部門の資料と全社向けの資料が同じ S3 バケットに入っている環境で Amazon Quick のナレッジベースを作るには、これまではナレッジベースを分けるか、機密文書をインデックスしないかの二択でした。ドキュメントレベル ACLでは、S3 ナレッジベース内の個々のドキュメントに対してユーザー・グループ単位の ALLOW / DENY を設定できるようになりましたので実際に試してみます。
ドキュメントレベル ACL とは
ドキュメントレベル ACLは、設定方法は2つあります。フォルダ単位で権限を一元管理するグローバル ACL 設定ファイルと、ドキュメントごとにメタデータファイルを置く方法です。また、ACL の有効化はナレッジベース作成時のみで、後から変更できません。Amazon Quick の S3 ナレッジベースのドキュメントレベル ACLは、「事前フィルタ」のみです。
ACL 対応のナレッジベースでは、Quick は取り込み時に読み取った権限情報をインデックスと一緒に保持し、検索のたびにそれを評価します。権限を評価できない場合は、フィルタしていない結果を返すのではなく、ドキュメントを一切返しません。
グローバル ACL 設定ファイル
S3 のキープレフィックスごとに ACL エントリを並べた JSON ファイルです。ナレッジベース作成時にこのファイルの S3 URI を指定します。
[
{
"keyPrefix": "s3://BUCKETNAME/prefix1/",
"aclEntries": [
{
"Name": "user1@example.com",
"Type": "USER",
"Access": "ALLOW"
},
{
"Name": "group1",
"Type": "GROUP",
"Access": "DENY"
}
]
}
]
Name は USER の場合は Quick に登録されているユーザーのメールアドレス、GROUP の場合は Quick のグループ名です。権限構造が安定している組織向けで、ファイルを変更すると該当プレフィックス全体の再インデックスが必要になります。
ドキュメントレベルのメタデータファイル
ドキュメントごとに <ドキュメント名>.<拡張子>.metadata.json を用意し、AccessControlList フィールドに ACL エントリを書く方法です。
{
"DocumentId": "meta-allow-incident-4210",
"Title": "セキュリティインシデント報告書 #4210",
"ContentType": "MD",
"AccessControlList": [
{
"Name": "user1@example.com",
"Type": "USER",
"Access": "ALLOW"
}
]
}
権限が変わったドキュメントだけを再インデックスすればよいため、権限の変更が頻繁な場合はこちらが向いています。
やってみた
前提条件
- AWS アカウント: Quick の ENTERPRISE サブスクリプション済み(認証タイプは IDENTITY_POOL)
- 検証リージョン: ap-northeast-1
- AWS CLI: aws-cli/2.36.40
検証の構成
1つの S3 バケットに、ACL の効き方を比べるための6つのドキュメントを配置しました。それぞれのファイルには識別用の管理番号を埋め込んであります。
ACL の設定は次のようにしました。quick-acl-blog-group は検証用に作成したグループで、自分は所属していません。
| ドキュメント | 管理番号 | xxxxxxxxxx@classmethod.jp | quick-acl-blog-group |
|---|---|---|---|
| global/allow/holiday-policy.md | ALPHA-GLOBAL-ALLOW | ALLOW | - |
| global/deny/exec-compensation.md | BRAVO-GLOBAL-DENY | DENY | ALLOW |
| global/noacl/uncontrolled-memo.md | CHARLIE-GLOBAL-NOACL | ACL エントリなし | ACL エントリなし |
| meta/allow/incident-report.md | DELTA-META-ALLOW | ALLOW | - |
| meta/deny/ma-valuation.md | ECHO-META-DENY | DENY | ALLOW |
| meta/noacl/orphan-note.md | FOXTROT-META-NOACL | メタデータファイルなし | メタデータファイルなし |
docs
├── acl
│ └── acl.json
├── global
│ ├── allow
│ │ └── holiday-policy.md
│ ├── deny
│ │ └── exec-compensation.md
│ └── noacl
│ └── uncontrolled-memo.md
└── meta
├── allow
│ ├── incident-report.md
│ └── incident-report.md.metadata.json
├── deny
│ ├── ma-valuation.md
│ └── ma-valuation.md.metadata.json
└── noacl
└── orphan-note.md
ステップ1: S3 にドキュメントと ACL ファイルを配置
グローバル ACL ファイルの中身は次の通りです。global/allow/ と global/deny/ の2つのプレフィックスだけを書き、global/noacl/ はあえて記載していません。
ACLファイル: acl/acl.json
[
{
"keyPrefix": "s3://quick-acl-blog-1b5c2683/global/allow/",
"aclEntries": [
{
"Name": "xxxxxxxxxx@classmethod.jp",
"Type": "USER",
"Access": "ALLOW"
}
]
},
{
"keyPrefix": "s3://quick-acl-blog-1b5c2683/global/deny/",
"aclEntries": [
{
"Name": "quick-acl-blog-group",
"Type": "GROUP",
"Access": "ALLOW"
},
{
"Name": "xxxxxxxxxx@classmethod.jp",
"Type": "USER",
"Access": "DENY"
}
]
}
]
global/deny/ にはグループに ALLOW、自分に DENY を与えています。ファイル名は任意で、パスはナレッジベース作成時に aclConfigurationFilePath で指定します。今回は acl/acl.json に置きました。この acl/ プレフィックスはクロール対象に含めていないため、ACL ファイル自体はインデックスされません。
ドキュメントと ACL ファイルを S3 にアップロードします。
% aws s3 sync work/docs/ s3://quick-acl-blog-1b5c2683/
upload: work/docs/global/allow/holiday-policy.md to s3://quick-acl-blog-1b5c2683/global/allow/holiday-policy.md
upload: work/docs/meta/noacl/orphan-note.md to s3://quick-acl-blog-1b5c2683/meta/noacl/orphan-note.md
upload: work/docs/acl/acl.json to s3://quick-acl-blog-1b5c2683/acl/acl.json
upload: work/docs/global/noacl/uncontrolled-memo.md to s3://quick-acl-blog-1b5c2683/global/noacl/uncontrolled-memo.md
upload: work/docs/meta/deny/ma-valuation.md.metadata.json to s3://quick-acl-blog-1b5c2683/meta/deny/ma-valuation.md.metadata.json
upload: work/docs/global/deny/exec-compensation.md to s3://quick-acl-blog-1b5c2683/global/deny/exec-compensation.md
upload: work/docs/meta/allow/incident-report.md to s3://quick-acl-blog-1b5c2683/meta/allow/incident-report.md
upload: work/docs/meta/allow/incident-report.md.metadata.json to s3://quick-acl-blog-1b5c2683/meta/allow/incident-report.md.metadata.json
upload: work/docs/meta/deny/ma-valuation.md to s3://quick-acl-blog-1b5c2683/meta/deny/ma-valuation.md
メタデータファイルはドキュメントと同じフォルダに置きました。別フォルダにまとめる場合は、ドキュメント側と同じ階層構造を維持する必要があります。
ステップ2: S3 ナレッジベース用のデータソースを作成
ナレッジベースを作る前に、S3 バケットを指すデータソースを作成します。
% aws quicksight create-data-source \
--aws-account-id 123456789012 \
--data-source-id quick-acl-blog-s3-1b5c2683 \
--name "quick-acl-blog-s3-integration" \
--type S3_KNOWLEDGE_BASE \
--data-source-parameters '{"S3KnowledgeBaseParameters":{"BucketUrl":"s3://quick-acl-blog-1b5c2683"}}'
{
"Status": 202,
"Arn": "arn:aws:quicksight:ap-northeast-1:123456789012:datasource/quick-acl-blog-s3-1b5c2683",
"DataSourceId": "quick-acl-blog-s3-1b5c2683",
"CreationStatus": "CREATION_IN_PROGRESS"
}
数秒で CREATION_SUCCESSFUL になりました。このデータソースは3つのナレッジベースで共用します。1つの S3 データソースから複数のナレッジベースを作成できるためです。
以降、ステップ3 〜 ステップ5 で検証用のナレッジベースを作成します。
ステップ3: ACL を有効にしたナレッジベースを作成(グローバル ACL 方式)
ACL を有効にするには、2か所の設定が必要です。1つはコネクタ側の accessControlConfiguration.crawlAcl、もう1つはナレッジベース側の --access-control-configuration isACLEnabled です。
まず、コネクタ側の設定を kb-a-global.json として保存します。
{
"templateConfiguration": {
"template": {
"type": "S3V2",
"connectionConfiguration": {
"bucketName": "quick-acl-blog-1b5c2683",
"bucketOwnerAccountId": "123456789012"
},
"filterConfiguration": {
"inclusionPrefixes": ["global/"]
},
"accessControlConfiguration": {
"crawlAcl": true,
"aclConfigurationFilePath": "s3://quick-acl-blog-1b5c2683/acl/acl.json"
}
}
}
}
--knowledge-base-configuration に渡すこのファイルは、コネクタがバケットをどうクロールしてインデックスするかを定義します。各フィールドの役割は次の通りです。
| フィールド | 役割 |
|---|---|
type |
コネクタ種別。Amazon S3 は S3V2 です。この値によって connectionConfiguration 以下に書ける項目が変わります |
connectionConfiguration |
接続先の指定。S3 では bucketName と bucketOwnerAccountId が必須です |
filterConfiguration |
クロール対象の絞り込み。inclusionPrefixes のほかに exclusionPrefixes、inclusionPatterns、exclusionPatterns、maxFileSizeInMegaBytes を指定できます |
accessControlConfiguration |
今回のアップデートで追加されたドキュメントレベル ACL の設定 |
crawlAcl を true にすると、コネクタが ACL を読み取って適用します。aclConfigurationFilePath にはグローバル ACL ファイルの S3 URI を指定します。このパスを省略するとメタデータファイル方式になります。
このほかに defaultAccessType があり、ACL 設定に載っていないプレフィックスの扱いを決めますが、サポートされる値は ALLOW のみです。今回は指定していません。
inclusionPrefixes に global/ を指定しているのは、同じバケットから3つのナレッジベースを作り分けるためです。
% aws quicksight create-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683 \
--name "quick-acl-blog-global-acl" \
--data-source-arn arn:aws:quicksight:ap-northeast-1:123456789012:datasource/quick-acl-blog-s3-1b5c2683 \
--knowledge-base-configuration file://kb-a-global.json \
--access-control-configuration isACLEnabled=true
{
"Status": 202,
"KnowledgeBaseArn": "arn:aws:quicksight:ap-northeast-1:123456789012:knowledge-base/kb-acl-global-1b5c2683",
"KnowledgeBaseId": "kb-acl-global-1b5c2683",
"CreationStatus": "CREATING"
}
CLI のヘルプには、この2つの設定について「Enabling only one of the two settings does not produce a fully ACL-enforced knowledge base(2つの設定のうち一方のみを有効にしても、ACLが完全に適用されたナレッジベースにはなりません。)」と書かれています。片方だけでは ACL が完全には適用されません。
ステップ4: メタデータ方式のナレッジベースを作成
2つ目はドキュメントメタデータ方式のナレッジベースです。aclConfigurationFilePath を指定せず、crawlAcl だけを true にします。対象プレフィックスは meta/ です。こちらは kb-b-metadata.json として保存しました。
{
"templateConfiguration": {
"template": {
"type": "S3V2",
"connectionConfiguration": {
"bucketName": "quick-acl-blog-1b5c2683",
"bucketOwnerAccountId": "123456789012"
},
"filterConfiguration": {
"inclusionPrefixes": ["meta/"]
},
"accessControlConfiguration": {
"crawlAcl": true
}
}
}
}
ナレッジベースquick-acl-blog-metadata-aclを作成します。
% aws quicksight create-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-meta-1b5c2683 \
--name "quick-acl-blog-metadata-acl" \
--data-source-arn arn:aws:quicksight:ap-northeast-1:123456789012:datasource/quick-acl-blog-s3-1b5c2683 \
--knowledge-base-configuration file://kb-b-metadata.json \
--access-control-configuration isACLEnabled=true
{
"Status": 202,
"KnowledgeBaseArn": "arn:aws:quicksight:ap-northeast-1:123456789012:knowledge-base/kb-acl-meta-1b5c2683",
"KnowledgeBaseId": "kb-acl-meta-1b5c2683",
"CreationStatus": "CREATING"
}
データソースはステップ2 で作ったものを使い回しています。--data-source-arn はステップ3 と同じです。
ステップ5: ACL を有効にしないナレッジベースを作成(比較用)
3つ目は ACL を有効にしないナレッジベースです。accessControlConfiguration を書かず、--access-control-configuration も指定せずに作成します。対象プレフィックスは global/ と meta/ の両方です。kb-c-noacl.json として保存しました。
{
"templateConfiguration": {
"template": {
"type": "S3V2",
"connectionConfiguration": {
"bucketName": "quick-acl-blog-1b5c2683",
"bucketOwnerAccountId": "123456789012"
},
"filterConfiguration": {
"inclusionPrefixes": ["global/", "meta/"]
}
}
}
}
% aws quicksight create-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-noacl-1b5c2683 \
--name "quick-acl-blog-no-acl" \
--data-source-arn arn:aws:quicksight:ap-northeast-1:123456789012:datasource/quick-acl-blog-s3-1b5c2683 \
--knowledge-base-configuration file://kb-c-noacl.json
{
"Status": 202,
"KnowledgeBaseArn": "arn:aws:quicksight:ap-northeast-1:123456789012:knowledge-base/kb-noacl-1b5c2683",
"KnowledgeBaseId": "kb-noacl-1b5c2683",
"CreationStatus": "CREATING"
}
このナレッジベースは比較の基準として用意しました。ACL 有効のナレッジベースでチャットが回答を拒否したとき、それだけでは「ACL が効いた」のか「そもそも検索にヒットしなかった」のか区別できません。同じバケット・同じドキュメント・同じユーザーで ACL の有無だけを変えたナレッジベースがあれば、結果の差が ACL によるものだと確かめられます。
ステップ6: ナレッジベースに所有者権限を付与する
CLI から作成したナレッジベースは --primary-owner-arn を指定しないと所有者が設定されず、describe-knowledge-base-permissions の Permissions が空になります。この状態ではコンソールのチャットから選択できないため、作成した3つすべてに対して権限を付与しました。
付与する権限は grant.json に書きます。Principal は IAM ユーザーではなく、user/<ネームスペース>/<ユーザー名> 形式の Quick のユーザー ARN です。
[
{
"Principal": "arn:aws:quicksight:ap-northeast-1:123456789012:user/default/xxxxxxxxxx/xxxxxxxxxx",
"Actions": [
"quicksight:DescribeKnowledgeBase",
"quicksight:DescribeKnowledgeBasePermissions",
"quicksight:UpdateKnowledgeBase",
"quicksight:UpdateKnowledgeBasePermissions",
"quicksight:DeleteKnowledgeBase",
"quicksight:CreateKnowledgeBaseRefreshSchedule",
"quicksight:DescribeKnowledgeBaseRefreshSchedule",
"quicksight:ListKnowledgeBaseRefreshSchedules",
"quicksight:UpdateKnowledgeBaseRefreshSchedule",
"quicksight:DeleteKnowledgeBaseRefreshSchedule",
"quicksight:CreateKnowledgeBaseIngestion",
"quicksight:CancelKnowledgeBaseIngestion",
"quicksight:DescribeKnowledgeBaseIngestion",
"quicksight:ListKnowledgeBaseIngestions"
]
}
]
このアクションの組み合わせは、コンソールから作成済みだった既存のナレッジベースに describe-knowledge-base-permissions を実行し、所有者に付いていたものをそのまま写しました。コンソールで作成したときに所有者へ付与される権限セットと同じです。
ステップ3 から5 で作成した3つのナレッジベースすべてに対して実行します。
% for KB in kb-acl-global-1b5c2683 kb-acl-meta-1b5c2683 kb-noacl-1b5c2683; do
aws quicksight update-knowledge-base-permissions \
--aws-account-id 123456789012 \
--knowledge-base-id "${KB}" \
--grant-permissions file://grant.json
done
{
"Status": 200,
"KnowledgeBaseArn": "arn:aws:quicksight:ap-northeast-1:123456789012:knowledge-base/kb-acl-global-1b5c2683",
"KnowledgeBaseId": "kb-acl-global-1b5c2683",
"Permissions": [
{
"Principal": "arn:aws:quicksight:ap-northeast-1:123456789012:user/default/xxxxxxxxxx/xxxxxxxxxx",
"Actions": [
...
]
}
]
}
...
3つとも Status: 200 が返り、Permissions に自分のユーザー ARN が入ります。これでコンソールのチャットからナレッジベースを選べるようになります。
このアクション一覧に quicksight:CreateKnowledgeBaseIngestion が含まれている点は、後のステップ12で効いてきます。
ステップ7: 取り込み結果を比較する
3つのナレッジベースは作成直後に自動で初回の取り込みが始まりました。2分ほどで完了したので、DocumentCount を比べます。
% aws quicksight list-knowledge-bases --aws-account-id 123456789012
KnowledgeBaseId Name Docs
kb-acl-global-1b5c2683 quick-acl-blog-global-acl 2
kb-acl-meta-1b5c2683 quick-acl-blog-metadata-acl 2
kb-noacl-1b5c2683 quick-acl-blog-no-acl 6
ACL を有効にした2つは、対象プレフィックスに3件ずつ置いたうち2件しか取り込まれていません。ACL エントリのない uncontrolled-memo.md と、メタデータファイルのない orphan-note.md が落ちています。一方、ACL 無効のナレッジベースは6件すべて取り込まれました。What's New に書かれていた「ACL エントリが関連付けられていないドキュメントは取り込まれない」という挙動をそのまま確認できました。
.metadata.json 自体はドキュメントとしてカウントされていません。ACL 無効のナレッジベースでもメタデータファイル2件を含めた8件ではなく6件だったため、メタデータファイルはインデックス対象から自動的に除外されているようです。
取り込みのステータスにも違いが出ました。
% aws quicksight describe-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683
Status : ACTIVE
DocumentCount : 2
ACL : {"isACLEnabled": true}
acc in template : {"crawlAcl": true, "aclConfigurationFilePath": "s3://quick-acl-blog-1b5c2683/acl/acl.json"}
filter : {"inclusionPrefixes": ["global/"]}
LatestIngestionSummary {"IngestionId": "d113464a-...", "IngestionStatus": "INCOMPLETE", "StartTime": "2026-09-11T10:22:43+09:00", "EndTime": "2026-09-11T10:24:44+09:00"}
ACL 有効の2つは INCOMPLETE、ACL 無効のものは COMPLETED でした。コンソールの一覧でも「Completed with issues」と表示されます。

取り込めなかったドキュメントがあると成功扱いにならないため、ACL 運用では「ACL エントリの書き漏れ」が同期ステータスの警告として現れることになります。
ステップ8: 同期レポートで取り込まれなかった理由を確認する
コンソールのナレッジベース詳細画面には Sync reports タブがあり、ドキュメント単位の結果を確認できます。


uncontrolled-memo.md が FAILED になり、エラータイプは VALIDATION_ERROR、エラーメッセージは次の通りでした。
No ACL entries found for document in ACL-enabled data source. Please ensure the document has valid ACL entries either via per-document metadata or the global ACL file.
ACLが有効なデータソース内のドキュメントについて、ACLエントリが見つかりませんでした。ドキュメントごとのメタデータ、またはグローバルACLファイルのいずれかを通じて、そのドキュメントに有効なACLエントリが設定されていることを確認してください。
失敗理由がドキュメント単位で明示されるので、ACL の書き漏れはこの画面で特定できます。
ステップ9: チャットで ACL の効き目を確認する
ここからは Quick のチャットで確認します。まず ALLOW を与えた ALPHA-GLOBAL-ALLOW を聞いてみます。

holiday-policy.md を引用して、休業日の内容が返ってきました。
次に、自分を DENY にした BRAVO-GLOBAL-DENY を聞きます。

申し訳ありませんが、管理番号 BRAVO-GLOBAL-DENY の文書は、石川様のアクセス権限では参照できないため、内容をお伝えすることができません。役員報酬の改定についても、同様の理由でお答えできません。
内容を答えないだけでなく、アクセス権限が理由であることも明示されました。ソースの引用も表示されません。
ACL エントリがなく取り込まれなかった CHARLIE-GLOBAL-NOACL はどうなるかも確認しました。

こちらは「文書は見つかりませんでした」という応答でした。DENY の場合と文言が異なりますが、いずれも内容は返りません。
メタデータ方式のナレッジベースでも、ALLOW と DENY を1つの質問にまとめて聞いてみました。

DELTA-META-ALLOW はインシデント報告書の内容を返し、ECHO-META-DENY は「アクセス権限の制限により閲覧できない可能性があります」と返しました。2つの設定方法で挙動に差はありませんでした。
ステップ10: ACL 無効のナレッジベースと比較する
ACL が本当に効いているのかを確かめるため、同じドキュメントを ACL 無効のナレッジベースに対して聞いてみます。

BRAVO-GLOBAL-DENY の役員報酬改定率 12.5% も、ECHO-META-DENY の想定評価額 48億円も、そのまま返ってきました。同じ S3 バケット、同じドキュメント、同じユーザーで結果が分かれたので、差分は ACL 設定だけです。
ステップ11: ACL 設定は後から変更できない
ドキュメントには「ACL 設定は永続的」と書かれています。API でどう表現されているかを確認しました。
まず、ACL 有効のナレッジベースを無効にしてみます。
aws quicksight update-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683 \
--access-control-configuration isACLEnabled=false
An error occurred (InvalidRequestException) when calling the UpdateKnowledgeBase operation: Update request must include at least one of: name, description, media extraction configuration, knowledge base configuration or isEmailNotificationOptedForIngestionFailures.
更新可能な項目の一覧に access control configuration が含まれていません。--access-control-configuration は受け付けられますが、更新対象としては数えられていないということです。
次に、名前の変更と同時に指定してみます。
aws quicksight update-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683 \
--name "quick-acl-blog-global-acl-renamed" \
--access-control-configuration isACLEnabled=false
An error occurred (InvalidRequestException) when calling the UpdateKnowledgeBase operation: ACL configuration in template did not match ACL configuration in request
今度はテンプレート側の crawlAcl: true と食い違っているというエラーになりました。名前の変更も適用されていません。
それならばとテンプレート側の crawlAcl を false にして更新してみます。kb-a-disable-crawlacl.json は、kb-a-global.json の crawlAcl を false にして aclConfigurationFilePath を削除したものです。
aws quicksight update-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683 \
--knowledge-base-configuration file://kb-a-disable-crawlacl.json
An error occurred (InvalidRequestException) when calling the UpdateKnowledgeBase operation: ACL enablement cannot be changed
ACL enablement cannot be changed という明快なエラーでした。ACL 無効のナレッジベースを有効にする方向でも同じです。ドキュメントの記述どおり、作成時に決めた ACL の有無は後から変えられません。
エラーメッセージから分かるもう1つのポイントは、--knowledge-base-configuration でナレッジベースを更新するときには、テンプレート内の ACL 設定と --access-control-configuration を一致させる必要があるということです。ACL 有効のナレッジベースでプレフィックスだけを変えたい場合も、次のように両方を渡します。
aws quicksight update-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683 \
--knowledge-base-configuration file://kb-a-global.json \
--access-control-configuration isACLEnabled=true
ステップ12: ACL を更新して反映させる
権限を変更するには ACL ファイルを更新して再同期します。global/deny/ の設定を DENY から ALLOW に書き換えました。
aws s3 cp acl.json s3://quick-acl-blog-1b5c2683/acl/acl.json
ここで問題になったのが再同期の方法です。aws quicksight には 2026年9月11日時点でナレッジベースの同期を開始するコマンドがありません。create-ingestion はデータセット用で --data-set-id が必須です。
同じ設定で update-knowledge-base を実行すれば再同期が走るかと考えて試しましたが、6分間ポーリングしても新しい取り込みは始まりませんでした。LatestIngestionSummary の IngestionId は初回のままです。
結局、コンソールのナレッジベース詳細画面にある [Sync now] ボタンから手動同期を実行しました。所有者権限のアクションとしては quicksight:CreateKnowledgeBaseIngestion が存在するので、CLI 側の対応は今後に期待したいところです。
なお、CLI で設定した ACL の内容はコンソールの Advanced settings にそのまま表示されます。

手動同期は2分ほどで完了しました。同期レポートを見ると、ACL だけを変更したドキュメントの扱いが分かります。

exec-compensation.md が MODIFIED、ACL を変えなかった holiday-policy.md は UNMODIFIED でした。ファイル本体は一切変更していないので、ACL の変更だけでドキュメントが更新扱いになることが分かります。
ステップ13: Permission Checker でアクセス可否を確認する
同期レポートの各ドキュメントには「View Access Details」があり、Permission Checker でユーザー単位のアクセス可否を確認できます。ACL 更新前の exec-compensation.md で試した結果がこちらです。

does not have access to this document と表示され、アクセスできるのは quick-acl-blog-group だけであることも確認できます。
ACL を ALLOW に更新して同期した後は、同じ画面の表示が変わりました。

has access to this document に変わり、Users and group membership も自分のメールアドレスに置き換わっています。インデックス上は権限が更新されたということです。
ところが、同じタイミングでチャットに聞くと結果が違いました。

同期完了から約7分後、新しい会話で聞いても「アクセス権限がないためご提供することができません」と拒否されたままでした。Permission Checker の表示とチャットの挙動にずれがあります。
ドキュメントには「Quick は ID とドキュメント権限の変更をナレッジベースのリフレッシュスケジュール(デフォルトは24時間ごと)で同期する」と書かれています。今回は検証時間の都合でこれ以上は追えませんでしたが、少なくとも「ACL ファイルを更新して手動同期すれば即座にチャットに反映される」とは考えないほうがよさそうです。
考察
検証して分かったことを整理します。
ACL は取り込み段階と検索段階の2層で効く
ACL エントリのないドキュメントはそもそもインデックスされません。これは「うっかり機密文書を取り込んでしまう」事故を防ぐ設計です。一方で、ACL エントリはあるが DENY のドキュメントはインデックスされたうえで検索時にフィルタされます。前者は同期レポートの FAILED として、後者は Permission Checker で確認できます。
権限の反映にはタイムラグがある
ACL ファイルの更新は次回の同期まで反映されません。加えて今回は、同期完了後も約7分間チャットの回答が変わりませんでした。アクセス権の剥奪を急ぐ場面では、ACL ファイルの更新だけに頼らず、ナレッジベースの共有設定を外すといった別の手段も併用する必要があります。ナレッジベースの共有とドキュメントのアクセス権は別の制御である点はドキュメントにも明記されています。
ACL の有無は作り直し以外で変えられない
ACL enablement cannot be changed というエラーが示すとおり、後から方針を変えるにはナレッジベースを作り直すことになります。インデックスの再構築には時間もインデックスキャパシティもかかるため、最初に ACL を有効にしておくか、検証用のナレッジベースで試してから本番を作るのが安全です。
ACL ファイル自体の保護を忘れない
ACL ファイルを書き換えられる人は、自分に任意のドキュメントへのアクセスを与えられます。AWS のブログでも、ACL ファイルへの s3:PutObject を限られた管理者に絞ることと、S3 バージョニングで変更履歴を残すことが推奨されています。今回は検証用に同じバケットへ置きましたが、実運用では ACL ファイルの書き込み権限を分離すべきです。
運用上の注意点
公式ドキュメントには、今回の検証では扱わなかった制限もいくつか挙げられています。
- ACL 有効のナレッジベースは Quick Research と互換性がない
- 同一ネームスペース内で同じメールアドレスを複数ユーザーが共有している場合、そのメールアドレスの全員がアクセスを拒否される
- ACL はナレッジベース作成者のネームスペース内で解決される
- メールアドレスの大文字小文字は区別されない
CLI で完結しない部分がある
ナレッジベースの作成・設定確認・削除は CLI で完結しますが、手動同期と ACL 検証(Permission Checker)はコンソールが必要でした。IaC でナレッジベースを管理する場合、初回同期は自動で走るものの、その後の運用オペレーションはコンソールと併用することになります。
よくある質問
Q. グローバル ACL 方式とメタデータ方式の設定方法の使い分けは?
A. 今回の検証では、グローバル ACL ファイルとメタデータファイルで挙動の差はありませんでした。選択基準は運用側にあります。権限変更時の再インデックス範囲がプレフィックス全体か対象ドキュメントのみか、という違いです。ドキュメント数が多い環境でグローバル ACL ファイルを使う場合、1行の変更が大規模な再インデックスを引き起こす点は意識しておく必要があります。
Q. 設定から漏れたときのは?
A. ドキュメントレベル ACL は、そのナレッジベースの中で誰にどの文書を返すかを決める仕組みです。S3 のオブジェクト ACL のようにファイル自体へ権限が張り付くわけではなく、ナレッジベースが取り込み時に読み込んで初めて効きます。ステップ10 で ACL 無効のナレッジベースから DENY 設定のドキュメントを読み出せたのは、そのナレッジベースが ACL を読んでいないためです。
つまり、ナレッジベースを作成できるユーザーであれば、同じバケットに対して ACL なしのナレッジベースを作り、機密文書を含めて全件を読み出せます。ドキュメントレベル ACL を設定しただけでは、この経路は塞げません。
公式ドキュメントでは、Quick の IAM ポリシー割り当てを使って、ナレッジベースの作成に使える S3 バケットをユーザーやグループ単位で制限する方法が案内されています。ACL 対応のナレッジベースを含めて、誰がどのバケットからナレッジベースを作れるかを制御できます。Quick 経由で割り当てた IAM ポリシーは、AWS のリソースレベルのポリシーより優先されます。
Q. ALLOW と DENY の設定がコンフリクトしたときの挙動は?
A. 同一のユーザーまたはグループに対して、同じドキュメントまたはプレフィックスへ ALLOW と DENY の両方が指定されている場合、DENY が優先されます。チーム全体に広く ALLOW を与えたうえで、特定のドキュメントやフォルダだけを DENY で絞り込む、という使い方が想定されています。ACL の構成全体を組み直さずに例外を作れます。
そもそも ACL 設定に列挙されていないプレフィックスやドキュメントは、DENY と書かなくても拒否されます。今回の検証で uncontrolled-memo.md と orphan-note.md が取り込まれなかったのは、この挙動によるものです。
ただし、この優先順位が明記されているのは AWS のブログで、ユーザーガイド側には記述が見当たりません。また今回の検証では、DENY を設定した自分と ALLOW を設定した quick-acl-blog-group が重なっていないため、同一ユーザーに ALLOW と DENY が同時に当たるケースは試していません。
最後に
Amazon Quick の S3 ナレッジベースにおけるドキュメントレベル ACL を、AWS CLI とコンソールの両方から試しました。
同じバケットの同じドキュメントに対して、ACL 有効のナレッジベースでは DENY したドキュメントの内容が返らず、ACL 無効のナレッジベースでは返るという差を確認できました。ACL エントリのないドキュメントはそもそも取り込まれず、その理由も同期レポートに明示されます。
機密度の異なる資料が同じバケットに同居している環境では、これまでナレッジベースを分けるしかありませんでした。今回のアップデートで、1つのナレッジベースにまとめたまま人単位・グループ単位の出し分けができるようになります。
一方で、ACL の有効化は作成時にしか決められず、権限変更の反映にもタイムラグがあります。まずは検証用のナレッジベースで自組織の権限構造を ACL ファイルに落とし込み、Permission Checker で意図した通りに解決されるかを確認してから本番に展開するのが確実です。







