NodeアプリからAzure Key Vaultに接続する方法

Key Vaultに接続するためには必ず認証を行う必要がある。
主に **どうやって認証を行うか** が課題。
また前提条件として、Key Vaultにアクセスする際に **APIキーのようなものを使わない**  (いくらkey-vault上に暗号化キーをセキュアに保存しても、APIキーなどをコード内に置いていたら意味ないため)
Azure App ServiceにNodeアプリを **デプロイ** してからKey Vaultに接続する場合と、 **ローカル** のNodeアプリからKey Vaultに接続する場合に分けて紹介する。

# Azure App ServiceにNodeアプリをデプロイしてからKey Vaultに接続する方法

 **マネージドID** を使ってKey Vaultに接続するための認証を行う。

## マネージドIDとは

システム割り当てマネージド IDとユーザー割り当てマネージド IDの2種類がある。
簡単に説明すると、システム割り当てマネージドIDはAzureサービス(今回でいうとAzure App Service)の特有のIDで、このIDからの接続をKey Vaultの方で許可することで認証をパスすることができる。
ユーザー割り当てマネージドIDは、これ自体が1つのリソースとなっており、このIDをユーザーがAzureサービスに割り当てることで、マネージドIDをAzureサービスに持たせることができる。
今回はシステム割り当てマネージドIDを使う。
詳しくは[Azure リソースのマネージド ID とは](https://docs.microsoft.com/ja-jp/azure/active-directory/managed-identities-azure-resources/overview/) を参照。

## 設定方法

1. Azure App Serviceのリソースを作成する。
2. Azure App Serviceの設定→IDからシステム割り当て済みの状態を「オン」にする。

![7d0c5357-bca4-e085-e307-861b35995661.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/b299837a-e99e-9c04-e30d-783be23be05c.png)
3. NodeアプリをApp Serviceにデプロイする。
4. Azure Key Vaultに戻り、設定→アクセスポリシーから「アクセスポリシーの追加」を押す。
5. 適当な許可を選択し、プリンシパルの選択から先程のApp Serviceを選択し追加。

![1e2b078c-cd4d-c0c7-b27c-e58fbb0657b7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/b647fd83-dbbe-bfc2-c2df-48660b5472c6.png)

以上で設定は完了。

## コードの中身と結果

まずはモジュールをインストール

```console
npm install @azure/identity
npm install @azure/keyvault-keys
npm install @azure/keyvault-secrets
```

### コードの中身

```javascript:index.js

const { ManagedIdentityCredential } = require("@azure/identity");
const { KeyClient,CryptographyClient } = require("@azure/keyvault-keys");
const { SecretClient } = require('@azure/keyvault-secrets')

// Build the URL to reach your key vault
const vaultName = "xxxxxxxxxxxxx"; //Key Vaultのリソース名
const url = `https://${vaultName}.vault.azure.net`;

 
// Lastly, create our keys client and connect to the service
const credential = new ManagedIdentityCredential()

const keyName = "MyKeyName"; //キーの名前
const secretName = "secret-test" //シークレットの名前

async function main() {
    const client = new KeyClient(url, credential); //キー取得のためのclient
    const secretClient = new SecretClient(url, credential) //シークレット取得のためのclient
    const key = await client.getKey(keyName, "RSA");
    const cryptographyClient = new CryptographyClient(key.id, credential) //暗号化、復号化のclient
    const encryptResult = await cryptographyClient.encrypt("RSA1_5", Buffer.from("My Message")); //暗号化結果
    console.log("encrypt result: ", encryptResult.result);
    const decryptResult = await cryptographyClient.decrypt("RSA1_5", encryptResult.result); //復号化結果
    console.log("decrypt result: ", decryptResult.result.toString());
    const secretResult = await secretClient.getSecret(secretName) //シークレットの取得結果
    console.log("secret result: ",secretResult)
}

main();
```

### 結果


```console
encrypt result:  <Buffer xx xx xx xx xx xx xx xx xx xx ... 206 more bytes>
decrypt result:  My Message
secret result:  {
  value: 'test',
  name: 'secret-test',
  properties: {
    vaultUrl: 'https://xxxxxxxxxxxxxxxxx.vault.azure.net',
    expiresOn: undefined,
    createdOn: 2020-03-31T08:38:01.000Z,
    updatedOn: 2020-04-10T07:15:30.000Z,
    value: 'test',
    id: 'https://xxxxxxxxxxxxxx.vault.azure.net/secrets/secret-test/xxxxxxxxxxxxxxx',
    name: 'secret-test',
    version: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    enabled: true,
    recoveryLevel: 'Recoverable+Purgeable'
  }
}
```


参考:
 [システム割り当てマネージドIDを使用してAzure Key Vaultにアクセスする](https://docs.microsoft.com/ja-jp/azure/key-vault/managed-identity) 
[クイック スタート:Node.js 用 Azure Key Vault クライアント ライブラリ (v4)](https://docs.microsoft.com/ja-jp/azure/key-vault/quick-create-node) 
[@azure/identity - npm](https://www.npmjs.com/package/@azure/identity) 

# ローカルのNodeアプリからKey Vaultに接続する方法

こちらでは成功例と失敗例を示す
まずは成功例(1個)を示した後、失敗例(複数個)を示す

## 成功例

DeviceCodeCredential(モジュール : [@azure/identity](https://www.npmjs.com/package/@azure/identity))を使って認証を行う。
Nodeアプリを起動させると、URLとデバイスコードといわれる9桁のコードがコンソールに出力される。
そのURLにアクセスし、デバイスコードを入力するとAzureのアプリ(後述するところのrpa-test)にサインインすると認証をパスできる。

### 設定方法

1. AzureのホームからAzure Active Directory(AAD)にて、管理→アプリの登録→新規作成(今回はrpa-testと名付けた)

![0915c473-b594-4898-17e3-1d277de5da0d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/6982e393-ac5d-9281-5584-519e25581fa0.png)
2. rpa-testの概要からテナントID、クライアントIDを取得
3. これらのIDを環境変数に書き込む（今回は、環境変数に書き込むのは面倒なので、.envファイルに書き込む)
4. rpa-testの認証から下の方にある アプリケーションは、パブリック クライアントとして扱います　を「はい」にする
5. サポートされているアカウントの種類を、この組織ディレクトリのみに含まれるアカウント　にする

![80b40365-ee7a-bd48-a730-73714491c855.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/43a68f7f-1510-7fc7-9a13-90f263c081ee.png)
6. key-vaultの方に戻る
7. key-vaultのアクセスポリシーにアクセスできるユーザーを追加する
![aaa2e6cf-c740-ddad-d3f2-8e178d952f13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/ff0ef7ad-0af7-9a19-8e6a-e89fd55a468b.png)

以上で設定は完了

### コードの中身と結果

#### コードの中身

```javascript:index.js
const { DeviceCodeCredential } = require("@azure/identity");
const { KeyClient,CryptographyClient } = require("@azure/keyvault-keys");
const { SecretClient } = require('@azure/keyvault-secrets')
require('dotenv').config()

// Build the URL to reach your key vault
const vaultName = "xxxxxxxxxxxxxxxx";
const url = `https://${vaultName}.vault.azure.net`;

 
// DeviceCodeCredentialの引数にさっき取得したテナントIDとクライアントIDを渡す
const credential = new DeviceCodeCredential(process.env.AZURE_TENANT_ID,process.env.AZURE_CLIENT_ID,async function(err,credential){
        if(err) console.log(err)
        else console.log(credential)
    })

const keyName = "MyKeyName";
const secretName = "secret-test"

async function main() {
    const client = new KeyClient(url, credential);
    const secretClient = new SecretClient(url, credential) //シークレットのclient
    const key = await client.getKey(keyName, "RSA");
    const cryptographyClient = new CryptographyClient(key.id, credential) //暗号化、復号化のclient
    const encryptResult = await cryptographyClient.encrypt("RSA1_5", Buffer.from("My Message")); //暗号化結果
    console.log("encrypt result: ", encryptResult.result);
    const decryptResult = await cryptographyClient.decrypt("RSA1_5", encryptResult.result); //復号化結果
    console.log("decrypt result: ", decryptResult.result.toString());
    const secretResult = await secretClient.getSecret(secretName) //シークレットの取得結果
    console.log("secret result: ",secretResult)
}

main();
```

#### 結果

```console
{ userCode: 'H3LSY66TU',
  verificationUri: 'https://microsoft.com/devicelogin',
  message: 'To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code XXXXXXXXX to authenticate.' }
```

コンソールに出力されたURLにいくと...

1. 先程のコンソールに出力されたコードを入力
![451c0029-34d6-881d-81ab-9752400dc76e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/fbdcf67f-aa56-1671-7c08-fe6a4634b7dd.png)
2. サインインをする
![f12d8e6a-623b-4047-238d-7640397fa9ad.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/4c8bf880-7b05-35f4-2c4d-165337e50c12.png)
3. これでコンソールに結果が表示される

```console
{ userCode: 'XXXXXXXXX',
  verificationUri: 'https://microsoft.com/devicelogin',
  message: 'To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code XXXXXXXXX to authenticate.' }
encrypt result:  <Buffer xx xx xx xx xx xx xx xx xx... >
decrypt result:  My Message
secret result:  { value: 'test',
  name: 'secret-test',
  properties:
   { vaultUrl: 'https://xxxxxxxxxxxxxxx.vault.azure.net',
     expiresOn: undefined,
     createdOn: 2020-03-31T08:38:01.000Z,
     updatedOn: 2020-04-10T07:15:30.000Z,
     value: 'test',
     id: 'https://xxxxxxxxxxxxx.vault.azure.net/secrets/secret-test/xxxxxxxxxxxxxxxxxxx',
     name: 'secret-test',
     version: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
     enabled: true,
     recoveryLevel: 'Recoverable+Purgeable' } }
```

参考: 
[DeviceCodeCredential | @azure/identity](https://azuresdkdocs.blob.core.windows.net/$web/javascript/azure-identity/1.0.0/classes/devicecodecredential.html)
 

## 失敗例

ここから失敗例を挙げていく
なるべく簡潔に書くように努める

### DefaultAzureCredential(モジュール : [@azure/identity](https://www.npmjs.com/package/@azure/identity))

[クイック スタート:Node.js 用 Azure Key Vault クライアント ライブラリ (v4)](https://docs.microsoft.com/ja-jp/azure/key-vault/quick-create-node) にも書いてあるやり方で、設定方法は途中まで成功例と似ている。コードも上述のURL内に書いてあるため省略。

#### 設定方法
1. Azure Active Directory(AAD)にて、管理→アプリの登録→新規作成(今回はrpa-testと名付けた)
2. rpa-testの概要からテナントID、クライアントIDを取得し、管理→証明書とシークレットから新しいクライアントシークレットを作成＆クライアントシークレットIDを取得
3. これらのIDを環境変数に書き込む（今回は、環境変数に書き込むのは面倒なので、.envファイルに書き込む）
4. Key Vaultに戻り、アクセスポリシーでrpa-testを追加

#### 結果
キーやシークレットを取得でき、使用もできるが
結局、クライアントシークレットIDはAPIキーのようなものなので前提条件に当てはまらないため保留（失敗）

### msRestAzure.interactiveLogin(モジュール : [ms-rest-azure](https://www.npmjs.com/package/ms-rest-azure))

成功例と非常に似ていて、nodeで起動させるとコンソールにURLとコードが出力される。成功例と異なる点は、成功例のDeviceCodeCredentialの引数にアプリケーション（先程のrpa-test）のテナントIDとクライアントIDを渡し、ブラウザではアプリケーションにサインインしていたが、こちらのinteractiveLoginは引数に何も渡さないので単純にAzureにログインしているだけという点。

#### 結果
Key Vaultのアクセスポリシーにサインインするユーザーを追加していれば行けそうな気がしたが、エラーがでた
→credentials must be one of: ApplicationTokenCredentials, UserTokenCredentials, DeviceTokenCredentials, MSITokenCredentials

とのことで、そもそもこの認証ではアクセスできないため失敗

### loginWithUsernamePassword(モジュール : [ms-rest-azure](https://www.npmjs.com/package/ms-rest-azure))

[.NETとかNode.jsでKeyVaultを触る](https://tkrtkhsh.hatenablog.com/entry/2018/10/12/085206)を参考にした。
引数にユーザー名とユーザーのパスワード渡すもの。

#### 結果
以下のようなエラーが出た
Error: Failed to acquire token for the user.
you must use multi-factor authentication to access
アクセスするためにはMFA（多段階認証）が必要といわれたため失敗

ほかに様々の方法を試したが、
このloginWithUsernamePasswordにマウスホバーすると
Provides a UserTokenCredentials object. This method is applicable only for organizational ids that are not 2FA enabled. Otherwise please use interactive login.
と書いてあって、2ファクタ認証には対応してない、interactiveなlogin を使えと書いてあるのに気づいた
先程のinteractiveLogin以外で、interactiveなloginする方法はないかと探したところ成功例にたどり着いた。

### UserTokenCredentials(モジュール : [ms-rest-azure](https://www.npmjs.com/package/ms-rest-azure))
アプリケーションのclientIdとtenantId、またユーザー名とユーザーのパスワードを引数に渡す認証

Failed to acquire token for the user
となり失敗

### InteractiveBrowserCredential
なぜかnode非対応

### UsernamePasswordCredential
引数に要求されているのはアプリケーションのtenantId, clientId, ユーザー名, ユーザーのパスワードのみだが、なぜかエラーが出てアプリケーションのクライアントシークレットを要求されたため却下

### http requestでauthorization codeを取得する

[OAuth 2.0 auth code grant](https://docs.microsoft.com/en-us/azure/active-directory/azuread-dev/v1-protocols-oauth-code#use-the-access-token-to-access-the-resource)を参考に、 http requestでauthorization codeを取得→accesstokenを取得→リソースにアクセス
をやってみたが、レスポンスで返ってきたauthorizatin codeが見つからないので、保留

# この記事について
この記事は[Thinkings株式会社](https://thinkings.co.jp/)にて長期インターンの業務として社内Qiitaに投稿したものを、私の個人用Qiitaに再投稿したものになります。許可を受けて再投稿しております。
また、この記事は2020/04/17に社内Qiitaに投稿されたものです。