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 とは を参照。
設定方法
- Azure App Serviceのリソースを作成する。
- Azure App Serviceの設定→IDからシステム割り当て済みの状態を「オン」にする。
3. NodeアプリをApp Serviceにデプロイする。
4. Azure Key Vaultに戻り、設定→アクセスポリシーから「アクセスポリシーの追加」を押す。
5. 適当な許可を選択し、プリンシパルの選択から先程のApp Serviceを選択し追加。
以上で設定は完了。
コードの中身と結果
まずはモジュールをインストール
npm install @azure/identity
npm install @azure/keyvault-keys
npm install @azure/keyvault-secrets
コードの中身
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();
結果
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にアクセスする
クイック スタート:Node.js 用 Azure Key Vault クライアント ライブラリ (v4)
@azure/identity - npm
ローカルのNodeアプリからKey Vaultに接続する方法
こちらでは成功例と失敗例を示す
まずは成功例(1個)を示した後、失敗例(複数個)を示す
成功例
DeviceCodeCredential(モジュール : @azure/identity)を使って認証を行う。
Nodeアプリを起動させると、URLとデバイスコードといわれる9桁のコードがコンソールに出力される。
そのURLにアクセスし、デバイスコードを入力するとAzureのアプリ(後述するところのrpa-test)にサインインすると認証をパスできる。
設定方法
- AzureのホームからAzure Active Directory(AAD)にて、管理→アプリの登録→新規作成(今回はrpa-testと名付けた)
2. rpa-testの概要からテナントID、クライアントIDを取得
3. これらのIDを環境変数に書き込む(今回は、環境変数に書き込むのは面倒なので、.envファイルに書き込む)
4. rpa-testの認証から下の方にある アプリケーションは、パブリック クライアントとして扱います を「はい」にする
5. サポートされているアカウントの種類を、この組織ディレクトリのみに含まれるアカウント にする
6. key-vaultの方に戻る
7. key-vaultのアクセスポリシーにアクセスできるユーザーを追加する
以上で設定は完了
コードの中身と結果
コードの中身
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();
結果
{ 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にいくと...
{ 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
失敗例
ここから失敗例を挙げていく
なるべく簡潔に書くように努める
DefaultAzureCredential(モジュール : @azure/identity)
クイック スタート:Node.js 用 Azure Key Vault クライアント ライブラリ (v4) にも書いてあるやり方で、設定方法は途中まで成功例と似ている。コードも上述のURL内に書いてあるため省略。
設定方法
- Azure Active Directory(AAD)にて、管理→アプリの登録→新規作成(今回はrpa-testと名付けた)
- rpa-testの概要からテナントID、クライアントIDを取得し、管理→証明書とシークレットから新しいクライアントシークレットを作成&クライアントシークレットIDを取得
- これらのIDを環境変数に書き込む(今回は、環境変数に書き込むのは面倒なので、.envファイルに書き込む)
- Key Vaultに戻り、アクセスポリシーでrpa-testを追加
結果
キーやシークレットを取得でき、使用もできるが
結局、クライアントシークレットIDはAPIキーのようなものなので前提条件に当てはまらないため保留(失敗)
msRestAzure.interactiveLogin(モジュール : 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)
.NETとかNode.jsでKeyVaultを触るを参考にした。
引数にユーザー名とユーザーのパスワード渡すもの。
結果
以下のようなエラーが出た
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)
アプリケーションのclientIdとtenantId、またユーザー名とユーザーのパスワードを引数に渡す認証
Failed to acquire token for the user
となり失敗
InteractiveBrowserCredential
なぜかnode非対応
UsernamePasswordCredential
引数に要求されているのはアプリケーションのtenantId, clientId, ユーザー名, ユーザーのパスワードのみだが、なぜかエラーが出てアプリケーションのクライアントシークレットを要求されたため却下
http requestでauthorization codeを取得する
OAuth 2.0 auth code grantを参考に、 http requestでauthorization codeを取得→accesstokenを取得→リソースにアクセス
をやってみたが、レスポンスで返ってきたauthorizatin codeが見つからないので、保留
この記事について
この記事はThinkings株式会社にて長期インターンの業務として社内Qiitaに投稿したものを、私の個人用Qiitaに再投稿したものになります。許可を受けて再投稿しております。
また、この記事は2020/04/17に社内Qiitaに投稿されたものです。