Azure Managed Identityを使ってソースから資格情報を追い出す

# はじめに

クラウドでアプリケーションを開発するにあたり真っ先に考えるのが資格情報をどう取り扱うかですよね。

たとえば、FunctionsからBlobStorageにファイルを保存するときのことを考えてみましょう。
BlobStorageのストレージアカウントのアクセスキーをソースコードに直接書くことでアクセスすることは可能ですが、そのソースコードをそのままGithubにcommitしてしまい漏洩してしまう・・・なんてことは想像できますしAWSではよく話題になってますよね。

* [AWSが不正利用され300万円の請求が届いてから免除までの一部始終](https://qiita.com/AkiyoshiOkano/items/72002409e3be9215ae7e)
* [【実録】アクセスキー流出、攻撃者のとった行動とその対策](https://dev.classmethod.jp/cloud/aws/accesskey-leak/)
* [[まとめ] AWSのアクセスキーを安全に管理する方法](https://nishiyama.hatenablog.com/entry/how-to-manage-aws-access-key-safely)

**こういったリスクを排除するためにあらかじめアクセスするサービス同士を繋いでおくことで、資格情報（アクセスキー）を使わずリソースにアクセスすることが可能となります。**
この仕組みを実現するのが**Azure Managed Identity**です。

Azure Managed IdentityはAzure ADの機能の一つで**料金はかかりません。**

ちなみにAzure Managed Identityは以前はMSI（Managed Service Identity)なんて呼ばれていました。

# AWSだと？

AWSでもほぼ似たような仕組みが存在します。
EC2にS3のIAM Roleを付与しておくことで、EC2上のアプリケーションでAWS SDKを使って実装するときにアクセスキー・シークレットアクセスキーを使わずS3にアクセスできます。

AWSでは以下のURLにアクセスすることで一時的なセキュリティ情報を取得してリソースにアクセスることができます。
ちなみにAWS SDKを使っている場合は以下のURLを意識することはありません。

```
http://169.254.169.254/latest/meta-data/iam/security-credentials/[ロール名]
```

# やっていこう

AWSの場合は、IAMロールを作成してEC2 RoleやLambda Functionsに割り当てていくだけで実現できるのですがAzureの場合は少し面倒です。実際にやってみましょう。

nodejsとJavaのサンプルを用意しました。

## Azure FunctionsからBlobStorageにデータを保存する

Azure Functions(nodejs)からBlobStorageにデータを保存する部分をManagedIdentityを使って実装してみたいと思います。

マネージドIDを有効にする

Functionsの設定画面を開いて、IDをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/76e2b708-3af6-c67a-4928-511a2edc6196.png)

オンにして保存すると、オブジェクトIDが振られます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/2bdbd1c7-3945-8f29-9fa0-cbedc6409ccc.png)

オブジェクトIDが発番されると、オブジェクトIDに対して権限を設定することができるようになります。
つまりFunctionがBlobStorageのアクセス権を持つように設定することが可能になります。

ストレージアカウントでIAMの設定を開きます。

「ロールの割り当て」タブを選択し、「追加」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/d8cbcef6-69f7-6134-f7d6-65e28d5258c4.png)

Blobに対するアクセス権を設定して保存をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/7c0e711c-8365-4f85-8b4f-a1d280321bd6.png)

これでFunctionにBlobStorageにアクセスする権限が付きました。

あとは、nodejsでBlobへアクセスするコードをいつも通り実装するだけです。

```javascript
    const az = require("@azure/identity");
    const tokenCredential = new az.ManagedIdentityCredential();
    const blobServiceClient = new BlobServiceClient('https://' + storageAccount + '.blob.core.windows.net', tokenCredential);
```

ポイントは2行目です。new ManagedIdentityCredential()ってやるだけで資格情報（Credential）が取れるので、あとはBlobServiceClientに渡してあげるだけです。とても簡単ですね。
内部では`http://169.254.169.254/metadata/identity/oauth2/token`にアクセスして一時的な認証情報を取得しています。
BlobServiceClientが取れてしまえばこっちのものなので、あとはAPIに従って実装しましょう。

## Azure FunctionsでKeyVaultのSecretの値を取得する

とある仕事でAzureFunctionsからOAuth2.0(ClientCredentialsFlow)を使ってデータを取得する必要がでてきました。
話はそれますが、ClientCredentialsFlowはClientID, ClientSecret, Scopeを設定してAccessTokenを取得するフローになります。ClientIDとClientSecretは正しく管理されていることが前提ですので、ソースコード上にハードコードするのはマズいですよね。今回はAzureKeyVaultに格納することにしました。FunctionsでKeyVaultを扱ってみましょう。

FunctionsのマネージドIDの設定は先ほどと同じなので省略します。

KeyVaultのIAMの設定を行いますが流れはストレージアカウントの場合と同様です。
「ロールの割り当て」タブを選択し、「追加」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/3aa2aa4f-bbee-3f0c-e7d4-ff999fb823be.png)

役割を設定し、選択のテキストボックスでFunction名を使って検索し設定していきます。
（ここでFunction名が出てこない場合は、ManagedIdentityの設定がうまくいっていないということです）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/3e149ffd-a570-120e-ced3-4ce57bb6ed33.png)

これでFunctionにキーコンテナ（KeyVault）にアクセスする権限が付きました。

あとは同じように実装していきます。

```javascript
    const az = require("@azure/identity");
    const tokenCredential = new az.ManagedIdentityCredential();
    const url = 'https://' + config.keyContainer + '.vault.azure.net';
    const client = new SecretClient(url, tokenCredential);
    const clientId = await client.getSecret("xxxxxxxxxxxxxxx");
```

こちらもnew ManagedIdentityCredential()とするだけで資格情報が取得できるのでKeyVaultへアクセスするためのSecretClientのコンストラクタに設定してあげるだけです。

## ACI(AzureContainerInstances)からKeyVaultへアクセスする

AzureContainerInstances(ACI）からCosmosDBにアクセスすることを考えていたのですが、これもManagedIdentityを使って・・・とおもったらそうはいきませんでした。
これは後ほど触れますが、CosmosDBがAzure AD 認証をサポートしていないためです。

ということで・・・これもKeyVaultのSecretにCosmosDBの接続文字列を保存して実装していこうと思います。

ACIでManaged Identityを有効化します。

IDをクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/55b2e2e2-98d3-6fb8-f2bf-4339e35b9a0c.png)

「オン」にして保存されるとオブジェクトIDが発番されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/3e4980ee-a715-9819-524c-f3f354aad68e.png)

KeyVaultに対するIAMの設定は先ほどと同じなので省略します。

ACIからCosmosDBへのアクセスはJavaで書いていたのでManagedIdentityを使ってKeyVaultへアクセスする部分のコードを紹介します。

```java
        Azure azure;
        try {
            azure = Azure.authenticate(
                    new MSICredentials(
                            AzureEnvironment.AZURE)).withDefaultSubscription();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // アクセスキーを使ってKeyVaultからシークレットを取得
        Vault vault = azure.vaults().getByResourceGroup(keyvaultRg, keyvaultName);
        cosmosdbxxx = vault.secrets().getByName("cosmosdb-xxxxx").value();
```

SDKは[Version1.26.0](https://mvnrepository.com/artifact/com.microsoft.azure/azure)を使ったのですが、クラスにManagedIdentityの古い名称（MSI）が残っています。
この名称に気づけずちょっと苦戦しましたが、これを使うことで簡単に資格情報が取得できます。

# Managed Identityはまだまだ発展途上

今回の例ではACIからCosmosDBにアクセスするときにManagedIdentityを使って簡単に接続・・・・というわけにはいかず、CosmosDBの接続文字列を格納したKeyVaultへアクセスするためにKeyVaultを使いました。

このことからわかる通りすべてのサービスがすべてのサービスに対してManagedIdentityでアクセスできることがサポートされているわけではありません。

## ManagedIDをサポートするAzureサービス(2019/12/16時点)

ManagedIdentityが設定できるサービスです。

* Azure Virtual Machines
* Azure 仮想マシン スケール セット
* Azure App Service
* Azure Blueprint
* Azure Functions
* Azure Logic Apps
* Azure Data Factory V2
* Azure API Management
* Azure Container Instances
* Azure Container Registry タスク

https://docs.microsoft.com/ja-jp/azure/active-directory/managed-identities-azure-resources/services-support-managed-identities#azure-services-that-support-managed-identities-for-azure-resources

## Azure AD 認証をサポートしている Azure サービス(2019/12/16時点）

ManagedIdentityに対してIAMの設定が行えるサービスととらえてください。

* Azure Key Vault
* Azure Data Lake
* Azure SQL
* Azure Event Hubs
* Azure Service Bus
* Azure Storage の BLOB とキュー
* Azure Analysis Services

https://docs.microsoft.com/ja-jp/azure/active-directory/managed-identities-azure-resources/services-support-managed-identities#azure-services-that-support-azure-ad-authentication

# メタデータエンドポイントを使う上での注意点

[Capital Oneデータ漏洩経路の考察](https://medium.com/japan-d2/capital-one%E3%83%87%E3%83%BC%E3%82%BF%E6%BC%8F%E6%B4%A9%E7%B5%8C%E8%B7%AF%E3%81%AE%E8%80%83%E5%AF%9F-12151e8d776f)

CapitalOneのデータ漏洩事件に関してとても分かりやすくまとめられていました。
メタデータエンドポイントを使ったことが悪いわけではなく、IAMロールの権限が非常に強力だったことも事故の影響が大きくなった原因とあります。

これはどのサービスを使う上でもそうですが、最小権限を付与するように注意しておくとよいでしょう。

# さいごに

個人的に辛かったのはCosmosDBのAzureAD認証のサポートがなかったことと、AzureBatchにManagedIdentityが設定できなかったことです。AzureBatchはLogAnalyticsからも見放されているので、これからあまり発展しないのかな・・・ちょっと使うのやめようかな・・・と思ったりもしました。

新機能も良いのですが、MonitorやLogAnalyticsを幅広いサービスで使えるようになることやManagedIdentityといった地味だけどとても嬉しい機能のアップデートに力を入れてほしいなぁと思ってます。

マイクロソフトさんよろしくお願いします。