在 Azure App Service 和 Azure Functions 中使用 Key Vault 參考作為應用程式設定

本文說明如何使用 Azure Key Vault 中的祕密作為 App Service 或 Azure Functions 應用程式中的應用程式設定連接字串的值。

Azure Key Vault 是提供集中式祕密管理的服務,可完整控制存取原則和稽核歷程記錄。 當應用程式設定或連接字串是金鑰保存庫參考時,應用程式程式碼可以像任何其他應用程式設定或連接字串一樣來使用它。 如此一來,您就可以維護應用程式的設定之外的祕密。 應用程式設定會以靜止形式安全地加密,但如果您需要祕密管理功能,就應該將它們移至金鑰保存庫。

將您的應用程式存取權授與金鑰保存庫

若要從金鑰保存庫讀取祕密,您需要建立保存庫,並提供您的應用程式權限來存取它。

  1. 依照 Key Vault 快速入門來建立金鑰保存庫。

  2. 為您的應用程式建立受控識別

    依預設,Key Vault 參考會使用應用程式的系統指派身分識別,但您可指定使用者指派的身分識別

  3. 為您稍早建立的受控識別授與對金鑰保存庫祕密的讀取權限。 其執行方式取決於金鑰保存庫的權限模型:

存取具網路限制的保存庫

若保存庫已設定網路限制,請確保應用程式具有網路存取權。 保存庫不應依賴應用程式的公用輸出 IP,因為祕密要求的原始 IP 可能不同。 相反地,保存庫應該設定為接受來自應用程式所使用虛擬網路的流量。

  1. 請確定應用程式已設定輸出網路功能,如App Service 網路功能Azure Functions 網路功能選項所述。

    連線至私人端點的 Linux 應用程式必須明確設定為透過虛擬網路路由傳送所有流量。 即將推出的更新將會移除這項需求。 若要配置此設定,請執行下列命令:

    az webapp config set --subscription <sub> -g <group-name> -n <app-name> --generic-configurations '{"vnetRouteAllEnabled": true}'
    
  2. 請確定該保存庫的設定允許應用程式所使用的網路或子網路能夠進行存取。

以使用者指派的身分識別來存取保存庫

若系統指派的身分識別無法使用,則某些應用程式必須在建立時參考祕密。 在這些情況下,則可事先建立使用者指派的身分識別,並授與保存庫的存取權。

已授與權限給使用者指派的身分識別後,請遵循下列步驟:

  1. 指派身分識別給應用程式 (若尚未進行)。

  2. 將屬性 keyVaultReferenceIdentity 設為使用者指派身分識別的資源識別碼,以便設定應用程式使用此身分識別來進行金鑰保存庫參考作業。

    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 小時內自動更新,並開始使用最新版本。 延遲是由於 App Service 會快取金鑰保存庫參考的值,且每 24 小時重新擷取一次。 對應用程式的任何設定變更都會讓應用程式重新啟動,並立即重新擷取所有參考的祕密。

金鑰保存庫的來源應用程式設定

若要使用金鑰保存庫參考,請將參考設為設定值。 您的應用程式可以正常方式透過它的金鑰來參考祕密。 不需要變更程式碼。

提示

使用金鑰保存庫參考的大部分應用程式設定都應該標示為位置設定,因為每個環境都應該有不同的保存庫。

金鑰保存庫參考格式為 @Microsoft.KeyVault({referenceString}),其中 {referenceString} 採用下列其中一個格式:

參考字串 描述
SecretUri=secretUri SecretUri 應為保存庫中祕密的完整資料平面 URI (可選擇包含 https://myvault.vault.azure.net/secrets/mysecret/https://myvault.vault.azure.net/secrets/mysecret/ec96f02080254f109c51a1f14cdb1931 等版本)
VaultName=vaultName;SecretName=secretName;SecretVersion=secretVersion VaultName 是必要的,並且是保存庫名稱。 SecretName 是必要的,而且是祕密名稱。 SecretVersion 為選擇性,顯示時表示要使用的秘密版本。

例如,完整的參考會類似以下字串:

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

或者:

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

裝載 Azure 檔案儲存體的相關考量

應用程式可使用 WEBSITE_CONTENTAZUREFILECONNECTIONSTRING 應用程式設定,將 Azure 檔案儲存體裝載為檔案系統。 此設定具有驗證檢查,以確保應用程式可正確啟動。 該平台需要在 Azure 檔案儲存體內共用內容,並假設使用預設名稱 (除非透過 WEBSITE_CONTENTSHARE 設定指定名稱)。 針對修改這些設定的任何要求,平台會驗證此內容共用是否存在;若不存在,則會嘗試建立。 若找不到或無法建立內容共用,則會封鎖該要求。

當您在此設定中使用金鑰保存庫參考時,預設會驗證檢查失敗,因為處理傳入要求時無法解析祕密本身。 若要避免此問題,可將設定 WEBSITE_SKIP_CONTENTSHARE_VALIDATION 設為 「1」來跳過驗證。 此設定會告知 App Service 略過所有檢查,而且不會為您建立內容共用。 您應事先確定內容共用已建立。

警告

若跳過驗證,且連接字串或內容共用無效,則應用程式將無法正確啟動,只會出現 HTTP 500 錯誤。

建立應用程式時,可能由於未傳播受控識別權限或未設定虛擬網路整合,而導致內容共用嘗試裝載失敗。 您可延遲設定 Azure 檔案儲存體,稍後再於部署範本包含此設定。 如需深入了解,請參閱 Azure Resource Manager 部署。 在此情況下,App Service 會使用預設檔案系統,直到 Azure 檔案儲存體設定為止,而且不會複製檔案。 您必須確定在 Azure 檔案儲存體裝載之前,不會在過渡期間進行任何部署嘗試。

Application Insights 檢測設備的考量

應用程式可以使用 APPINSIGHTS_INSTRUMENTATIONKEYAPPLICATIONINSIGHTS_CONNECTION_STRING 應用程式設定來與 Application Insights 整合。 App Service 和 Azure Functions 的入口網站體驗也會使用這些設定來呈現來自資源的遙測資料。 如果從 Key Vault 參考這些值,則這些體驗無法使用,而您必須直接與 Application Insights 資源合作來檢視遙測。 不過,這些值不會被視為祕密,因此您也可以考慮直接設定這些值,而不是使用金鑰保存庫參考。

Azure Resource Manager 部署

透過 Azure Resource Manager 範本將資源部署自動化時,您可能需要以特定順序來排序相依性,才能使此功能運作。 請務必將您的應用程式設定定義為自己的資源,而不是在應用程式定義中使用 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 App Service 中的環境變數和應用程式設定

疑難排解金鑰保存庫參考

如果參考未正確解析,則會改用參考字串 (例如,@Microsoft.KeyVault(...))。 這可能會導致應用程式擲回錯誤,因為應用程式預期有不同值的祕密。

解析失敗通常是由於 Key Vault 存取原則設定錯誤所造成的。 但也可能是由於秘密不再存在,或參考本身的語法錯誤。

若語法正確,則可檢查入口網站中的目前解決狀態,以檢視錯誤的其他原因。 瀏覽至 [應用程式設定],針對有問題的參考選取 [編輯]。 [編輯] 對話框會顯示狀態資訊,包括任何錯誤。 如果您沒有看到狀態消息,即表示語法無效且無法辨識為金鑰保存庫參考。

您也可使用其中一個內建偵測器來取得其他資訊。

App Service 使用偵測器

  1. 在入口網站中,瀏覽至應用程式。
  2. 選取 [診斷並解決問題]
  3. 選擇 [可用性和效能],並選取 [Web 應用程式關閉]
  4. 在搜尋方塊中,搜尋並選取 [Key Vault 應用程式設定診斷]

Azure Functions 使用偵測器

  1. 在入口網站中,瀏覽至應用程式。
  2. 瀏覽至 [平台功能]
  3. 選取 [診斷並解決問題]
  4. 選擇 [可用性和效能],並選取 [函數應用程式關閉] 或 [報告錯誤]
  5. 選取 [Key Vault 應用程式設定診斷]