ASP.NET Core の Azure Key Vault 構成プロバイダー

この記事では、Azure Key Vault 構成プロバイダーを使用して、Azure Key Vault シークレットからアプリ構成値を読み込む方法について説明します。 Azure Key Vault はクラウドベースのサービスで、アプリとサービスで使われる暗号化キーとシークレットを保護するのに役立ちます。 ASP.NET Core アプリで Azure Key Vault を使用する一般的なシナリオは次のとおりです。

  • 機密性の高い構成データへのアクセスを制御する。
  • 構成データを保存するときに、FIPS 140-2 Level 2 で検証されたハードウェア セキュリティ モジュール (HSM) の要件を満たす。

パッケージ

次のパッケージのパッケージ参照を追加します。

サンプル アプリ

サンプル アプリは、#define の先頭にある Program.cs プリプロセッサ ディレクティブによって決まる 2 つのモードのどちらかで実行されます。

  • Certificate: Azure Key Vault クライアント ID と X.509 証明書を使用して、Azure Key Vault に格納されているシークレットにアクセスする方法を示します。 このサンプルは、Azure App Service または ASP.NET Core アプリにサービスを提供できる任意のホストのどちらにデプロイされるかに関係なく、どこからでも実行できます。
  • Managed: Managed を使用する方法を示します。 マネージド ID では、アプリのコードや構成に格納されている資格情報は使われず、Azure Active Directory (AD) 認証を使用して Azure Key Vault に対してアプリの認証が行われます。 サンプルの Managed バージョンは、Azure にデプロイする必要があります。 「Azure リソースのマネージド ID を使用する」セクションのガイダンスに従ってください。

プリプロセッサ ディレクティブ (#define) を使用したサンプル アプリの構成に関する詳細については、ASP.NET Core の概要に関する記事を参照してください。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

開発環境でのシークレット ストレージ

Secret Manager を使用して、シークレットをローカル環境に設定します。 開発環境のローカル コンピューターでサンプル アプリを実行すると、シークレットはローカル ユーザー シークレット ストアから読み込まれます。

Secret Manager には、アプリのプロジェクト ファイル内の <UserSecretsId> プロパティが必要です。 プロパティの値 ({GUID}) を任意の一意の GUID に設定します。

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

シークレットは、名前と値のペアとして作成されます。 階層値 (構成セクション) では、:のキー名の区切り記号として : (コロン) を使用します。

Secret Manager は、プロジェクトのコンテンツ ルートで開いたコマンド シェルから使用します。{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 portal で次のいずれかの方法を使って Azure Cloud Shell を開きます。

    • コード ブロックの右上隅にある [使ってみる] を選択します。 テキスト ボックスで検索文字列 "Azure CLI" を使います。
    • [Cloud Shell を起動する] ボタンを使って、ブラウザーで Cloud Shell を開きます。
    • Azure portal の右上隅にあるメニューの [Cloud Shell] ボタンを選びます。

    詳しくは、Azure CLI に関するページと、「Azure Cloud Shell の概要」をご覧ください。

  2. まだ認証されていない場合は、az login コマンドでサインインします。

  3. 次のコマンドを使ってリソース グループを作成します。{RESOURCE GROUP NAME} は新しいリソース グループの名前、{LOCATION} は Azure リージョンです。

    az group create --name "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  4. 次のコマンドを使ってリソース グループに Key Vault を作成します。{KEY VAULT NAME} は新しいコンテナーの名前、{LOCATION} は Azure リージョンです。

    az keyvault create --name {KEY VAULT NAME} --resource-group "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  5. コンテナーに名前と値の組としてシークレットを作成します。

    Azure Key Vault のシークレット名では、英数字とダッシュだけを使用できます。 Key Vault のシークレット名ではコロンを使えないので、階層値 (構成セクション) では区切り記号として -- (2 つのダッシュ) を使います。 ASP.NET Core の構成では、セクションとサブキーをコロンで区切ります。 アプリの構成にシークレットが読み込まれるときに、2 つのダッシュのシーケンスはコロンで置き換えられます。

    次のシークレットは、サンプル アプリで使用するためのものです。 値には、Secret Manager から開発環境に読み込まれた _dev サフィックスの値と区別するために、_prod サフィックスが含まれます。 {KEY VAULT NAME} を前のステップで作成した Key Vault の名前に置き換えます。

    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 でホストされていないアプリにアプリケーション ID と X.509 証明書を使用する

アプリが Azure の外部でホストされている場合に、Azure AD のアプリケーション ID と X.509 証明書を使ってコンテナーに対する認証を行うように、Azure AD、Azure Key Vault、アプリを構成します。 詳細については、「About keys, secrets, and certificates (キー、シークレット、証明書について)」を参照してください。

注意

Azure でホストされているアプリについても、アプリケーション ID と X.509 証明書を使用できますが、お勧めしません。 Azure でアプリをホストするときは、代わりに、Azure リソースのマネージド ID を使います。 マネージド ID を使うと、アプリまたは開発環境に証明書を保存する必要がありません。

#define の先頭にある Program.cs プリプロセッサ ディレクティブを Certificate に設定した場合、サンプル アプリにはアプリケーション ID と 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. Key Vault 名、アプリケーション ID、証明書のサムプリントを、アプリの appsettings.json ファイルに格納します。
  7. Azure portal で [Key Vault] に移動します。
  8. Azure Key Vault による運用環境のシークレット ストレージ」セクションで作成した Key Vault を選びます。
  9. [アクセス ポリシー] を選択します。
  10. [アクセス ポリシーの追加] を選択します。
  11. [シークレットのアクセス許可] を開き、アプリに GetList のアクセス許可を付与します。
  12. [プリンシパルの選択] を選び、登録済みのアプリの名前を選びます。 [選択] ボタンを選択します。
  13. [OK] を選択します。
  14. [保存] を選択します。
  15. アプリを展開します。

Certificate サンプル アプリは、シークレット名と同じ名前の IConfigurationRoot から構成値を取得します。

  • 非階層値: SecretName の値は、config["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();

値の例:

  • Key Vault 名: contosovault
  • アプリケーション ID: 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"
}

アプリを実行すると、Web ページに読み込まれたシークレット値が表示されます。 開発環境では、シークレット値は _dev サフィックス付きで読み込まれます。 運用環境では、値は _prod サフィックス付きで読み込まれます。

Azure リソースのマネージド ID を使用する

Azure にデプロイされたアプリでは、Azure リソースのマネージド ID を利用できます。 マネージド ID を使うと、アプリのコードや構成に資格情報を格納せず、アプリから Azure AD 認証を使って Azure Key Vault で認証することができます。

Program.cs の先頭にある #define プリプロセッサ ディレクティブを Managed に設定した場合、サンプル アプリにはシステム割り当てマネージド ID が使われます。 Azure App Service アプリのマネージド ID を作成するには、「App Service と Azure Functions でマネージド ID を使用する方法」を参照してください。 マネージド ID を作成したら、Azure portal の App Service の Identity パネルに表示されるアプリのオブジェクト ID をメモしておきます。

アプリの appsettings.json ファイルにコンテナー名を入力します。 Managed バージョンに設定すると、サンプル アプリにアプリケーション ID とパスワード (クライアント シークレット) は必要ないため、それらの構成エントリは無視してかまいません。 アプリは Azure にデプロイされており、Azure によって、 appsettings.json ファイルに格納されているコンテナー名のみを使用して、アプリによる Azure Key Vault へのアクセスが認証されます。

サンプル アプリを Azure App Service にデプロイします。

Azure CLI とアプリのオブジェクト ID を使って、コンテナーにアクセスするための list および get アクセス許可をアプリに付与します。

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

Azure CLI、PowerShell、または Azure portal を使って、アプリを再起動します

サンプル アプリによって、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());
}

Key Vault 名の値の例: contosovault

appsettings.json:

{
  "KeyVaultName": "Key Vault Name"
}

ユーザー割り当てマネージド ID を使うアプリの場合、次のいずれかの方法でマネージド ID のクライアント ID を構成します。

  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"]
        }));
    

アプリを実行すると、Web ページに読み込まれたシークレット値が表示されます。 開発環境では、シークレットの値は Secret Manager によって提供されるため、_dev サフィックスが付いています。 運用環境では、値は Azure Key Vault によって提供されるため、_prod サフィックスで読み込まれます。

Access denied エラーが発生する場合は、アプリが Azure AD に登録されていること、およびコンテナーへのアクセス権が提供されていることを確認します。 Azure でサービスを再起動したことを確認します。

マネージド ID と Azure Pipelines でプロバイダーを使用する方法については、「マネージド サービス ID を使用して 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 の実装を受け入れるオーバーロードを使用すると、Key Vault のシークレットが構成キーに変換される方法を制御できます。 たとえば、アプリの起動時に指定するプレフィックス値に基づいてシークレット値を読み込むように、インターフェイスを実装できます。 この手法を使用すると、たとえば、アプリのバージョンに基づいてシークレットを読み込むことができます。

警告

次の目的には、Key Vault のシークレットのプレフィックスを使わないでください。

  • 複数のアプリ用のシークレットを同じコンテナーに配置する。
  • 環境シークレット (たとえば、"開発" と "運用" シークレット) を同じコンテナーに配置する。

異なるアプリおよび開発と運用環境には、個別の Key Vault を使用し、最高レベルのセキュリティのためにアプリ環境を分離する必要があります。

次の例では、シークレットは 5000-AppSecret (Key Vault のシークレット名ではピリオドは使用できません) 用の Key Vault (および開発環境では Secret Manager を使用) に確立されます。 このシークレットは、アプリのバージョン 5.0.0.0 のアプリ シークレットを表します。 アプリの別のバージョン 5.1.0.0 のシークレットは、5100-AppSecret 用のコンテナー (および Secret Manager を使用) に追加されます。 各アプリ バージョンで、そのバージョン管理されたシークレット値が AppSecret として構成に読み込まれ、シークレットを読み込んだバージョンは削除されます。

AddAzureKeyVault は、KeyVaultSecretManager のカスタム実装で呼び出されます。

// 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:
    • シークレット名からプレフィックスを削除します。
    • 名前の 2 つのダッシュを、構成で使用される区切り記号 (通常はコロン) である 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>
    

    Secret Manager を使用して、次のシークレットをローカル環境に保存します。

    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. アプリが実行されると、Key Vault シークレットが読み込まれます。 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 に提供することもできます。 カスタム クライアントを使用すると、アプリ全体でクライアントの 1 つのインスタンスを共有できます。

配列をクラスにバインドする

プロバイダーで、構成の値を、POCO 配列にバインドするための配列に読み取ることができます。

キーにコロン (:) 区切り記号を含めることができる構成ソースから読み取る場合、配列 (:0::1:、… :{n}:) を構成するキーを区別するために、数値キー セグメントが使用されます。 詳しくは、構成での配列からクラスへのバインドに関する記事をご覧ください。

Azure Key Vault キーでは、区切り記号としてコロンを使用することはできません。 この記事で説明する方法では、階層値 (セクション) の区切り記号として二重ダッシュ (--) を使用します。 配列キーは、二重ダッシュと数値キー セグメント (--0----1--、… --{n}--) を使用して、Azure Key Vault に格納されます。

JSON ファイルによって提供される次の Serilog ログ プロバイダーの構成を調べてください。 WriteTo 配列では、2 つの Serilog "WriteTo" を反映した 2 つのオブジェクト リテラルが定義されており、ログ出力の宛先が記述されています。

"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 に格納されます。

キー
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;
}

このカスタムの KeyVaultSecretManagerAddAzureKeyVault に渡します。

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

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

無効なシークレットは Key Vault から取得できず、含まれません。

トラブルシューティング

アプリでプロバイダーを使用した構成の読み込みが失敗すると、エラー メッセージが ASP.NET Core ログ インフラストラクチャに書き込まれます。 次の状況では、構成を読み込むことができません。

  • Azure AD で、アプリまたは証明書が正しく構成されていません。
  • コンテナーが Azure Key Vault に存在しません。
  • アプリがコンテナーへのアクセスを承認されていません。
  • アクセス ポリシーに、GetList のアクセス許可が含まれません。
  • コンテナーの構成データ (名前と値の組) が、名前が正しくないか、存在しないか、無効です。
  • アプリの Key Vault 名 (KeyVaultName)、Azure AD アプリケーション ID (AzureADApplicationId)、Azure AD 証明書のサムプリント (AzureADCertThumbprint)、または Azure AD のディレクトリ ID (AzureADDirectoryId) が正しくありません。
  • アプリのKey Vault アクセス ポリシーを追加するときに、ポリシーが作成されましたが、[アクセス ポリシー] の UI で [保存] ボタンが選ばれませんでした。

その他の技術情報

この記事では、Azure Key Vault 構成プロバイダーを使用して、Azure Key Vault シークレットからアプリ構成値を読み込む方法について説明します。 Azure Key Vault はクラウドベースのサービスで、アプリとサービスで使われる暗号化キーとシークレットを保護するのに役立ちます。 ASP.NET Core アプリで Azure Key Vault を使用する一般的なシナリオは次のとおりです。

  • 機密性の高い構成データへのアクセスを制御する。
  • 構成データを保存するときに、FIPS 140-2 Level 2 で検証されたハードウェア セキュリティ モジュール (HSM) の要件を満たす。

パッケージ

次のパッケージのパッケージ参照を追加します。

サンプル アプリ

サンプル アプリは、#define の先頭にある Program.cs プリプロセッサ ディレクティブによって決まる 2 つのモードのどちらかで実行されます。

  • Certificate: Azure Key Vault クライアント ID と X.509 証明書を使用して、Azure Key Vault に格納されているシークレットにアクセスする方法を示します。 このサンプルは、Azure App Service または ASP.NET Core アプリにサービスを提供できる任意のホストのどちらにデプロイされるかに関係なく、どこからでも実行できます。
  • Managed: Managed を使用する方法を示します。 マネージド ID では、アプリのコードや構成に格納された資格情報は使われず、Azure Active Directory (AD) 認証を使用して Azure Key Vault に対してアプリの認証が行われます。 マネージ ID を使って認証を行うときは、Azure AD アプリケーション ID とパスワード (クライアント シークレット) は必要ありません。 サンプルの Managed バージョンは、Azure にデプロイする必要があります。 「Azure リソースのマネージド ID を使用する」セクションのガイダンスに従ってください。

プリプロセッサ ディレクティブ (#define) を使用したサンプル アプリの構成に関する詳細については、ASP.NET Core の概要に関する記事を参照してください。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

開発環境でのシークレット ストレージ

Secret Manager を使用して、シークレットをローカル環境に設定します。 開発環境のローカル コンピューターでサンプル アプリを実行すると、シークレットはローカル ユーザー シークレット ストアから読み込まれます。

Secret Manager には、アプリのプロジェクト ファイル内の <UserSecretsId> プロパティが必要です。 プロパティの値 ({GUID}) を任意の一意の GUID に設定します。

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

シークレットは、名前と値のペアとして作成されます。 階層値 (構成セクション) では、:のキー名の区切り記号として : (コロン) を使用します。

Secret Manager は、プロジェクトのコンテンツ ルートで開いたコマンド シェルから使用します。{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 portal で次のいずれかの方法を使って Azure Cloud Shell を開きます。

    • コード ブロックの右上隅にある [使ってみる] を選択します。 テキスト ボックスで検索文字列 "Azure CLI" を使います。
    • [Cloud Shell を起動する] ボタンを使って、ブラウザーで Cloud Shell を開きます。
    • Azure portal の右上隅にあるメニューの [Cloud Shell] ボタンを選びます。

    詳しくは、Azure CLI に関するページと、「Azure Cloud Shell の概要」をご覧ください。

  2. まだ認証されていない場合は、az login コマンドでサインインします。

  3. 次のコマンドを使ってリソース グループを作成します。{RESOURCE GROUP NAME} は新しいリソース グループの名前、{LOCATION} は Azure リージョンです。

    az group create --name "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  4. 次のコマンドを使ってリソース グループに Key Vault を作成します。{KEY VAULT NAME} は新しいコンテナーの名前、{LOCATION} は Azure リージョンです。

    az keyvault create --name {KEY VAULT NAME} --resource-group "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  5. コンテナーに名前と値の組としてシークレットを作成します。

    Azure Key Vault のシークレット名では、英数字とダッシュだけを使用できます。 Key Vault のシークレット名ではコロンを使えないので、階層値 (構成セクション) では区切り記号として -- (2 つのダッシュ) を使います。 ASP.NET Core の構成では、セクションとサブキーをコロンで区切ります。 アプリの構成にシークレットが読み込まれるときに、2 つのダッシュのシーケンスはコロンで置き換えられます。

    次のシークレットは、サンプル アプリで使用するためのものです。 値には、Secret Manager から開発環境に読み込まれた _dev サフィックスの値と区別するために、_prod サフィックスが含まれます。 {KEY VAULT NAME} を前のステップで作成した Key Vault の名前に置き換えます。

    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 でホストされていないアプリにアプリケーション ID と X.509 証明書を使用する

アプリが Azure の外部でホストされている場合に、Azure AD のアプリケーション ID と X.509 証明書を使ってコンテナーに対する認証を行うように、Azure AD、Azure Key Vault、アプリを構成します。 詳細については、「About keys, secrets, and certificates (キー、シークレット、証明書について)」を参照してください。

注意

Azure でホストされているアプリについても、アプリケーション ID と X.509 証明書を使用できますが、お勧めしません。 Azure でアプリをホストするときは、代わりに、Azure リソースのマネージド ID を使います。 マネージド ID を使うと、アプリまたは開発環境に証明書を保存する必要がありません。

#define の先頭にある Program.cs プリプロセッサ ディレクティブを Certificate に設定した場合、サンプル アプリにはアプリケーション ID と 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. Key Vault 名、アプリケーション ID、証明書のサムプリントを、アプリの appsettings.json ファイルに格納します。
  7. Azure portal で [Key Vault] に移動します。
  8. Azure Key Vault による運用環境のシークレット ストレージ」セクションで作成した Key Vault を選びます。
  9. [アクセス ポリシー] を選択します。
  10. [アクセス ポリシーの追加] を選択します。
  11. [シークレットのアクセス許可] を開き、アプリに GetList のアクセス許可を付与します。
  12. [プリンシパルの選択] を選び、登録済みのアプリの名前を選びます。 [選択] ボタンを選択します。
  13. [OK] を選択します。
  14. [保存] を選択します。
  15. アプリを展開します。

Certificate サンプル アプリは、シークレット名と同じ名前の IConfigurationRoot から構成値を取得します。

  • 非階層値: SecretName の値は、config["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>());

値の例:

  • Key Vault 名: contosovault
  • アプリケーション ID: 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"
}

アプリを実行すると、Web ページに読み込まれたシークレット値が表示されます。 開発環境では、シークレット値は _dev サフィックス付きで読み込まれます。 運用環境では、値は _prod サフィックス付きで読み込まれます。

Azure リソースのマネージド ID を使用する

Azure にデプロイされたアプリでは、Azure リソースのマネージド ID を利用できます。 マネージド ID を使用すると、資格情報 (アプリケーション ID とパスワードまたはクライアント シークレット) をアプリに格納することなく、アプリで Azure AD 認証を使って Azure Key Vault での認証を行うことができます。

#define の先頭にある Program.cs プリプロセッサ ディレクティブを Managed に設定した場合、サンプル アプリには Azure リソースのマネージド ID が使われます。

アプリの appsettings.json ファイルにコンテナー名を入力します。 Managed バージョンに設定すると、サンプル アプリにアプリケーション ID とパスワード (クライアント シークレット) は必要ないため、それらの構成エントリは無視してかまいません。 アプリは Azure にデプロイされており、Azure によって、 appsettings.json ファイルに格納されているコンテナー名のみを使用して、アプリによる Azure Key Vault へのアクセスが認証されます。

サンプル アプリを Azure App Service にデプロイします。

Azure App Service にデプロイされたアプリは、サービスの作成時に Azure AD に自動的に登録されます。 次のコマンドで使用するために、デプロイからオブジェクト ID を取得します。 Azure portal で、オブジェクト ID は App Service の [Identity] パネルに表示されます。

Azure CLI とアプリのオブジェクト ID を使って、コンテナーにアクセスするための list および get アクセス許可をアプリに付与します。

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

Azure CLI、PowerShell、または Azure portal を使って、アプリを再起動します

サンプル アプリで次のことが行われます。

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

Key Vault 名の値の例: contosovault

appsettings.json:

{
  "KeyVaultName": "Key Vault Name"
}

アプリを実行すると、Web ページに読み込まれたシークレット値が表示されます。 開発環境では、シークレットの値は Secret Manager によって提供されるため、_dev サフィックスが付いています。 運用環境では、値は Azure Key Vault によって提供されるため、_prod サフィックスで読み込まれます。

Access denied エラーが発生する場合は、アプリが Azure AD に登録されていること、およびコンテナーへのアクセス権が提供されていることを確認します。 Azure でサービスを再起動したことを確認します。

マネージド ID と Azure Pipelines でプロバイダーを使用する方法については、「マネージド サービス ID を使用して 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 の実装を受け入れるオーバーロードを使用すると、Key Vault のシークレットが構成キーに変換される方法を制御できます。 たとえば、アプリの起動時に指定するプレフィックス値に基づいてシークレット値を読み込むように、インターフェイスを実装できます。 この手法を使用すると、たとえば、アプリのバージョンに基づいてシークレットを読み込むことができます。

警告

次の目的には、Key Vault のシークレットのプレフィックスを使わないでください。

  • 複数のアプリ用のシークレットを同じコンテナーに配置する。
  • 環境シークレット (たとえば、"開発" と "運用" シークレット) を同じコンテナーに配置する。

異なるアプリおよび開発と運用環境には、個別の Key Vault を使用し、最高レベルのセキュリティのためにアプリ環境を分離する必要があります。

次の例では、シークレットは 5000-AppSecret (Key Vault のシークレット名ではピリオドは使用できません) 用の Key Vault (および開発環境では Secret Manager を使用) に確立されます。 このシークレットは、アプリのバージョン 5.0.0.0 のアプリ シークレットを表します。 アプリの別のバージョン 5.1.0.0 のシークレットは、5100-AppSecret 用のコンテナー (および Secret Manager を使用) に追加されます。 各アプリ バージョンで、そのバージョン管理されたシークレット値が AppSecret として構成に読み込まれ、シークレットを読み込んだバージョンは削除されます。

AddAzureKeyVault は、KeyVaultSecretManager のカスタム実装で呼び出されます。

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

実装は、シークレットのバージョン プレフィックスに対応して、適切なシークレットを構成に読み込みます。

  • シークレットの名前がプレフィックスで始まっていると、Load によって読み込まれます。 それ以外のシークレットは読み込まれません。
  • GetKey:
    • シークレット名からプレフィックスを削除します。
    • 名前の 2 つのダッシュを、構成で使用される区切り記号 (通常はコロン) である 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>
    

    Secret Manager を使用して、次のシークレットをローカル環境に保存します。

    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. アプリが実行されると、Key Vault シークレットが読み込まれます。 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 に提供することもできます。 カスタム クライアントを使用すると、アプリ全体でクライアントの 1 つのインスタンスを共有できます。

配列をクラスにバインドする

プロバイダーで、構成の値を、POCO 配列にバインドするための配列に読み取ることができます。

キーにコロン (:) 区切り記号を含めることができる構成ソースから読み取る場合、配列 (:0::1:、… :{n}:) を構成するキーを区別するために、数値キー セグメントが使用されます。 詳しくは、構成での配列からクラスへのバインドに関する記事をご覧ください。

Azure Key Vault キーでは、区切り記号としてコロンを使用することはできません。 この記事で説明する方法では、階層値 (セクション) の区切り記号として二重ダッシュ (--) を使用します。 配列キーは、二重ダッシュと数値キー セグメント (--0----1--、… --{n}--) を使用して、Azure Key Vault に格納されます。

JSON ファイルによって提供される次の Serilog ログ プロバイダーの構成を調べてください。 WriteTo 配列では、2 つの Serilog "WriteTo" を反映した 2 つのオブジェクト リテラルが定義されており、ログ出力の宛先が記述されています。

"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 に格納されます。

キー
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;
}

このカスタムの KeyVaultSecretManagerAddAzureKeyVault に渡します。

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

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

無効なシークレットは Key Vault から取得できず、含まれません。

トラブルシューティング

アプリでプロバイダーを使用した構成の読み込みが失敗すると、エラー メッセージが ASP.NET Core ログ インフラストラクチャに書き込まれます。 次の状況では、構成を読み込むことができません。

  • Azure AD で、アプリまたは証明書が正しく構成されていません。
  • コンテナーが Azure Key Vault に存在しません。
  • アプリがコンテナーへのアクセスを承認されていません。
  • アクセス ポリシーに、GetList のアクセス許可が含まれません。
  • コンテナーの構成データ (名前と値の組) が、名前が正しくないか、存在しないか、無効です。
  • アプリの Key Vault 名 (KeyVaultName)、Azure AD アプリケーション ID (AzureADApplicationId)、Azure AD 証明書のサムプリント (AzureADCertThumbprint)、または Azure AD のディレクトリ ID (AzureADDirectoryId) が正しくありません。
  • アプリのKey Vault アクセス ポリシーを追加するときに、ポリシーが作成されましたが、[アクセス ポリシー] の UI で [保存] ボタンが選ばれませんでした。

その他の技術情報