将密钥保管库引用用作 Azure 应用服务和 Azure Functions 中的应用设置

注意

从 2024 年 6 月 1 日开始,新创建的应用服务应用可以生成使用命名约定 <app-name>-<random-hash>.<region>.chinacloudsites.cn 的唯一默认主机名。 现有应用名称保持不变。 例如:

myapp-ds27dh7271aah175.chinanorth3-01.chinacloudsites.cn

本文介绍如何使用 Azure Key Vault 中的机密作为 Azure 应用服务或 Azure Functions 应用中 的应用设置连接字符串 的值。

Key Vault 是一项服务,提供集中式机密管理,可完全控制访问策略和审核历史记录。 当应用设置或连接字符串是 Key Vault 引用时,应用程序代码可以使用它,就像任何其他应用设置或连接字符串一样。 这样一来,除了应用的配置之外,还可以维护机密。 应用程序设置在静态时已安全加密,但如果您需要进行机密信息管理,应将它们存入密钥保管库。

请授予应用对密钥保管库的访问权限

若要从密钥保管库读取机密,首先需要创建保管库,并授予应用访问它的权限:

  1. 按照 Key Vault 快速入门中的说明创建一个密钥保管库。

  2. 为应用程序创建一个托管标识

    默认情况下,密钥保管库引用会使用应用的系统分配的标识,但你可以指定用户分配的标识

  3. 为已创建的托管标识授予对密钥保管库中的机密的读取访问权限。 操作方式取决于你的密钥保管库的权限模型:

访问网络受限保管库

如果你的保管库配置了网络限制,则请确保该应用程序具有网络访问权限。 保管库不应依赖于应用的公共出站 IP,因为机密请求的源 IP 可能不同。 应改将保管库配置为接受来自应用所使用的虚拟网络的流量。

  1. 确保应用程序配置了出站网络功能,如 应用服务网络功能和Azure Functions 网络选项中所述。

    目前,连接到专用终结点的 Linux 应用程序必须明确配置,以便所有流量通过虚拟网络路由。 若要配置此设置,请运行以下命令:

    az webapp config set --subscription <sub> -g <group-name> -n <app-name> --generic-configurations '{"vnetRouteAllEnabled": true}'
    
  2. 请确保保管库的配置允许您的应用所使用的网络或子网访问它。

使用用户分配的标识访问保管库

系统分配的标识尚不可用时,某些应用需要在创建时引用机密。 在这些情况下,可以创建用户分配的标识,并提前授予它对保管库的访问权限。

向用户分配的标识授予权限后,请执行以下步骤:

  1. 为您的应用程序分配标识,如果尚未这样做。

  2. 通过将 keyVaultReferenceIdentity 属性设置为用户分配的标识的资源 ID,配置应用以将此标识用于 Key Vault 引用操作:

    identityResourceId=$(az identity show --resource-group <group-name> --name <identity-name> --query id -o tsv)
    az webapp update --resource-group <group-name> --name <app-name> --set keyVaultReferenceIdentity=${identityResourceId}
    

此设置适用于应用的所有 Key Vault 引用。

了解轮换

如果引用中未指定机密版本,则应用会使用密钥保管库中存在的最新版本。 当较新版本可用(例如轮换事件)时,应用会自动更新,并在 24 小时内开始使用最新版本。

延迟是因为应用服务缓存 Key Vault 引用的值,并每隔 24 小时重新提取它们。 对应用所做的任何配置更改都会导致应用重启和立即重新提取所有引用的机密。

了解 Key Vault 中的源应用设置

若要使用 Key Vault 引用,请将引用设置为设置的值。 应用可以通过密钥正常引用机密。 不需更改代码。

提示

应该将大多数使用密钥保管库引用的应用程序设置标记为槽设置,因为你应该为每个环境设置单独的保管库。

Key Vault 引用以@Microsoft.KeyVault({referenceString})的形式,其中{referenceString}为以下格式之一:

引用字符串 说明
SecretUri=<secretUri> SecretUri 应该是保管库中机密的完整数据平面 URI,例如 https://myvault.vault.azure.cn/secrets/mysecret。 (可选)加入一个版本,例如 https://myvault.vault.azure.cn/secrets/mysecret/ec96f02080254f109c51a1f14cdb1931
VaultName=<vaultName>;SecretName=<secretName>;SecretVersion=<secretVersion> VaultName 值是必需的,是保管库名称。 该值 SecretName 是必需的,并且是机密名称。 该值 SecretVersion 是可选的,但如果存在,则表示要使用的机密版本。

例如,没有特定版本的完整引用类似于以下字符串:

@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.cn/secrets/mysecret)

也可使用以下命令:

@Microsoft.KeyVault(VaultName=myvault;SecretName=mysecret)

Azure 文件存储装载注意事项

应用可使用 WEBSITE_CONTENTAZUREFILECONNECTIONSTRING 应用程序设置将 Azure 文件存储装载为文件系统。 此设置有验证检查,旨在确保该应用可以正常启动。

平台依赖于在 Azure 文件存储中拥有内容共享目录,并且除非通过 WEBSITE_CONTENTSHARE 设置指定了名称,否则将采用默认名称。 对于修改这些设置的任何请求,平台将验证此内容共享是否存在。 如果内容共享不存在,平台会尝试创建它。 如果平台无法定位或创建内容共享,它会阻止该请求。

在此设置中使用 Key Vault 引用时,验证检查默认失败,因为在处理传入请求期间无法解析机密。 若要避免此问题,可以通过设置为 WEBSITE_SKIP_CONTENTSHARE_VALIDATION1 跳过验证。 此设置告知应用服务绕过所有检查,并且不会为你创建内容共享。 应确保提前创建内容共享。

注意

如果跳过验证,并且连接字符串或内容共享无效,则应用将无法正确启动,并且将处理 HTTP 500 错误。

在创建应用过程中,尝试装载内容共享可能会失败,因为未传播托管标识权限或未设置虚拟网络集成。 可以在部署模板中延迟设置 Azure 文件存储,以适应此行为。 若要了解详细信息,请参阅本文后面的 Azure 资源管理器部署 。 在这种情况下,应用服务会使用默认的文件系统,直到设置了 Azure 文件存储,并且不会将文件复制过去。 必须确保在装载 Azure 文件存储之前的过渡期间不会发生部署尝试。

Application Insights 检测的注意事项

应用可以使用 APPINSIGHTS_INSTRUMENTATIONKEYAPPLICATIONINSIGHTS_CONNECTION_STRING 应用程序设置来与 Application Insights 集成。

应用服务和 Azure Functions 的门户体验还使用这些设置来显示资源的遥测数据。 如果这些值引用自密钥保管库,则这些体验不可用,需要改为直接使用 Application Insights 资源来查看遥测数据。 但是,这些值 不被视为机密,因此可以考虑直接配置它们,而不是使用 Key Vault 引用。

Azure 资源管理器部署

通过 Azure 资源管理器模板自动执行资源部署时,可能需要按特定顺序对依赖项进行排序才能使此功能正常工作。 请务必将应用设置定义为其自身的资源,而不是使用应用定义中的 siteConfig 属性。 首先需要定义应用,以便系统分配的标识随它一起创建,并可在访问策略中使用。

下面的伪模板是函数应用的一种示例:

{
    //...
    "resources": [
        {
            "type": "Microsoft.Storage/storageAccounts",
            "name": "[variables('storageAccountName')]",
            //...
        },
        {
            "type": "Microsoft.Insights/components",
            "name": "[variables('appInsightsName')]",
            //...
        },
        {
            "type": "Microsoft.Web/sites",
            "name": "[variables('functionAppName')]",
            "identity": {
                "type": "SystemAssigned"
            },
            //...
            "resources": [
                {
                    "type": "config",
                    "name": "appsettings",
                    //...
                    "dependsOn": [
                        "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]",
                        "[resourceId('Microsoft.KeyVault/vaults/', variables('keyVaultName'))]",
                        "[resourceId('Microsoft.KeyVault/vaults/secrets', variables('keyVaultName'), variables('storageConnectionStringName'))]",
                        "[resourceId('Microsoft.KeyVault/vaults/secrets', variables('keyVaultName'), variables('appInsightsKeyName'))]"
                    ],
                    "properties": {
                        "AzureWebJobsStorage": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('storageConnectionStringName')).secretUriWithVersion, ')')]",
                        "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('storageConnectionStringName')).secretUriWithVersion, ')')]",
                        "APPINSIGHTS_INSTRUMENTATIONKEY": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('appInsightsKeyName')).secretUriWithVersion, ')')]",
                        "WEBSITE_ENABLE_SYNC_UPDATE_SITE": "true"
                        //...
                    }
                },
                {
                    "type": "sourcecontrols",
                    "name": "web",
                    //...
                    "dependsOn": [
                        "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]",
                        "[resourceId('Microsoft.Web/sites/config', variables('functionAppName'), 'appsettings')]"
                    ],
                }
            ]
        },
        {
            "type": "Microsoft.KeyVault/vaults",
            "name": "[variables('keyVaultName')]",
            //...
            "dependsOn": [
                "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]"
            ],
            "properties": {
                //...
                "accessPolicies": [
                    {
                        "tenantId": "[reference(resourceId('Microsoft.Web/sites/', variables('functionAppName')), '2020-12-01', 'Full').identity.tenantId]",
                        "objectId": "[reference(resourceId('Microsoft.Web/sites/', variables('functionAppName')), '2020-12-01', 'Full').identity.principalId]",
                        "permissions": {
                            "secrets": [ "get" ]
                        }
                    }
                ]
            },
            "resources": [
                {
                    "type": "secrets",
                    "name": "[variables('storageConnectionStringName')]",
                    //...
                    "dependsOn": [
                        "[resourceId('Microsoft.KeyVault/vaults/', variables('keyVaultName'))]",
                        "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
                    ],
                    "properties": {
                        "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountResourceId'),'2019-09-01').key1)]"
                    }
                },
                {
                    "type": "secrets",
                    "name": "[variables('appInsightsKeyName')]",
                    //...
                    "dependsOn": [
                        "[resourceId('Microsoft.KeyVault/vaults/', variables('keyVaultName'))]",
                        "[resourceId('Microsoft.Insights/components', variables('appInsightsName'))]"
                    ],
                    "properties": {
                        "value": "[reference(resourceId('microsoft.insights/components/', variables('appInsightsName')), '2019-09-01').InstrumentationKey]"
                    }
                }
            ]
        }
    ]
}

注意

在此示例中,源代码管理部署取决于应用程序设置。 此依赖项通常是不安全的行为,因为应用设置更新的行为是异步的。 但是,由于我们包括 WEBSITE_ENABLE_SYNC_UPDATE_SITE 了应用程序设置,因此更新是同步的。 仅当应用程序设置完全更新后,源代码管理部署才会开始。 有关更多应用设置,请参阅 Azure 应用服务中的环境变量和应用设置

排查 Key Vault 引用问题

如果未正确解析引用,则会改用引用字符串(例如 @Microsoft.KeyVault(...))。 此情况可能会导致应用程序引发错误,因为它需要具有不同值的机密。

未能解决的常见原因是密钥保管库访问策略配置不当。 但是,原因也可能是机密不再存在或引用包含语法错误。

如果语法正确,可以通过在门户中检查当前解析状态来查看其他错误原因。 转到 “应用程序设置” ,然后选择“ 编辑 ”以获取相关参考。 编辑对话框显示状态信息,包括任何错误。 如果未看到状态消息,则表示语法无效,无法识别为 Key Vault 引用。

也可以使用某个内置检测程序来获取更多信息。