演習 - Azure Key Vault に格納されているシークレットにアクセスする

完了

Azure リソースのマネージド ID を有効にすると、アプリで認証に使用する ID がどのように作成されるかがわかりました。 次は、その ID を使用してコンテナー内のシークレットにアクセスするアプリを作成します。

ASP.NET Core アプリでシークレットを読み取る

Azure Key Vault API は、キーとコンテナーの管理と使用をすべて処理する REST API です。 コンテナー内の各シークレットには、一意の URL があります。 シークレット値は、HTTP GET 要求で取得されます。

.NET Core の公式の Key Vault クライアントは、Azure.Security.KeyVault.Secrets NuGet パッケージの SecretClient クラスです。 ただし、直接使用する必要はありません。 ASP.NET Core の AddAzureKeyVault メソッドを使用すると、起動時にコンテナーから Configuration API にすべてのシークレットを読み込むことができます。 この手法では、残りの構成で使用するものと同じ IConfiguration インターフェイスを使用して、すべてのシークレットに名前を指定してアクセスできます。 AddAzureKeyVault を使用するアプリには、コンテナーに対する GetList の両方のアクセス許可が必要です。

ヒント

特別な理由がない限り、アプリのビルドに使用するフレームワークや言語に関係なく、起動時にシークレット値をローカルにキャッシュするか、メモリに読み込むように設計する必要があります。 必要なときに毎回コンテナーから直接読み取ると、不必要に低速でコストも高くなります。

AddAzureKeyVault には入力としてコンテナー名のみが必要です。コンテナー名はローカル アプリの構成から取得します。 また、マネージド ID 認証も自動的に処理されます。 Azure リソースのマネージド ID が有効な Azure App Service にデプロイされたアプリに使用すると、マネージド ID トークン サービスが検出され、認証に使用されます。 これはほとんどのシナリオに適しており、すべてのベスト プラクティスが実装されています。 このユニットの演習では、これを使用します。

Node.js アプリでシークレットを読み取る

Azure Key Vault API は、キーとコンテナーの管理と使用をすべて処理する REST API です。 コンテナー内の各シークレットには、一意の URL があります。 シークレット値は、HTTP GET 要求で取得されます。

Node.js アプリの公式 Key Vault クライアントは、@azure/keyvault-secrets npm パッケージの SecretClient クラスです。 構成またはコードにシークレットの名前を含むアプリの場合は、通常、getSecret メソッドが使用されます。このメソッドで名前を指定すると、シークレット値が読み込まれます。 getSecret を使用するには、アプリの ID に、コンテナーに対する Get アクセス許可が必要です。 コンテナーからすべてのシークレットを読み込むように設計されたアプリは、listPropertiesOfSecrets メソッドも使用します。このメソッドでシークレットの一覧が読み込まれます。このメソッドを使用するには、List アクセス許可が必要です。

アプリで SecretClient インスタンスを作成するには、事前にコンテナーへの認証を行って資格情報オブジェクトを取得しておく必要があります。 認証するには、@azure/identity npm パッケージに用意されている DefaultAzureCredential を使用します。 DefaultAzureCredential は、アプリケーションを最終的に Azure クラウドで実行することを目的とするほとんどのシナリオに適しています。DefaultAzureCredential により、デプロイ時の認証に一般的に使用される資格情報と、開発環境での認証に使用される資格情報が結合されるためです。 DefaultAzureCredential により、次のメカニズムを順に使用して認証が試行されます。

  • 環境。 DefaultAzureCredential により、環境変数を使用して指定されたアカウント情報が読み取られ、認証に使用されます。
  • マネージド ID。 マネージド ID が有効な Azure ホストにアプリケーションがデプロイされている場合、DefaultAzureCredential により、そのアカウントを使用して認証が行われます。
  • Visual Studio Code。 開発者が Visual Studio Code Azure Account プラグインを使用して認証を行った場合、DefaultAzureCredential により、そのアカウントを使用して認証が行われます。
  • Azure CLI。 開発者が Azure CLI コマンド az login を使用してアカウントを認証した場合、DefaultAzureCredential により、そのアカウントを使用して認証が行われます。

詳細については、ドキュメントを参照してください

ヒント

特別な理由がない限り、アプリのビルドに使用するフレームワークや言語に関係なく、起動時にシークレット値をローカルにキャッシュするか、メモリに読み込むように設計する必要があります。 必要なときに毎回コンテナーから直接読み取ると、不必要に低速でコストも高くなります。

アプリでシークレットを処理する

シークレットがアプリに読み込まれた後、シークレットを安全に処理する作業はアプリの担当です。 このモジュールで構築するアプリでは、シークレット値をクライアントの応答に書き出し、正常に読み込まれたことを示すために、それを Web ブラウザーに表示します。 クライアントにシークレット値を返す処理は、通常の作業とは "異なります"。 通常は、シークレットを使用して、データベースやリモート API のクライアント ライブラリの初期化などの処理を行います。

重要

常に注意してコードを見直し、ログ、ストレージ、応答など、どのような種類の出力にもシークレット情報を書き込まないようにしてください。

演習

コンテナーからシークレットを読み込むには、新しい ASP.NET Core Web API を作成し、AddAzureKeyVault を使用します。

アプリを作成する

  1. 新しい ASP.NET Core Web API アプリを作成してエディターで開くには、Azure Cloud Shell で次のコマンドを実行します。

    dotnet new webapi -o KeyVaultDemoApp
    cd KeyVaultDemoApp
    code .
    
  2. エディターの読み込みが完了したら、AddAzureKeyVault を含む NuGet パッケージを追加し、すべてのアプリの依存関係を復元します。 Azure Cloud Shell で次のコマンドを実行します。

    dotnet add package Azure.Identity
    dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
    dotnet restore
    

シークレットを読み込んで使用するコードを追加する

Key Vault の適切な使用方法を示すため、起動時にコンテナーからシークレットを読み込むようにアプリを変更します。 また、エンドポイントに新しいコントローラーを追加して、SecretPassword シークレットをコンテナーから取得します。

  1. アプリを起動するには、次のコマンドを入力して、エディターを起動します。

    code .
    
  2. Program.cs を開き、内容を削除して次のコードに置き換えます。

    using System;
    using Azure.Identity;
    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Hosting;
    
    namespace KeyVaultDemoApp
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                CreateHostBuilder(args).Build().Run();
            }
    
            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                    })
                    .ConfigureAppConfiguration((context, config) =>
                    {
                        // Build the current set of configuration to load values from
                        // JSON files and environment variables, including VaultName.
                        var builtConfig = config.Build();
    
                        // Use VaultName from the configuration to create the full vault URI.
                        var vaultName = builtConfig["VaultName"];
                        Uri vaultUri = new Uri($"https://{vaultName}.vault.azure.net/");
    
                        // Load all secrets from the vault into configuration. This will automatically
                        // authenticate to the vault using a managed identity. If a managed identity
                        // is not available, it will check if Visual Studio and/or the Azure CLI are
                        // installed locally and see if they are configured with credentials that can
                        // access the vault.
                        config.AddAzureKeyVault(vaultUri, new DefaultAzureCredential());
                    });
        }
    }
    

    重要

    編集が完了したら、必ずファイルを保存します。 [...] メニューまたはアクセラレータ キー (Windows と Linux の場合は Ctrl+S キー、macOS の場合は Cmd+S キー) を使用して、ファイルを保存できます。

    スターター コードからは、唯一 ConfigureAppConfiguration が追加され変更されています。 この要素は、構成からコンテナー名を読み込み、それを使用して AddAzureKeyVault を呼び出す場所です。

  3. コントローラーについては、Controllers フォルダーに SecretTestController.cs という新しいファイルを作成し、次のコードを貼り付けます。

    ヒント

    新しいファイルを作成するには、Cloud Shell で touch コマンドを使用します。 ここでは、touch Controllers/SecretTestController.cs コマンドを実行します。 見つけるには、エディターの [ファイル] ペインの右上で [更新] アイコンを選択します。

    using System;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    
    namespace KeyVaultDemoApp.Controllers
    {
        [Route("api/[controller]")]
        public class SecretTestController : ControllerBase
        {
            private readonly IConfiguration _configuration;
    
            public SecretTestController(IConfiguration configuration)
            {
                _configuration = configuration;
            }
    
            [HttpGet]
            public IActionResult Get()
            {
                // Get the secret value from configuration. This can be done anywhere
                // we have access to IConfiguration. This does not call the Key Vault
                // API, because the secrets were loaded at startup.
                var secretName = "SecretPassword";
                var secretValue = _configuration[secretName];
    
                if (secretValue == null)
                {
                    return StatusCode(
                        StatusCodes.Status500InternalServerError,
                        $"Error: No secret named {secretName} was found...");
                }
                else {
                    return Content($"Secret value: {secretValue}" +
                        Environment.NewLine + Environment.NewLine +
                        "This is for testing only! Never output a secret " +
                        "to a response or anywhere else in a real app!");
                }
            }
        }
    }
    
  4. Azure Cloud Shell で、dotnet build コマンドを実行して、すべてがコンパイルされることを確認します。 アプリを実行する準備ができました。 次に、これを Azure に取り込みます。

Express.js を使用して新しい Web API を作成し、@azure/keyvault-secrets および @azure/identity パッケージを使用してコンテナーからシークレットを読み込みます。

アプリケーションの作成

Azure Cloud Shell で次のコードを実行して、新しい Node.js アプリを初期化し、必要なパッケージをインストールし、エディターで新しいファイルを開きます。

mkdir KeyVaultDemoApp
cd KeyVaultDemoApp
npm init -y
npm install @azure/identity @azure/keyvault-secrets express
touch app.js
code app.js

シークレットを読み込んで使用するコードを追加する

Key Vault の適切な使用方法を示すため、このアプリでは、起動時にコンテナーからシークレットを読み込みます。 シークレットが読み込まれたことを示すために、SecretPassword シークレットの値を表示するエンドポイントを作成します。

  1. アプリを設定するために、次のコードをエディターに貼り付けます。 このコードは、必要なパッケージのインポート、ポートとコンテナーの URI 構成の設定、シークレットの名前と値を保持する新しいオブジェクトの作成を行います。

    // Importing dependencies
    const { DefaultAzureCredential } = require("@azure/identity");
    const { SecretClient } = require("@azure/keyvault-secrets");
    const app = require('express')();
    
    // Initialize port
    const port = process.env.PORT || 3000;
    
    // Create Vault URI from App Settings
    const vaultUri = `https://${process.env.VaultName}.vault.azure.net/`;
    
    // Map of key vault secret names to values
    let vaultSecretsMap = {};
    

    重要

    ファイルの編集時、特に完了時には必ず保存してください。 [...] メニューまたはアクセラレータ キー (Windows と Linux の場合は Ctrl+S キー、macOS の場合は Cmd+S キー) を使用して、ファイルを保存できます。

  2. 次に、コンテナーへの認証を行ってシークレットを読み込むコードを追加します。 このコードを 2 つの別個の関数として追加します。 以前に追加したコードの後に何行か空白行を挿入し、次のコードを貼り付けます。

    const getKeyVaultSecrets = async () => {
      // Create a key vault secret client
      let secretClient = new SecretClient(vaultUri, new DefaultAzureCredential());
      try {
        // Iterate through each secret in the vault
        listPropertiesOfSecrets = secretClient.listPropertiesOfSecrets();
        while (true) {
          let { done, value } = await listPropertiesOfSecrets.next();
          if (done) {
            break;
          }
          // Only load enabled secrets - getSecret will return an error for disabled secrets
          if (value.enabled) {
            const secret = await secretClient.getSecret(value.name);
            vaultSecretsMap[value.name] = secret.value;
          }
        }
      } catch(err) {
        console.log(err.message)
      }
    }
    
  3. シークレットが読み込まれたかどうかをテストするために、Express エンドポイントを作成します。 このコードに貼り付けます。

    app.get('/api/SecretTest', (req, res) => {
      let secretName = 'SecretPassword';
      let response;
      if (secretName in vaultSecretsMap) {
        response = `Secret value: ${vaultSecretsMap[secretName]}\n\nThis is for testing only! Never output a secret to a response or anywhere else in a real app!`;
      } else {
        response = `Error: No secret named ${secretName} was found...`
      }
      res.type('text');
      res.send(response);
    });
    
  4. 関数を呼び出してコンテナーからシークレットを読み込み、アプリを起動します。 アプリを完成させるために、この最後のスニペットを貼り付けます。

    (async () =>  {
      await getKeyVaultSecrets();
      app.listen(port, () => {
        console.log(`Server running at http://localhost:${port}`);
      });
    })().catch(err => console.log(err));
    
  5. コードが完成したので、必ずファイルを保存します。

アプリを実行する準備ができました。 次に、これを Azure に取り込みます。