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だという点です。
一般的な手順は次のとおりです。
- Azure ポータルで該当の AI Hub または AI Project リソースを開く。
- 左メニューから 「ID(Identity)」 を開き、システム割り当て / ユーザー割り当てのマネージド ID を確認する。
- そのマネージド ID を、Search サービス側の「アクセス制御 (IAM)」で検索し、以下のロールを割り当てる。
- Search Service Contributor(インデックス作成など制御プレーン用)
- Search Index Data Contributor(ドキュメント書き込みなどデータプレーン用)
- Blob などストレージから取り込みを行う場合は、ストレージアカウント側にも
- Storage Blob Data Reader / Contributor など適切なロール
こうすることで、サーバーレス 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 ロールを付与する手順は次のとおりです。
- 検索対象の Azure AI Search サービスを開く。
- 左メニューから 「アクセス制御 (IAM)」 を選ぶ。
- 「ロールの割り当てを追加」 をクリック。
- ロールとして
- Search Service Contributor
- Search Index Data Contributor(または Reader)
- 割り当て対象として
- VM / App Service / Function のシステム割り当て MSI
- ユーザー割り当て MSI
- Azure AI Foundry の Hub / Project のマネージド ID
- 確認して保存。
インデックス単位でアクセスを絞りたい場合は、/indexes/<index-name> まで scope を狭めることもできます(後述の CLI 例)。
Search サービスの「API アクセス制御」を RBAC 許可に変更
RBAC を使うのに忘れがちなのが、この設定です。
- Search サービスを開き、左メニューの 「キー」 または 「構成」 にある「API アクセス制御」を開く。
- モードが「API キーのみ」になっていないか確認。
- 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など別サービス用のスコープを使っていないか。
- REST 直叩き時は
- [ ] 必要な 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 つを意識して整理すると、原因の切り分けもしやすくなります。

コメント