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

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

Azure 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,配置应用以将此标识用于密钥保管库引用操作。

    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}
    

此设置适用于应用的所有密钥保管库引用。

旋转

如果引用中未指定机密版本,则应用会使用密钥保管库中存在的最新版本。 在有较新的版本可用时(例如通过轮换事件),应用会在 24 小时内自动更新并开始使用最新版本。 延迟是因为应用服务会缓存 Key Vault 引用的值,并且每 24 小时会重新提取一次。 对应用所做的任何配置更改都会导致应用重启和立即重新提取所有引用的机密。

密钥保管库中的源应用设置

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

提示

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

密钥保管库引用采用格式 @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 设置指定名称,否则其会采用默认名称。 对于修改这些设置的任何请求,平台会验证此内容共享是否存在,如果不存在,则将尝试创建共享。 如果它找不到或无法创建内容共享,则会阻止请求。

在此设置中使用密钥保管库引用时,默认情况下验证检查会失败,因为在处理传入请求时无法解析机密本身。 若要避免此问题,可以通过将 WEBSITE_SKIP_CONTENTSHARE_VALIDATION 设置为“1”来跳过验证。 此设置让应用服务绕过所有检查,并且不会为你创建内容共享。 应确保提前创建内容共享。

注意

如果跳过验证,并且连接字符串或内容共享无效,该应用将无法正常启动,并且只会显示 HTTP 500 错误。

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

Application Insights 检测的注意事项

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

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 应用服务中的环境变量和应用设置

排查密钥保管库引用问题

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

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

如果语法正确,可以通过在门户中检查当前解析状态来查看其他错误原因。 导航到“应用程序设置”,然后选择有问题的引用对应的“编辑”。 编辑对话框显示状态信息,包括任何错误。 如果未看到状态消息,则表示语法无效,无法识别为密钥保管库引用。

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