Azure Key Vault - Functionsからのシークレット取得（Python / Nodejs）

# 目的

`Azure SDK for Python`を使用して`Azure Functions（トリガー：EventGrid）`から`Azure Key Vault`のシークレット取得を行う

# Azure Key Vault シークレットの生成

1. Azure portal でキー コンテナーに移動
2. シークレット → 生成/インポートをクリック
3. 任意の名前、値を入力し作成をクリック

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/8572779d-eb0f-2cd2-df05-49e7a9a9d4ec.png)
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/cc28d267-4cda-1c1d-ed3a-731dd35ce01c.png)

# Key Vault へのアクセス許可をアプリに付与する

公式ドキュメント

- [App Service と Azure Functions の Key Vault 参照を使用する](https://docs.microsoft.com/ja-jp/azure/app-service/app-service-key-vault-references)
- [App Service と Azure Functions でマネージド ID を使用する方法](https://docs.microsoft.com/ja-jp/azure/app-service/overview-managed-identity?tabs=dotnet)

## 1．Azure Functionsにシステム割り当てのマネージドIDを作成

1. Azure portalで関数アプリに移動
2. IDをクリック
3. システム割り当て済タブで状態をオンに変更

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/27b67ece-62c8-aa6c-3da1-679f06441cc5.png)

## 2．Key Vault のアクセス ポリシーを作成

1．Azure portal でキー コンテナーに移動
2．アクセスポリシー → +アクセスポリシーの追加をクリック

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/7a45099d-dbfb-db53-04c0-3aa5f021b3ab.png)

3．シークレットのアクセス許可から必要な操作をチェック
4．プリンシパルの選択の右にあるリンクをクリック
5．先ほどマネージドIDを割り当てたAzure Functionsのプロジェクト名を検索、選択し、追加をクリック

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/c0a53951-104c-0ed8-f03d-4f08e8b993d7.png)
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/1a683fa3-e09c-24ae-88b7-7b5398dce566.png)

# 実装

## Azure Functions（Python）

```python
import json
import logging

import azure.functions as func
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential

def main(event: func.EventGridEvent):
  keyVaultName = "xxxxx"  # キーコンテナ名
  KVUri = f"https://{keyVaultName}.vault.azure.net"

  credential = DefaultAzureCredential() # 資格情報の取得
  client = SecretClient(vault_url=KVUri, credential=credential)

  secretName = "yyyyy"  # シークレット名
  retrieved_secret = client.get_secret(secretName)
  logging.info(f"Your secret is : '{retrieved_secret.value}'.")

```

[DefaultAzureCredential](https://docs.microsoft.com/en-us/python/api/overview/azure/identity-readme?view=azure-python#defaultazurecredential)は、環境変数、マネージドID、Visual StudioCodeで登録しているアカウント情報等を順番にチェックし資格情報の取得を行う
今回の場合はマネージドIDから資格情報が取得される

### 結果：シークレット取得失敗

Azure Functions＋Pythonの場合はシークレット取得失敗
以下、実行時のログ、[401](https://docs.microsoft.com/en-us/azure/key-vault/general/rest-error-codes#http-401-unauthenticated-request)なので認証に失敗している模様
`ManagedIdentityCredential will use App Service managed identity`からマネージドIDが使用されていることがわかる
※原因不明：2021/05/05

```
[Information]   Executing 'Functions.EventGridTrigger-key-vault' (Reason='EventGrid trigger fired at 2021-05-03T06:03:44.8917058+00:00', Id=4c
[Verbose]   Sending invocation id:4c4b8da5-
[Verbose]   Posting invocation id:4c4b8da5- on workerId:7461e294-
[Information]   ManagedIdentityCredential will use App Service managed identity
[Information]   send request
[Information]   Request URL: 'https://xxxxxx.vault.azure.net/secrets/xxxxx/?api-version=REDACTED'
[Information]   Request method: 'GET'
[Information]   Request headers:
[Information]       'Accept': 'application/json'
[Information]       'x-ms-client-request-id': '5193e'
[Information]       'User-Agent': 'azsdk-python-keyvault-secrets/4.2.0 Python/3.8.9 (Linux-5.4.81-microsoft-standard-x86_64-with-glibc2.2.5)'
[Information]       'Content-Length': '0'
[Information]   No body was attached to the request
[Information]   Response status: 401
[Information]   Response headers:
[Information]       'Cache-Control': 'no-cache'
[Information]       'Pragma': 'no-cache'
[Information]       'Content-Length': '87'
[Information]       'Content-Type': 'application/json; charset=utf-8'
[Information]       'Expires': '-1'
[Information]   Response status: 200
[Information]   Executing 'Functions.EventGridTrigger-key-vault' (Reason='EventGrid trigger fired at 2021-05-03T06:03:44.8917058+00:00', Id=4c4b8da
```

### 解決 [2021/05/19 追記]

FunctionsをAzure API Managementと連携し、
取得したシークレットをレスポンスで返却したところ、問題なくシークレットは取得できていた

![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/0c724a03-5c73-47ee-6a25-514f76f4cad8.png)

Azure API Managementとの連携方法については以下参照
[Azure API Management - SwaggerからのAPIインポート、Functionsとの連携](https://qiita.com/takmot/items/6605ab69fb49d930c4a0)

#### 原因

Azure portalの関数テストのログが、
リアルタイム性が低く、ログの量が多いと正常に全て出力されないようで、
SDKのシークレット取得（`get_secret`）を実行するとHTTPリクエスト/レスポンス内容がログに出力されており、
その影響で取得したシークレットの値が出力されなかったと思われる。
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/37a61828-4282-c97e-9939-d6162b6f25c9.png)


## ローカル実行（Python）

### 結果：シークレット取得成功

ローカル実行＋Pythonの場合は問題なくシークレット取得成功
以下、実行時のログ、
ローカルで実行した場合は、AzureCLIで登録しているアカウント情報から資格情報が取得されるため、
環境変数 → マネージドID → Visual StudioCodeでの資格情報の取得は失敗している

```
EnvironmentCredential.get_token failed: EnvironmentCredential authentication unavailable. Environment variables are not fully configured.
ManagedIdentityCredential.get_token failed: ManagedIdentityCredential authentication unavailable, no managed identity endpoint found.
SharedTokenCacheCredential.get_token failed: SharedTokenCacheCredential authentication unavailable. No accounts were found in the cache.
Your secret is '1234567789'.
```

## Azure Functions（Nodejs）

```javascript
const { DefaultAzureCredential } = require("@azure/identity");
const { SecretClient } = require("@azure/keyvault-secrets");

module.exports = async function (context, eventGridEvent) {

    const keyVaultName = "xxxxx";
    const KVUri = "https://" + keyVaultName + ".vault.azure.net";
    const secretName = "yyyyy";

    const credential = new DefaultAzureCredential();
    const client = new SecretClient(KVUri, credential);

    const retrievedSecret = await client.getSecret(secretName);
    context.log("Your secret is '" + retrievedSecret.value + "'.");
};
```

### 結果：シークレット取得成功

Azure Functions＋Nodejsの場合は問題なくシークレット取得成功
以下、実行時のログ

```
[Information] Executing 'Functions.EventGridTrigger-node-key-vault' (Reason='EventGrid trigger fired at 2021-05-04T23:24:06.05164
[Information] Your secret is '1234567789'.
[Information] Executed 'Functions.EventGridTrigger-node-key-vault' (Succeeded, Id=788e3f19, Duration=
```