網站放上雲端,通常一次執行多台,分擔流量兼互相備援。這種模式下連線字串、API Key 等機密設定不適合寫在設定檔,最好集中管理並嚴密管控存取。

Azure 在這方面提供的解決方案是 Azure Key Vault (金鑰保存庫),可用來保存 Secret (密碼、Token、API Key... 等)、Key (加解密用的金鑰) 與 Certificate (TLS 憑證),管理人員可透過網頁管理設定,程式則走 API 讀取,並能設定存取權限、限定來源 IP,金鑰的話還支援硬體安全模組(HSM)保護(註:價格不同),且 Azure App Service / Azure Functions 內建整合,比自己土砲的解法更安全方便。

今天的練習科目是建立一個 Azure Key Vault,存入密碼,並開放在 Azure VM 上的 .NET 程式讀取。

先用 Azure Portal 網頁介面新增 Key Vault,順便介紹設定選項,上手後可改用 CLI 或 PowerShell 操作更有效率。Key Vault 名稱會被註冊成 DNS 名稱,故需避免重複;定價層則分為標準跟進階(支援 HSM 硬體保護金鑰)。

再來是存取原則。權限模型分為保存庫存取原則及 Azure 角色型存取控制(Role-Based Access Control, RBAC),後者複雜但有彈性,跟 NTFS 一樣可以授權給角色,再將成員加入角色,方便管理。

網路部分,開放公用存取代表 Key Vault 可透過 your-vault-name.vault.azure.net 的對外 IP 存取,較簡單易用。即使對外公開,存取仍需識別身分檢查權限,有一定的安全防護;如果覺得不夠,還可以限定特定的虛擬網路(例如:VM 所在的虛擬網路)及指定的 IP 存取,將攻擊者完全擋在防火牆外,安全可再上一層樓:

如果限定 VM 所在虛擬網路才能存取,用網頁介面新增 Secret 將因瀏覽器所在 IP 不在清單被阻擋,故需將操作電腦對外 IP 加入清單:

為了測試,我用系統管理員身分透過網頁介面新增一筆 Secret - MyPassword,內容為 "P@ssW0rd":

Secret 設定完成,再來要試著在 VM 跑 .NET 程式讀取它。為了讓 VM 有權限讀取,VM 必須啟用 Managed Identity。選擇系統指派,會在 AD 註冊一筆該 VM 專用的 Service Principle (服務主體),Service Principle 名稱就是 VM 名稱,VM 刪除時 Service Principle 則會一併刪除,不需額外管理。使用者指派則需手動建立及移除 Service Principle,多了 Service Principal 管理的工作,但好處是同一組 Service Principal 可指派給多個資源使用。

接著設定 Key Vault 存取原則,允許 VM 讀取 Secret:

建立存取原則時,要先選對 Key、Secret、Certificate 開放的動作:(這裡只設定取得跟列示,Get/List)

主體部分則要找到 VM 名稱的 Service Principle:

以上步驟有點繁瑣,了解原理後可改用 PowerShell/CLI 執行較省事:參考

$vm = Get-AzVM -Name <NameOfYourVirtualMachine>
Update-AzVM -ResourceGroupName <YourResourceGroupName> -VM $vm -IdentityType SystemAssigned
Set-AzKeyVaultAccessPolicy -ResourceGroupName <YourResourceGroupName> -VaultName '<your-unique-key-vault-name>' -ObjectId '<VMSystemAssignedIdentity>' -PermissionsToSecrets get,list

接著來寫程式。用 ssh 登入 VM,dotnet 建立 .NET 6 Console 專案並參考 Azure.Security.KeyVault.Secrets 及 Azure.Identity:

dotnet new console -o read-secret
cd ./read-secret
dotnet add package Azure.Security.KeyVault.Secrets
dotnet add package Azure.Identity

在 Program.cs 加幾行程式,new DefaultAzureCredential() 將自動取得 VM 的 Managed Identity 作為身分識別以獲得存取權限,配合 SecretClient.GetSecretAsync() 可讀取 MyPassword 內容:

using System;
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

var kvUri = "https://secretforapp.vault.azure.net";
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
KeyVaultSecret secret = await client.GetSecretAsync("MyPassword");
Console.WriteLine($"MyPassword={secret.Value}");

測試成功:

若程式要部署為 App Service,App Service 可比照 VM 開啟系統指派的身分識別,並在 Key Vault 設定存取原則允許讀取。要讀取設定一樣是用 new DefaultAzureCredential() 就能獲得權限讀取 Secret:參考

如果程式要在 Docker 或非 Azure 環境執行,要怎麼獲得 Key Vault 的存取權限?簡單解法是在 Azre AD 註冊應用程式,取得 Client Id 並產生 Client Secret,用它們識別身分取得權限:(註冊應用程式的操作方法可參考之前的文章)

記得先在 Key Vault 為此應用程式建立存取原則,然後將 Tenant ID、Client Id、Client Secret 設成 AZURE_TENANT_ID、AZURE_CLIENT_ID、AZURE_CLIENT_SECRET,new DefaultAzureCredential() 便用改用這組應用程式 API 身分存取: 參考

using System;
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

var clientId = "a7a10d2b-a634-496d-ac1d-c61478c17d1b";
var clientSecret = "4xx8Q~ClvVV*****Ed2JR6yO3Bz0AEpCencMD";
var tenantId = "12345678-1234-4321-1234-123456789012";
Environment.SetEnvironmentVariable("AZURE_TENANT_ID", tenantId);
Environment.SetEnvironmentVariable("AZURE_CLIENT_ID", clientId);
Environment.SetEnvironmentVariable("AZURE_CLIENT_SECRET", clientSecret);
var kvUri = "https://secretforapp.vault.azure.net";
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
KeyVaultSecret secret = await client.GetSecretAsync("MyPassword");
Console.WriteLine($"MyPassword={secret.Value}");

實測,從開發機也能成功讀取!

最後,Azure Key Vault 的一大優點是能整合進 ASP.NET Core IConfiguration,使用 AddAzureKeyVault() 將 Key Vault 的設定加入 IConfiguration 集合:(註:需參照 Azure.Extensions.AspNetCore.Configuration.Secrets): 參考

using Azure.Identity;

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsProduction())
{
    builder.Configuration.AddAzureKeyVault(
        new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
        new DefaultAzureCredential());
}

之後透過 IConfiguration["MyPassword"] 就能讀取 Key Vault 儲存的設定,這樣可以做到「開發階段由 appsettings.json 讀取設定,上線後改由 Key Vault 取得」,非常方便。

最後小結學到的新東西:

  1. Azure Key Vault 是在雲端集中保存機密設定的機制,方便儲存 Secret、金鑰及憑證等機密資訊。
  2. Azure Key Vault 可針對使用者、VM、應用程式設定存取權限,並可限定 IP 來源進一步提高安全性。
  3. 透過 Managed Identity,VM、App Service 可建立專屬識別身分,在上面運行的程式不需設定即可獲得 Key Vault 存取權限。
  4. 在 Azure AD 註冊應用程式取得 Client Id 及 Secret 並設定存取原則,程式便能從 Azure VM/App Service 以外的主機或 Docker 讀寫 Key Vault。
  5. Azure Key Vault Provider 能將 Key Vault 儲存的設定整合到 IConfiguration 中,使用起來更方便。

Tutorial of Azure Key Vault creation and using.


Comments

# by kelun

請問若是透過Azure App service來存取Azure Key Vault,因App service上沒綁固定IP,Azure Key Vault的FW是否就不能使用IP來限制存取呢? 或者Azure Key Vault可以直接存取資源群組裡的虛擬網路來進行限制?

# by Jeffrey

to kelun, 可建立虛擬網路讓 App Service 加入,Key Vault 再限定該 vNet 存取,前篇文章有用類以方管控 Storage 存取 https://blog.darkthread.net/blog/azure-appsvc-4-container/

Post a comment