はまったりひらめいたり…とか…

Angularや.NETやAzureやその他色々。

Azure.IdentityのTokenCredentialの認証全部試す(その1)

はじめに

この記事は2021年10月時点の記事になります。

参照時期によっては記載している実装内容が動作しない可能性がありますので

その点ご留意いただければと思います。

Azure.IdentityのTokenCredential

Azure Key Vaultのシークレット値などを使用するときに大活躍してくれるAzure.Identityですが

認証のルールなどがいまいちわかりにくい状態になっています。

DefautCredentialを使うとAzure CLIの認証状態から引っ張ってきたりと、実装側の意図しない動作になることもよくあります。

Key Vaultのシークレットを取得する際に使用するSecretClientなどのインスタンス生成時に引数として提供するTokenCredentialのDocsを見ると認証は色々なものを使用できるようです。

どれがどの認証なんだろうということや、DefaultAzureCredentialの内部で行われているフローが原因でドハマりすることも多いので

とりあえず全部試してどのような認証ができるのか確認してみようと思います。

AuthorizationCodeCredential

AADアプリケーションを利用した認証で取得した承認コードを利用するものです。

承認コードの取得はこちらが参考になると思います。

認証は別のビジネスフローで終了してて別途シークレット取得したいときとかに使用する感じと思います。

ただこれはPKCEに対応していない古い認証方式で取得した認証コードでしか使用できないようです(参考)。

なので、使用範囲は限定的、かつあまり使うという選択肢は取らないほうがよいものという印象です。

下記のように使用します。

var cred = new AuthorizationCodeCredential(<TenantId>, <ClientId>, <ClientSecret>, <AuthCode(0.AVY...)>, new AuthorizationCodeCredentialOptions { RedirectUri = new Uri(@<RedirectUri>) });
var client = new SecretClient(new Uri(<KeyVaultUrl>), cred);
var d = client.GetSecret("<SecretName>");

必要なパラメータから分かる通り、内部でアクセストークンを取得するフローが実行されるので

Azure ADアプリケーションでリダイレクトURLの設定や、シークレットの生成、Azure Key Vaultへのアクセス許可が必要です。

また前述の通りv1エンドポイントを使用する必要があるため、プラットフォームはSingle Page ApplicationではなくWebを選択します。

AzureCliCredential

DefaultAzureCredentialの図中にあるAzure CLIを利用した認証です。

f:id:TakasDev:20211031122508p:plain

Azure CLIを使用している人しかいない開発現場であれば積極的に使ってもいいかと思いますが

個人的にはあまり外部ツールに依存する作りにするのは好きじゃないので僕は積極的に使わないと思います。

下記のように使用します。

var cred = new AzureCliCredential();
var client = new SecretClient(new Uri(<KeyVaultUrl>), cred);
var d = client.GetSecret("<SecretName>");

テナントをいくつも持っている人は下記のようにテナントを指定したほうが良いかもしれません。

var cred = new AzureCliCredential(new AzureCliCredentialOptions { TenantId = TenantId });
var client = new SecretClient(new Uri(<KeyVaultUrl>), cred);
var d = client.GetSecret("<SecretName>");

az loginしているユーザーがKey Vaultにアクセスすることになるので、アクセスポリシーにユーザーを追加しといてあげましょう。

AzurePowerShellCredential

DefaultAzureCredentialの図中にあるAzure PowerShellを利用した認証です。

Azure CLIと同じような感じなので感想は割愛します。

下記のように使用します。

var cred = new AzurePowerShellCredential();
// テナントをいくつも持っている人は下記のようにテナントを指定したほうが良いかもしれません。
// var cred = new AzurePowerShellCredential(new AzurePowerShellCredentialOptions { TenantId = TenantId });
var client = new SecretClient(new Uri(<KeyVaultUrl>), cred);
var d = client.GetSecret("<SecretName>");

ChainedTokenCredential

TokenCredentialの配列を食わせることができます。

f:id:TakasDev:20211031122508p:plain

👆のような構成を自前で作り動作させることができます。

与えられた配列の先頭から検証が行われるようです(参考)

下記のように使用します。

var f = new AzurePowerShellCredential(new AzurePowerShellCredentialOptions { TenantId = TenantId });
var s = new AzureCliCredential(new AzureCliCredentialOptions { TenantId = TenantId });
var t = new ClientCertificateCredential(TenantId, ClientId, CertPath);
var list = new List<TokenCredential> { f, s, t };

var cred = new ChainedTokenCredential(list.ToArray());
var client = new SecretClient(new Uri(<KeyVaultUrl>), cred);
var d = client.GetSecret("<SecretName>");

PowerShell -> Azure CLI -> ClientCertificateの順番でトークン取得が検証/実行されます。

個人的に実装するなら、Env->Managed Identity->Intaractiveな順番が良いかと思ってます。

Azure上に展開するときはEnvironment/ManagedIdentityを参照し、開発時はユーザーがログインすることでシークレットにアクセス可能になります。

上述の通り外部ツールに依存する構成が個人的に好きではないというだけの理由です。

最終的にIntractive見るって書いてあるんですけど、なーんかあんまりいい印象がないので自前で途中に変なのが入らないようにしちゃうのが良いと思ってます。

ClientCertificateCredential

クライアント証明書による認証です。Azure ADアプリケーションに証明書を登録することで使用します。

証明書ストアにある証明書でもローカルのpfxファイルを指定する方法でも使用可能です。

この認証では裏で生成されるトークン内にユーザー情報が付与されません。

そのため、Azure Key VaultのアクセスポリシーにはユーザーではなくAzure ADアプリケーションを設定しておく必要があります。

下記のように使用します。

var cred = new ClientCertificateCredential(<TenantId>, <ClientId>, <CertPath>);
var client = new SecretClient(new Uri(<KeyVaultUrl>), cred);
var d = client.GetSecret("<SecretName>");

個人的にはあまり使わないと思います。

ClientSecretCredential

Azure ADアプリケーションのシークレット認証です。

AuthorizationCodeCredentialと同じくシークレットを使用しますが

ClientCertificateCredentialと同じくユーザー情報を伴わない認証となります。

下記のように使用します。

var cred = new ClientSecretCredential(<TenantId>, <ClientId>, <ClientSecret>);
var client = new SecretClient(new Uri(<KeyVaultUrl>), cred);
var d = client.GetSecret("<SecretName>");

EnvironmentやManaged IdentityがあるためAzureのリソース上で利用する場合はあまり使うことはないと思います。

Azureのリソース外で展開する場合で、ユーザーの認証を伴わない場合に利用することになるかと思います。

DefaultAzureCredential

f:id:TakasDev:20211031122508p:plain

これです。

サンプルなどで頻繁に登場しますし割愛します。

DeviceCodeCredential

Intuneなどで管理されているデバイスで使用できるDeviceCodeフローです。

僕の個人環境はIntune管理されているデバイスはないので割愛します。

EnvironmentCredential

環境変数に設定された値を参照して認証が行われます。

引数として値を指定するClientSecretCredentialUsernamePasswordCredentialと違うのは環境変数内の値を参照する点ですね。

どのような値を設定するかはこちらを参照してください。

下記のように使用します。

var cred = new EnvironmentCredential();
var client = new SecretClient(new Uri(<KeyVaultUrl>), cred);
var d = client.GetSecret("<SecretName>");

Azureのリソースの場合は基本Managed Instanceを利用することになると思います。

Azureのリソース外などで使用する場合に使用する感じですね。

内部で行われているのはClientSecretCredentialUsernamePasswordCredentialと同じなので、設定としてもそれらと同じことをしてあげればOKです。

InteractiveBrowserCredential

名前の通りInteractiveに認証が行われます。ブラウザでユーザーID/パスワード入力画面が立ち上がります。

デスクトップアプリケーションから実行する場合はプラットフォームMobile and Desktop Applicationsで認証が通るようにしておく必要があります。

下記のように使用します(コンソールアプリケーションで使用するための実装なのでその点ご留意ください)。

var cred = new InteractiveBrowserCredential(new InteractiveBrowserCredentialOptions { ClientId = ClientId, TenantId = TenantId, RedirectUri= new Uri(@"http://localhost/") });
var client = new SecretClient(new Uri(<KeyVaultUrl>), cred);
var d = client.GetSecret("<SecretName>");

ローカルからアクセスする場合はこれが一番都合が良いかなと思います。

ManagedIdentityCredential

Azureのリソースに展開する場合はこれがド安定だと思います。

生成されたManaged IdentityをAzure Key Vaultのアクセスポリシーに設定してあげましょう。

下記のように使用します。

var cred = new ManagedIdentityCredential();
var client = new SecretClient(new Uri(<KeyVaultUrl>), cred);
var d = client.GetSecret("<SecretName>");

ただ当然ながらローカルだと使えないので、開発時はChainedTokenCredentialを使って他の認証方法と組み合わせて使うことになると思います。

以下次回

思ったよりボリュームが多かったので残ったものは次回にしようと思います。

ただ、ここまで見てわかったとおり、多くはAzure ADアプリケーションを使用した認証フローで構成されていることがわかります。

認証周りのDocsの内容を抑えておけば、どのような認証ができるのか、どのような実装/設定になるのかといった点が想像しやすくなると思います。

今回実装したもののベースは下記リポジトリに格納しています ※色々自分で検証しやすくしているので、記事中のコードと少し構成が違います

github.com