Azure 函数托管身份和 Key Vault 访问策略(鸡蛋情况)

时间:2021-03-24 10:53:07

标签: azure-functions azure-keyvault arm-template azure-managed-identity

只是想知道是否有一种方法可以将存储帐户连接字符串存储在函数使用的密钥保管库中。该函数依赖于 WEBSITE_CONTENTAZUREFILECONNECTIONSTRINGAzureWebJobsStorage。我正在尝试将这些添加到密钥保管库中,但我遇到的问题是..

  1. 预配存储帐户
  2. 配置密钥保管库
  3. 提供功能应用
  4. 将函数的访问策略添加到 Key Vault

这里的问题是,当函数应用创建(第 3 步)时,它会失败,因为它无法访问密钥保管库(缺少访问策略)。我无法创建策略,因为函数需要存在。

其他人是如何解决这个问题的?我正在考虑预先创建 AD 应用程序(第 0 步)而不使用托管身份(这并不理想)。

2 个答案:

答案 0 :(得分:0)

如果您使用 azure 门户来部署资源,您可以部署一个没有 keyvault 引用的函数应用,然后手动配置 keyvault 引用。

如果您使用 ARM 模板来部署资源,doc 已提到您的问题。

<块引用>

请注意,您需要将应用程序设置定义为它们自己的资源,而不是在站点定义中使用 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('storageConnectionStringResourceId')).secretUriWithVersion, ')')]",
                        "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('storageConnectionStringResourceId')).secretUriWithVersion, ')')]",
                        "APPINSIGHTS_INSTRUMENTATIONKEY": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('appInsightsKeyResourceId')).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(concat('Microsoft.Web/sites/',  variables('functionAppName'), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-PREVIEW').tenantId]",
                        "objectId": "[reference(concat('Microsoft.Web/sites/',  variables('functionAppName'), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-PREVIEW').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'),'2015-05-01-preview').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')), '2015-05-01').InstrumentationKey]"
                    }
                }
            ]
        }
    ]
}

答案 1 :(得分:0)

许多其他需要访问 Key Vault 的资源(包括服务总线、事件中心、API 管理)也存在同样的问题。

我们如何使用这些循环依赖创建可重新部署的 ARM 模板?

使用客户管理的密钥加密的服务总线部署示例

  • 首先创建的服务总线未加密,因为全新的资源无法访问密钥保管库密钥。它获得一个托管身份/服务主体。
  • 更新 KeyVault 以授予对服务总线托管身份的访问权限。
  • 服务总线使用 Key Vault 访问进行加密更新。
  • 如果由于需要更新基础架构而重新部署 ARM 模板,则服务总线重新部署将失败,因为无法删除加密
  • 不支持检查资源是否已存在于 ARM 模板中以避免在后续部署中重复步骤 1

用户分配的托管标识似乎可以解决问题,但 ARM 模板不支持它们!