0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Bedrock ナレッジベースで Entra ID の部署ごとに検索範囲を絞る

0
Last updated at Posted at 2026-10-13

はじめに

社内向け RAG を Amazon Bedrock ナレッジベースで作ると、ほぼ必ず「人事部の資料は人事部だけ、営業資料は営業部だけに見せたい」という要件が出てきます。社員の ID 基盤が Microsoft Entra ID なら、そこに登録された部署(department 属性)をキーに検索範囲を絞りたくなります。

この記事では、Entra ID の部署情報を使って Bedrock ナレッジベースの検索結果を部署ごとに絞り込む方法を、3つの実装パターンに分けて整理します。どのパターンにも共通する「部署情報をどこから、どう信頼して取るか」という前提と、運用でハマりやすい点もあわせて解説します。

結論:部署と文書の対応は3つのパターンから選ぶ

ナレッジベース自体は Entra ID を直接参照しません。アプリ側でユーザーの部署を確定し、それを検索リクエストに反映させる、という構造はどのパターンでも同じです。違いは「部署と文書の対応関係をどこで持つか」です。

パターン 部署と文書の対応をどこで持つか 向いているケース 主な注意点
A. メタデータフィルタリング 文書ごとの .metadata.json に部署名を書き、検索時にアプリがフィルタを強制 S3 に文書を置く一般的な構成。部署数が多すぎない フィルタはアプリが付け忘れると全件検索になる
B. マネージド KB の ACL 対応検索 + SharePoint SharePoint の権限(Entra のグループ)をそのまま使い、検索時はメールアドレスを渡すだけ 文書がもともと SharePoint にあり、権限管理も SharePoint でやっている ユーザーは常にメールで識別。S3/カスタムでは ACL が USER 単位のみ
C. 部署ごとにナレッジベースを分ける ナレッジベース(とデータソース)を部署単位で分割し、アプリが呼び先を選ぶ 部署数が少なく、混在を絶対に避けたい 部署横断の検索や共通文書の扱いが面倒。コストと運用対象が増える

bedrock-kb-entra-department.png

迷ったら、文書が S3 にあるならパターン A、SharePoint にあるならパターン B から検討する感じ。
実際、SharePoint採用ならAzureに寄せると思うので、AWSならパターンAかパターンCですかね。

前提:部署情報はサーバー側で、検証済みのトークンから取る

どのパターンでも、部署はブラウザから送られてきた値ではなく、バックエンドが検証した Entra ID のトークン(または Graph API の応答)から取ります。Bedrock ナレッジベースのドキュメント自身が、ACL 機能は認可ではなくフィルタであり、渡されたユーザー情報が本物かどうかは検証しないと明記しています。つまり「誰として検索するか」をナレッジベースの外側で担保しないと、いずれのパターンも違う部署の情報にアクセスできてしまいます。

ユーザの部署情報を取る方法は3つ

  1. ID トークンに department クレームを載せる:Entra ID の department は標準ではトークンに入らないため、Entra 側でクレームを追加します。設定方法は2つあります。
    • 管理センターの画面から:Microsoft Entra 管理センターで「Entra ID」→「エンタープライズ アプリ」→ 対象アプリ →「シングル サインオン」→「属性とクレーム」を編集し、ソースが user.department のクレームを追加します。作業には少なくとも「クラウド アプリケーション管理者」ロールが必要です。
    • クレームマッピングポリシーで:New-MgPolicyClaimMappingPolicy でポリシーを作り、New-MgServicePrincipalClaimMappingPolicyByRef でサービスプリンシパルに割り当てます(アプリ登録ではない点に注意)。ポリシーを割り当てると、画面からはクレームを編集できなくなります。
    • アプリ側で「改変されたトークン」を受け入れる設定が必須:これをしないと AADSTS50146 エラーでサインインできません。シングルテナントのアプリはマニフェストの acceptMappedClaims を true にします(トークンの audience がアプリ自身の ID か検証済みドメインである必要あり)。マルチテナントのアプリはカスタム署名キーを設定し、トークン検証時はメタデータ URL に ?appid={client-id} を付けて鍵を取得します。
  2. Microsoft Graph API で取る:サインイン後に GET /me?$select=department,mail を呼びます。トークン設定をいじらずに済み、部署変更も次回呼び出しで反映されます。
  3. IAM Identity Center の Identity Store から取る(SCIM 同期):Entra ID から IAM Identity Center に SCIM でユーザーを同期していれば、department も enterprise 拡張属性として同期されます。バックエンドは identitystore:GetUserId でメールアドレスからユーザー ID を引き、DescribeUser に Extensions: ["aws:identitystore:enterprise"] を付けて呼ぶと department が返ります。IAM 権限だけで呼べ、Microsoft 側のアプリ権限は不要です。
取り方 鮮度 必要な準備 向いているケース
ID トークンのクレーム サインイン時点 クレームマッピングポリシー 最もシンプルにしたい
Graph API 呼び出し時点 Graph の User.Read など Entra 中心の構成。兼務などグループ情報も使いたい
Identity Store API(SCIM 同期) 同期間隔ぶん遅れる Identity Center + SCIM、IAM 権限 Identity Center をすでに運用している。AWS 側の ID に揃えたい

Entra ID でサインインする自前アプリでは、1 か 2 が一般的です。個人的には手を加える範囲がAWSに寄せられる3が良いのですが、3 を選ぶ場合は、SCIM の挙動に注意してください。Entra のプロビジョニングは定期実行のため、異動の反映が遅れます。また、Entra ID で department を空にしても Identity Center 側の値は削除されず、ネストしたグループのメンバーも同期されません。

部署名の表記ゆれ(「営業部」と「営業本部」など)は、そのままメタデータの不一致になります。Entra ID 側の値を正とし、文書側のメタデータもその値に揃える必要があります。

バックエンドの最小構成

bedrock-kb-entra-department.png

  • フロントエンドは Bedrock を直接呼ばない。Retrieve 権限を持つ IAM ロールはバックエンドだけに付ける
  • バックエンドはトークンの署名・発行者・audience を検証してから、部署やメールアドレスを取り出す
  • 検索条件(フィルタや userContext)はバックエンドがコードで組み立て、クライアントからの値で上書きさせない

ブラウザが持ってくるのはトークンだけで、部署の確定と検索条件の組み立てはすべてバックエンド(図では API Gateway + Lambda)の中で完結させます。図はパターン A(S3 + メタデータ)の例です。

パターンA:メタデータフィルタリング

文書に部署名のメタデータを付け、検索のたびにバックエンドが「この部署の文書だけ」というフィルタを強制する方法です。AWS 公式ブログでも、メタデータフィルタをアクセス制御に使う構成が紹介されています(認証は Amazon Cognito の例)。

1. 文書にメタデータを付ける

S3 データソースでは、文書と同じ場所に ファイル名.拡張子.metadata.json を置きます。たとえば sales/price-list.pdf なら sales/price-list.pdf.metadata.json です。

{
  "metadataAttributes": {
    "department": "営業部"
  }
}

全社公開の文書には "department": "全社" のような共通値を付けておきます。メタデータを追加・変更したらデータソースの同期が必要です。

数万ファイル規模では metadata.json を自動生成する

文書が数万件になると、.metadata.json を手で書くのは現実的ではありません。部署ごとにプレフィックス(sales/、hr/ など)を分けて文書を置き、S3 のアップロードイベントで Lambda を動かして、パスから部署を判定した metadata.json を書き出します。人が管理するのはフォルダだけになります。

import json, urllib.parse, boto3

s3 = boto3.client("s3")
DEPT_BY_PREFIX = {"sales/": "営業部", "hr/": "人事部", "common/": "全社"}

def handler(event, context):
    for r in event["Records"]:
        bucket = r["s3"]["bucket"]["name"]
        key = urllib.parse.unquote_plus(r["s3"]["object"]["key"])
        if key.endswith(".metadata.json"):
            continue  # 自分が書いたファイルで再発火しないように
        dept = next((d for p, d in DEPT_BY_PREFIX.items() if key.startswith(p)), None)
        if dept is None:
            continue  # 部署不明の文書にはメタデータを付けない
        s3.put_object(
            Bucket=bucket,
            Key=f"{key}.metadata.json",
            Body=json.dumps({"metadataAttributes": {"department": dept}}, ensure_ascii=False),
            ContentType="application/json",
        )
  • 部署不明の文書:metadata.json のない文書は、部署フィルタ付きの検索では誰にも返されません。安全側に倒れますが、定期的に「メタデータなし」の文書を洗い出してください
  • 部署名の変更は重い:部署名やフォルダ構成を変えると、配下の metadata.json をすべて書き直して再同期する必要があります。値には表示名より変わりにくい部署コードを使うのがおすすめです
  • サイズ上限:metadata.json は1ファイル 10 KB までです

文書を取り込むパイプラインを自前で持っているなら、カスタムデータソースの IngestKnowledgeBaseDocuments で、文書と一緒に部署をインライン属性として渡す方法もあります。サイドカーファイルが不要になり、部署は元の文書管理システムから引いて渡せます。

なお、1つのナレッジベースに部署ごとのデータソース(取り込み対象プレフィックスを部署ごとに指定)を作り、データソース ID で絞る方法も考えられます。ただしこのフィルタが効かなかったという報告が AWS re:Post にあり、公式ドキュメントでも動作を確認できなかったため、部署の分離に使うなら事前の検証が必須です。

2. 検索時にフィルタを強制する

バックエンドで、トークンから取った部署と「全社」の OR 条件を組み立てて Retrieve に渡します。

import boto3

client = boto3.client("bedrock-agent-runtime", region_name="ap-northeast-1")
KB_ID = "XXXXXXXXXX"

def retrieve_for_user(query: str, department: str):
    if not department:
        # 部署が取れないユーザーはフィルタなしで検索させない
        raise PermissionError("department is required")

    dept_filter = {
        "orAll": [
            {"equals": {"key": "department", "value": department}},
            {"equals": {"key": "department", "value": "全社"}},
        ]
    }

    return client.retrieve(
        knowledgeBaseId=KB_ID,
        retrievalQuery={"text": query},
        retrievalConfiguration={
            "vectorSearchConfiguration": {
                "numberOfResults": 5,
                "filter": dept_filter,
            }
        },
    )

RetrieveAndGenerate を使う場合も、retrieveAndGenerateConfiguration.knowledgeBaseConfiguration.retrievalConfiguration に同じフィルタを入れます。

3. 1つの文書を複数部署に見せたいとき

部署をリスト型の属性(例:"departments": ["営業部", "経営企画部"])で持ち、listContains で「ユーザーの部署を含む文書」を引く方法があります。ただし listContains や in は、ドキュメント上「Amazon OpenSearch Serverless で最もよくサポートされている」という扱いです。ベクトルストアに S3 Vectors や Aurora を使う場合は、事前に動作を確認してください。

このパターンの注意点

  • equals/orAll は多くのベクトルストアで使えますが、OpenSearch Serverless のインデックスが nmslib エンジンだとメタデータフィルタが使えません。faiss で作り直す必要があります
  • マネージド KB では startsWith と stringContains は非対応です
  • 「暗黙的フィルタ(implicitFilterConfiguration)」はモデルが質問文からフィルタを生成する機能です。ユーザーの入力に左右されるため、アクセス制御には使わないでください
  • 部署の異動はトークン/Graph 側で即時反映されますが、文書側の部署変更は同期が終わるまで反映されません

パターンB:マネージド KB の ACL 対応検索 + SharePoint

マネージド型のナレッジベース(ドキュメント上の名称は Bedrock Managed Knowledge Base)には、データソース側の権限を取り込んで検索結果を絞る「ACL 対応検索」があります。SharePoint コネクタは Entra ID のセキュリティグループも解決できるため、「部署 = Entra のグループ」にしてしまえば、部署単位の制御を SharePoint の権限設定に任せられます。

構成の流れ

  1. 部署ごとの動的グループを作る:Entra ID の動的メンバーシップルールで、(user.department -eq "営業部") のように department 属性からグループを自動生成します。動的グループには Entra ID P1 以上のライセンスが必要です。
  2. SharePoint の権限をグループに付ける:部署サイトやライブラリのアクセス権を、手順1のグループに付与します。
  3. データソースで ACL を有効化する:SharePoint データソースを作成し、ACL を有効にします。ACL を使うには認証方式を証明書ベースの ENTRA_ID_APP_ONLY にする必要があります(ユーザー委任型のセットアップでは ACL 非対応)。
  4. 検索時にユーザーのメールアドレスを渡す:Retrieve リクエストに userContext を付けます。
resp = client.retrieve(
    knowledgeBaseId=KB_ID,
    retrievalQuery={"text": query},
    userContext={"userId": user_email},  # 検証済みトークンから取ったメール
)

この方式で知っておくべき挙動

  • 2段階で絞り込む:同期時に取り込んだ ACL で事前フィルタし、さらに返却候補ごとに SharePoint へ問い合わせて現在の権限をリアルタイムに確認します。異動直後でも、権限を外せば同期を待たずに見えなくなります
  • ユーザーは常にメールで識別:userId はデータソース側のメールアドレスと完全一致が必要で、別名の解決はされません。UPN とメールが異なるテナントでは要注意です
  • フェイルクローズ:userContext なしの検索、ACL のない文書、グループ解決の失敗などはすべて「返さない」側に倒れます。ACL のある文書が 0 件になったら、まずメールの不一致を疑ってください
  • 件数が減ることがある:リアルタイム確認で除外された分は補充されないため、numberOfResults より少ない結果が返ることがあります
  • 大きなグループは反映に時間がかかる:同期完了後もしばらく見えない場合があります

S3 やカスタムデータソースではどうか

S3 とカスタムデータソースにも ACL 機能はありますが、ACL のエントリは Type: USER(メールアドレス単位)しか指定できず、リアルタイム確認もありません。部署で制御するには、取り込み時に「部署 → 所属メンバーのメール一覧」へ展開して書き込み、異動のたびに再同期する必要があります。S3 の文書を部署単位で絞るなら、パターン A の方が素直です。

確認できなかった点

userContext について、公式ドキュメントで確認できたのは Retrieve API での使い方です。RetrieveAndGenerate で同じように使えるかは確認できていないため、確実にやるなら Retrieve で取得した結果を自前でモデルに渡す構成にしてください。SDK のバージョンが古いと userContext パラメータ自体が使えない可能性もあります。

パターンC:部署ごとにナレッジベースを分ける

最も単純で、混在事故が起きにくいのが「部署ごとに別のナレッジベースを作る」方法です。バックエンドはトークンの部署を見て、呼び出す knowledgeBaseId を切り替えるだけです。

KB_BY_DEPT = {
    "営業部": "KBSALES000",
    "人事部": "KBHR000000",
}

kb_id = KB_BY_DEPT.get(department)
if kb_id is None:
    raise PermissionError("no knowledge base for this department")

さらに IAM ポリシーで、部署ごとのアプリやロールが呼べるナレッジベースの ARN を限定すれば、アプリのバグがあっても他部署の KB は呼べなくなります。

一方で、部署が増えるたびに KB・データソース・ベクトルインデックスが増え、コストと運用対象も比例して増えます。全社共通文書は各 KB に重複して入れるか、共通 KB を別に作って2回検索して結果をまとめる必要があります。部署数が数個程度で、情報の分離を最優先する場合に向いた方法です。

ハマりどころとセキュリティ上の注意

  • フィルタは「付け忘れ = 全件公開」:パターン A ではフィルタなしの Retrieve がそのまま全文書検索になります。Retrieve の呼び出しを1つの関数に集約し、”部署が空なら例外にする”をコードで強制してください
  • クライアントの値を信じない:部署やメールをリクエストボディやヘッダーで受け取る実装は、改ざんで他部署の検索ができてしまいます。必ず検証済みトークンか Graph API から取ります
  • 兼務・複数部署:Entra ID の department は単一の文字列です。兼務を表したい場合は、グループ所属(groups クレームや Graph の memberOf)を使う設計を検討してください
  • 部署名の変更:組織改編で部署名が変わると、文書側のメタデータや動的グループのルールも直す必要があります。表示名ではなく変わりにくいコード値(部署コードなど)を Entra の拡張属性に持たせると楽になります
  • 回答文にも注意:フィルタは検索結果を絞るだけです。会話履歴やエージェントのツール経由で別部署の情報が混ざらないよう、セッションをユーザー単位で分けてください
  • テストは「見えないこと」を確認する:自部署の文書が出ることだけでなく、他部署ユーザーで同じ質問をして出ないことをテストケースに入れます

まとめ

Bedrock ナレッジベースで Entra ID の部署ごとに情報を絞るには、バックエンドが検証済みトークンから部署を確定し、それを検索リクエストに必ず反映させる仕組みが土台になります。そのうえで、文書が S3 ならメタデータフィルタ(パターン A)、SharePoint なら ACL 対応検索と部署の動的グループ(パターン B)、分離を最優先するなら KB の分割(パターン C)を選ぶ流れです。

個人的にはパターンAだと力技な感じが否めないので、メタデータフィルタでもっとスマートに管理する方法を模索したいのと、SCIM同期を活用する道も探りたいです。

参考資料

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?