どーもです。本日は技術系ネタです。
最近ガンプラ作ってないので作ってから投稿したいと思いますが、いかんせんやりたいことありすぎて時間が・・・。最近はバイクばっかですね。
本日は、最近の様々なセキュリティを高める潮流に合わせちょこちょことパスワードとかをより安全な場所に保管していくように意識を変えていければなぁとAzure Key Vaultに触れてみたいと思います。
Azure Key Vaultとは?
一言でいうと、「シークレットや証明書を安全に保管しておくAzureが提供するクラウドサービス」です。Vault(ボールト)とは金庫とか保管庫のことで、要するに貴重品入れる金庫ということですね。
この金庫に重要なシークレットなんかを保管しておけば、情シスやセキュリティ担当者にしてみれば、暗号化された安全な環境で機密情報の一元管理や監視ができ、開発者にとっては、アプリに機密情報を含める必要がなくなったり証明書の管理や更新を自動化できたりと開発に注力できるようになります。
本日のゴール
Azure Functions内で利用するアカウントID、パスワードをAzure Key Vault内に保持し取得することで、Azure Functions内からの機密情報の排除シナリオを実装してみます。
本当は、接続文字列とかアプリシークレットとかだと思うのですが、まぁそこは読み替えていければKey Vault内に保管するものは何でもOKって感じです。
さらに、おまけとして、Azure Key Vaultの価格が「秘密情報の操作」として4.485円/10,000トランザクション(2023年10月現在)かかるので、これをバックエンドAPIとの連携で利用してWEBサービスの課金が跳ね上がらないか?の検証、課金タイミングもちょい気になったので・・・。
ってことで、
- Azure Functionsコード内部からAzure Key Vault SDKでアクセスし、シークレットを取得する。
- Azure Functionsの環境変数にKey Vault参照としてシークレットを取得する。
この2パターンで呼び出し回数の変化を見てみます。
まぁ、自明かもしれませんが念のため。あんまりはっきりした情報が見つからなかったのもあるので。
Azure Key Vaultからのシークレット取得手順
Azure Key Vaultリソースの作成
それでは早速ですが、Key Vaultリソースを作成していきます。
リソース追加から、「Key Vault」を選択します。
「作成」をクリックします。
以下の画面はよしなに。重要なのはKey Vault名と価格レベルくらいかな。Key Vault名はURLに組み込まれるので、グローバルで一意である必要あります。入力できたら「次へ」をクリックします。
アクセス構成ではデフォの「Azureロールベースのアクセス制御(推奨)」でいきましょう。ネットワーク、タグもデフォでいいので「確認および作成」をクリック。
確認画面で「作成」をクリックします。
デプロイが完了しました。早速リソースに移動しまそ。
KeyVaultコンテナーへのユーザーアクセス権の設定
KeyVaultのアクセス制御はRBACで行います。たとえ、リソースを作成できても、適切な権限がなければ「この操作は RBAC で許可されていません。」となってシークレットなどを設定することができません。
ですので、まずはユーザーにアクセス許可を設定してみます。
KeyVaultリソースの「アクセス制御(IAM)」をクリックします。
ここでは「ロール割り当ての追加」をクリックします。(RBACの制御はお好みの方法でどうぞ。)
ロール割り当ての追加では、「キーコンテナー管理者」を選択してみます。ロールはご自身の環境に合わせて設定ください。
次の画面で、アクセスの割り当て先に「ユーザー、グループ、またはサービスプリンシパル」を選択し、「メンバーを選択する」をクリックします。右のブレードで対象のユーザーを選択し、「次へ」をクリックしましょう。
確認画面で「レビューと割り当て」をクリックすればユーザーへのアクセス権設定は完了です。
Azure FunctionsマネージドID発行とKeyVaultコンテナーへのアクセス権の設定
Key VaultにアクセスするにはAzure Functionsも認証される必要があります。これには前回投稿した便利機能「マネージドID」を利用します。これでAzure Functionsが認証されます。
そして、認可はRBACで設定します。
それでは「Azure FunctionsへのマネージドID発行」→「マネージドIDへのアクセス権設定」の手順を実施していきましょう。
まずはAzure Functionsリソースにアクセスします。
次に設定の「ID」をクリックします。
したらば、システム割り当てマネージドIDの画面になるので、スイッチを「オン」に切り替え、「保存」をクリックします。
システム割り当てマネージドID有効化の確認が出ますので「はい」をクリックします。
簡単にマネージドIDが有効化されましたね。そしたら、次にAzure FunctionsにAzure Key Vaultリソースへのアクセス権を設定していきます。ひとまず1つ目のAzure FunctionsはマネージドID有効化した画面にある「Azureロールの割り当て」から行ってみましょう。
以下の画面になるので、「+ロール割り当ての追加」をクリックします。
右のブレードでスコープに「Key Vault」、サブスクリプション、対象のKeyVaultリソースを指定し、役割に「キーコーンテナーシークレットユーザー」を指定します。このロールを設定すれば対象KeyVaultリソースに設定されているシークレットの値を読み取ることができると思います。最後に「保存」をクリックしましょう。特に一覧にも反映されず設定完了してました。プレビューだからかなwww
Azure Functions、もう一つあるので、そっちはKey Vaultリソースの方からアクセス権設定していってみますね。お好きな手順でいいと思います。マネージドIDの有効化を忘れずに。
Azure Key Vaultリソースに移動し、「アクセス制御(IAM)」をクリックします。
以下の画面で、「ロースの割り当ての追加」をクリックします。
ロール一覧がでますので、「キーコンテナーシークレットユーザー」を選択し、「次へ」をクリックします。
アクセスの割り当て先を「マネージドID」にします。そして、「メンバーを選択する」をクリックすると、右ブレードにマネージドIDを有効化したAzure Functionsが表示されるので対象となるAzure Functionsリソースを選択してください。「次へ」をクリックします。
Azure Functionsが表示されない場合は、もう一度マネージドIDを有効化したか確認しましょう。
確認画面で「レビューと割り当て」をクリックすれば完了です。
Azure Key Vaultへのシークレットの保存
やっとこさAzure Key Vaultへのシークレットの保存になります。
Key Vaultリソースにアクセスし、[オブジェクト]-「シークレット」をクリックしましょう。
以下の画面を表示します。「+生成/インポート」をクリックします。
「この操作は RBAC で許可されていません。」警告が表示される場合はもう一度ユーザーのKeyVaultリソースへのアクセス制御(IAM)を確認しましょう。
シークレットの作成画面を表示するので、「名前」にシークレット名を、「シークレット値」にその値、つまり、機密情報を入力します。あとはデフォルトでOK。「作成」をクリックします。
ここでは、名前に「accountid」、値に「hoge」という値を設定しました。例がややこしいですがアプリで利用するaccountidという変数へセットする値がhogeです。
なので、もう1つ追加した例です。
「password」というキー(シークレットの名前)で「password12345」という値を取得できるようにシークレットを追加しました。こちらも「作成」で保存します。
二つのシークレットが登録されたのが確認できます。したらば、ここで登録した設定をクリックします。
以下の画面を表示します。現在のバージョンをクリックします。
次の画面で、「シークレット識別子」の値をコピーしましょう。あとで利用します。
Azure FunctionsにKey Vaultシークレット取得の追加
最後の手順です。前の手順でKey Vaultに追加したシークレットをAzure Functionsから取得できるようにしましょう。といっても、今回はAzure Functionsリソースを2つ作成していますが、この手順を行うのは環境変数、つまり、アプリケーション設定にKey Vaultへの参照を保持する場合のみです。というか、Azure Functionsで利用するならこれ一択といっても言い過ぎではないかもしれません。
対象のAzure Functionsリソースにアクセスします。
[設定]の「構成」をクリックします。
アプリケーション設定の画面で「+新しいアプリケーション設定」をクリックします。
アプリケーション設定の追加/編集ブレードが表示されるので、名前にKeyVaultに追加したシークレットの「名前」、値には前の手順の最後でコピーした値を「@Microsoft.KeyVault(SecretUri=シークレット識別子)」といった形式で入力します。ちなみに「@Microsoft.KeyVault(VaultName=KeyVaultリソース名;SecretName=シークレットの名前)」でもOKです。
アプリケーション設定を追加したら「保存」をクリックします。追加した設定のソースは「App Service」となっていますが・・・
Key Vault内のシークレットへのアクセスがうまくいけば「キーコンテナーの参照」に変化します。やったね!以上でKey Vaultに保存したシークレットを取得する環境の準備は整いました。
Azure FunctionsからKey Vaultのシークレット取得を実行
さて、ここからはKey Vaultに設定したシークレットをAzure Functionsから利用していくんですが、冒頭でもお話しした通り、Azure Functionsは二つ用意します。
一つは、コード内でAzure Key Vaultライブラリ実装を利用し、Key Vaultにアクセスする方法。
もう一つは、前手順で行ったAzure Functionsアプリケーション設定からKeyVault参照を利用し取得する方法です。
コード(.NET6 分離ワーカープロセス)を以下に提示しておきます。
こちらはコード内にKey Vaultへのアクセスをゴリゴリ書く方法。
「Azure.Identity」ライブラリ、および、「Azure.Security.KeyVault.Secrets」ライブラリのインストールが必要です。お好きな方法でどうぞ。ちな、自分はNuGetパッケージマネージャーからです。
どーでもいいんですが、Value.Valueってブルっちゃいますよね・・・w
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 |
using System.Net; using Azure.Identity; using Azure.Security.KeyVault.Secrets; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; namespace KeyVaultUsingIncodeFunctionApp { public class Function1 { private readonly ILogger _logger; public Function1(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger<Function1>(); } [Function("Function1")] public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req) { _logger.LogInformation("C# HTTP trigger function processed a request."); var containerUri = "https://sei-keyvault-example.vault.azure.net"; var client = new SecretClient(new Uri(containerUri), new DefaultAzureCredential()); // シークレットの取得 var accountId = await client.GetSecretAsync("accountid"); var password = await client.GetSecretAsync("password"); var response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); response.WriteString($"AccountId:{accountId.Value.Value},Password:{password.Value.Value}"); return response; } } } |
こちらは、KeyVault参照を利用する方法。
なんてことはない、KeyVault使っていようが普通のアプリケーション設定にアクセスするのと同様に環境変数にアクセスしているだけです。アプリケーション設定側でよしなにしてくれているのでとっても楽♪KeyVault参照すごくいい!!
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 |
using System.Net; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; namespace KeyVaultUsingEnvSettingsFunctionApp { public class Function1 { private readonly ILogger _logger; public Function1(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger<Function1>(); } [Function("Function1")] public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req) { _logger.LogInformation("C# HTTP trigger function processed a request."); var response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); response.WriteString($"AccountId:{Environment.GetEnvironmentVariable("accountid")},Password:{Environment.GetEnvironmentVariable("password")}"); return response; } } } |
ってことでそれぞれのAzure Functionsを10回ずつ呼び出してみます。シークレットは2つ。
KeyVaultコード内実装のパターン。ちゃんとシークレットを取得できているのがわかります。
こちらはKeyVault参照のパターン。こちらもちゃんとシークレットを取得できていますね。
ってことでそれぞれ10回ずつ呼び出したのでメトリックを見てみます。
結果は、コード内実装の場合は予想通り2シークレット×10回で計20リクエストがsecretで発生しているのが確認できました。まぁ予想通りですが、KeyVaultの取得をバックエンドAPIに書いちゃこうなるわなぁ・・・。
で、KeyVault参照のAzure Functionsの方なんですが、ごっそりリクエスト数がなかったです。これはAzure Functions、というかAppServiceの仕組みに詳しい方に言わせれば当然の結果なのかもしれませんが、KeyVaultの値はキャッシュされ、フェッチはAzure Functions起動時や定期的に行われているそうです。(自明すぎた?)
ってことで安心してKeyVaultをAzure Functionsに組み込む算段がつきました。それにしてもKey Vault参照うぃいねぇ~~~!
コメント