你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

在 Azure AI 搜索中配置客户管理的密钥以用于数据加密

Azure AI 搜索会自动使用服务管理的密钥加密静态数据。 如果需要更多保护,可以使用在 Azure Key Vault 中创建和管理的密钥,用另一个加密层来补充默认加密。

本文将指导你完成设置客户管理的密钥 (CMK) 或“自带密钥”(BYOK) 加密的步骤。

注意

如果索引已使用 CMK 加密,则仅当搜索服务有权访问密钥时,才能访问该索引。 如果撤销了访问权限,则索引不可用,并且在删除索引或还原对密钥的访问权限之前,服务将无法缩放。

CMK 加密的对象

CMK 加密是对单个对象执行的。 如果需要跨搜索服务使用 CMK,请设置强制策略

创建对象时,CMK 加密将开始运行。 不能加密已经存在的对象。 每次将对象保存到磁盘时,就会进行 CMK 加密,无论是用于长期存储的静态数据还是用于短期存储的临时数据。 使用 CMK 时,磁盘永远不会看到未加密的数据。

可加密的对象包括索引、同义词列表、索引器、数据源和技能组。 加密的计算开销高于解密,因此只会加密敏感内容。

加密对以下内容执行:

  • 索引和同义词列表中的所有内容。

  • 索引器、数据源、技能集和矢量器中的敏感内容。 此内容仅包含存储连接字符串、说明、标识、密钥和用户输入的字段。 例如,技能组有 Azure 认知服务密钥,而某些技能接受用户输入,例如自定义实体。 在这两种情况下,技能中的密钥和用户输入将会进行加密。 对外部资源(例如 Azure 数据源或 Azure OpenAI 模型)的任何引用也都经过加密。

全双重加密

引入 CMK 加密时,你会将内容加密两次。 对于上一部分中提到的对象和字段,首先使用 CMK 加密内容,然后使用 Microsoft 管理的密钥进行加密。 在用于长期存储的数据磁盘和用于短期存储的临时磁盘上,内容均进行双重加密。

启用 CMK 加密会增大索引大小,降低查询性能。 根据迄今为止的观察结果,查询时间预期会增加 30%-60%,不过,实际性能根据索引定义和查询类型而有所不同。 由于性能下降,我们建议仅对真正需要此功能的对象启用此功能。

虽然双重加密目前在所有区域都可用,但支持分两个阶段推出:

  • 第一次推出是在 2020 年 8 月 1 日,包括下面列出的五个区域。 在以下区域中创建的搜索服务支持对数据磁盘进行 CMK,但不支持临时磁盘:

    • 美国西部 2
    • 美国东部
    • 美国中南部
    • US Gov 弗吉尼亚州
    • US Gov 亚利桑那州
  • 2021 年 5 月 13 日的第二次推出增加了对临时磁盘的加密,并将 CMK 加密扩展到所有受支持的区域

    如果你使用的是首次推出期间创建的服务的 CMK,并且还希望通过临时磁盘进行 CMK 加密,则需要在所选区域中创建新的搜索服务并重新部署内容。 要确定服务创建日期,请参阅如何查看服务创建日期

先决条件

限制

  • 不支持 Azure 密钥保管库托管硬件安全模型 (HSM)。

  • 无跨订阅支持。 Azure 密钥保管库和 Azure AI 搜索必须位于同一订阅中。

Key Vault 提示

如果你不熟悉 Azure Key Vault,请查看以下快速入门来了解基本任务:使用 PowerShell 在 Azure Key Vault 中设置和检索机密

下面是有关使用 Key Vault 的一些提示:

  • 使用所需数量的密钥保管库。 管理的密钥可以位于不同的密钥保管库中。 一个搜索服务可以有多个已加密的对象,每个对象通过不同密钥保管库中存储的不同客户管理的加密密钥进行加密。

  • 使用相同的租户,以便可以通过系统或用户托管标识进行连接来检索托管密钥。 此行为要求这两个服务共享同一租户。 有关创建租户的详细信息,请参阅设置新租户

  • 启用清除保护软删除。 删除你的 Azure Key Vault 密钥后,无人可以检索你的数据,这是使用客户管理的密钥进行的加密的本质决定的。 若要防止意外删除 Key Vault 密钥造成数据丢失,必须在密钥保管库上启用“软删除”和“清除保护”。 默认情况下,软删除处于启用状态,因此,只有在你特意禁用了此功能时才会遇到问题。 “清除保护”在默认情况下不启用,但它是 Azure AI 搜索中客户管理的密钥加密所必需的。

  • 在密钥保管库上启用日志记录,以便可以监视密钥使用情况。

  • 启用密钥轮换或在例行轮换密钥保管库密钥、应用程序机密和注册期间遵循严格的过程。 在删除旧机密和密钥之前,始终更新所有已加密内容以使用新机密和密钥。 如果缺少此步骤,你的内容将无法解密。

步骤 1:在密钥保管库中创建密钥

如果 Azure Key Vault 中已包含你要使用的密钥,请跳过密钥生成步骤,但要收集密钥标识符。 创建加密的对象时需要此信息。

在添加密钥之前,请确保已将“密钥保管库加密管理人员”角色分配给自己。

Azure AI 搜索加密支持大小为 2048、3072 和 4096 的 RSA 密钥。 有关支持的密钥类型详细信息,请参阅关于密钥

  1. 登录到 Azure 门户,然后打开密钥保管库概述页面。

  2. 在左侧选择“对象”>“密钥”,然后选择“生成/导入”。

  3. 在“创建密钥”窗格的“选项”列表中,选择“生成”以创建新密钥。

  4. 输入密钥的“名称”,并接受其他密钥属性的默认值。

  5. 可以选择设置密钥轮换策略以启用自动轮换

  6. 选择“创建”以开始部署。

  7. 选择密钥,选择当前版本,然后记下密钥标识符。 它由键值 URI、密钥名称和密钥版本组成。 稍后需要使用该标识符在 Azure AI 搜索中定义加密的索引。

    创建新的 Key Vault 密钥

步骤 2:创建安全主体

可以使用多个选项来设置 Azure AI 搜索在运行时对加密密钥的访问权限。 最简单的方法是使用搜索服务的托管标识来检索密钥。 可以使用系统托管标识或用户管理的标识。 通过此操作,可以省略应用程序注册和应用程序机密的步骤。 或者,可以创建和注册 Microsoft Entra 应用程序,并要求搜索服务在请求时提供应用程序 ID。

建议使用托管标识。 搜索服务可以使用托管标识向 Azure Key Vault 进行身份验证,而无需在代码中存储凭据(ApplicationID 或 ApplicationSecret)。 此类托管标识的生命周期与只能具有一个系统分配托管标识的搜索服务的生命周期相关联。 若要详细了解托管标识工作原理,请参阅什么是 Azure 资源托管标识?

为搜索服务启用系统分配的托管标识。

启用系统分配的托管标识的屏幕截图。

步骤 3:授予权限

Azure 密钥保管库支持使用基于角色的访问控制进行授权。 建议对密钥保管库访问策略使用此方法。 有关详细信息,请参阅使用 Azure 角色提供对密钥保管库密钥、证书和机密的访问权限

在此步骤中,请向搜索服务分配“密钥保管库加密服务加密用户”角色。 如果是在本地进行测试,请同时将此角色分配给自己。

  1. 登录到 Azure 门户并查找密钥保管库。

  2. 选择“访问控制 (IAM)”,然后选择“添加角色分配”

  3. 选择“密钥保管库加密服务加密用户”,然后选择“下一步”。

  4. 依次选择托管标识和成员,然后选择搜索服务的托管标识。

  5. 选择“查看 + 分配”。

等待几分钟,以便角色分配正常运行。

步骤 4:加密内容

创建对象时会添加加密密钥。 若要在索引、同义词映射、索引器、数据源或技能组上添加客户管理的密钥,请使用 Azure 门户、搜索 REST API 或 Azure SDK 以创建已启用加密的对象。 要使用 Azure SDK 添加加密,请参阅本文中的 Python 示例

  1. 调用创建 API 以指定 encryptionKey 属性

  2. 将 encryptionKey 构造插入到对象定义中。 此属性是一级属性,其级别与名称和说明相同。 如果使用的是相同的保管库、密钥和版本,则可以将相同的 encryptionKey 构造粘贴到每个对象定义中。

    第一个示例展示了使用托管标识进行连接的搜索服务的 encryptionKey:

    {
      "encryptionKey": {
        "keyVaultUri": "<YOUR-KEY-VAULT-URI>",
        "keyVaultKeyName": "<YOUR-ENCRYPTION-KEY-NAME>",
        "keyVaultKeyVersion": "<YOUR-ENCRYPTION-KEY-VERSION>"
      }
    }
    

    第二个示例包括 accessCredentials,这在 Microsoft Entra ID 中注册应用程序时是必需的:

    {
      "encryptionKey": {
        "keyVaultUri": "<YOUR-KEY-VAULT-URI>",
        "keyVaultKeyName": "<YOUR-ENCRYPTION-KEY-NAME>",
        "keyVaultKeyVersion": "<YOUR-ENCRYPTION-KEY-VERSION>",
        "accessCredentials": {
          "applicationId": "<YOUR-APPLICATION-ID>",
          "applicationSecret": "<YOUR-APPLICATION-SECRET>"
        }
      }
    }
    
  3. 通过在对象上发出 GET 来验证加密密钥是否存在。

  4. 通过执行任务(例如查询已加密的索引)来验证对象是否正常运行。

在搜索服务上创建加密的对象后,可以像使用其他任何相同类型的对象一样使用它。 加密对于用户和开发人员是透明的。

这些密钥保管库详细信息都不被视为机密,在 Azure 门户中浏览到相关的 Azure Key Vault 页即可轻松检索这些信息。

重要

Azure AI 搜索中已加密的内容被配置为使用特定版本的特定 Azure 密钥保管库密钥。 如果更改了密钥或版本,则必须先更新对象以使用它,然后才能删除上一个密钥或版本。 否则,将会导致该对象不可用。 如果密钥丢失,你将无法解密内容。

步骤 5:测试加密

要验证加密是否正常工作,请撤销加密密钥,查询索引(应处于不可用状态),然后恢复加密密钥。

使用 Azure 门户执行此任务。

  1. 在 Azure 密钥保管库页上,选择“对象”>“密钥”

  2. 选择刚刚创建的密钥,然后选择“删除”。

  3. 在 Azure AI 搜索页上,选择“搜索管理”>“索引”。

  4. 选择索引并使用搜索资源管理器运行查询。 应会收到错误。

  5. 返回到“Azure 密钥保管库“对象”>“密钥”页面。

  6. 选择“管理已删除的密钥”。

  7. 选择密钥,然后选择“恢复”。

  8. 返回到 Azure AI 搜索中的索引并重新运行查询。 应会看到搜索结果。 如果未看到即时结果,请等待一分钟,然后重试。

设置策略以强制实施 CMK 符合性

Azure 策略可帮助实施组织标准并大规模评估合规性。 Azure AI 搜索具有一个可选的适用于服务级 CMK 实施的内置策略

在本部分中,你将设置为搜索服务定义 CMK 标准的策略。 然后,你将设置你的搜索服务来实施此策略。

  1. 在 Web 浏览器中导航到内置策略。 选择“分配”

    分配内置 CMK 策略的屏幕截图。

  2. 设置策略范围。 在“参数”部分中,取消选中“仅显示参数...”并将“效果”设置为拒绝

    在评估请求期间,与拒绝策略定义匹配的请求将被标记为不符合条件。 假设你的服务的标准是 CMK 加密,“拒绝”意味着未指定 CMK 加密的请求不符合条件。

    将内置 CMK 策略效果更改为拒绝的屏幕截图。

  3. 完成策略创建。

  4. 调用服务 - 更新 API 以在服务级别启用 CMK 策略实施。

PATCH https://management.azure.com/subscriptions/<your-subscription-Id>/resourceGroups/<your-resource-group-name>/providers/Microsoft.Search/searchServices/<your-search-service-name>?api-version=2023-11-01

{
    "properties": {
        "encryptionWithCmk": {
            "enforcement": "Enabled"
        }
    }
}

轮换或更新加密密钥

建议使用 Azure 密钥保管库的自动轮换功能,但也可以手动轮换密钥。

更改密钥或其版本时,必须先更新使用该密钥的任何对象以使用新值,然后再删除旧值。 否则,由于无法解密,对象将会处于不可用状态。

  1. 确定索引或同义词映射所使用的密钥

  2. 在密钥保管库中创建新密钥,但保持原始密钥可用。

  3. 更新索引或同义词映射上的 encryptionKey 属性以使用新值。 只有最初创建时带有此属性的对象才能更新为使用其他值。

  4. 禁用或删除密钥保管库中的上一个密钥。 监视密钥访问以验证是否正在使用新密钥。

出于性能方面的原因,搜索服务最多会缓存此密钥几个小时。 如果在不提供新密钥的情况下禁用或删除该密钥,则查询将暂时性地继续工作,直到缓存过期。 但是,一旦搜索服务无法解密内容,你就会收到以下消息:“禁止访问”。 使用的查询键可能已被撤消 - 请重试。”

使用加密内容

使用客户管理的密钥加密时,你会注意到,由于额外的加密/解密工作,索引编制和查询都会出现相应的延迟。 Azure AI 搜索不记录加密活动,但你可以通过密钥保管库日志记录功能来监视密钥访问。

建议在配置密钥保管库的过程中启用日志记录

  1. 创建 Log Analytics 工作区

  2. 在密钥保管库中添加诊断设置,以使用工作区进行数据保留。

  3. 为该类别选择“审核”或“allLogs”,为诊断设置命名,然后保存它。

加密密钥配置的 Python 示例

本部分显示了对象定义中 encryptionKey 的 Python 表示形式。 相同的定义适用于索引、数据源、技能组、索引器和同义词映射。 要在搜索服务和密钥保管库上试用此示例,请从 azure-search-python-samples 下载笔记本。

安装一些包。

! pip install python-dotenv
! pip install azure-core
! pip install azure-search-documents==11.5.1
! pip install azure-identity

创建具有加密密钥的索引。

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

endpoint="<PUT YOUR AZURE SEARCH SERVICE ENDPOINT HERE>"
credential = DefaultAzureCredential()

index_name = "test-cmk-index"
index_client = SearchIndexClient(endpoint=endpoint, credential=credential)  
fields = [
        SimpleField(name="Id", type=SearchFieldDataType.String, key=True),
        SearchableField(name="Description", type=SearchFieldDataType.String)
    ]

scoring_profiles = []
suggester = []
encryption_key = SearchResourceEncryptionKey(
    key_name="<PUT YOUR KEY VAULT NAME HERE>",
    key_version="<PUT YOUR ALPHANUMERIC KEY VERSION HERE>",
    vault_uri="<PUT YOUR KEY VAULT ENDPOINT HERE>"
)

index = SearchIndex(name=index_name, fields=fields, encryption_key=encryption_key)
result = index_client.create_or_update_index(index)
print(f' {result.name} created')

获取索引定义以验证加密密钥配置是否存在。

index_name = "test-cmk-index-qs"
index_client = SearchIndexClient(endpoint=AZURE_SEARCH_SERVICE, credential=credential)  

result = index_client.get_index(index_name)  
print(f"{result}")  

使用几个文档加载索引。 所有字段内容将被视为敏感内容,并使用客户管理的密钥在磁盘上加密。

from azure.search.documents import SearchClient

# Create a documents payload
documents = [
    {
    "@search.action": "upload",
    "Id": "1",
    "Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities."
    },
    {
    "@search.action": "upload",
    "Id": "2",
    "Description": "The hotel is situated in a  nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts."
    },
    {
    "@search.action": "upload",
    "Id": "3",
    "Description": "The hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services."
    },
    {
    "@search.action": "upload",
    "Id": "4",
    "Description": "The hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace."
    }
]

search_client = SearchClient(endpoint=AZURE_SEARCH_SERVICE, index_name=index_name, credential=credential)
try:
    result = search_client.upload_documents(documents=documents)
    print("Upload of new document succeeded: {}".format(result[0].succeeded))
except Exception as ex:
    print (ex.message)

    index_client = SearchClient(endpoint=AZURE_SEARCH_SERVICE, credential=credential)

运行查询以确认索引是否正常运行。

from azure.search.documents import SearchClient

query = "historic"  

search_client = SearchClient(endpoint=AZURE_SEARCH_SERVICE, credential=credential, index_name=index_name)
  
results = search_client.search(  
    query_type='simple',
    search_text=query, 
    select=["Id", "Description"],
    include_total_count=True
    )
  
for result in results:  
    print(f"Score: {result['@search.score']}")
    print(f"Id: {result['Id']}")
    print(f"Description: {result['Description']}")

查询的输出应生成类似于以下示例的结果。

Score: 0.6130029
Id: 4
Description: The hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.
Score: 0.26286605
Id: 1
Description: The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.

由于加密内容是在数据刷新或查询之前解密的,因此不会看到加密的可视证据。 要验证加密是否正常工作,请检查资源日志。

后续步骤

如果你不熟悉 Azure 安全体系结构,请查看 Azure 安全文档,具体而言,是以下文章: