You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Azure AI Search索引级RBAC权限异常:无法创建指定索引

Azure Search索引级RBAC问题最小复现示例

您需要准备一个Azure AI Search服务以及两个账号:一个管理员账号,一个可在订阅、资源组、资源及索引级别修改权限的服务账号。

本Jupyter Notebook测试显示:当服务账号的角色被限定在索引级别时,创建该索引会被禁止,但删除该索引却被允许。这是不符合预期的行为,按常理服务账号应被允许创建其被授权的索引。

此情况是否属于Bug?

#%% md
## Azure Search索引级RBAC问题最小复现代码示例

您需要准备一个Azure AI Search服务以及两个账号:管理员账号,以及一个可在订阅、资源组、资源和索引级别修改权限的服务账号。

本测试代码显示:当服务账号的角色仅限定在索引级别时,创建该索引会被拒绝,但删除该索引却被允许。这是不符合预期的行为,按常理服务账号应该被允许创建其已获授权的索引。
#%%
import dotenv
import os

dotenv.load_dotenv(".env.rbac-bug")

ENDPOINT = os.getenv("ENDPOINT")
INDEX_NAME = os.getenv("INDEX_NAME")
SUBSCRIPTION = os.getenv("SUBSCRIPTION")
RESOURCE_GROUP = os.getenv("RESOURCE_GROUP")
SEARCH_SERVICE = os.getenv("SEARCH_SERVICE")
SERVICE_PRINCIPAL = os.getenv("SERVICE_PRINCIPAL")
PASSWORD = os.getenv("PASSWORD")
TENANT_ID = os.getenv("TENANT_ID")


#%%
from azure.identity import AzureCliCredential
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents.indexes.models import SearchIndex
from azure.core.exceptions import ResourceNotFoundError

def get_search_index_client(endpoint):
    credential = AzureCliCredential()
    !az account show
    # 创建SearchIndexClient实例
    return SearchIndexClient(endpoint=endpoint, credential=credential)

# 检查索引是否存在,不存在则创建
def ensure_index_exists(index_name, search_index_client):
    try:
        # 尝试获取索引信息
        index = search_index_client.get_index(index_name)
        print(f"索引 '{index_name}' 已存在。")
    except ResourceNotFoundError:
        # 索引不存在,创建索引
        print(f"索引 '{index_name}' 不存在。正在创建索引...")
        index = SearchIndex(name=index_name, fields=[
            {"name": "id", "type": "Edm.String", "key": True, "searchable": False},
            {"name": "content", "type": "Edm.String"}
        ])
        search_index_client.create_index(index)
        print(f"索引 '{index_name}' 创建成功。")
    except Exception as e:
        print(f"检查/创建/删除索引 '{index_name}' 失败:{e}")


#%%
# 使用管理员账号登录
!az login
#%%
# 使用管理员账号创建索引
admin_search_index_client = get_search_index_client(ENDPOINT)
ensure_index_exists(INDEX_NAME, admin_search_index_client)
#%%
# 重要!手动删除服务主体的所有角色权限。操作完成后,以下所有命令的输出应为 "[]"

subscription_scope = f"/subscriptions/{SUBSCRIPTION}"
resource_group_scope = f"{subscription_scope}/resourceGroups/{RESOURCE_GROUP}"
resource_scope = f"{resource_group_scope}/providers/Microsoft.Search/searchServices/{SEARCH_SERVICE}"
index_scope = f"{resource_scope}/indexes/{INDEX_NAME}"

role_search_index_data_contributor = "搜索索引数据参与者"
role_search_service_contributor = "搜索服务参与者"
#%%
# 删除一些常见角色权限。如果删除成功则无输出,若提示无匹配的权限分配也属正常。

!az role assignment delete --assignee $SERVICE_PRINCIPAL --role "搜索索引数据参与者" --scope $index_scope
!az role assignment delete --assignee $SERVICE_PRINCIPAL --role "搜索服务参与者" --scope $index_scope
!az role assignment delete --assignee $SERVICE_PRINCIPAL --role "搜索索引数据参与者" --scope $resource_scope
!az role assignment delete --assignee $SERVICE_PRINCIPAL --role "搜索服务参与者" --scope $resource_scope
#%%

print(f"作用域:{subscription_scope}")
!az role assignment list --assignee $SERVICE_PRINCIPAL --scope $subscription_scope
#%%

print(f"作用域:{resource_group_scope}")
!az role assignment list --assignee $SERVICE_PRINCIPAL --scope $resource_group_scope
#%%

print(f"作用域:{resource_scope}")
!az role assignment list --assignee $SERVICE_PRINCIPAL --scope $resource_scope
#%%

print(f"作用域:{index_scope}")
!az role assignment list --assignee $SERVICE_PRINCIPAL --scope $index_scope

#%% md

### 分配角色权限。选择你要测试的作用域。

#%%
!az role assignment create --assignee $SERVICE_PRINCIPAL --role "搜索索引数据参与者" --scope $index_scope
# !az role assignment create --assignee $SERVICE_PRINCIPAL --role "搜索索引数据参与者" --scope $resource_scope

!az role assignment create --assignee $SERVICE_PRINCIPAL --role "搜索服务参与者" --scope $index_scope
# !az role assignment create --assignee $SERVICE_PRINCIPAL --role "搜索服务参与者" --scope $resource_scope


#%%
# 使用服务主体登录
!az login --service-principal -u $SERVICE_PRINCIPAL -p $PASSWORD --tenant $TENANT_ID
#%%
search_index_client = get_search_index_client(ENDPOINT)
search_client = search_index_client.get_search_client(index_name=INDEX_NAME)
#%% md
### 新角色权限生效可能需要等待几分钟
#%%
# 列出搜索服务中的所有索引
# 需要服务主体拥有资源级别的"搜索服务参与者"角色。
# 如果角色仅限定在索引级别,此操作应被拒绝。
try:
    indexes = search_index_client.list_indexes()
    print("搜索服务中的索引:")
    for index in indexes:
        print(f" - {index.name}")
except Exception as e:
    print(f"列出索引失败:{e}")


#%%
# 确保索引存在。假设索引已存在,若服务主体拥有索引级别的"搜索服务参与者"角色,此操作应成功。
ensure_index_exists(INDEX_NAME, search_index_client)
#%%
# 尝试获取索引详情。若服务主体拥有索引级别的"搜索服务参与者"角色,此操作应成功。
try:
    index = search_index_client.get_index(INDEX_NAME)
    print(f"索引详情:{index}")
except Exception as e:
    print(f"获取索引详情失败:{e}")
#%%
# 尝试执行搜索操作。若服务主体拥有索引级别的"搜索索引数据参与者"角色,此操作应成功。由于索引为空,输出将无内容。
try:
    results = search_client.search("搜索文本")
    for result in results:
        print(result)
except Exception as e:
    print(f"执行搜索失败:{e}")
#%%
# 删除索引。若服务主体拥有索引级别的"搜索服务参与者"角色,此操作应成功。
search_index_client.delete_index(INDEX_NAME)
#%%
# 删除索引后,应能重新创建它。若服务主体拥有索引级别的"搜索服务参与者"角色,此操作应成功。
# 但实际测试中,此操作会被拒绝(Forbidden)。
ensure_index_exists(INDEX_NAME, search_index_client)

内容的提问来源于stack exchange,提问作者Alejandro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 15:14:52