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
相关产品推荐
相关产品推荐

