Azure Key Vaultは安全に保管したいキーや証明書などをREST APIでアクセスすることができるAzure上のサービスです。
キーにアクセスするための権限を細かく管理したり、そもそもキーや証明書の管理操作を開発者などから分離して集中管理することができるようになります。監査などもあるので便利です。
このエントリでは .NET Core 2.0 / ASP.NET Core 2.0 でKey Vaultの証明書を利用する方法を簡単にまとめます。
事前準備
とりあえずKey Vaultの概要とかは置いておきます。ドキュメント見てください。
アプリから扱うための準備として先にKey Vaultを作っておきます。
作ったらCertificatesからアプリで使いたい証明書を登録します。今回は面倒なのでKey Vaultに自己署名の証明書を登録させます。
簡単!作った後で秘密鍵付きでExportもできるのでもうmakecertコマンドとおさらばできます!まぁそれはさておき出来上がりました。
追加後、Certificate Identitferの値が必要になるのでメモっておきます。
さて次はアプリからアクセスするための権限を設定します。Azure ADでアプリケーションを登録しましょう。App Registrationで追加するだけです。
追加後はApplication IDを控えておきます。次にKeyを追加します。
値を自動生成した場合は保存後に1度だけ表示されるので忘れずコピーして控えます。
次は作ったIDでKey Vaultから証明書を取得できるように権限を設定します。
権限は最低限SecretとCertificateのそれぞれGetだけです。
追加したら忘れずSaveしましょう。
とりあえずテストするためのKey Vault側の準備はこれで終了。
アプリ側の準備とローカル開発
適当にASP.NET Core 2.0なアプリを作ります。
NuGetパッケージの Microsoft.Azure.KeyVault.Core と Microsoft.Azure.Services.AppAuthentication を追加しておきます。(Microsoft.Azure.Services.AppAuthenticationはPreviewです)
※ Microsoft.Azure.Services.AppAuthentication があるとKeyVaultへのアクセストークンを取得するコードが楽になります。自前でとってくるなら別になくてもいい。
実際に使うコードはこんな感じです。(適当にHomeControllerいじりました)
using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; namespace keyvaulttest.Controllers { using Microsoft.Azure.Services.AppAuthentication; using Microsoft.Azure.KeyVault; using System.Security.Cryptography.X509Certificates; public class HomeController : Controller { // appsettingsから読み込むようにするなど適宜工夫すること private const string CertId = "https://plzreplase.vault.azure.net/certificates/plzreplase/xxxxxxxxxxxxx"; public async Task<iactionresult> Index() { var azureServiceTokenProvider = new AzureServiceTokenProvider(); var keyVaultClient = new KeyVaultClient( new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); var cert = await keyVaultClient.GetCertificateAsync(CertId); var secret = await keyVaultClient.GetSecretAsync(cert.SecretIdentifier.Identifier); var pfxb = Convert.FromBase64String(secret.Value); var x509cert = new X509Certificate2(rawData: pfxb, password: "", keyStorageFlags: X509KeyStorageFlags.MachineKeySet); ViewBag.Thumbprint = x509cert.Thumbprint; return View(); } } }
コード的にはこれだけです。お手軽ですね。
実際に実行してみましょう。ローカルで実行するとAccess Deniedになります。
コード的にはあってるけどKey Vaultにアクセスするための情報を何も渡してないので当然ですね。いくつかやり方はあります。
1. AzureServiceTokenProvider のコンストラクタに接続文字列を渡す
2. 自前でアクセストークンを所得するコードを書いてKeyVaultClient.AuthenticationCallbackに渡す
2番は論外です。1番もわざわざ接続文字列持たせるのは面倒です。特に後述するAzure App Serviceなどで使えるManaged Service Identityを使う場合に具合が悪いです。
ローカル開発時の対応ですが、環境変数のAzureServicesAuthConnectionStringに接続文字列を入れてあげると自動的にそちらを使ってくれます。実施に設定しましょう。
値は以下のような形式です。
RunAs=App;AppId=xxxxxxx;TenantId=xxxxxxxxxx;AppKey=xxxxxxxxxxxxxxxx
AppIdは最初にAzure ADに登録したアプリのID、TenantIdはAzure ADのID、AppKeyは登録したアプリのKeyになります。
※環境変数設定後はVisual Studioを再起動しましょう。
実行してみると今度はちゃんと取得できました。Key Vaultで設定した証明書と同じ拇印ですね。
さてコードの中身ですが、証明書取得するだけだとGetCertificateAsync呼ぶだけですみます。こちらのCerプロパティの値を使ってX509Certificate2のインスタンスを生成できますが、公開鍵しかありません。(PrivateKeyがnull)
これだと大体のケースで都合が悪いので、別途GetSecretAsyncを呼んで秘密鍵を取得し、そちらでX509Certificate2を生成します。この際、keyStorageFlagsをX509KeyStorageFlags.MachineKeySetにセットするのがコツです。他の値だとAzure Web Appsとかで以下のような例外がでます。
2018-03-01T23:05:48 sandboxproc.exe complete successfully. Elapsed = 1270.00 ms 2018-03-01 23:05:54.032 +00:00 [Critical] Microsoft.AspNetCore.Hosting.Internal.WebHost: Application startup exception Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: The system cannot find the file specified at SafeCertContextHandle Internal.Cryptography.Pal.CertificatePal.FilterPFXStore(byte[] rawData, SafePasswordHandle password, PfxCertStoreFlags pfxCertStoreFlags) at ICertificatePal Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(byte[] rawData, string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) at new System.Security.Cryptography.X509Certificates.X509Certificate(byte[] data)
さて秘密鍵はKey VaultのSecretに入ってるようですが、ポータル上で見てもSecretは空なので内部的に持ってるようです。まぁ深く考えなくてもいいでしょう。
とりあえずこれで ローカル開発でコードから証明書と証明書へのアクセスに関する部分を分離できました。このままGitなどにCommitしても安全です。
Azure Web Appsへの展開
Azure Web Appsにアプリを展開する場合、Azure ADに登録したアプリの情報とAzureServicesAuthConnectionString環境変数を使ってもいいのですが面倒ですよね。
そういうときはManaged Service Identityを使うことで透過的にアクセスさせることができます。
最初にWeb Apps上でManaged Service IdentityをOnにします。※設定後念のために再起動したりするといいでしょう。
これだけでWeb Appsのサービスプリンシパルが内部的に生成されるので後はKey VaultにこのWeb Appsのアクセス権を設定するだけです。
権限(Permissions)は同じようにSecretとCertificateそれぞれGetを設定します。
問題なしですね。Web Apps側の設定を最小限に、コードの修正や対応を最小限にして安全に証明書の管理と役割の分離ができました。
まとめ
Key Vaultの考え方とかコツはちょっと必要ですが、慣れたらキーなどの面倒くさい情報をコードに含めず分離して集中管理できる、かつローカルと本番、Azure上など環境を気にせず対応できるのでぜひ活用してください。