C#でAzure KeyVaultのシークレットを操作するサンプルを紹介します。
目次
概要
- Azure KeyVaultで、シークレット、キー、証明書の管理が可能ですが、ここではシークレットの一覧取得・作成・更新・削除・履歴取得のサンプルを紹介します。
- 次の環境で動作確認しています。
OS Windows 10(64ビット) IDE Microsoft Visual Studio Community 2022(17.1.3) 言語 C#(10.0) + .NET6 パッケージ Azure.Security.KeyVault.Secrets(4.3.0)
Azure.Identity (1.6.0)
System.Linq.Async (6.0.1) - 以降では、シークレットの論理削除を「削除」(Delete)、シークレットの物理削除を消去(Purge)と表記します。
- 次のリファレンスを参考にしています。
- マイクロソフトのリファレンス
- githubリファレンス
前提知識
シークレットの作成
シークレットの削除仕様
- [論理削除]が[有効](変更不可)になっており、削除されたシークレットは削除領域に退避されます。
- [削除されたコンテナーを保持する日数]は既定で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
サンプルコード
実装方法の概要
- 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | 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); |
実行結果の例は次の通りです。
(キーコンテナの消去保護が無効かつ、特権シークレット操作の削除権限がある前提)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | === 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 |
参考
シークレット消去時の権限不足
削除されたシークレットを消去(物理削除)する際に権限不足で次のエラーが発生する場合があります。
こちらで紹介しているように、実行ユーザに対して[特権シークレット操作]の削除を有効にする必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 |
消去保護されたシークレットの消去
消去保護が有効化されたキーコンテナのシークレットを消去(物理削除)すると次のエラーになります。
こちらで説明しているように、消去保護は一度有効化すると無効化できないので、このエラーを回避できません。設計を考え直すか、消去保護が無効なキーコンテナを新規作成して使う必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 |