Azure Functions と KeyVault を連携させてみる

1 Star2 Stars3 Stars4 Stars5 Stars (まだ評価されていません)
Loading...

今日は、プロジェクトで関係している、Azure Functions で KeyVault を使ってみるということを試してみた。Key Vault は、アプリケーションで使用するキーやシークレットの安全性を守ってくれるサービスだ。HSM というキーやシークレットを守るための専用のハードも使われている。

 今回は、Azure Functions で使われているキーやシークレットの情報を Key Vault に格納して、取得するための方法について書いてみる。

1. KeyVault の作成

Key Vault は、Azure CLI, PowerShell 等様々な方法で作成できるが、ここでは簡単に Portal から、作成してしまおう。特に難しいことは何もない。

KeyVault01.png

2. KeyVault へのデータのセット

これまた様々な方法が可能ですが、簡単に Portal でいっときましょう。

KeyVault02.png

ポータルから選びます。今回はキーではなく、シークレットを登録してみましたが、Certificate というファイル形式と、Manual というテキストを入力するのと両方選べます。今回簡単に、SomeSecret / ushio みたいなシークレットを登録してみました。

KeyVault03.png

3. Service Principle (password) で KeyVault に接続する

Azure Functions から Key Vault に接続するためには2つの方法があります。基本的には、Service Principle を作成して、それを、KeyVault に紐づけて、権限を振るという考えです。その Service Principle の作り方で、よく使うパスワードを使う方法、Certificate を使う方法があります。まずは、
簡単な方で、Azure Function (Visual Studio で開発) に対して、Service Principle (password) で接続するための設定とコードを書いてみます。

3.1. Service Principle を作成する。

Service Principle は、Active Directory が、あるアプリケーションを、管理し、それに対して様々な権限を設定するための仕組みです。Azure を操作したい場合は、大体これを使います。

最近では、Azure CLI 2.0 を使うと、ワンコマンドで、Service Principle が作れます。Create an Azure service principal with Azure CLI 2.0ところが、インターネットで見たどの手順もこれを使わず、PowerShell と、Azure でゴリゴリに設定して、作る方法が採用されています。おそらく、CLI でサービスプリンシプルを作って、Azure CLI で、KeyVault に紐づけて権限をつければたぶん一番簡単だと思います。(ただし、Service Principle + certificate の方法は、Azure CLI で出来るかどうかは不明です)いづれにしても、今度 Azure CLI でも試してみたいと思います。

 さて、Azure Key Vault From Azure Functionsの手順を踏襲してやってみます。大体そのままでいけました。しかし、Azure 上で、Service Principle をゴリゴリに作るのは久々ですが、勉強がてらやっていましょう。

3.1.1. 久々に、Azure 旧 Portal から Service Principle を作ってみる。

Service Principle の作りは簡単です。Create an Azure service principal with Azure CLI 2.0が一番簡単ですが、ポータルで作るとこんな感じ。細かく制御できます。

Azure Active Directory から、アプリケーションを追加します。すると、サインオン URL、アプリケーションID/URL が要求されます。これは、今回のアプリの場合は、ユニークであればいいので、適当に入力してみてください。(おそらく、本当にシングルサインオンする案件とかでは考える必要あり)

AD01.png

アプリケーションの名前も、実際のアプリではあまりつかいませんので、分かりやすい名前を付けてください。

AD02.png

AD03.png

すると Azure Function に相当する、アプリケーションが出来ました。ただ、アプリケーションが出来ただけでなんの、関連付けもありませんし、なんのアクセス権限もありません。

AD04e.png

最後の画面で、1. クライアント ID が表示されます。これが、CLIENT_ID と呼ばれる Service Principle の設定に必要となるIDなので保存しておいてください。また、キーの項目は最初何もありませんが、キーをここで生成することで、このサービスプリンシパルがいつから、いつまで有効かを設定できて、キーの値が表示されます。1回しか表示されないので、これも保存しておいてください。

 ちなみに、アプリケーションは、Service Principle によって、Azure などへのアクセスが許可されますが、不正利用とかあると、この「アプリケーション」を削除すれば、そのアプリケーションは、Azure に接続できなくなるので、便利です。

 さて、Application と、その Service Principle は作りました。次は、それをKey Vault と紐づけます。これは元のブログでたぶん間違っていたところですが、下記のコマンドレット(PowerShell) を使います。
ちなみに、この PowerShell に相当することも、Azure CLI で可能っぽいです。Manage Key Vault using CLI

Set-AzureRmKeyVaultAccessPolicy -VaultName 'SpikeKey' -ServicePrincipalName '{ここに、CLIENT_IDを}' -PermissionsToSecret all -PermissionsToKey

という感じで、KeyVault と Service Principle を紐づけて権限 (Secret と、Key へのアクセスを、all として与える) 付与しています。

さて、最後は、コードですが、Visual Studio でコードを書いてみました。Azure Functions のタイプは、Web Trigger の C# にしてみました。 ちなみに、Manage Nuget Packages… を選択して、 専用のライブラリを取得しておきます。Microsoft.Azure.KeyVault と Microsoft.IndentityModel.Clients.ActiveDirectoryです。あとはコーディングし放題!

using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Azure.KeyVault;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace KeyVaultIntegration
{
public static class KeyVaultIntegration
{
private const string applicationId = "{CLIENT_IDをここに}";
private const string applicationSecret = "{取得したキーをここに}";
[FunctionName("KeyVaultSpike")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
var keyClient = new KeyVaultClient(async (authority, resource, scope) =>
{
var adCredential = new ClientCredential(applicationId, applicationSecret);
var authenticationContext = new AuthenticationContext(authority, null);
return (await authenticationContext.AcquireTokenAsync(resource, adCredential)).AccessToken;
});
var secretIdentifier = "https://spikekey.vault.azure.net/secrets/SomeSecret/"; // KeyVault の Secret Identifier を入れる。
var secret = await keyClient.GetSecretAsync(secretIdentifier);
log.Info($"Secret is {secret}");
return secret == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a secret on the query string or in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Your secret is " + secret.Value);
}
}
}

ちなみに、Secret Identifier は次のページで取れます。
KeyVault04.png

https://spikekey.vault.azure.net/secrets/SomeSecret/{英数字の羅列}なのですが、最後の英数字は、バージョン番号のようで、なくしたら最新を撮ってくるという仕様なので、このように、SomeSecretというシークレット名の後のバージョンは取らないようにしています。

これで、Azure Functions をローカルで起動して、指示されたURLを単純にリクエストすると、ちゃんとシークレットが取得できています。

AF01.png

基本的にサービスプリンシパルの、CLIENT_ID と、キー(パスワード)があり、Key Vault と紐づければ、アクセスできるみたいですね。簡単!

4. Service Principle (certificate) で KeyVault に接続する

次に、Service Principle を キー(パスワード)ではなくて、Certificate で認証する方法を試してみます。
こっちのほうがセキュリティ的に硬いですが、手順は面倒です。

4.1. Winodws SDK のダウンロード

最初に、Private Key, Certificate, pfx ファイルを作る必要があります。Verisign とかにお金を払って試すのもいいですが、最初は、オレオレ証明書でやってみましょう。オレオレ証明書は WIndows 10 SDK をダウンロードすれば、 makecert/pvk2pfx というキー関連のツールが取得できます。しかし、なんということか、pvk2pfx の方はどこを探してもありませんでした。あまりよくありませんが、このサイトから落としました。自己責任で。ただ、昔を思い出すと、たしか、PowerShell だけで、自己証明書を作る手順があったと思います。だから、たぶんこれらのコマンドなしでも大丈夫だと思います。今回はこのコマンドを使って作る手順を。

なんと、これらのコマンドは、コマンドプロンプトでしか動きません。

4.2. Private Key と、 Certificate を作成する

下記のコマンドで、Private Key と、Certificate を作成しています。mykey.pvkが、Private Key で、 ADTestVaultApplication.cer がセルフサインのCertificate (証明書) です。オレオレ証明書ですね。パスワードを聞かれますので、入力しましょう。

"C:\Program Files (x86)\Windows Kits\10\bin\10.0.15063.0\x64\makecert.exe"  -sv mykey.pvk -n "cn=AD Test Vault Application" ADTestVaultApplication.cer -b 01/01/2014 -e 01/01/2019 -r
Succeeded

そして、これらを、pfx ファイルにまとめます。

C:\Users\tsushi\Downloads\pvk2pfx.exe  -pvk mykey.pvk -spc ADTestVaultApplication.cer -pfx ADTestVaultApplication.pfx -po test

private key と、certificate から、pfx ファイル(バイナリで、Private Key, Certificate が格納されている形式) に変換して、パスワード test をつけています。

4.3. pfx ファイルの Azure Functions への登録

次に、Azure Functions に、作った pfx ファイルを登録してみましょう。

Azure Functions のこの画面から

AF02.png

先ほど作った、ADTestValueApplication.pfx を登録してみましょう。パスワードは、先ほど -po オプションで渡したtestです。
AF04.png

4.3. Service Principle の作成

先ほど作成した、Certificate を元に、Service Principle を作ります。
“`
$certificateFilePath = “C:\Users\tsushi\Codes\certs\ADTestVaultApplication.cer” // ここ
$certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$certificate.Import($certificateFilePath)
$rawCertificateData = $certificate.GetRawCertData()
$credential = [System.Convert]::ToBase64String($rawCertificateData)
$startDate= [System.DateTime]::Now
$endDate = $startDate.AddYears(1)
$adApplication = New-AzureRmADApplication -DisplayName “CertAdApplication” -HomePage “http://www.kvtest.co.jp” -IdentifierUris “http://www.kvtest.co.jp” -CertValue $credential -StartDate $startDate -EndDate $endDate // ここ

$servicePrincipal = New-AzureRmADServicePrincipal -ApplicationId $adApplication.ApplicationId

Set-AzureRmKeyVaultAccessPolicy -VaultName ‘SpikeKey’ -ServicePrincipalName $servicePrincipal.ServicePrincipalNames[0] -PermissionsToSecrets all -PermissionsToKeys all
“`

「ここ」と書いている2行が変更可能性のある場所です。具体的にはたいしたことをやっていなくて、Certificate を読み込んで、New-AzureRmADApplication というコマンドレットで、Application を作って、New-AzureADServicePrincipal というコマンドレットで、Service Principle を作っています。先ほどとの違いは、Azure Portal でこの部分を実行したので、CLIENT_ID や、キーは、画面から取得しました。

今回は上記のコマンドを実行後、次のように取得できます。

CLIENT_ID

$adApplication.ApplicationId

キーは、今回はCertificate を使うので取得不要です。

4.4. WEBSITE_LOAD_CERTIFICATES 環境変数の設定

簡単にアップロードできました。次に Application Settingsに移ります。ここで、Certificate のThumbnail を取得します。Thumbprint は親指の指紋という意味です。実体は、Hashのようで、これにより、Certficate を一位に特定できます。

先ほどのコマンドを実行したのち、次のコマンドを実行すれば取得できます。

$certificate.Thumbprint

Af05.png

4.5. Azure Functions を Portal で作ってみる。

さて、この構成だと、クライアントで 実行するのは難しそうなので、Portal で実行を試してみました。Nuget で取得するライブラリは、Microsoft.Azure.KeyVaultMicrosoft.IndentityModel.Clients.ActiveDirectoryですが、Portal から実行する場合は、project.json に書いておきます。

AF06.png

そして、いよいよコードです。Certificate 周りのコードは面倒になりましたが、本質的には、KeyVault クライアントを取得して、実行しているだけです。

using System.Net;
using Microsoft.Azure.KeyVault;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
private const string applicationId = "{ここに、CLIENT_ID}";
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
string certificateThumbprint = System.Environment.GetEnvironmentVariable("WEBSITE_LOAD_CERTIFICATES");
log.Info("C# HTTP trigger function processed a request.");
var keyClient = new KeyVaultClient(async (authority, resource, scope)=> {
var authenticationContext = new AuthenticationContext(authority, null);
X509Certificate2 certificate;
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try {
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificateCollection = store.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbprint, false);
if (certificateCollection == null || certificateCollection.Count ==0)
{
throw new Exception("Certificate not installed in the store");
}
certificate = certificateCollection[0];
}
finally
{
store.Close();
}
var clientAssertionCertificate = new ClientAssertionCertificate(applicationId, certificate);
var result = await authenticationContext.AcquireTokenAsync(resource, clientAssertionCertificate);
return result.AccessToken;
});
var secretIdentifier = "https://spikekey.vault.azure.net/secrets/SomeSecret/";
var secret = await keyClient.GetSecretAsync(secretIdentifier);
log.Info($"Secret is {secret}");
return secret == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please store a secret")
: req.CreateResponse(HttpStatusCode.OK, "Secret is " + secret);
}

テスト実行するとうまく動いています。

AF07.png

お疲れさまでした。明日はこれをテストしやすいようにして実装して終了です。


1 Star2 Stars3 Stars4 Stars5 Stars (まだ評価されていません)
Loading...
      この投稿は審査処理中  | 元のサイトへ