Amazon Bedrock Managed Knowledge Base の S3 ナレッジベースのドキュメントレベル ACL を Amazon Quick から検証してみた - ユーザーごとに機密文書を出し分ける

Amazon Bedrock Managed Knowledge Base の S3 ナレッジベースのドキュメントレベル ACL を Amazon Quick から検証してみた - ユーザーごとに機密文書を出し分ける

Amazon Quickのナレッジソースとして、Amazon Bedrock Managed Knowledge Baseを接続し、ドキュメントレベルのアクセスコントロールリスト(ACL)を試してみました。前回検証したQuick側のACL設定との違いや、Bedrock側でのACL定義・検証の方法をまとめます。
2026.09.14

クラウド事業統括本部の石川です。Amazon Quick のナレッジソースとして Amazon Bedrock Managed Knowledge Base を接続し、ドキュメントレベルのアクセスコントロールリスト(ACL)によるユーザー単位のアクセス制御を試してみました。

https://docs.aws.amazon.com/quick/latest/userguide/quick-byo-bedrock-kb.html

先日、Amazon Quick の S3 ナレッジベースそのものが持つドキュメントレベル ACL を検証しました。バケットやプレフィックス単位ではなく、個々のドキュメントに対してユーザー・グループ単位の ALLOW / DENY を設定できる機能です。

https://dev.classmethod.jp/articles/20260911-amazon-quick-s3-acl/

今回試したのは別の経路です。Amazon Quick は Amazon Bedrock Managed Knowledge Base を「持ち込みのナレッジソース」として接続できます。この場合、ACL の定義も評価も Bedrock 側で行われ、Quick はクエリ時にユーザーの ID を Bedrock へ渡す役割を担います。

同じ「S3 上のドキュメントに ACL を付けて出し分ける」という目的でも、設定する場所も使える機能も変わります。どこがどう違うのかを、同じ構成のドキュメントを参考にしました。

https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-acl.html

https://docs.aws.amazon.com/quick/latest/userguide/byo-bedrock-kb-acl.html

また、Amazon Bedrock Managed Knowledge Base に Amazon Quick から日本語で自然言語問合せする流れについては、以下のブログをご覧ください。

https://dev.classmethod.jp/articles/20260813-amazon-mkb-with-quick/

Bedrock Managed Knowledge Base のドキュメントレベル ACL とは

Bedrock Managed Knowledge Base は、ベクトルストアの用意やチャンキングの設定なしに使えるナレッジベースです。knowledgeBaseConfiguration.typeMANAGED を指定して作成します。

この Managed Knowlage Base では、データソースコネクタごとに ACL 対応の有無が分かれます。

コネクタ 事前フィルタ リアルタイム ACL 検証
SharePoint 対応 対応
OneDrive 対応 対応
Google Drive 対応 対応
Confluence 対応 対応
Amazon S3 対応 非対応
Custom 対応 非対応
Web Crawler 非対応

https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-acl.html

なお、今回検証したのは Amazon S3 コネクタだけです。この表と、以下のリアルタイム検証・ID モデルの説明は公式ドキュメントの記述によるもので、SharePoint や Google Drive の挙動は確認していません。

S3 と Custom がリアルタイム検証に非対応なのは、ACL の情報源が「利用者が用意した設定ファイル」だからです。SharePoint や Google Drive のように問い合わせ先の権限システムが存在しないため、取り込み時に読み込んだ内容がそのまま正となります。

ID の照合にはメールアドレスを使います。クエリ時に渡したメールアドレスと、ACL に書かれたメールアドレスが完全一致する必要があります。別名解決やIDプロバイダー間のマッピングは行われません。

ACL の定義方法は2つあります。

グローバル ACL 設定ファイル

S3 のキープレフィックスごとに ACL エントリを並べた JSON ファイルです。データソース作成時に aclConfiguration.globalAccessControlListS3Uri でこのファイルの S3 URI を指定します。

[
  {
    "keyPrefix": "s3://BUCKETNAME/finance/",
    "aclEntries": [
      {
        "Name": "user1@example.com",
        "Type": "USER",
        "Access": "ALLOW"
      }
    ]
  }
]

Name はユーザーのメールアドレス、TypeUSERAccessALLOW または DENY です。DENY が ALLOW に優先します。

ここが前回検証した Quick の S3 ナレッジベースとの最初の違いです。Quick 側の ACL では TypeGROUP を指定して Quick のグループ名を書けましたが、Bedrock の S3 コネクタではドキュメント上 USER のみとされています。この点は後のステップで実際に確かめます。

ドキュメント単位のメタデータファイル

ドキュメントごとに <ファイル名>.metadata.json を同じ S3 パスに置き、accessControlList に ACL エントリを書く方法です。

{
  "metadataAttributes": {},
  "accessControlList": [
    {
      "Name": "user1@example.com",
      "Type": "USER",
      "Access": "ALLOW"
    }
  ]
}

ドキュメントには「ドキュメント単位のメタデータはグローバル ACL ファイルより優先される」と明記されています。両方に設定がある場合はメタデータ側が使われます。

また、ACL エントリが関連付けられていないドキュメントは取り込まれません。この挙動は前回の Quick S3 ナレッジベースと同じです。

https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-ds-s3-acl.html

やってみた

前提条件

  • AWS アカウント: Amazon Quick の ENTERPRISE サブスクリプション済み(認証タイプは IDENTITY_POOL)
  • 検証リージョン: us-east-1
  • AWS CLI: aws-cli/2.36.40

リージョンには制約があります。Quick への Managed Knowlage Base 接続がサポートされるのは、バージニア北部・オレゴン・アイルランド・シドニーの4リージョンのみです。東京リージョンは含まれていません。加えて、Managed Knowlage Base と Quick インスタンスは同一リージョンである必要があります。

接続できる Managed Knowlage Base は Quick インスタンスあたり2件までです。

https://docs.aws.amazon.com/quick/latest/userguide/byo-bedrock-kb-limitations.html

検証の構成

1つの S3 バケットに8つのドキュメントと ACL 設定ファイルを配置し、ACL の有効・無効だけが異なる2つの Managed Knowlage Base を作成しました。

2つのナレッジベースの識別子は次の通りです。以降のコマンドではこの ID を使い分けます。

MKB-A(ACL 有効) MKB-B(比較用・ACL 無効)
ナレッジベース名 mkb-acl-blog-acl-987164eb mkb-acl-blog-noacl-987164eb
ナレッジベース ID O3VLSO3CS9 VZCVVT2YJL
データソース名 s3-acl-connector s3-noacl-connector
データソース ID YBNLLH2UBM 61AKBSB7TR
aclEnabled true 指定なし(false

ドキュメントには識別用の管理番号を埋め込んであります。ACL の設定は次の通りです。other-user@example.com は Quick に存在しないダミーのメールアドレスで、DENY の対比として使います。

ドキュメント 管理番号 定義場所 自分 other-user
global/allow/holiday-policy.md ALPHA-GLOBAL-ALLOW グローバル ACL ALLOW -
global/deny/exec-compensation.md BRAVO-GLOBAL-DENY グローバル ACL DENY ALLOW
global/noacl/uncontrolled-memo.md CHARLIE-GLOBAL-NOACL なし - -
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 なし - -
group/board-minutes.md GOLF-GROUP-TYPE メタデータ(Type: GROUP) - -
override/override-doc.md HOTEL-OVERRIDE グローバル DENY + メタデータ ALLOW 競合 -

group/Type: GROUP を書いたときの挙動を見るため、override/ はグローバルとメタデータが競合したときの優先順位を見るために用意しました。

docs
├── acl
   └── global-acl.json
├── global
   ├── allow
   └── holiday-policy.md
   ├── deny
   └── exec-compensation.md
   └── noacl
       └── uncontrolled-memo.md
├── group
   ├── board-minutes.md
   └── board-minutes.md.metadata.json
├── meta
   ├── allow
   ├── incident-report.md
   └── incident-report.md.metadata.json
   ├── deny
   ├── ma-valuation.md
   └── ma-valuation.md.metadata.json
   └── noacl
       └── orphan-note.md
└── override
    ├── override-doc.md
    └── override-doc.md.metadata.json

ステップ1: S3 にドキュメントと ACL ファイルを配置

グローバル ACL ファイルを global-acl.json という名前で用意し、acl/ プレフィックスに配置します。内容は次の通りです。meta/ 配下はメタデータファイルで定義するため、ここには書きません。override/ は DENY にしておき、メタデータ側の ALLOW と競合させます。データソース作成時に globalAccessControlListS3Uri で S3 URI を指定します。

[
  {
    "keyPrefix": "s3://mkb-acl-blog-987164eb/global/allow/",
    "aclEntries": [
      { "Name": "xxxxxxxxxx@classmethod.jp", "Type": "USER", "Access": "ALLOW" }
    ]
  },
  {
    "keyPrefix": "s3://mkb-acl-blog-987164eb/global/deny/",
    "aclEntries": [
      { "Name": "xxxxxxxxxx@classmethod.jp", "Type": "USER", "Access": "DENY" },
      { "Name": "other-user@example.com", "Type": "USER", "Access": "ALLOW" }
    ]
  },
  {
    "keyPrefix": "s3://mkb-acl-blog-987164eb/override/",
    "aclEntries": [
      { "Name": "xxxxxxxxxx@classmethod.jp", "Type": "USER", "Access": "DENY" }
    ]
  }
]

group/board-minutes.md.metadata.json には、サポート外のはずの Type: GROUP を含めました。

{
  "metadataAttributes": {},
  "accessControlList": [
    { "Name": "mkb-acl-blog-group", "Type": "GROUP", "Access": "ALLOW" },
    { "Name": "xxxxxxxxxx@classmethod.jp", "Type": "USER", "Access": "ALLOW" }
  ]
}

バケットを作成してアップロードします。

% aws s3api create-bucket --bucket mkb-acl-blog-987164eb --region us-east-1
% aws s3 sync docs/ s3://mkb-acl-blog-987164eb/ --region us-east-1
% aws s3 ls s3://mkb-acl-blog-987164eb/ --recursive --region us-east-1
2026-09-12 00:20:59        621 acl/global-acl.json
2026-09-12 00:20:59        460 global/allow/holiday-policy.md
2026-09-12 00:20:59        435 global/deny/exec-compensation.md
2026-09-12 00:20:59        332 global/noacl/uncontrolled-memo.md
2026-09-12 00:20:59        273 group/board-minutes.md
2026-09-12 00:20:59        219 group/board-minutes.md.metadata.json
2026-09-12 00:20:59        423 meta/allow/incident-report.md
2026-09-12 00:20:59        145 meta/allow/incident-report.md.metadata.json
2026-09-12 00:20:59        327 meta/deny/ma-valuation.md
2026-09-12 00:20:59        221 meta/deny/ma-valuation.md.metadata.json
2026-09-12 00:20:59        315 meta/noacl/orphan-note.md
2026-09-12 00:20:59        453 override/override-doc.md
2026-09-12 00:20:59        145 override/override-doc.md.metadata.json

ACL 設定ファイルは、データソースの対象バケットと同じバケットに置く必要があります。

ステップ2: IAM サービスロールを作成

Managed Knowlage Base では、ベクトルストア用の権限が不要になります。必要なのは Bedrock からの AssumeRole を許可する信頼ポリシーと、S3 データソースへの読み取り権限です。

信頼ポリシーを trust-policy.json として保存します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "bedrock.amazonaws.com" },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": { "aws:SourceAccount": "123456789012" },
        "ArnLike": { "AWS:SourceArn": "arn:aws:bedrock:us-east-1:123456789012:knowledge-base/*" }
      }
    }
  ]
}

権限ポリシーには s3:ListBuckets3:GetObject、それに埋め込みモデル呼び出しの権限を付けます。こちらは kb-permissions.json として保存します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "S3ListBucketStatement",
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::mkb-acl-blog-987164eb"],
      "Condition": { "StringEquals": { "aws:ResourceAccount": "123456789012" } }
    },
    {
      "Sid": "S3GetObjectStatement",
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3:::mkb-acl-blog-987164eb/*"],
      "Condition": { "StringEquals": { "aws:ResourceAccount": "123456789012" } }
    },
    {
      "Sid": "BedrockModelStatement",
      "Effect": "Allow",
      "Action": ["bedrock:ListFoundationModels", "bedrock:ListCustomModels", "bedrock:InvokeModel"],
      "Resource": "*"
    }
  ]
}

2つのファイルを使ってロールを作成します。

% aws iam create-role \
  --role-name mkb-acl-blog-role-987164eb \
  --assume-role-policy-document file://trust-policy.json \
  --query 'Role.Arn' --output text
arn:aws:iam::123456789012:role/mkb-acl-blog-role-987164eb

% aws iam put-role-policy \
  --role-name mkb-acl-blog-role-987164eb \
  --policy-name mkb-acl-blog-policy \
  --policy-document file://kb-permissions.json

ステップ3: Bedrock Managed Knowledge Base を作成

knowledgeBaseConfigurationtype: MANAGEDmanagedKnowledgeBaseConfiguration を渡します。embeddingModelTypeMANAGED にすると、サービス管理の埋め込みモデルが使われ、モデル ARN の指定もベクトルストアの構成も不要です。

% aws bedrock-agent create-knowledge-base \
  --name "mkb-acl-blog-acl-987164eb" \
  --description "ACL 有効の Bedrock Managed Knowlage Base (blog verification)" \
  --role-arn arn:aws:iam::123456789012:role/mkb-acl-blog-role-987164eb \
  --knowledge-base-configuration '{"type":"MANAGED","managedKnowledgeBaseConfiguration":{"embeddingModelType":"MANAGED"}}' \
  --region us-east-1
{
    "knowledgeBase": {
        "knowledgeBaseId": "O3VLSO3CS9",
        "name": "mkb-acl-blog-acl-987164eb",
        "knowledgeBaseArn": "arn:aws:bedrock:us-east-1:123456789012:knowledge-base/O3VLSO3CS9",
        "roleArn": "arn:aws:iam::123456789012:role/mkb-acl-blog-role-987164eb",
        "knowledgeBaseConfiguration": {
            "type": "MANAGED",
            "managedKnowledgeBaseConfiguration": {
                "embeddingModelType": "MANAGED"
            }
        },
        "status": "CREATING"
    }
}

比較用の ACL 無効ナレッジベースも同じコマンドで作成しました(ID は VZCVVT2YJL)。変えたのは --namemkb-acl-blog-noacl-987164eb にした点と --description だけで、--role-arn--knowledge-base-configuration は同一です。ACL の有無はナレッジベースではなく次のステップのデータソース側で決まるため、この時点では両者に違いはありません。

ステップ4: S3 データソースを作成(ACL 有効)

Managed Knowlage Base のデータソースは typeMANAGED_KNOWLEDGE_BASE_CONNECTOR を指定し、コネクタ固有の設定を connectorParameters に書きます。この設定を ds-acl.json として保存します。

{
  "type": "MANAGED_KNOWLEDGE_BASE_CONNECTOR",
  "managedKnowledgeBaseConnectorConfiguration": {
    "connectorParameters": {
      "type": "S3",
      "version": "1",
      "aclEnabled": true,
      "connectionConfiguration": {
        "bucketName": "mkb-acl-blog-987164eb",
        "bucketOwnerAccountId": "123456789012"
      },
      "aclConfiguration": {
        "globalAccessControlListS3Uri": "s3://mkb-acl-blog-987164eb/acl/global-acl.json"
      }
    }
  }
}

aclEnabledtrue にし、aclConfiguration.globalAccessControlListS3Uri にグローバル ACL ファイルの S3 URI を指定します。このパスを省略した場合はメタデータファイル方式のみになります。今回は両方式を1つのデータソースで併用しています。

% aws bedrock-agent create-data-source \
  --knowledge-base-id O3VLSO3CS9 \
  --name "s3-acl-connector" \
  --data-source-configuration file://ds-acl.json \
  --region us-east-1
{
    "dataSource": {
        "knowledgeBaseId": "O3VLSO3CS9",
        "dataSourceId": "YBNLLH2UBM",
        "name": "s3-acl-connector",
        "status": "CREATING",
        "dataSourceConfiguration": {
            "type": "MANAGED_KNOWLEDGE_BASE_CONNECTOR",
            "managedKnowledgeBaseConnectorConfiguration": {
                "connectorParameters": "{\"type\":\"S3\",\"aclEnabled\":true,\"aclConfiguration\":{\"globalAccessControlListS3Uri\":\"s3://mkb-acl-blog-987164eb/acl/global-acl.json\"},\"connectionConfiguration\":{\"bucketName\":\"mkb-acl-blog-987164eb\",\"bucketOwnerAccountId\":\"123456789012\"},\"filterConfiguration\":{\"maxFileSizeInMegaBytes\":\"500\"},\"version\":\"1\"}"
            }
        },
        "dataDeletionPolicy": "DELETE"
    }
}

レスポンスを見ると、connectorParameters は文字列として保持され、指定していない filterConfiguration.maxFileSizeInMegaBytes に既定値 500 が補われています。

ステップ5: S3 データソースを作成(ACL 無効)

対照実験の基準として、ACL を読まないデータソースを MKB-B に作ります。ACL 有効側との差は connectorParameters から aclEnabledaclConfiguration を落とすだけで、connectionConfiguration のバケットは同一です。ds-noacl.json として保存しました。

{
  "type": "MANAGED_KNOWLEDGE_BASE_CONNECTOR",
  "managedKnowledgeBaseConnectorConfiguration": {
    "connectorParameters": {
      "type": "S3",
      "version": "1",
      "connectionConfiguration": {
        "bucketName": "mkb-acl-blog-987164eb",
        "bucketOwnerAccountId": "123456789012"
      }
    }
  }
}

同じバケットを指したまま、ナレッジベースだけ MKB-B(VZCVVT2YJL)に向けて作成します。

% aws bedrock-agent create-data-source \
  --knowledge-base-id VZCVVT2YJL \
  --name "s3-noacl-connector" \
  --description "ACL 無効の S3 コネクタ(比較用)" \
  --data-source-configuration file://ds-noacl.json \
  --region us-east-1

このレスポンスでは aclEnabledfalse が明示的に入ります。

{"type":"S3","connectionConfiguration":{...},"filterConfiguration":{"maxFileSizeInMegaBytes":"500"},"aclEnabled":false,"version":"1"}

これで、同じ S3 バケットの同じドキュメントを、ACL を見るデータソースと見ないデータソースの両方から取り込む構成ができました。

ステップ6: 取り込み結果を比較する

両方のナレッジベースで取り込みを開始します。

% aws bedrock-agent start-ingestion-job \
  --knowledge-base-id O3VLSO3CS9 \
  --data-source-id YBNLLH2UBM \
  --region us-east-1

2分ほどで完了しました。結果に明確な差が出ます。

% aws bedrock-agent get-ingestion-job \
  --knowledge-base-id O3VLSO3CS9 --data-source-id YBNLLH2UBM \
  --ingestion-job-id FWZKF0FLMX --region us-east-1

{
    "status": "COMPLETE",
    "stats": {
        "numberOfDocumentsScanned": 8,
        "numberOfMetadataDocumentsScanned": 3,
        "numberOfNewDocumentsIndexed": 5,
        "numberOfDocumentsFailed": 3,
        "numberOfDocumentsSkipped": 0
    },
    "fail": [
        "[\"The sync completed with partial failures. Some documents could not be crawled.\"]"
    ]
}

ACL 無効のほうは次の通りです。

{
    "status": "COMPLETE",
    "stats": {
        "numberOfDocumentsScanned": 9,
        "numberOfMetadataDocumentsScanned": 4,
        "numberOfNewDocumentsIndexed": 9,
        "numberOfDocumentsFailed": 0
    }
}

数字を並べると違いがはっきりします。

MKB-A(ACL 有効) MKB-B(ACL 無効)
スキャンしたドキュメント 8 9
スキャンしたメタデータ 3 4
インデックスされた件数 5 9
失敗した件数 3 0

ACL 有効側のスキャン数が8、無効側が9なのは、acl/global-acl.json の扱いが違うためです。ACL 有効側では ACL 設定ファイルとして解釈されドキュメントから除外されますが、無効側ではただの JSON ファイルとして取り込み対象になります。

メタデータのスキャン数が3と4で分かれているのも同様で、Type: GROUP を含む board-minutes.md.metadata.json が ACL 有効側では有効なメタデータとして数えられていません。

取り込まれたドキュメントを確認します。

% aws bedrock-agent list-knowledge-base-documents \
  --knowledge-base-id O3VLSO3CS9 --data-source-id YBNLLH2UBM --region us-east-1

INDEXED    global/allow/holiday-policy.md
INDEXED    global/deny/exec-compensation.md
INDEXED    meta/allow/incident-report.md
INDEXED    meta/deny/ma-valuation.md
INDEXED    override/override-doc.md
 5

落ちた3件は次のドキュメントでした。

  • global/noacl/uncontrolled-memo.md(グローバル ACL に記載なし)
  • meta/noacl/orphan-note.md(メタデータファイルなし)
  • group/board-minutes.mdType: GROUP を指定)

ACL エントリのないドキュメントが取り込まれないことはドキュメント通りです。加えて、Type: GROUP を書いたドキュメントも取り込まれませんでした。同じメタデータファイル内に自分のメールアドレスの USER ALLOW も併記していましたが、それでも通りませんでした。エントリ単位で無視されるのではなく、ドキュメントごと弾かれる挙動です。

DENY を設定した exec-compensation.mdma-valuation.md は取り込まれています。ACL は「取り込むかどうか」と「検索時に返すかどうか」の2段階で効いており、DENY は後者で処理されます。

一方、ACL 無効のナレッジベースは9件すべて取り込まれました。

% aws bedrock-agent list-knowledge-base-documents \
  --knowledge-base-id VZCVVT2YJL --data-source-id 61AKBSB7TR --region us-east-1

INDEXED    acl/global-acl.json
INDEXED    global/allow/holiday-policy.md
INDEXED    global/deny/exec-compensation.md
INDEXED    global/noacl/uncontrolled-memo.md
INDEXED    group/board-minutes.md
INDEXED    meta/allow/incident-report.md
INDEXED    meta/deny/ma-valuation.md
INDEXED    meta/noacl/orphan-note.md
INDEXED    override/override-doc.md
 9

バケットに置いたファイルは全部で13個ですが、ここに並ぶのは9件です。差の4件は incident-report.md.metadata.json などのメタデータファイルで、ACL の有効・無効にかかわらずドキュメントとしてはインデックスされません。統計上も numberOfDocumentsScanned ではなく numberOfMetadataDocumentsScanned に計上されます。

acl/global-acl.json が9件目に入っているのは、これがメタデータファイルの命名規則(<ファイル名>.metadata.json)に合致しないためです。ACL を読まないデータソースからは、ただの JSON ファイルとして扱われます。

ステップ7: 取り込まれた ACL を確認する

get-ingested-document-acl で、インデックスに保存された ACL を確認できます。--document-id には S3 URI をそのまま渡します。

DENY を設定したドキュメントの結果です。

% aws bedrock-agent-runtime get-ingested-document-acl \
  --knowledge-base-id O3VLSO3CS9 --data-source-id YBNLLH2UBM \
  --document-id "s3://mkb-acl-blog-987164eb/global/deny/exec-compensation.md" \
  --region us-east-1
{
    "documentAcl": {
        "allowList": {
            "conditions": [
                {
                    "conditionOperator": "OR",
                    "users": [
                        { "id": "other-user@example.com", "type": "KNOWLEDGE_BASE" }
                    ]
                }
            ],
            "memberRelation": "AND"
        },
        "denyList": {
            "conditions": [
                {
                    "conditionOperator": "OR",
                    "users": [
                        { "id": "xxxxxxxxxx@classmethod.jp", "type": "KNOWLEDGE_BASE" }
                    ]
                }
            ],
            "memberRelation": "AND"
        }
    }
}

JSON に書いた ALLOW / DENY が allowListdenyList に分解されて保持されています。

ここで override/override-doc.md を見ると、優先順位の答えが出ます。このドキュメントはグローバル ACL ファイルで DENY、メタデータファイルで ALLOW を設定したものです。

% aws bedrock-agent-runtime get-ingested-document-acl \
  --knowledge-base-id O3VLSO3CS9 --data-source-id YBNLLH2UBM \
  --document-id "s3://mkb-acl-blog-987164eb/override/override-doc.md" \
  --region us-east-1
{
    "documentAcl": {
        "allowList": {
            "conditions": [
                {
                    "conditionOperator": "OR",
                    "users": [
                        { "id": "xxxxxxxxxx@classmethod.jp", "type": "KNOWLEDGE_BASE" }
                    ]
                }
            ],
            "memberRelation": "AND"
        }
    }
}

denyList が存在しません。グローバル側の DENY は取り込み時点で捨てられ、メタデータ側の ALLOW だけが残っています。「DENY が ALLOW に優先する」というルールは、あくまで同一の ACL 情報源の中での話です。情報源そのものが差し替わる場合は、メタデータファイルが勝ちます。

ステップ8: アクセス可否を確認する

check-ingested-document-acl を使うと、ユーザー単位の可否を直接問い合わせられます。前回検証した Quick の Permission Checker に相当する機能が、CLI から使えます。

自分のメールアドレスで5件を確認した結果です。

% for key in global/allow/holiday-policy.md global/deny/exec-compensation.md \
             meta/allow/incident-report.md meta/deny/ma-valuation.md \
             override/override-doc.md; do
    aws bedrock-agent-runtime check-ingested-document-acl \
      --knowledge-base-id O3VLSO3CS9 --data-source-id YBNLLH2UBM \
      --document-id "s3://mkb-acl-blog-987164eb/${key}" \
      --user-context "userId=xxxxxxxxxx@classmethod.jp" --region us-east-1
  done

global/allow/holiday-policy.md      {"hasAccess": true}
global/deny/exec-compensation.md    {"hasAccess": false}
meta/allow/incident-report.md       {"hasAccess": true}
meta/deny/ma-valuation.md           {"hasAccess": false}
override/override-doc.md            {"hasAccess": true}

DENY 側に ALLOW を与えた other-user@example.com では、結果が反転します。

global/allow/holiday-policy.md      {"hasAccess": false}
global/deny/exec-compensation.md    {"hasAccess": true}
meta/deny/ma-valuation.md           {"hasAccess": true}
override/override-doc.md            {"hasAccess": false}

ACL にまったく登場しないメールアドレスでも試しました。

% aws bedrock-agent-runtime check-ingested-document-acl \
  --knowledge-base-id O3VLSO3CS9 --data-source-id YBNLLH2UBM \
  --document-id "s3://mkb-acl-blog-987164eb/global/allow/holiday-policy.md" \
  --user-context "userId=unknown@example.com" --region us-east-1
{"hasAccess": false}

ALLOW リストに載っていなければ拒否されます。DENY を明示的に書く必要はありません。

ステップ9: retrieve で ACL の効き目を確認する

実際の検索で確認します。「役員報酬の改定率と休業日」という、ALLOW されたドキュメントと DENY されたドキュメントの両方に関わる質問を投げます。

% aws bedrock-agent-runtime retrieve \
  --knowledge-base-id O3VLSO3CS9 \
  --retrieval-query '{"text":"役員報酬の改定率と休業日について教えてください"}' \
  --user-context "userId=xxxxxxxxxx@classmethod.jp" \
  --region us-east-1

score=0.4403  global/allow/holiday-policy.md
ヒット件数: 1

休業日のドキュメントだけが返り、DENY した役員報酬のドキュメントは返りません。

同じ質問を other-user@example.com で実行すると、逆になります。

score=0.3646  global/deny/exec-compensation.md
ヒット件数: 1

userContext を省略するとどうなるかも確認しました。

% aws bedrock-agent-runtime retrieve \
  --knowledge-base-id O3VLSO3CS9 \
  --retrieval-query '{"text":"役員報酬の改定率と休業日について教えてください"}' \
  --region us-east-1

ヒット件数: 0

0件です。ドキュメントには「Retrieve リクエストで userContext を指定しない場合、ACL 有効のデータソースはゼロ件を返す」と明記されており、その通りの挙動でした。ACL の評価が行えない状況ではフィルタせずに返すのではなく、何も返さない設計です。

https://docs.aws.amazon.com/bedrock/latest/userguide/kb-test-retrieve-acl.html

ステップ10: ACL 無効のナレッジベースと比較する

ACL が本当に効いているのかを確かめるため、同じ質問を ACL 無効のナレッジベース(MKB-B、VZCVVT2YJL)に投げます。ここまでのステップと --knowledge-base-id が変わる点に注意してください。

% aws bedrock-agent-runtime retrieve \
  --knowledge-base-id VZCVVT2YJL \
  --retrieval-query '{"text":"役員報酬の改定率と休業日について教えてください"}' \
  --region us-east-1

score=0.6565  global/deny/exec-compensation.md
score=0.3101  global/allow/holiday-policy.md
score=0.1837  acl/global-acl.json
score=0.1686  group/board-minutes.md
score=0.1664  meta/deny/ma-valuation.md
ヒット件数: 5

userContext なしで5件返りました。同じバケット、同じドキュメント、同じクエリで結果が分かれたので、差は ACL 設定だけです。

この結果にはもう1つ見逃せない点があります。acl/global-acl.json 自体が検索結果に含まれています。ACL 定義ファイルには「誰がどのプレフィックスにアクセスできるか」がそのまま書かれているため、ACL 無効のナレッジベースを同じバケットに向けて作ると、権限設計の中身が検索できてしまいます。

ACL 無効のナレッジベースに userContext を渡した場合も試しましたが、結果は変わらず5件返りました。ドキュメントの記述通り、ACL 非対応のデータソースは通常通り結果を返します。

ステップ11: グローバルとメタデータの優先順位を検索で確認する

ステップ7では ACL の中身を見て優先順位を確認しました。検索でも同じ結果になるかを見ます。

% aws bedrock-agent-runtime retrieve \
  --knowledge-base-id O3VLSO3CS9 \
  --retrieval-query '{"text":"OVERRIDE-VALUE-7788 優先順位検証用ドキュメント"}' \
  --user-context "userId=xxxxxxxxxx@classmethod.jp" --region us-east-1

score=0.4346  override/override-doc.md
score=0.1608  meta/allow/incident-report.md
ヒット件数: 2

グローバル ACL では DENY だったにもかかわらず、ドキュメントが返りました。メタデータファイルの ALLOW が勝っています。

ステップ12: Amazon Quick に接続する

ここからは Quick 側の作業です。Quick に接続するのは ACL 有効の MKB-A(O3VLSO3CS9)だけにしました。接続できる Managed Knowlage Base は Quick インスタンスあたり2件までで、検証に使ったアカウントには別件の Bedrock 連携が既に1件あったためです。ACL 無効の MKB-B は Quick には登録せず、前のステップまでの CLI での比較にとどめています。

コンソールから作成します。Quick の画面左メニューの「さらに詳しく」から「ナレッジ」を開くと、接続できるアプリケーションの一覧が表示されます。

01-quick-knowledge-connectors

左上の「Amazon Bedrock Managed Knowledge Base」を選び、名前と Bedrock 側のナレッジベース ARN を入力します。

02-quick-connect-bedrock-kb-arn

ARN を入力すると警告が表示されました。

Amazon Quick needs access to this knowledge base before the connection can read from it.
An account administrator can add it under AWS resources.

接続がこのナレッジベースから読み取る前に、Amazon Quick はこのナレッジベースへのアクセスを必要とします。アカウント管理者が AWS リソース配下で追加できます。

「Add this knowledge base to AWS resources」のリンクから、Quick のサービスロールにこのナレッジベースへのアクセスを許可する画面へ遷移します。

03-quick-aws-resources-permission

Quick が Bedrock のナレッジベースに対して必要とするのは bedrock:Retrievebedrock:GetDocumentContent の2つです。この画面で登録すると、Quick が該当 ARN にスコープを絞った IAM ポリシーをサービスロールへ付与します。保存後、ナレッジベースの名前と説明を入力して作成しました。

作成されたナレッジベースの設定を確認します。

% aws quicksight describe-knowledge-base \
  --aws-account-id 123456789012 \
  --knowledge-base-id ced09646-bb81-4528-8bd0-89682a7c8ffc \
  --region us-east-1

Name        : mkb-acl-blog-acl-enabled
Type        : FULLY_MANAGED_KNOWLEDGE_BASE
Status      : ACTIVE
Template    : {"templateConfiguration": {"template": {"type": "BEDROCKFMKB", "retrievalScope": "ALL", ...}}}
ACL config  : {"isACLEnabled": false}

気になるのは isACLEnabledfalse である点です。前回検証した Quick の S3 ナレッジベースでは、ACL を有効にするためにこのフラグを true にする必要がありました。Managed Knowlage Base 経由の場合、ACL の定義も評価も Bedrock 側にあるため、Quick 側のフラグは false のままです。

ドキュメントにも「ACL サポートを有効にするために Amazon Quick 側での追加設定は不要です」と書かれており、この表示と整合します。

ステップ13: Quick のチャットで確認する

チャット画面のデータソース選択で、作成したナレッジベースを指定します。まず ALLOW を与えた ALPHA-GLOBAL-ALLOW を聞きます。

04-chat-global-allow

holiday-policy.md を引用して、休業日が表形式で返ってきました。

次に、自分を DENY にした BRAVO-GLOBAL-DENY を聞きます。

DENY したドキュメントは回答されない

管理番号 BRAVO-GLOBAL-DENY の文書に関する情報は見つかりませんでした。
該当の文書へのアクセスが制限されているか、現在利用可能なデータソースに含まれていない可能性があります。

お手数ですが、以下をご確認いただけますでしょうか:
- 管理番号が正しいかどうか
- 該当文書へのアクセス権限があるかどうか

改定率 12.5% は返りませんでした。Quick 側で ACL の設定を一切していないにもかかわらず、Bedrock 側の DENY がチャットの応答に効いています。

メタデータ方式の ALLOW / DENY と、優先順位の検証用ドキュメントを1つの質問でまとめて聞きました。

メタデータ方式と優先順位の確認

1) 管理番号 DELTA-META-ALLOW — インシデント報告書の発生日時
✅ 参照できました。 セキュリティインシデント報告書 #4210 の発生日時は 2026年8月24日 14時32分 です。

2) 管理番号 ECHO-META-DENY — 想定評価額
❌ 参照できませんでした。 該当の文書は見つかりませんでした。アクセスが制限されているか、利用可能なデータソースに含まれていない可能性があります。

3) 管理番号 HOTEL-OVERRIDE — 確認用の値
✅ 参照できました。 優先順位検証用ドキュメントに記載されている確認用の値は OVERRIDE-VALUE-7788 です。

CLI の retrieve と同じ結果です。グローバル ACL で DENY していた HOTEL-OVERRIDE は、メタデータファイルの ALLOW が効いて参照できています。

最後に、取り込まれなかった2件を聞きます。

取り込まれなかったドキュメント

CHARLIE-GLOBAL-NOACLGOLF-GROUP-TYPE はどちらも参照できませんでした。ACL エントリの書き漏れも、サポート外の Type: GROUP も、結果としては同じ「参照できない」に着地します。

ステップ14: コンソールから ACL を検証する

Bedrock のコンソールでデータソースの詳細を開くと、CLI で設定した ACL がそのまま表示されます。

データソースの ACL 設定

「アクセスコントロールリスト (ACL) をクロールする: 有効化済み」「グローバル ACL ファイル S3 URI」が確認できます。

同期履歴では、スキャン8・追加5・失敗3という内訳が一覧で見えます。

同期履歴

「警告を表示」で表示される内容は、全体に対する1行のメッセージのみでした。

The sync completed with partial failures. Some documents could not be crawled.

同期は部分的な失敗を伴って完了しました。一部のドキュメントをクロールできませんでした。

前回検証した Quick の S3 ナレッジベースでは、同期レポートにドキュメント単位の失敗理由(No ACL entries found for document in ACL-enabled data source.)が表示されました。今回の Managed Knowlage Base では、どのファイルがなぜ落ちたのかはこの画面からは分かりません。get-knowledge-base-documents で個別に問い合わせても、返るのは NOT_FOUND のみでした。

% aws bedrock-agent get-knowledge-base-documents \
  --knowledge-base-id O3VLSO3CS9 --data-source-id YBNLLH2UBM \
  --document-identifiers '{"dataSourceType":"S3","s3":{"uri":"s3://mkb-acl-blog-987164eb/group/board-minutes.md"}}' \
  --region us-east-1

  status: NOT_FOUND
  reason: (なし)

同じ画面の下部には「Document access control」があり、CLI の2つの API がそのまま GUI として使えます。

Check document access

Document ID とユーザーのメールアドレスを入れて「Check access」を押すと、判定が返ります。

ishikawa.satoru@classmethod.jp does not have access to this document.

「Get document access list」では、ドキュメントに設定された ACL の一覧が表示されます。override/override-doc.md で実行した結果です。

Get document access list

Access identifier Type Access
xxxxxxxxxx@classmethod.jp User Allow

エントリは1件だけです。グローバル ACL ファイルに書いた DENY はここにも現れません。CLI で確認した内容と一致します。

考察

検証して分かったことを整理します。

ACL の主戦場が Quick から Bedrock へ移る

Managed Knowlage Base を使う場合、ACL に関する設定は Quick 側に一切ありません。Quick の isACLEnabledfalse のままで、グローバル ACL ファイルもメタデータファイルも Bedrock のデータソース設定が参照します。Quick が担うのはクエリ時にユーザーの ID を渡すことだけです。

この構造には利点があります。同じ Bedrock ナレッジベースを Quick 以外のアプリケーションからも使う場合、ACL の定義を1か所にまとめられます。逆に、Quick のグループを使った制御はできなくなります。

S3 コネクタではグループが使えない

Type: GROUP を書いたドキュメントは取り込まれませんでした。同じメタデータファイルに USER の ALLOW を併記していても結果は同じです。前回の Quick S3 ナレッジベースでは Quick のグループ名を指定できたので、ここは明確な後退です。

グループ単位の制御を Bedrock Managed Knowlage Base で行いたい場合、SharePoint・OneDrive・Google Drive・Confluence のいずれかのコネクタを使うことになります。これらはグループのメンバーシップを接続先からクロールし、クエリ時にユーザーの所属を解決します。S3 に置いたファイルを対象にする限り、権限はメールアドレスの列挙で表現するしかありません。

メタデータファイルはグローバル ACL ファイルを上書きする

ドキュメントに記載のある通りですが、実際の挙動として確認できたのは収穫でした。denyList そのものが消えるため、「グローバルで広く DENY しておき、例外的に個別 ALLOW する」という運用が成立します。

ただし裏を返せば、メタデータファイルを1つ置くだけでグローバル側の制限を無効化できるということです。ACL ファイルの書き込み権限を絞っても、ドキュメントと同じ場所に .metadata.json を置ける人がいれば迂回できます。バケットへの書き込み権限そのものを設計対象にする必要があります。

ACL 定義ファイルが漏れる経路がある

ACL 無効のナレッジベースを同じバケットに向けて作ると、global-acl.json が検索結果に出てきます。この1ファイルには、どのプレフィックスに誰がアクセスできるかが列挙されています。機密文書そのものが読めなくても、組織の権限構造は読めてしまいます。

対策としては、ACL ファイルをデータソースの inclusionPrefixes から外すか、exclusionPrefixes で明示的に除外するのが素直です。ただし ACL ファイルはデータソースと同じバケットに置く必要があるため、バケットを分けるという選択はできません。

取り込み失敗の原因究明は前回より手間がかかる

前回の Quick S3 ナレッジベースでは、同期レポートにドキュメント単位の失敗理由が明示されました。今回の Managed Knowlage Base では、コンソールにも API にも「部分的な失敗」以上の情報がありません。ACL エントリの書き漏れを疑うにしても、どのファイルが落ちたのかは「S3 に置いたファイル一覧」と「インデックスされたファイル一覧」の差分を自分で取る必要があります。

同期履歴の画面には CloudWatch ログへのリンクがあるので、ログ配信を設定しておけば追える可能性はあります。今回は設定していなかったため確認できていません。

CLI で ACL を検証できるようになった

前回はコンソールの Permission Checker しか手段がありませんでしたが、今回は check-ingested-document-aclget-ingested-document-acl が CLI から使えます。CI で「意図した権限が実際に反映されているか」を機械的に検証できるようになった意味は大きいと思います。

ナレッジベースの作成だけ CLI で完結しない

Quick 側は、データソースの作成は CLI で通るのにナレッジベースの作成だけが拒否されます。エラーメッセージに「他の公開ナレッジベース API はこのコネクタタイプをサポートしています」とあるので、作成の対応は今後に期待したいところです。

よくある質問

Q. 前回の Quick S3 ナレッジベースの ACL と、どちらを使うべきですか?

A. 用途によります。両者の違いを整理すると次のようになります。

観点 Quick の S3 ナレッジベース Bedrock Managed Knowlage Base 経由
ACL の設定場所 Quick のナレッジベース Bedrock のデータソース
Quick 側の isACLEnabled true が必要 false のまま
Type: GROUP 使える 使えない(S3 コネクタ)
ACL の有効・無効の変更 作成後は変更不可 未検証
アクセス可否の検証 コンソールの Permission Checker CLI とコンソールの両方
取り込み失敗の理由 同期レポートにドキュメント単位で表示 全体の警告のみ
東京リージョン 利用可能 利用不可
他アプリからの再利用 Quick 専用 Bedrock を叩ける任意のアプリ

Quick だけで完結し、グループ単位の制御が必要なら前者です。Quick 以外のアプリケーションからも同じナレッジベースを使う予定があるなら後者が向きます。東京リージョンで使いたい場合は前者しか選べません。

Q. ACL が正しく設定されているかは、どう確認すればよいですか?

A. check-ingested-document-acl でユーザーごとの可否を、get-ingested-document-acl で取り込まれた ACL の中身を確認できます。どちらもインデックスに保存された内容を見るので、「書いたつもりの ACL」ではなく「実際に効いている ACL」が分かります。

ACL の設定ミスは検索時にエラーとして現れません。結果が減るか、ゼロになるだけです。クエリの結果が想定と違うときは、まずこの2つの API で ACL 自体を確認するのが早道です。

Q. ACL の設定を後から変更できますか?

A. ACL ファイルの内容変更については、影響するプレフィックスを再同期すれば反映されます。グローバル ACL ファイルを1行変えると、そのプレフィックス配下すべてが対象になります。変更頻度が高い権限はメタデータファイル側で持つほうが、再インデックスの範囲を小さく抑えられます。

一方、ACL そのものの有効・無効を後から切り替えられるかは、今回は検証していません。前回の Quick S3 ナレッジベースでは ACL enablement cannot be changed というエラーで拒否されました。Bedrock の Managed Knowlage Base では ACL 設定がデータソース側にあるため事情が異なる可能性はありますが、確認していないため断定は避けます。方針が固まっていない段階では、検証用のナレッジベースで試してから本番を作るのが安全です。

Q. 認証はどこまで Bedrock がやってくれますか?

A. 何もやりません。ドキュメントには繰り返し「ACL awareness is not authorization(ACL 認識は認可ではない)」と書かれています。Bedrock は渡されたメールアドレスが本物かどうかを検証しません。アプリケーション側で認証を済ませ、確認済みの ID を渡すことが前提です。

今回の構成では Quick がその役割を担っています。Quick にサインインしたユーザーの ID が Bedrock に渡るため、利用者が任意のメールアドレスを詐称することはできません。一方、retrieve API を直接叩けるアプリケーションを自作する場合は、userContext に何を入れるかを制御するのは実装者の責任になります。

最後に

Amazon Quick のナレッジソースとして Amazon Bedrock Managed Knowledge Base を接続し、S3 コネクタのドキュメントレベル ACL を AWS CLI とコンソールの両方から試しました。

同じバケットの同じドキュメントに対して、ACL 有効のナレッジベースでは DENY したドキュメントが返らず、ACL 無効のナレッジベースでは返るという差を、CLI の retrieve で確認できました。そのうち ACL 有効のほうを Quick に接続してチャットから聞くと、こちらでも DENY したドキュメントの内容は返りません。Quick 側で ACL の設定を一切していないにもかかわらず、Bedrock 側の権限がそのまま効いています。

前回検証した Quick の S3 ナレッジベースと比べると、グループが使えなくなる代わりに、ACL の検証が CLI でできるようになり、ナレッジベース自体を Quick 以外からも再利用できるようになります。どちらが優れているというより、ナレッジベースを Quick 専用の資産として持つか、複数のアプリケーションで共有する資産として持つかの違いが、そのまま ACL の設計場所の違いになって現れているように思います。

東京リージョンでの提供が待たれるところですが、Quick 以外からも同じナレッジベースを使う構成を検討している方は、権限の表現がメールアドレスの列挙に限られる点を踏まえて設計を始めるとよいと思います。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事