ASP.NET Core 中的 Azure Key Vault 組態提供者

本文說明如何使用 Azure Key Vault 組態提供者,從 Azure Key Vault 祕密載入應用程式組態值。 Azure Key Vault 是雲端式服務,可協助保護應用程式和服務所使用的密碼編譯金鑰及祕密。 搭配 ASP.NET Core 應用程式使用 Azure Key Vault 的常見案例包括:

  • 控制機密組態資料的存取權。
  • 儲存組態資料時,符合 FIPS 140-2 等級 2 驗證的硬體安全性模組 (HSM) 需求。

套件

新增以下套件的套件參考:

範例應用程式

Program.cs 最上方的 #define 前置處理器指示詞會決定範例應用程式的執行模式,而該模式有以下兩種:

  • Certificate:示範如何使用 Azure Key Vault 用戶端識別碼和 X.509 憑證來存取儲存在 Azure Key Vault 中的祕密。 此範例可以從任何位置執行,無論是部署至 Azure App Service 的位置,或是可以提供 ASP.NET Core 應用程式的任何主機。
  • Managed:示範如何使用 Azure 資源受控識別。 受控識別會使用 Azure Active Directory (AD) 驗證機制對 Azure Key Vault 驗證應用程式,而不須將認證資料儲存在應用程式的程式碼或組態中。 範例的 Managed 版本必須部署至 Azure。 請遵循使用 Azure 資源受控識別一節中的指引。

如需使用前置處理器指示詞 (#define) 設定範例應用程式的詳細資訊,請參閱 ASP.NET Core 概觀

檢視或下載範例程式碼 \(英文\) (如何下載)

開發環境中的祕密存放區

使用祕密管理員在本機設定祕密。 範例應用程式在開發環境中的本機電腦執行時,會從本機使用者祕密存放區載入祕密。

祕密管理員需要應用程式專案檔中的 <UserSecretsId> 屬性。 將屬性值 ({GUID}) 設定為任何唯一 GUID:

<PropertyGroup>
  <UserSecretsId>{GUID}</UserSecretsId>
</PropertyGroup>

祕密會建立為成對的名稱和數值。 階層值 (組態區段) 使用 : (冒號) 作為 ASP.NET Core 組態金鑰名稱中的分隔符號。

祕密管理員是從開啟至專案內容根目錄的命令殼層中使用,其中 {SECRET NAME} 是名稱,{SECRET VALUE} 是值:

dotnet user-secrets set "{SECRET NAME}" "{SECRET VALUE}"

在專案內容根目錄的命令殼層中執行以下命令,為範例應用程式設定祕密:

dotnet user-secrets set "SecretName" "secret_value_1_dev"
dotnet user-secrets set "Section:SecretName" "secret_value_2_dev"

若這些祕密存放在使用 Azure Key Vault 在實際執行環境中建立祕密存放區一節所建立的 Azure Key Vault,後置詞 _dev 會變更為 _prod。 後置詞會在應用程式的輸出中提供視覺提示,指出組態值的來源。

使用 Azure Key Vault 在實際執行環境中建立祕密存放區

完成下列步驟來建立 Azure Key Vault,並將範例應用程式的祕密存放於此。 如需詳細資訊,請參閱快速入門:使用 Azure CLI 從 Azure Key Vault 設定和擷取祕密

  1. Azure 入口網站中使用下列任一方法開啟 Azure Cloud Shell:

    • 選取程式碼區塊右上角的 [試試看]。 在文字方塊中使用搜尋字串「Azure CLI」。
    • 使用 [啟動 Cloud Shell] 按鈕在瀏覽器中開啟 Cloud Shell。
    • 選取 Azure 入口網站右上角功能表中的 [Cloud Shell] 按鈕。

    如需詳細資訊,請參閱 Azure CLIAzure Cloud Shell 概觀

  2. 如果您尚未通過驗證,請使用 az login 命令登入。

  3. 使用下列命令建立資源群組,其中 {RESOURCE GROUP NAME} 是新資源群組的名稱,而 {LOCATION} 是 Azure 區域:

    az group create --name "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  4. 使用下列命令在資源群組中建立 金鑰保存庫,其中 {KEY VAULT NAME} 是新的保存庫名稱,而 {LOCATION} 是 Azure 區域:

    az keyvault create --name {KEY VAULT NAME} --resource-group "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  5. 在保存庫中建立秘密做為名稱/值組。

    Azure Key Vault 祕密名稱僅限使用英數字元和虛線。 階層式值(組態區段)使用 -- (兩個破折號) 作為分隔符,因為 金鑰保存庫 秘密名稱中不允許冒號。 冒號會從 ASP.NET Core 組態中的子機碼分隔區段。 在祕密載入到應用程式組態時,冒號會取代雙虛線序列。

    下列祕密可用於範例應用程式。 這些值包含 _prod 後置詞,以區別從祕密管理員載入到開發環境中的 _dev 後置詞。 {KEY VAULT NAME}取代為您在上一個步驟中建立的 金鑰保存庫 名稱:

    az keyvault secret set --vault-name {KEY VAULT NAME} --name "SecretName" --value "secret_value_1_prod"
    az keyvault secret set --vault-name {KEY VAULT NAME} --name "Section--SecretName" --value "secret_value_2_prod"
    

針對非 Azure 裝載的應用程式使用應用程式識別碼和 X.509 憑證

設定 Azure AD、Azure 金鑰保存庫 和應用程式,以在應用程式裝載於 Azure 外部時,使用 Azure AD 應用程式識別碼和 X.509 憑證向保存庫進行驗證。 如需詳細資訊,請參閱關於金鑰、祕密與憑證

注意

雖然 Azure 裝載的應用程式支援使用應用程式識別碼和 X.509 憑證,但不建議這麼做。 在 Azure 中裝載應用程式時,請改用 Azure 資源受控識別。 受控識別不需要將憑證存放在應用程式或開發環境中。

Program.cs 最上方的 #define 前置處理器指示詞設定為 Certificate 時,範例應用程式會使用應用程式識別碼和 X.509 憑證。

  1. 建立 PKCS#12 封存 (.pfx) 憑證。 建立憑證的選項包括 Windows 上的 New-SelfSignedCertificateOpenSSL
  2. 將憑證安裝至目前使用者的個人憑證存放區。 您可以選擇是否將金鑰標示為可匯出。 請記下憑證指紋,以供此流程稍後使用。
  3. 將 PKCS#12 封存 (.pfx) 憑證匯出為 DER 編碼憑證 (.cer)。
  4. 向 Azure AD 註冊應用程式 (應用程式註冊)。
  5. 將 DER 編碼憑證 (.cer) 上傳至 Azure AD:
    1. 在 Azure AD 中選取應用程式。
    2. 流覽至 [ 憑證與秘密]。
    3. 選取 [上傳憑證] 以上傳包含公開金鑰的憑證。 可接受 .cer.pem.crt 憑證。
  6. 將 金鑰保存庫名稱、應用程式識別碼和憑證指紋儲存在應用程式的appsettings.json檔案中。
  7. 流覽至 Azure 入口網站中的 金鑰保存庫
  8. 使用 Azure 金鑰保存庫 區段,選取您在生產環境中的秘密記憶體中建立的 金鑰保存庫
  9. 選取存取原則
  10. 選取新增存取原則
  11. 開啟 [祕密權限],並提供應用程式 [取得] 和 [清單] 權限。
  12. 選取 [選取主體],然後依名稱選取已註冊的應用程式。 選取 [選取] 按鈕。
  13. 選取 [確定]。
  14. 選取 [儲存]。
  15. 部署應用程式。

Certificate 範例應用程式會使用與祕密名稱相同的名稱從 IConfigurationRoot 取得組態值:

  • 非階層值:使用 config["SecretName"] 取得 SecretName 的值。
  • 階層值 (區段):使用 : (冒號) 標記法或 GetSection 方法。 使用下列其中一種方法來取得組態值:
    • config["Section:SecretName"]
    • config.GetSection("Section")["SecretName"]

X.509 憑證由 OS 管理。 應用程式會使用檔案所提供的 appsettings.json 值來呼叫 AddAzureKeyVault


using System.Security.Cryptography.X509Certificates;
using Azure.Identity;

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsProduction())
{
    using var x509Store = new X509Store(StoreLocation.CurrentUser);

    x509Store.Open(OpenFlags.ReadOnly);

    var x509Certificate = x509Store.Certificates
        .Find(
            X509FindType.FindByThumbprint,
            builder.Configuration["AzureADCertThumbprint"],
            validOnly: false)
        .OfType<X509Certificate2>()
        .Single();

    builder.Configuration.AddAzureKeyVault(
        new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
        new ClientCertificateCredential(
            builder.Configuration["AzureADDirectoryId"],
            builder.Configuration["AzureADApplicationId"],
            x509Certificate));
}

var app = builder.Build();

範例值:

  • 金鑰保存庫 名稱:contosovault
  • 應用程式識別碼:627e911e-43cc-61d4-992e-12db9c81b413
  • 憑證指紋:fe14593dd66b2406c5269d742d04b6e1ab03adb1

appsettings.json

{
  "KeyVaultName": "Key Vault Name",
  "AzureADApplicationId": "Azure AD Application ID",
  "AzureADCertThumbprint": "Azure AD Certificate Thumbprint",
  "AzureADDirectoryId": "Azure AD Directory ID"
}

您執行應用程式時,網頁會顯示載入的祕密值。 在開發環境中,祕密值載入時帶有 _dev 後置詞。 在實際執行環境中,祕密值載入時則帶有 _prod 後置詞。

使用 Azure 資源受控識別

部署至 Azure 的應用程式可以使用 Azure 資源受控識別。 受控識別可讓應用程式使用 Azure AD 驗證機制向 Azure Key Vault 進行驗證,而不須將認證資料儲存在應用程式的程式碼或組態中。

Program.cs 最上方的 #define 前置處理器指示詞設定為 Managed 時,範例應用程式會使用系統指派的受控識別。 若要建立 Azure App Service 應用程式的受控識別,請參閱如何使用 App Service 和 Azure Functions 的受控識別。 建立受控識別之後,請前往 Azure 入口網站,記下 App Service 的 Identity 面板顯示的應用程式物件識別碼。

在應用程式的 appsettings.json 檔案中輸入保存庫名稱。 設定為 Managed 版本時,範例應用程式不需要應用程式識別碼和密碼 (用戶端密碼),因此您可以忽略這些組態項目。 應用程式會部署至 Azure,而 Azure 只會使用存放在 appsettings.json 檔案中的保存庫名稱來驗證應用程式以存取 Azure Key Vault。

將範例應用程式部署至 Azure App Service。

使用 Azure CLI 和應用程式的物件識別碼,提供應用程式 list 以及 get 存取保存庫的權限:

az keyvault set-policy --name {KEY VAULT NAME} --object-id {OBJECT ID} --secret-permissions get list

使用 Azure CLI、PowerShell 或 Azure 入口網站重新啟動應用程式

範例應用程式會建立 DefaultAzureCredential 類別的執行個體。 認證會嘗試從 Azure 資源的環境取得存取權杖:

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());
}

金鑰保存庫 名稱範例值:contosovault

appsettings.json

{
  "KeyVaultName": "Key Vault Name"
}

針對使用使用者指派受控識別的應用程式,請採用下列其中一種方法來設定受控識別的用戶端識別碼:

  1. 設定 AZURE_CLIENT_ID 環境變數。

  2. 呼叫 AddAzureKeyVault 時設定 DefaultAzureCredentialOptions.ManagedIdentityClientId 屬性:

    builder.Configuration.AddAzureKeyVault(
        new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
        new DefaultAzureCredential(new DefaultAzureCredentialOptions
        {
            ManagedIdentityClientId = builder.Configuration["AzureADManagedIdentityClientId"]
        }));
    

您執行應用程式時,網頁會顯示載入的祕密值。 在開發環境中,祕密值是由祕密管理員提供,因此帶有 _dev 後置詞。 在實際執行環境中,祕密值是由 Azure Key Vault 提供,因此載入時帶有 _prod 後置詞。

如果您收到 Access denied 錯誤,請確認應用程式已向 Azure AD 註冊,並提供保存庫的存取權。 確認您已在 Azure 中重新啟動服務。

如需搭配受控識別和 Azure Pipelines 使用提供者的相關資訊,請參閱使用受控服務識別建立與 VM 的 Azure Resource Manager 服務連線

設定選項

AddAzureKeyVault 可接受 AzureKeyVaultConfigurationOptions 物件:

// using Azure.Extensions.AspNetCore.Configuration.Secrets;

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

AzureKeyVaultConfigurationOptions 物件包含下列屬性:

屬性 說明
Manager 用於控制祕密載入的 KeyVaultSecretManager 執行個體。
ReloadInterval TimeSpan 表示在輪詢保存庫進行變更的嘗試之間等候。 預設值為 null (未重新載入組態)。

使用金鑰名稱前置詞

AddAzureKeyVault提供接受 實作 的多KeyVaultSecretManager載,可讓您控制如何將 金鑰保存庫 秘密轉換成組態密鑰。 例如,您可以實作介面,根據您在應用程式啟動時提供的前置詞值載入祕密值。 此技術可讓您根據應用程式版本載入祕密。

警告

請勿在 金鑰保存庫 秘密上使用前置詞來:

  • 將多個應用程式的祕密放在相同的保存庫。
  • 將環境祕密 (例如開發實際執行環境的祕密) 放在相同的保存庫。

不同的應用程式和開發/生產環境應該使用不同的 金鑰保存庫 來隔離應用程式環境,以達到最高層級的安全性。

在下列範例中,會在 金鑰保存庫 中建立秘密(並使用開發環境的秘密管理員)來5000-AppSecret建立秘密(金鑰保存庫 秘密名稱中不允許使用句點)。 此祕密代表應用程式 5.0.0.0 版的應用程式祕密。 對於另一個版本的應用程式,5.1.0.0,密碼會新增至保存庫(並使用秘密管理員)。5100-AppSecret 每個應用程式版本會將其版本祕密值以 AppSecret 形式載入至組態,並移除載入祕密時的版本。

使用自訂 KeyVaultSecretManager 實作呼叫 AddAzureKeyVault

// using Azure.Extensions.AspNetCore.Configuration.Secrets;

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

實作會回應祕密的版本前置詞,將正確的祕密載入組態:

  • Load 會載入名稱以前置詞為開頭的祕密。 其他祕密則不會載入。
  • GetKey:
    • 從祕密名稱中移除前置詞。
    • 將任何名稱中的雙虛線取代為 KeyDelimiter,這是組態中使用的分隔符號 (通常為冒號)。 Azure Key Vault 不允許祕密名稱使用冒號。
public class SamplePrefixKeyVaultSecretManager : KeyVaultSecretManager
{
    private readonly string _prefix;

    public SamplePrefixKeyVaultSecretManager(string prefix)
        => _prefix = $"{prefix}-";

    public override bool Load(SecretProperties properties)
        => properties.Name.StartsWith(_prefix);

    public override string GetKey(KeyVaultSecret secret)
        => secret.Name[_prefix.Length..].Replace("--", ConfigurationPath.KeyDelimiter);
}

Load 方法是由提供者演算法呼叫,該演算法會逐一查看保存庫祕密,以尋找版本前置祕密。 演算法找到具有 Load 的版本前置詞時,會使用 GetKey 方法來傳回祕密名稱的組態名稱。 它會從祕密名稱中移除版本前置詞。 祕密名稱的其餘部分則會傳回,以載入應用程式組態的成對名稱和數值。

實作此方法時:

  1. 應用程式的專案檔中有指定的應用程式版本。 在下列範例中,應用程式版本設為 5.0.0.0

    <PropertyGroup>
      <Version>5.0.0.0</Version>
    </PropertyGroup>
    
  2. 確認應用程式的專案檔中存在 <UserSecretsId> 屬性,其中 {GUID} 是使用者提供的 GUID:

    <PropertyGroup>
      <UserSecretsId>{GUID}</UserSecretsId>
    </PropertyGroup>
    

    使用祕密管理員在本機儲存下列祕密:

    dotnet user-secrets set "5000-AppSecret" "5.0.0.0_secret_value_dev"
    dotnet user-secrets set "5100-AppSecret" "5.1.0.0_secret_value_dev"
    
  3. 使用下列 Azure CLI 命令將祕密儲存在 Azure Key Vault 中:

    az keyvault secret set --vault-name {KEY VAULT NAME} --name "5000-AppSecret" --value "5.0.0.0_secret_value_prod"
    az keyvault secret set --vault-name {KEY VAULT NAME} --name "5100-AppSecret" --value "5.1.0.0_secret_value_prod"
    
  4. 執行應用程式時,會載入 金鑰保存庫 秘密。 5000-AppSecret 的字串祕密與應用程式專案檔中的指定應用程式版本相符 (5.0.0.0)。

  5. 版本 5000 (含虛線) 會從金鑰名稱中去除。 在整個應用程式中,讀取具有金鑰 AppSecret 的組態時會載入祕密值。

  6. 如果專案檔中的應用程式版本變更為 5.1.0.0,且應用程式再次執行,則傳回的祕密值在開發環境中為 5.1.0.0_secret_value_dev,在實際執行環境中為 5.1.0.0_secret_value_prod

注意

您也可以將自己的 SecretClient 實作提供給 AddAzureKeyVault。 自訂用戶端允許跨應用程式共用用戶端的單一執行個體。

將陣列繫結到類別

提供者可以將組態值讀入陣列,以便繫結 POCO 陣列。

從允許金鑰包含冒號 (:) 分隔符號的組態來源進行讀取時,會使用數值金鑰區段來區分組成陣列的金鑰 (:0::1:、... :{n}:)。 如需詳細資訊,請參閱組態:將陣列繫結到類別

Azure Key Vault 金鑰無法使用冒號作為分隔符號。 本文所述的方法是使用雙虛線 (--) 作為階層值 (區段) 的分隔符號。 陣列金鑰會儲存在 Azure Key Vault 中,且包含雙虛線和數值金鑰區段 (--0----1--、... --{n}--)。

檢查 JSON 檔提供的以下 Serilog 記錄提供者組態。 WriteTo 陣列中有兩個定義的物件常值以反映兩個 Serilog 接收器,它們描述了記錄輸出的目的地:

"Serilog": {
  "WriteTo": [
    {
      "Name": "AzureTableStorage",
      "Args": {
        "storageTableName": "logs",
        "connectionString": "DefaultEnd...ountKey=Eby8...GMGw=="
      }
    },
    {
      "Name": "AzureDocumentDB",
      "Args": {
        "endpointUrl": "https://contoso.documents.azure.com:443",
        "authorizationKey": "Eby8...GMGw=="
      }
    }
  ]
}

上述 JSON 檔中顯示的組態會以雙虛線 (--) 標記法和數值區段儲存在 Azure Key Vault 中:

Key
Serilog--WriteTo--0--Name AzureTableStorage
Serilog--WriteTo--0--Args--storageTableName logs
Serilog--WriteTo--0--Args--connectionString DefaultEnd...ountKey=Eby8...GMGw==
Serilog--WriteTo--1--Name AzureDocumentDB
Serilog--WriteTo--1--Args--endpointUrl https://contoso.documents.azure.com:443
Serilog--WriteTo--1--Args--authorizationKey Eby8...GMGw==

重新載入祕密

根據預設,組態提供者會快取應用程式存留期的祕密。 應用程式會忽略後續停用或更新保存庫中的秘密。

若要重新載入祕密,請呼叫 IConfigurationRoot.Reload

config.Reload();

若要以指定的間隔定期重新載入祕密,請設定 AzureKeyVaultConfigurationOptions.ReloadInterval 屬性。 如需詳細資訊,請參閱組態選項

已停用和過期的祕密

預設會將過期的秘密包含在組態提供者中。 若要排除應用程式組態中這些秘密的值,請更新過期的秘密,或使用自定義組態提供者提供組態:

class SampleKeyVaultSecretManager : KeyVaultSecretManager
{
  public override bool Load(SecretProperties properties) =>
    properties.ExpiresOn.HasValue &&
    properties.ExpiresOn.Value > DateTimeOffset.Now;
}

將此自訂的 KeyVaultSecretManager 傳遞至 AddAzureKeyVault

// using Azure.Extensions.AspNetCore.Configuration.Secrets;

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

停用的秘密無法從 金鑰保存庫 擷取,且永遠不會包含。

疑難排解

應用程式無法使用提供者載入組態時,錯誤訊息會寫入至 ASP.NET Core 記錄基礎結構。 下列條件會防止組態載入:

  • Azure AD 中未正確設定應用程式或憑證。
  • 保存庫不存在於 Azure 金鑰保存庫 中。
  • 應用程式無權存取保存庫。
  • 存取原則不包含 GetList 權限。
  • 在保存庫中,組態數據 (name-value pair) 未正確命名、遺失或停用。
  • 應用程式 金鑰保存庫 名稱 ()、Azure AD 應用程式識別碼 (KeyVaultNameAzureADApplicationId) 或 Azure AD 憑證指紋 (), 或 Azure AD 目錄識別碼 (AzureADCertThumbprintAzureADDirectoryId) 錯誤。
  • 新增應用程式的 金鑰保存庫 存取原則時,會建立原則,但在存取原則 UI 中未選取 [儲存] 按鈕。

其他資源

本文說明如何使用 Azure Key Vault 組態提供者,從 Azure Key Vault 祕密載入應用程式組態值。 Azure Key Vault 是雲端式服務,可協助保護應用程式和服務所使用的密碼編譯金鑰及祕密。 搭配 ASP.NET Core 應用程式使用 Azure Key Vault 的常見案例包括:

  • 控制機密組態資料的存取權。
  • 儲存組態資料時,符合 FIPS 140-2 等級 2 驗證的硬體安全性模組 (HSM) 需求。

套件

新增以下套件的套件參考:

範例應用程式

Program.cs 最上方的 #define 前置處理器指示詞會決定範例應用程式的執行模式,而該模式有以下兩種:

  • Certificate:示範如何使用 Azure Key Vault 用戶端識別碼和 X.509 憑證來存取儲存在 Azure Key Vault 中的祕密。 此範例可以從任何位置執行,無論是部署至 Azure App Service 的位置,或是可以提供 ASP.NET Core 應用程式的任何主機。
  • Managed:示範如何使用 Azure 資源受控識別。 受控識別會使用 Azure Active Directory (AD) 驗證機制對 Azure Key Vault 驗證應用程式,而無須將認證資料儲存在應用程式的程式碼或組態中。 使用受控識別進行驗證時,不需要 Azure AD 應用程式識別碼和密碼 (用戶端密碼)。 範例的 Managed 版本必須部署至 Azure。 請遵循使用 Azure 資源受控識別一節中的指引。

如需使用前置處理器指示詞 (#define) 設定範例應用程式的詳細資訊,請參閱 ASP.NET Core 概觀

檢視或下載範例程式碼 \(英文\) (如何下載)

開發環境中的祕密存放區

使用祕密管理員在本機設定祕密。 範例應用程式在開發環境中的本機電腦執行時,會從本機使用者祕密存放區載入祕密。

祕密管理員需要應用程式專案檔中的 <UserSecretsId> 屬性。 將屬性值 ({GUID}) 設定為任何唯一 GUID:

<PropertyGroup>
  <UserSecretsId>{GUID}</UserSecretsId>
</PropertyGroup>

祕密會建立為成對的名稱和數值。 階層值 (組態區段) 使用 : (冒號) 作為 ASP.NET Core 組態金鑰名稱中的分隔符號。

祕密管理員是從開啟至專案內容根目錄的命令殼層中使用,其中 {SECRET NAME} 是名稱,{SECRET VALUE} 是值:

dotnet user-secrets set "{SECRET NAME}" "{SECRET VALUE}"

在專案內容根目錄的命令殼層中執行以下命令,為範例應用程式設定祕密:

dotnet user-secrets set "SecretName" "secret_value_1_dev"
dotnet user-secrets set "Section:SecretName" "secret_value_2_dev"

若這些祕密存放在使用 Azure Key Vault 在實際執行環境中建立祕密存放區一節所建立的 Azure Key Vault,後置詞 _dev 會變更為 _prod。 後置詞會在應用程式的輸出中提供視覺提示,指出組態值的來源。

使用 Azure Key Vault 在實際執行環境中建立祕密存放區

完成下列步驟來建立 Azure Key Vault,並將範例應用程式的祕密存放於此。 如需詳細資訊,請參閱快速入門:使用 Azure CLI 從 Azure Key Vault 設定和擷取祕密

  1. Azure 入口網站中使用下列任一方法開啟 Azure Cloud Shell:

    • 選取程式碼區塊右上角的 [試試看]。 在文字方塊中使用搜尋字串「Azure CLI」。
    • 使用 [啟動 Cloud Shell] 按鈕在瀏覽器中開啟 Cloud Shell。
    • 選取 Azure 入口網站右上角功能表中的 [Cloud Shell] 按鈕。

    如需詳細資訊,請參閱 Azure CLIAzure Cloud Shell 概觀

  2. 如果您尚未通過驗證,請使用 az login 命令登入。

  3. 使用下列命令建立資源群組,其中 {RESOURCE GROUP NAME} 是新資源群組的名稱,而 {LOCATION} 是 Azure 區域:

    az group create --name "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  4. 使用下列命令在資源群組中建立 金鑰保存庫,其中 {KEY VAULT NAME} 是新的保存庫名稱,而 {LOCATION} 是 Azure 區域:

    az keyvault create --name {KEY VAULT NAME} --resource-group "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  5. 在保存庫中建立秘密做為名稱/值組。

    Azure Key Vault 祕密名稱僅限使用英數字元和虛線。 階層式值(組態區段)使用 -- (兩個破折號) 作為分隔符,因為 金鑰保存庫 秘密名稱中不允許冒號。 冒號會從 ASP.NET Core 組態中的子機碼分隔區段。 在祕密載入到應用程式組態時,冒號會取代雙虛線序列。

    下列祕密可用於範例應用程式。 這些值包含 _prod 後置詞,以區別從祕密管理員載入到開發環境中的 _dev 後置詞。 {KEY VAULT NAME}取代為您在上一個步驟中建立的 金鑰保存庫 名稱:

    az keyvault secret set --vault-name {KEY VAULT NAME} --name "SecretName" --value "secret_value_1_prod"
    az keyvault secret set --vault-name {KEY VAULT NAME} --name "Section--SecretName" --value "secret_value_2_prod"
    

針對非 Azure 裝載的應用程式使用應用程式識別碼和 X.509 憑證

設定 Azure AD、Azure 金鑰保存庫 和應用程式,以在應用程式裝載於 Azure 外部時,使用 Azure AD 應用程式識別碼和 X.509 憑證向保存庫進行驗證。 如需詳細資訊,請參閱關於金鑰、祕密與憑證

注意

雖然 Azure 裝載的應用程式支援使用應用程式識別碼和 X.509 憑證,但不建議這麼做。 在 Azure 中裝載應用程式時,請改用 Azure 資源受控識別。 受控識別不需要將憑證存放在應用程式或開發環境中。

Program.cs 最上方的 #define 前置處理器指示詞設定為 Certificate 時,範例應用程式會使用應用程式識別碼和 X.509 憑證。

  1. 建立 PKCS#12 封存 (.pfx) 憑證。 建立憑證的選項包括 Windows 上的 New-SelfSignedCertificateOpenSSL
  2. 將憑證安裝至目前使用者的個人憑證存放區。 您可以選擇是否將金鑰標示為可匯出。 請記下憑證指紋,以供此流程稍後使用。
  3. 將 PKCS#12 封存 (.pfx) 憑證匯出為 DER 編碼憑證 (.cer)。
  4. 向 Azure AD 註冊應用程式 (應用程式註冊)。
  5. 將 DER 編碼憑證 (.cer) 上傳至 Azure AD:
    1. 在 Azure AD 中選取應用程式。
    2. 流覽至 [ 憑證與秘密]。
    3. 選取 [上傳憑證] 以上傳包含公開金鑰的憑證。 可接受 .cer.pem.crt 憑證。
  6. 將 金鑰保存庫名稱、應用程式識別碼和憑證指紋儲存在應用程式的appsettings.json檔案中。
  7. 流覽至 Azure 入口網站中的 金鑰保存庫
  8. 使用 Azure 金鑰保存庫 區段,選取您在生產環境中的秘密記憶體中建立的 金鑰保存庫。
  9. 選取存取原則
  10. 選取新增存取原則
  11. 開啟 [祕密權限],並提供應用程式 [取得] 和 [清單] 權限。
  12. 選取 [選取主體],然後依名稱選取已註冊的應用程式。 選取 [選取] 按鈕。
  13. 選取 [確定]。
  14. 選取 [儲存]。
  15. 部署應用程式。

Certificate 範例應用程式會使用與祕密名稱相同的名稱從 IConfigurationRoot 取得組態值:

  • 非階層值:使用 config["SecretName"] 取得 SecretName 的值。
  • 階層值 (區段):使用 : (冒號) 標記法或 GetSection 方法。 使用下列其中一種方法來取得組態值:
    • config["Section:SecretName"]
    • config.GetSection("Section")["SecretName"]

X.509 憑證由 OS 管理。 應用程式會使用檔案所提供的 appsettings.json 值來呼叫 AddAzureKeyVault

// using System.Linq;
// using System.Security.Cryptography.X509Certificates;
// using Azure.Extensions.AspNetCore.Configuration.Secrets;
// using Azure.Identity;

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, config) =>
        {
            if (context.HostingEnvironment.IsProduction())
            {
                var builtConfig = config.Build();

                using var store = new X509Store(StoreLocation.CurrentUser);
                store.Open(OpenFlags.ReadOnly);
                var certs = store.Certificates.Find(
                    X509FindType.FindByThumbprint,
                    builtConfig["AzureADCertThumbprint"], false);

                config.AddAzureKeyVault(new Uri($"https://{builtConfig["KeyVaultName"]}.vault.azure.net/"),
                                        new ClientCertificateCredential(builtConfig["AzureADDirectoryId"], builtConfig["AzureADApplicationId"], certs.OfType<X509Certificate2>().Single()),
                                        new KeyVaultSecretManager());

                store.Close();
            }
        })
        .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());

範例值:

  • 金鑰保存庫 名稱:contosovault
  • 應用程式識別碼:627e911e-43cc-61d4-992e-12db9c81b413
  • 憑證指紋:fe14593dd66b2406c5269d742d04b6e1ab03adb1

appsettings.json

{
  "KeyVaultName": "Key Vault Name",
  "AzureADApplicationId": "Azure AD Application ID",
  "AzureADCertThumbprint": "Azure AD Certificate Thumbprint",
  "AzureADDirectoryId": "Azure AD Directory ID"
}

您執行應用程式時,網頁會顯示載入的祕密值。 在開發環境中,祕密值載入時帶有 _dev 後置詞。 在實際執行環境中,祕密值載入時則帶有 _prod 後置詞。

使用 Azure 資源受控識別

部署至 Azure 的應用程式可以使用 Azure 資源受控識別。 受控識別可讓應用程式使用 Azure AD 驗證機制向 Azure Key Vault 進行驗證,而不須將認證資料 (應用程式識別碼和密碼/用戶端密碼) 儲存在應用程式中。

Program.cs 最上方的 #define 前置處理器指示詞設定為 Managed 時,範例應用程式會使用 Azure 資源受控識別。

在應用程式的 appsettings.json 檔案中輸入保存庫名稱。 設定為 Managed 版本時,範例應用程式不需要應用程式識別碼和密碼 (用戶端密碼),因此您可以忽略這些組態項目。 應用程式會部署至 Azure,而 Azure 只會使用存放在 appsettings.json 檔案中的保存庫名稱來驗證應用程式以存取 Azure Key Vault。

將範例應用程式部署至 Azure App Service。

部署至 Azure App Service 的應用程式會在建立服務時自動向 Azure AD 註冊。 從部署取得物件識別碼並用於下列命令。 物件識別碼會顯示在 Azure 入口網站中 App Service 的 Identity 面板。

使用 Azure CLI 和應用程式的物件識別碼,提供應用程式 list 以及 get 存取保存庫的權限:

az keyvault set-policy --name {KEY VAULT NAME} --object-id {OBJECT ID} --secret-permissions get list

使用 Azure CLI、PowerShell 或 Azure 入口網站重新啟動應用程式

範例應用程式:

  • 建立 DefaultAzureCredential 類別的執行個體。 認證會嘗試從 Azure 資源的環境取得存取權杖。
  • 使用 DefaultAzureCredential 執行個體建立新的 SecretClient
  • SecretClient 執行個體會與 KeyVaultSecretManager 執行個體搭配使用,後者會載入祕密值,並以金鑰名稱中的冒號 (:) 取代雙虛線 (--)。
// using Azure.Security.KeyVault.Secrets;
// using Azure.Identity;
// using Azure.Extensions.AspNetCore.Configuration.Secrets;

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, config) =>
        {
            if (context.HostingEnvironment.IsProduction())
            {
                var builtConfig = config.Build();
                var secretClient = new SecretClient(
                    new Uri($"https://{builtConfig["KeyVaultName"]}.vault.azure.net/"),
                    new DefaultAzureCredential());
                config.AddAzureKeyVault(secretClient, new KeyVaultSecretManager());
            }
        })
        .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());

金鑰保存庫 名稱範例值:contosovault

appsettings.json

{
  "KeyVaultName": "Key Vault Name"
}

您執行應用程式時,網頁會顯示載入的祕密值。 在開發環境中,祕密值是由祕密管理員提供,因此帶有 _dev 後置詞。 在實際執行環境中,祕密值是由 Azure Key Vault 提供,因此載入時帶有 _prod 後置詞。

如果您收到 Access denied 錯誤,請確認應用程式已向 Azure AD 註冊,並提供保存庫的存取權。 確認您已在 Azure 中重新啟動服務。

如需搭配受控識別和 Azure Pipelines 使用提供者的相關資訊,請參閱使用受控服務識別建立與 VM 的 Azure Resource Manager 服務連線

設定選項

AddAzureKeyVault 可接受 AzureKeyVaultConfigurationOptions 物件:

config.AddAzureKeyVault(
    new SecretClient(
        new Uri("Your Key Vault Endpoint"),
        new DefaultAzureCredential(),
        new AzureKeyVaultConfigurationOptions())
    {
        ...
    });

AzureKeyVaultConfigurationOptions 物件包含下列屬性。

屬性 說明
Manager 用於控制祕密載入的 KeyVaultSecretManager 執行個體。
ReloadInterval TimeSpan 表示在輪詢保存庫進行變更的嘗試之間等候。 預設值為 null (未重新載入組態)。

使用金鑰名稱前置詞

AddAzureKeyVault提供接受 實作 的多KeyVaultSecretManager載,可讓您控制如何將 金鑰保存庫 秘密轉換成組態密鑰。 例如,您可以實作介面,根據您在應用程式啟動時提供的前置詞值載入祕密值。 此技術可讓您根據應用程式版本載入祕密。

警告

請勿在 金鑰保存庫 秘密上使用前置詞來:

  • 將多個應用程式的祕密放在相同的保存庫。
  • 將環境祕密 (例如開發實際執行環境的祕密) 放在相同的保存庫。

不同的應用程式和開發/生產環境應該使用不同的 金鑰保存庫 來隔離應用程式環境,以達到最高層級的安全性。

在下列範例中,會在 金鑰保存庫 中建立秘密(並使用開發環境的秘密管理員)5000-AppSecret來建立秘密(金鑰保存庫 秘密名稱中不允許句點)。 此祕密代表應用程式 5.0.0.0 版的應用程式祕密。 對於另一個版本的應用程式,5.1.0.0,密碼會新增至保存庫(並使用秘密管理員)。5100-AppSecret 每個應用程式版本會將其版本祕密值以 AppSecret 形式載入至組態,並移除載入祕密時的版本。

使用自訂 KeyVaultSecretManager 實作呼叫 AddAzureKeyVault

config.AddAzureKeyVault(
    $"https://{builtConfig["KeyVaultName"]}.vault.azure.net/",
    builtConfig["AzureADApplicationId"],
    certs.OfType<X509Certificate2>().Single(),
    new PrefixKeyVaultSecretManager(versionPrefix));

實作會回應祕密的版本前置詞,將正確的祕密載入組態:

  • Load 會載入名稱以前置詞為開頭的祕密。 其他祕密則不會載入。
  • GetKey:
    • 從祕密名稱中移除前置詞。
    • 將任何名稱中的雙虛線取代為 KeyDelimiter,這是組態中使用的分隔符號 (通常為冒號)。 Azure Key Vault 不允許祕密名稱使用冒號。
public class PrefixKeyVaultSecretManager : KeyVaultSecretManager
{
    private readonly string _prefix;

    public PrefixKeyVaultSecretManager(string prefix)
    {
        _prefix = $"{prefix}-";
    }

    public override bool Load(SecretProperties secret)
    {
        return secret.Name.StartsWith(_prefix);
    }

    public override string GetKey(KeyVaultSecret secret)
    {
        return secret.Name
            .Substring(_prefix.Length)
            .Replace("--", ConfigurationPath.KeyDelimiter);
    }
}

Load 方法是由提供者演算法呼叫,該演算法會逐一查看保存庫祕密,以尋找版本前置祕密。 演算法找到具有 Load 的版本前置詞時,會使用 GetKey 方法來傳回祕密名稱的組態名稱。 它會從祕密名稱中移除版本前置詞。 祕密名稱的其餘部分則會傳回,以載入應用程式組態的成對名稱和數值。

實作此方法時:

  1. 應用程式的專案檔中有指定的應用程式版本。 在下列範例中,應用程式版本設為 5.0.0.0

    <PropertyGroup>
      <Version>5.0.0.0</Version>
    </PropertyGroup>
    
  2. 確認應用程式的專案檔中存在 <UserSecretsId> 屬性,其中 {GUID} 是使用者提供的 GUID:

    <PropertyGroup>
      <UserSecretsId>{GUID}</UserSecretsId>
    </PropertyGroup>
    

    使用祕密管理員在本機儲存下列祕密:

    dotnet user-secrets set "5000-AppSecret" "5.0.0.0_secret_value_dev"
    dotnet user-secrets set "5100-AppSecret" "5.1.0.0_secret_value_dev"
    
  3. 使用下列 Azure CLI 命令將祕密儲存在 Azure Key Vault 中:

    az keyvault secret set --vault-name {KEY VAULT NAME} --name "5000-AppSecret" --value "5.0.0.0_secret_value_prod"
    az keyvault secret set --vault-name {KEY VAULT NAME} --name "5100-AppSecret" --value "5.1.0.0_secret_value_prod"
    
  4. 執行應用程式時,會載入 金鑰保存庫 秘密。 5000-AppSecret 的字串祕密與應用程式專案檔中的指定應用程式版本相符 (5.0.0.0)。

  5. 版本 5000 (含虛線) 會從金鑰名稱中去除。 在整個應用程式中,讀取具有金鑰 AppSecret 的組態時會載入祕密值。

  6. 如果專案檔中的應用程式版本變更為 5.1.0.0,且應用程式再次執行,則傳回的祕密值在開發環境中為 5.1.0.0_secret_value_dev,在實際執行環境中為 5.1.0.0_secret_value_prod

注意

您也可以將自己的 SecretClient 實作提供給 AddAzureKeyVault。 自訂用戶端允許跨應用程式共用用戶端的單一執行個體。

將陣列繫結到類別

提供者可以將組態值讀入陣列,以便繫結 POCO 陣列。

從允許金鑰包含冒號 (:) 分隔符號的組態來源進行讀取時,會使用數值金鑰區段來區分組成陣列的金鑰 (:0::1:、... :{n}:)。 如需詳細資訊,請參閱組態:將陣列繫結到類別

Azure Key Vault 金鑰無法使用冒號作為分隔符號。 本文所述的方法是使用雙虛線 (--) 作為階層值 (區段) 的分隔符號。 陣列金鑰會儲存在 Azure Key Vault 中,且包含雙虛線和數值金鑰區段 (--0----1--、... --{n}--)。

檢查 JSON 檔提供的以下 Serilog 記錄提供者組態。 WriteTo 陣列中有兩個定義的物件常值以反映兩個 Serilog 接收器,它們描述了記錄輸出的目的地:

"Serilog": {
  "WriteTo": [
    {
      "Name": "AzureTableStorage",
      "Args": {
        "storageTableName": "logs",
        "connectionString": "DefaultEnd...ountKey=Eby8...GMGw=="
      }
    },
    {
      "Name": "AzureDocumentDB",
      "Args": {
        "endpointUrl": "https://contoso.documents.azure.com:443",
        "authorizationKey": "Eby8...GMGw=="
      }
    }
  ]
}

上述 JSON 檔中顯示的組態會以雙虛線 (--) 標記法和數值區段儲存在 Azure Key Vault 中:

Key
Serilog--WriteTo--0--Name AzureTableStorage
Serilog--WriteTo--0--Args--storageTableName logs
Serilog--WriteTo--0--Args--connectionString DefaultEnd...ountKey=Eby8...GMGw==
Serilog--WriteTo--1--Name AzureDocumentDB
Serilog--WriteTo--1--Args--endpointUrl https://contoso.documents.azure.com:443
Serilog--WriteTo--1--Args--authorizationKey Eby8...GMGw==

重新載入祕密

IConfigurationRoot.Reload 被呼叫之前,系統會快取祕密。 在執行之前 Reload ,應用程式不會遵守保存庫中已停用或更新的秘密。

Configuration.Reload();

已停用和過期的祕密

預設會將過期的秘密包含在組態提供者中。 若要排除應用程式組態中這些秘密的值,請更新過期的秘密,或使用自定義組態提供者提供組態:

class SampleKeyVaultSecretManager : KeyVaultSecretManager
{
  public override bool Load(SecretProperties properties) =>
    properties.ExpiresOn.HasValue &&
    properties.ExpiresOn.Value > DateTimeOffset.Now;
}

將此自訂的 KeyVaultSecretManager 傳遞至 AddAzureKeyVault

// using Azure.Extensions.AspNetCore.Configuration.Secrets;

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

停用的秘密無法從 金鑰保存庫 擷取,且永遠不會包含。

疑難排解

應用程式無法使用提供者載入組態時,錯誤訊息會寫入至 ASP.NET Core 記錄基礎結構。 下列條件會防止組態載入:

  • Azure AD 中未正確設定應用程式或憑證。
  • 保存庫不存在於 Azure 金鑰保存庫 中。
  • 應用程式無權存取保存庫。
  • 存取原則不包含 GetList 權限。
  • 在保存庫中,組態數據 (name-value pair) 未正確命名、遺失或停用。
  • 應用程式 金鑰保存庫 名稱 ()、Azure AD 應用程式識別碼 (KeyVaultNameAzureADApplicationId) 或 Azure AD 憑證指紋 (), 或 Azure AD 目錄識別碼 (AzureADCertThumbprintAzureADDirectoryId) 錯誤。
  • 新增應用程式的 金鑰保存庫 存取原則時,會建立原則,但在存取原則 UI 中未選取 [儲存] 按鈕。

其他資源