Azure AI SearchでRBAC+MSI利用時に403 Forbiddenになる原因と対処まとめ【Python対応】

Azure AI Search を RBAC(Azure AD / Entra ID)+マネージド ID(MSI)で使おうとしたとき、「インデックス作成で 403 Forbidden」「ドキュメント upload で Authorization failed」といったハマりを一気に解消するためのまとめです。Azure AI Foundry(旧 Azure AI Studio)のサーバーレス実行環境で MSI が見つからない場合の対処も含めて整理します。

目次

Azure AI Search × RBAC(MSI)で 403 Forbidden が起きる典型パターン

まず、よくあるパターンを整理しておきます。

  • Python SDK(azure-search-documents)で、インデックス作成時に HttpResponseError: Forbidden が出る。
  • 環境 A ではインデックス作成は通るのに、ドキュメントの upload(書き込み)で Authorization failed が出る。
  • Azure AI Foundry の「VM 自動選択(サーバーレス)」を使っていて、どのマネージド ID に RBAC をつければいいか分からない。

このあたりの症状は、だいたい次のいずれか(または複数)の組み合わせで起きます。

  • 認証トークンの扱いが誤っている
  • RBAC ロールの不足(特に「制御プレーン」と「データプレーン」の役割違い)
  • Search サービスの「API アクセス制御」がキーのみになっている
  • 誤った MSI が使われている(想定外の ID でトークンが出ている)
  • ネットワーク制限(FW / Private Endpoint)で 403 に見える

ここから、原因ごとに丁寧に潰していきます。

結論の整理:まずここを確認する

認証の渡し方:TokenCredential をそのまま渡す

Azure AI Search を RBAC(ID ベース認証)で使う場合、Python SDK では TokenCredential をそのままクライアントに渡すのが正しいやり方です。

  • OK:DefaultAzureCredential() を SearchIndexClient / SearchClient の credential 引数にそのまま渡す。
  • NG:get_token() で取得したアクセストークン文字列を AzureKeyCredential に詰め替える。

AzureKeyCredential は「API キー(管理キー / クエリ キー)」専用で、RBAC トークンを突っ込んでも 403 になります。Microsoft 公式ドキュメントでも、RBAC を使う場合は “Azure roles”+TokenCredential を推奨しており、キー認証とは別扱いです。

SDK を正しく使うと、トークン取得(get_token())は SDK 内部で勝手に行ってくれるので、アプリ側でトークンを意識する必要はありません。

REST 直叩きする場合のスコープ

一方、SDK ではなく REST API を直接叩く場合は、自分でトークンを取得する必要があります。そのときのスコープは以下が正解です。

  • 正しいスコープ:https://search.azure.com/.default
  • よくある間違い:https://cognitiveservices.azure.com/.default

https://cognitiveservices.azure.com/.default を使うと、Search サービスに対してはスコープが合わず 401 / 403 になります。Stack Overflow などでも、スコープを https://search.azure.com/.default に変更することで解消した事例が多数報告されています。

RBAC ロール不足:制御プレーンとデータプレーンを分けて考える

Azure AI Search には、代表的に次の 3 つの組み込みロールがあります。

ロール名用途典型的な操作例
Search Service Contributorサービス全体の管理(制御プレーン)インデックス作成・削除、インデクサー定義の作成・実行、データソース定義の管理など
Search Index Data Contributorインデックスへの読み書き(データプレーン・書き込み)ドキュメントの upload / merge / delete、検索実行など
Search Index Data Readerインデックスの読み取り(データプレーン・読み取り専用)検索クエリ、Suggest、Autocomplete など

これを踏まえると、次のように整理できます。

  • インデックス作成・削除・定義変更:Search Service Contributor が必要
  • ドキュメント upload / merge / delete:Search Index Data Contributor が必要
  • 検索専用クライアント:Search Index Data Reader で十分

よくあるのが「インデックス作成は通るが、ドキュメント upload でだけ 403 / Authorization failed」になるケースです。この場合、Search Service Contributor は付いているが、Search Index Data Contributor が不足しているパターンが多いです。

API アクセス制御が「キーのみ」になっている

Search サービスには、「API アクセス制御」という設定があります。ポータルの「キー」ページ(または「構成」)から確認でき、だいたい以下のようなモードを選べます。

  • API キーのみ
  • RBAC のみ(キーなし)
  • キー+RBAC(両方許可)

ここが「API キーのみ」の状態だと、いくら RBAC ロールを設定しても、ID ベース認証での呼び出しは全て拒否されます。つまり、DefaultAzureCredential やマネージド ID でトークンを取得しても、Search サービス側の設定で弾かれてしまい、403 Forbidden になります。

RBAC を使う場合は、最低でも「キー+RBAC」か「RBAC のみ」に切り替えてください。Terraform や CLI で設定する場合も、このフラグを忘れがちなので注意が必要です。

MSI の選択ミス:意図したマネージド ID を確実に使う

マネージド ID を複数使っている環境(ユーザー割り当て MSI を併用している VM / App Service など)では、「思っていたのと違う ID でトークンが発行されていた」というケースも頻発します。

Python の DefaultAzureCredential は、環境によって順番に様々な認証方式を試します。その中に「システム割り当て MSI」「ユーザー割り当て MSI」も含まれますが、どの MSI を使うかをアプリ側で明示することも可能です。

典型的な指定方法は次の 2 つです。

  • 環境変数 AZURE_CLIENT_ID にユーザー割り当て MSI の Client ID を設定する
  • DefaultAzureCredential(managed_identity_client_id="...") のように引数で指定する

こうしておくと、「思っていた別のサービスプリンシパルでトークンが出ていた」というミスを防げます。

Azure AI Foundry(サーバーレス)で MSI が見当たらない場合

Azure AI Foundry(旧 Azure AI Studio)のプロジェクトから Search を呼ぶとき、「VM 自動選択(サーバーレス)」を使うと、どの MSI にロールを付ければよいか分からなくなりがちです。

ポイントは、サーバーレスで実際に Search を叩いているのは「Notebook の VM」ではなく、Hub / Project リソースに紐づくマネージド IDだという点です。

一般的な手順は次のとおりです。

  1. Azure ポータルで該当の AI Hub または AI Project リソースを開く。
  2. 左メニューから 「ID(Identity)」 を開き、システム割り当て / ユーザー割り当てのマネージド ID を確認する。
  3. そのマネージド ID を、Search サービス側の「アクセス制御 (IAM)」で検索し、以下のロールを割り当てる。
    • Search Service Contributor(インデックス作成など制御プレーン用)
    • Search Index Data Contributor(ドキュメント書き込みなどデータプレーン用)
  4. Blob などストレージから取り込みを行う場合は、ストレージアカウント側にも
    • Storage Blob Data Reader / Contributor など適切なロール
    を同じマネージド ID に割り当てる。

こうすることで、サーバーレス Notebook から Search やストレージにアクセスする際、正しい ID と権限で動作させることができます。

ネットワーク制限:Private Endpoint や FW も疑う

最後に、Search サービスへアクセスできるネットワークが制限されている場合(IP 制限、VNet 統合、Private Endpoint 経由など)、認可エラーのように見えて実はネットワークでブロックされているケースがあります。

  • エンドポイント URL が https://<サービス名>.search.windows.net または .search.azure.com になっているか
  • 実行環境(VM / App Service / Azure AI Foundry サーバーレス)が、Search サービスの許可された IP / VNet に含まれているか
  • Private Endpoint を使っている場合、DNS 解決先が Private IP を向いているか

これらもあわせて確認しておくと安心です。

Python での正しいコード例(RBAC+MSI)

ここからは、実際に使える Python サンプルコードをベースに、RBAC+MSI での正しい書き方を確認します。

インデックス作成&ドキュメント upload のフルサンプル

# pip install azure-identity azure-search-documents

import os
from azure.identity import DefaultAzureCredential
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents.indexes.models import (
    SearchIndex, SimpleField, SearchableField, SearchFieldDataType
)
from azure.search.documents import SearchClient

# ★ユーザー割り当て MSI を使う場合のみ(不要ならこの2行は省略)
# os.environ["AZURE_CLIENT_ID"] = "ユーザー割り当てMSIのクライアントID"

endpoint = "https://my-search-service.search.windows.net"
index_name = "my_test_index"

# RBAC: TokenCredential をそのまま渡す(AzureKeyCredential は使わない)
credential = DefaultAzureCredential()

# --- インデックス作成(制御プレーン) ---
index_client = SearchIndexClient(endpoint=endpoint, credential=credential)

fields = [
    SimpleField(
        name="page_no",
        type=SearchFieldDataType.String,
        key=True,
        filterable=True
    ),
    SearchableField(
        name="text",
        type=SearchFieldDataType.String,
        sortable=True
    ),
]

index = SearchIndex(name=index_name, fields=fields)

# 冪等に作成/更新
index_client.create_or_update_index(index)

# --- ドキュメント投入(データプレーン) ---
docs = [
    {"page_no": "1", "text": "Hello"},
    {"page_no": "2", "text": "World"},
]

search_client = SearchClient(
    endpoint=endpoint,
    index_name=index_name,
    credential=credential,
)

result = search_client.upload_documents(docs)
print([r.succeeded for r in result])

このサンプルで重要なのは次のポイントです。

  • SearchIndexClient と SearchClient どちらにも 同じ TokenCredential を渡していること。
  • AzureKeyCredential を一切使っていないこと(API キーではなく ID ベース認証で動いている)。
  • ユーザー割り当て MSI を使う場合は、AZURE_CLIENT_ID でどの ID を使うか明示していること。

やってはいけない NG 例

よくある間違いパターンも併せて載せておきます。

from azure.identity import DefaultAzureCredential
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient

endpoint = "https://my-search-service.search.windows.net"
index_name = "my_test_index"

# ❌ NG パターン:get_token() で取ったトークンを AzureKeyCredential に詰める
token = DefaultAzureCredential().get_token("https://cognitiveservices.azure.com/.default")
credential = AzureKeyCredential(token.token)

search_client = SearchClient(
    endpoint=endpoint,
    index_name=index_name,
    credential=credential,
)

# ここで 403 Forbidden / Authorization failed になる
result = search_client.upload_documents([{"page_no": "1", "text": "bad example"}])

このように、スコープが誤っているうえに、トークンを AzureKeyCredential に誤用しているため、まず動きません。

  • RBAC を使うなら → TokenCredential をそのまま渡す。
  • API キーを使うなら → AzureKeyCredential に「キー」を渡す。

この線引きを徹底するだけで、多くの 403 を潰せます。

操作ごとに必要な RBAC ロールとシナリオ整理

ここで改めて、「どの操作にどのロールが必要か」を一覧で整理しておきます。

操作必要なロール補足
Search サービス設定の変更
インデックス作成・削除
インデクサー / データソース定義
Search Service Contributorサービス全体を管理する制御プレーン操作。
インデックスへのドキュメント書き込み
(upload / merge / delete)
Search Index Data Contributorデータプレーン書き込み。サービス単位またはインデックス単位でスコープを絞って付与可能。
インデックスの読み取り・検索実行Search Index Data Reader読み取り専用クライアント(フロントエンドの検索 UI など)に付けるとよい。
API キーでの利用RBAC 不要(キーを知っていれば利用可)ただし Search サービスの設定が「API キーを許可」になっている必要がある。

RBAC ベースで安全に運用するのであれば、原則として以下のような方針が推奨されます。

  • 管理系ツールやバッチ:Search Service Contributor + Search Index Data Contributor
  • バックエンド API:Search Index Data Contributor(書き込み系)/Reader(読み取り系)
  • フロントエンドの公開 Web:Search Index Data Reader のみ

Azure ポータルでの設定手順(RBAC+API アクセス制御)

Search サービスのアクセス制御(IAM)でロールを付与

Azure ポータルで、Search サービスに対して RBAC ロールを付与する手順は次のとおりです。

  1. 検索対象の Azure AI Search サービスを開く。
  2. 左メニューから 「アクセス制御 (IAM)」 を選ぶ。
  3. 「ロールの割り当てを追加」 をクリック。
  4. ロールとして
    • Search Service Contributor
    • Search Index Data Contributor(または Reader)
    を選択。
  5. 割り当て対象として
    • VM / App Service / Function のシステム割り当て MSI
    • ユーザー割り当て MSI
    • Azure AI Foundry の Hub / Project のマネージド ID
    を検索して指定。
  6. 確認して保存。

インデックス単位でアクセスを絞りたい場合は、/indexes/<index-name> まで scope を狭めることもできます(後述の CLI 例)。

Search サービスの「API アクセス制御」を RBAC 許可に変更

RBAC を使うのに忘れがちなのが、この設定です。

  1. Search サービスを開き、左メニューの 「キー」 または 「構成」 にある「API アクセス制御」を開く。
  2. モードが「API キーのみ」になっていないか確認。
  3. RBAC を使いたい場合は
    • 「API キーとロール(Both)」、または
    • 「ロールのみ(Roles)」
    に切り替える。

これを設定しないと、せっかくロールを付けてもトークンベースの呼び出しがすべて 403 になります。

ストレージから取り込む場合のロール

Blob ストレージなどから Search にデータを取り込む場合は、Search サービスのインデクサーがストレージにアクセスします。このとき、呼び出し主体(Search サービスのマネージド ID など)に対して、ストレージ側でも RBAC ロールを付与する必要があります。

  • 読み取りのみ → Storage Blob Data Reader
  • 書き込みも行う → Storage Blob Data Contributor など

どの ID にロールを付けるべきか分からないときは、Search サービスや AI Foundry プロジェクトの「ID」画面でマネージド ID の Object ID を確認し、その ID をストレージ側の IAM で検索してロールを付与します。

CLI でのロール割り当て例(スクリプト化向け)

ポータル操作の代わりに、Azure CLI でロール割り当てを自動化することもできます。

制御プレーン用ロール(インデックス作成など)

# Search Service Contributor を Search サービス全体に付与する例

ASSIGNEE_OBJECT_ID="00000000-0000-0000-0000-000000000000"  # MSI やアプリのオブジェクトID
SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"
RESOURCE_GROUP="my-resource-group"
SEARCH_SERVICE_NAME="my-search-service"

az role assignment create \
  --assignee-object-id "${ASSIGNEE_OBJECT_ID}" \
  --role "Search Service Contributor" \
  --scope "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.Search/searchServices/${SEARCH_SERVICE_NAME}"

データプレーン用ロール(ドキュメント書き込み)

# Search Index Data Contributor を Search サービス全体に付与

az role assignment create \
  --assignee-object-id "${ASSIGNEE_OBJECT_ID}" \
  --role "Search Index Data Contributor" \
  --scope "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.Search/searchServices/${SEARCH_SERVICE_NAME}"

特定のインデックスにだけ権限を絞りたい場合は、scope の末尾に /indexes/インデックス名 を付ければ OK です。

# 特定のインデックスだけに書き込み権限を付ける例

INDEX_NAME="my_test_index"

az role assignment create \
  --assignee-object-id "${ASSIGNEE_OBJECT_ID}" \
  --role "Search Index Data Contributor" \
  --scope "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.Search/searchServices/${SEARCH_SERVICE_NAME}/indexes/${INDEX_NAME}"

Azure AI Foundry から Search を使うときの設計ポイント

Azure AI Foundry で「自分のデータを追加」したり、Notebook から Search を操作したりする場合、認証方式の選択肢としては大きく 2 つあります。

方式メリットデメリット向いているケース
API キー(管理キー / クエリ キー)設定が簡単で、RBAC を意識しなくても動くキーが漏れるとサービス全体に影響、権限の絞り込みが難しい検証環境、サンプル、閉じたネットワーク内だけで使う用途
RBAC(マネージド ID / アプリ登録)キーをコードに書かずに済む、最小権限で細かく制御できる最初のセットアップが少し複雑(本記事の内容)本番環境、企業内の RAG / エンタープライズ検索など

本記事のように RBAC を使う場合は、Project / Hub リソースのマネージド ID に対して Search / ストレージ / OpenAI など必要なロールを付けていく形が基本です。

よくあるハマりどころチェックリスト

最後に、403 Forbidden / Authorization failed に遭遇したときに、上から順に確認していけるチェックリストをまとめます。

  • [ ] クライアントの credential に TokenCredential を渡しているか
    • SearchIndexClient(..., credential=DefaultAzureCredential()) のようになっているか。
    • AzureKeyCredential にトークン文字列を詰めていないか。
  • [ ] トークンのスコープが正しいか
    • REST 直叩き時は https://search.azure.com/.default を使っているか。
    • https://cognitiveservices.azure.com/.default など別サービス用のスコープを使っていないか。
  • [ ] 必要な RBAC ロールが付いているか
    • インデックス作成 → Search Service Contributor が付いているか。
    • ドキュメント書き込み → Search Index Data Contributor が付いているか。
    • 検索だけ → Search Index Data Reader で足りるか。
  • [ ] Search サービスの API アクセス制御が RBAC を許可しているか
    • 「API キーのみ」になっていないか。
    • RBAC を使う場合は「キー+ロール」または「ロールのみ」になっているか。
  • [ ] 正しい MSI / アプリが使われているか
    • AZURE_CLIENT_ID で意図したユーザー割り当て MSI を指定しているか。
    • Azure AI Foundry の場合は Hub / Project のマネージド ID にロールを付けているか。
  • [ ] ネットワーク的に Search に到達できているか
    • IP 制限や Private Endpoint の設定に実行環境が含まれているか。
    • エンドポイント URL をタイプミスしていないか。
  • [ ] 設定変更後に新しいトークンで試しているか
    • 古い資格情報やキャッシュされたトークンで再試行していないか(アプリ再起動・再ログイン済みか)。

ここまでのポイントを押さえておけば、Azure AI Search の RBAC+MSI 周りのトラブルシュートはかなりスムーズになるはずです。制御プレーンとデータプレーン、API キーと RBAC、実行主体(MSI/アプリ)の 3 つを意識して整理すると、原因の切り分けもしやすくなります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次