NDW

アプリ開発やトラブルシューティング等のノウハウ、キャンプや登山の紹介や体験談など。

.NET Core 1. システムエンジニアリング Azure 基盤技術 実装技術

C#: Azure KeyVaultシークレットの操作サンプル(一覧取得・作成・更新・削除・ 履歴取得)

投稿日:

C#でAzure KeyVaultのシークレットを操作するサンプルを紹介します。

概要

前提知識

シークレットの作成

  • シークレットの作成時、シークレット値以外にタグ等の情報を付加できます。

シークレットの削除仕様

  • [論理削除]が[有効](変更不可)になっており、削除されたシークレットは削除領域に退避されます。
  • [削除されたコンテナーを保持する日数]は既定で90日、[消去保護を無効にする]が有効になっています。削除領域にあるシークレットは復元することが可能ですが、90日経過後に消去(パージ)されます。
  • シークレットを削除しても削除領域に残っており消去されていない場合、同名のシークレットを新規作成することはできません。
  • キーコンテナ毎に「消去保護」の有効・無効を選択できます。
  • キーコンテナの消去保護が無効な場合(既定)
    • 既定の権限ではシークレットを消去できませんが、アクセスポリシーの[シークレットのアクセス許可]の[特権シークレット操作]の削除を有効にすることで消去できます。
  • キーコンテナの消去保護が有効な場合
    • シークレットの消去はできません。([特権シークレット操作]の削除が有効でも。)
    • 消去保護を一度有効にすると無効に戻せません。(無効にしたい場合、別途キーコンテナを作成し直す必要がある。)

シークレットの変更履歴

  • 個々のシークレットは変更履歴を持っており、シークレットの値が変更される度に、変更履歴が追加されます。各バージョンでのシークレット値を確認することも可能です。
  • シークレットのコンテンツタイプやタグ等のプロパティの更新では、変更履歴は追加されません。
  • シークレットの変更履歴は、Azureポータル上の[以前のバージョン]で確認できます。
    ここで表示されているバージョンは「変更した順番」ではないことに注意が必要です。(更新順番に関わらず、1,2,…,9,a,b,c,…の順に並んでいる。)
  • 変更履歴上の特定のバージョンを削除、などの変更履歴の編集はできません。
    azure Key Vault:how do delete latest version of secret using Azure CLI · Issue #8114 · Azure/azure-cli

キーコンテナのURI

  • サンプルプログラム等で定義する必要があるキーコンテナのURIは、キーコンテナの[概要]で確認できます。

サンプルコード

実装方法の概要

  • C#でAzure KeyVauultを操作する場合はAzure Key Vault secret client library for .NET(Azure.Security.KeyVault.Secretsパッケージ)を使用する必要があります。
  • このパッケージで提供されるSecretClientクラスを使用して、一覧取得/新規作成/更新/削除等のシークレットに対する操作を行います。
    個々のシークレットはKeyVaultSecretクラスで表現され、シークレット作成・更新時や特定シークレット取得時にこのクラスオブジェクトが返却されます。
  • KeyValut接続時の認証は、Azure.Identityパッケージが提供するDefaultAzureCredentialクラスを使用します。環境変数、マネージドID、Visual Studio資格情報等のように実行環境に応じた資格情報を使って自動的に認証が行われます。後述のサンプルでは、Visual Studioに登録した資格情報を使う前提のため、サンプルコードに資格情報は定義していません。
  • 個々のシークレットは変更履歴を保持しており、SecretClientを介して変更履歴の一覧を取得することもできます。SecretClientで取得した変更履歴は変更順にソートされておらず、変更順を意識した処理を行いたい場合は作成日時(CreatedOn)でソートする必要があります。KeyVaultのREST APIの問題なのかKeyVaultの内部実装の問題なのか分かりませんが、CreatedOnの精度は秒単位になっており、単位時間内で複数変更が発生した場合は正しい変更順を特定できません。
  • SecretClientの削除系メソッドは、論理削除の場合はDelete、消去(物理削除)はPurgeという単語が使用されています。
  • Azure Core共有ライブラリに起因するtips
    • Azure Key Vault secret client library for .NET(Azure.Security.KeyVault.Secretsパッケージ)は、Azure Core shared client library for .NET(Azure.Coreパッケージ)を使用しています。そのため、必要に応じてAzure.Coreの実装方法を理解する必要があります。
    • SecretClientのシークレット設定・取得(SetSecret, GetSecret)等の一部メソッドの戻り値はAzure.Response<T>になっています。Azure.Response<T>は返却値の自動型変換(implicit operator)が実装されており、varではなく明示的にKeyVaultSecret型を指定した変数に代入することで、型自動変換をトリガーしています。
    • SecretClientのシークレット一覧取得(GetPropertiesOfSecrets)等の一覧系メソッドは、返却値としてPeageable<T>, AsyncPageable<T>を返却します。一覧をループ処理したい場合、非同期メソッドの場合、await foreachを使用しますが、ここでは非同期Linq(System.Linq.Asyncパッケージ)を使用しています。詳細はこちらをご覧ください。
    • KeyVault接続のリトライ設定等は、SecretClientコンストラクタの引数(SecretClientOptions)で指定可能です。この機能もAzure Coreライブラリ(ClientOptions)に依存しています。詳細はこちらをご覧ください。

サンプルコード

次の操作を行うサンプルです。
完全なソースコードはこちらをご覧ください。

  • シークレット一覧取得
  • シークレット新規作成
  • シークレット更新、プロパティ更新(コンテンツタイプ、タグ)
  • シークレット更新履歴一覧取得
  • シークレット論理削除(delete)
  • シークレット物理削除(purge)
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

// このサンプルで使用するシークレット名
const string SecretName = "mysecret01";

// キーコンテナURI(Azureポータルのキーコンテナの[概要]を参照)
const string KeyVaultUri = "https://deveval.vault.azure.net/";

// シークレット情報をコンソール出力するアクション
Action<string, KeyVaultSecret> ShowKey = (prefix, k)
    => Console.WriteLine($"{prefix}: {k.Value} " +
        $"({k.Properties.CreatedOn}, {k.Properties.Version}, " +
        $"{k.Properties.Tags.Count}, {k.Properties.ContentType})");

// シークレット操作の中心となるクライアントを作成
var client = new SecretClient(new Uri(KeyVaultUri), new DefaultAzureCredential());

// シークレットの一覧取得
// - 個々のシークレット値は含まれない
// - 返却型Pageable<T>の扱い方は次のURIを参照
//   https://docs.microsoft.com/ja-jp/dotnet/azure/sdk/pagination
Console.WriteLine($"=== secret list");
var propsPageable = client.GetPropertiesOfSecretsAsync();
var propsList = await propsPageable
    .Where(x => x.Enabled.Value) // 有効なシークレットのみ
    .OrderBy(x => x.Name)
    .ToListAsync();
for (var i = 0; i < propsList.Count; i++) // 非同期Linqを使わずawait foreachでも可
    Console.WriteLine($"{i}: {propsList[i].Name}");

Console.WriteLine($"=== secret: {SecretName}");

// シークレットの作成(version1)
// - 同名シークレットがある場合は更新
// - Azure.Response<T>の自動型変換を利用するためにvarでなくKeyVaultSecretを指定
KeyVaultSecret s1 = await client.SetSecretAsync(SecretName, "secret1");
ShowKey("created", s1);

Thread.Sleep(1000); // 履歴上の更新順番が識別しやすいよう少々待機

// シークレットの更新(version2)
KeyVaultSecret s2 = await client.SetSecretAsync(SecretName, "secret2");
ShowKey("updated", s2);

// シークレットのプロパティの更新
// - プロパティはバージョンに紐づき、この更新はversion2のみで有効
// - プロパティを更新してもversionは変わらない
var u2Props = s2.Properties;
u2Props.ContentType = "text/plain";
u2Props.Tags["updater"] = "KeyVaultBasic";
await client.UpdateSecretPropertiesAsync(u2Props);

Thread.Sleep(1000);

// シークレットの更新(version3)
KeyVaultSecret s3 = await client.SetSecretAsync(SecretName, "secret3");
ShowKey("updated", s3);


// シークレットの取得(最新のversion3を取得)
KeyVaultSecret latest = await client.GetSecretAsync(SecretName);
var latestProps = latest.Properties;
ShowKey("latest ", latest);

// シークレットの履歴一覧の取得
var versPageable = client.GetPropertiesOfSecretVersionsAsync(SecretName);
var verList = await versPageable.ToListAsync();
var orderdVerList = verList
    .OrderByDescending(x => x.CreatedOn.Value.ToUniversalTime()).ToList();
for (var i = 0; i < orderdVerList.Count; i++)
{
    // バージョン指定でシークレットを取得
    KeyVaultSecret s = client.GetSecret(SecretName, orderdVerList[i].Version);
    ShowKey(string.Format("history[{0, 2}]", i), s);
}

// シークレットの論理削除
Console.WriteLine("deleting...");
var operation = await client.StartDeleteSecretAsync(SecretName);

// シークレットの物理削除(purge)
// - 物理削除(purge)や回復(recovery)を行う場合、論理削除の完了の待機が必要
// - アクセスポリシーの[特権シークレットの操作]の[削除]権限が必要
// - 権限がないや消去保護が有効な場合、次の例外になる
//   Azure.RequestFailedException(0x8013150): 403 (Forbidden)
Console.WriteLine("wait deleting: start");
await operation.WaitForCompletionAsync();
Console.WriteLine("wait deleting: end");
await client.PurgeDeletedSecretAsync(SecretName);

実行結果の例は次の通りです。
(キーコンテナの消去保護が無効かつ、特権シークレット操作の削除権限がある前提)

=== secret list
0: test01
1: test02
2: zebra

=== secret: mysecret01

created: secret1 (2022/08/07 9:30:35 +00:00, 94b...3c7, 0, )
updated: secret2 (2022/08/07 9:30:36 +00:00, 8a8...8ee, 0, )
updated: secret3 (2022/08/07 9:30:37 +00:00, d5a...d8e, 0, )

latest : secret3 (2022/08/07 9:30:37 +00:00, d5a...d8e, 0, )

history[ 0]: secret3 (2022/08/07 9:30:37 +00:00, d5a...d8e, 0, )
history[ 1]: secret2 (2022/08/07 9:30:36 +00:00, 8a8...8ee, 1, text/plain)
history[ 2]: secret1 (2022/08/07 9:30:35 +00:00, 94b...3c7, 0, )

deleting...
wait deleting: start
wait deleting: end

参考

シークレット消去時の権限不足

削除されたシークレットを消去(物理削除)する際に権限不足で次のエラーが発生する場合があります。
こちらで紹介しているように、実行ユーザに対して[特権シークレット操作]の削除を有効にする必要があります。

Azure.RequestFailedException
  HResult=0x80131500
  Message=The user, group or application 'appid=xxx;oid=xxx;numgroups=1;iss=https://sts.windows.net/xxx/' does not have secrets purge permission on key vault 'deveval;location=japaneast'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287
Status: 403 (Forbidden)
ErrorCode: Forbidden

Content:
{"error":{"code":"Forbidden","message":"The user, group or application 'appid=xxx;oid=xxx;numgroups=1;iss=https://sts.windows.net/xxx/' does not have secrets purge permission on key vault 'deveval;location=japaneast'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287","innererror":{"code":"ForbiddenByPolicy"}}}

Headers:
Cache-Control: no-cache
Pragma: no-cache
x-ms-keyvault-region: japaneast
x-ms-client-request-id: xxx
x-ms-request-id: xxx
x-ms-keyvault-service-version: 1.9.472.5
x-ms-keyvault-network-info: conn_type=Ipv4;addr=xxx;act_addr_fam=InterNetwork;
X-Content-Type-Options: REDACTED
Strict-Transport-Security: REDACTED
Date: Sat, 06 Aug 2022 09:39:48 GMT
Content-Length: 451
Content-Type: application/json; charset=utf-8
Expires: -1

消去保護されたシークレットの消去

消去保護が有効化されたキーコンテナのシークレットを消去(物理削除)すると次のエラーになります。
こちらで説明しているように、消去保護は一度有効化すると無効化できないので、このエラーを回避できません。設計を考え直すか、消去保護が無効なキーコンテナを新規作成して使う必要があります。

Azure.RequestFailedException
  HResult=0x80131500
  Message=Operation "purge" is not allowed because purge protection is enabled for this vault. Key Vault service will automatically purge it after the retention period has passed.
Vault: deveval;location=japaneast
Status: 403 (Forbidden)
ErrorCode: Forbidden

Content:
{"error":{"code":"Forbidden","message":"Operation \"purge\" is not allowed because purge protection is enabled for this vault. Key Vault service will automatically purge it after the retention period has passed.\r\nVault: deveval;location=japaneast"}}

Headers:
Cache-Control: no-cache
Pragma: no-cache
x-ms-keyvault-region: japaneast
x-ms-client-request-id: xxx
x-ms-request-id: xxx
x-ms-keyvault-service-version: 1.9.472.5
x-ms-keyvault-network-info: conn_type=Ipv4;addr=xxx;act_addr_fam=InterNetwork;
X-Content-Type-Options: REDACTED
Strict-Transport-Security: REDACTED
Date: Sat, 06 Aug 2022 13:50:55 GMT
Content-Length: 251
Content-Type: application/json; charset=utf-8
Expires: -1






-.NET Core, 1. システムエンジニアリング, Azure, 基盤技術, 実装技術

関連記事

postgresqlでの拡張のインストール

PostgreSQL10で暗号化関数を使用するため拡張(pgcrypto)をインストールした際の出来事を記載します。 事象 pgcryptoの拡張をインストールしたり、インストールの確認を行うと、その …

ASP.NET Core: タグヘルパーでのHTML編集方法

ASP.NET Coreで独自のタグを生成するためにTagHelperを使用します。 TagHelperでどのようにHTMLを生成できるかを説明します。 概要 ここではタグヘルパーで出力するHTMLの …

Visual Studio 2022とIIS構成ファイル(applicationhost.config)

Windows環境で、クライアント証明書を使った認証を行うASP.NETアプリを開発するために、IIS設定ファイルであるapplicationhost.configを変更しようと考えました。 事象 V …

Windows10で開発・検証用のプロキシサーバを構築

本番環境でプロキシを介して通信を行うアプリを開発したいが、開発環境ではプロキシがない… そんな時のためにローカルに検証用のプロキシサーバを構築してみます。 概要 Windows 10(64 …

slf4jで独自ログ項目を追加(MDC)

はじめに 業務要件、障害発生時の処理追跡や証跡等、ログ出力に特定項目を含めたい場合があります。 共通のログ出力ユーティリティやライブラリを作って、その中でログ出力内容を変更しても良いのですが、MDC( …