Serverless Webhook

Serverless Webhook

こんにちは、UiPath株式会社の劉です。

今回は、Serverlessアーキテクチャを活用し、Orchestratorのジョブをリアルタイムに監視する仕組みを簡単に実装できる方法を紹介します。

この記事は、Orchestratorとプログラミングの知識がすこしある方でも一通りにServerless Webhookを作れることを目指しています。 ぜひ記事を読みながら、手を動かしてみてください。 

Orchestrator Webhookについて

UiPath Orchestrator 2018.04から、ジョブやQueueなどの詳しい処理状況をよりプロアクティブに監視やほかのシステムとの連携ができるように、Webhookという機能が追加されました。それより前のバージョンでは、Orchestratorのアラートメール機能で、ある程度ジョブなどへの監視できますが、監視できる項目は限られていて、ほかのシステムとの連携も容易にできないという制限もありました。

Webhookについて端的に説明すると、システムの更新情報をリアルタイムにほかのシステムに通知する仕組みです。もうすこしシステム的な説明すると、Webhookは、システムの何らかのイベントをトリガーにして、ほかのシステムに対してHTTP POST送信する仕組みです。OrchestratorのWebhookには、このようなイベントが大きく6種類があります(詳細は公式資料を参照)。それらのイベントを購読することによって、ジョブ、キューなどの監視が簡単に実現できます。例えば、Orchestratorにジョブ失敗したら知らせてほしい時に、ジョブ失敗のイベントだけを送付する設定をすることができます。

Serverlessアーキテクチャ

以上の説明から、Orchestratorのwebhook機能を利用するには、少なくともHTTPのPOSTリクエストを受け付ける仕組みを用意する必要があると分っていただけたでしょうか。自分たちでサーバ用意したり、システム開発したりしなければならないとハードルが高いと思われているかもしれませんが、実際には、最近その仕組みを簡単に実現できるためのクラウドサービスが数多く存在しています。コーディングしなくてもwebhookを受け付ける仕組みを簡単に作れるサービスとしてZapier、Microsoft Flowなどが有名です。だが、こういったサービスは、予め提供された機能しか使えないため、場合によっては実現したいことが実現できなかったりします。例えば、OrchestratorのWebhookは、ハッシュ値の比較を通して送信元を認証するための仕組みを用意しています。ハッシュ値の計算もコーディングなしで実現できるサービスが限られています。

しかし、少しコーディングを加えれば、安全なWebhookサービスを簡単に作れるクラウトサービスも数多く存在しています。いわゆるServerlessアーキテクチャです。

Serverlessアーキテクチャとは、サーバの構築と管理を必要としない実行環境のことです。有名なServerlessアーキテクチャとして、AWS Lambda式、Azure Functionなどがあります。これらのサービスは、基本的にコーディングを前提にしていて、そのために様々なプログラミング言語の実行環境を提供しています。

かつ実行回数や演算時間に応じた課金も可能のため、非常に注目されています。以下の表にWebhookを受け付ける仕組みの実装方法とそのメリット・デメリットをまとめました。

serverless-webhook1

ここで、今回紹介したいのが、Azure Functionを利用したServerless webhookの実装です。Azure Functionでは様々なプログラミング言語が利用可能です。今回は簡単に書けるNode.jsを利用します。今回のシナリオは、失敗したジョブを担当者にリアルタイムにメールで知らせるまでとします(下図を参照)。

serverless-webhook2

Serverless Webhookの実装

全体の流れ

1.Azure Functionの作成

Azure Functionを作成することで、Webhookを受け付けるURLを生成してくれます。このURLは、OrchestratorのWebhookのPOST送信先になります。後は、必要なロジックをAzure Portal画面から追加するだけで完成します。

2.Orchestrator Webhookの追加

Azure Functionの作成で生成されたURLを利用し、OrchestratorのWebhookを追加します。 

3.送信元認証機能の追加

Azure Functionが作成できた時点で、生成されたURLに対してHTTP送信可能な状態になっています。これだと、Azure Functionに送信されているどんなリクエストでも処理されてしまうため、セキュリティ上望ましくありません。そのため、Azure上でネットワークセキュリティ設定で、 Inboundに対してOrchestratorのサーバIPだけを許可することで十分防げますが、さらにセキュリティが必要な場合、Orchestratorが提供しているハッシュ値認証機能を利用し、送信元の特定のみならず、改ざん防止などの実現もできます。ここでは、ハッシュ値認証を実装します。

4.SendGrid機能の追加

SendGridは、クラウドベースのメール配信サービスです。Azure Function内で、SendGrid API Keyを使ってSendGridを利用して、メール送信できます。

メール送信機能というと、Outlookという選択肢もあるのですが、会社のセキュリティポリシーによってAzure上で使えなかったりすることもあるので、SMTPなどの情報が無くても簡単に使えるSendGridを利用します。

事前準備

1、Azureのアカウントの作成

Azure Functionを利用するには、Azure Portalのアカウントを持つ必要があります。こちら、新規サインアップすると、無料に使える枠があります。

持っていない方は、事前にアカウントを作成しておいてください。

2、SendGridのAPI Keyを取得

Azure FunctionのSendGrid機能を使うには、SendGridのAPI Keyを事前に取得する必要があります。こちらも無料な枠があり、SendGridのHP からサインアップしてから作成・取得できます。アカウント作成できた後に、確認のメールが来るので、必ずメールにある確認ボタンをクリックしましょう。

作成できたアカウントでSendGridのHP にログインし、左下の「API Keys」をクリックして、API Keysを作成する画面に遷移されます。右上の「Create API key」をクリックします。

serverless-webhook3

「Create API Key」の入力フォームが表示されたら、下図の赤枠のところを埋めましょう。keyの名前は何でも良いです。ここでは、「mySendGridApiKey」にしました。「Create&View」をクリックします。

serverless-webhook4

下記の画面が表示されたら、API Keyをコピーして任意のファイルに保存しましょう。

serverless-webhook5

これで事前準備ができました。これからWebhookを受け付けるAzure Functionを作成していきます。

Azure Functionの作成

Microsoft Azureポータル画面を開いて、検索欄に「Function」と入力し、サジェストに出てきたFunction Appをクリックします。

serverless-webhook6

Function Appの画面にて新規作成ボタンをクリックすると下記の画面が出てきます。各項目を埋めて作成をクリックします。

各項目の詳細については以下の表を参照してください。

serverless-webhook7
serverless-webhook8

作成できたら、下の画面が表示されます(少し時間掛かる)。oc-webhookをクリックします。

serverless-webhook9

関数作成画面が表示されたら、新しい関数を追加します。Azure Functionはこうやって1つ1つの関数を定義し、リクエストを受け付けることができるようになります。

serverless-webhook10

開発環境を選ぶ画面です。Azure FunctionはVisual StudioなどのIDEで開発してアップロードできますが、分かりやすく説明するため、今回は「ポータル内」を選択して次へ進みます。これで、ウェブ画面からコードを書けます。

serverless-webhook11

Webhook APIを選択して作成をクリックします。ほかにスケジュール実行などのテンプレートも用意されていますが、今回は使いません。

serverless-webhook12

Function Appサービスを作成できましたので、下記の編集画面が表示されます。この画面を簡単に説明します。

真ん中のエディターは編集する領域です。これより先のコーディングは、このエリアで行われます。

右はファイル構成です。2つのファイルがありますが、必要に応じてファイルを追加するのも良いです。

 ・Function.json: Functionのメタデータを定義するファイルです。メール機能を追加する時に直接そのファイルを開いて編集して定義することも可能です。

 ・index.js: Azure Functionのエントリーファイルです。リクエストが来るたびに、最初に読み込まれるのがこのファイルです。

一番下のエリアにはログとコンソールの機能があります。ソースにログを明示的に出す時に、ログがこちらに出力されます。コンソールからNPMのコマンドが使えます。

必要に応じてモジュールのインストールも可能です。基本的にローカル環境でコーディングするのと近い感じです。

以上のステップで、作成されたFunctionのURLつまりWebhookのPOST通信先のURLが生成されています。下のキャプチャーにある「実行」ボタンの右側のリンク「関数のURLの取得」をクリックします。

serverless-webhook13

出てきたURLをコピーします。このURLを使ってOrchestrator画面からWebhookを作成します。

serverless-webhook14

Orchestrator Webhookの追加

Orchestrator環境を持っていなければ、UiPath Community Editionから登録して利用してください。

Orchestratorの画面の右上のユーザアイコンをクリックし、メニューからWebhookをクリックします。

serverless-webhook15

追加ボタンをクリックします。

serverless-webhook16

Webhook作成の画面が表示されますので、今回は赤枠のところだけを入力して保存します。各項目の詳細は以下の通りです。

  • URL:先ほどコピーしたURLです。Webhookの送信先となります。

  • シークレット:123456  (これはOrchestratorからのリクエストかどうかを認証するための秘密情報です。言うまでもなく本番環境のシークレットは他人に見せないでください)

  • イベントタイプ:Job.Faultedを指定します。複数の指定も可能ですが、今回のシナリオでは、ジョブ失敗のイベントだけを監視するので、それだけを通知するようにします。

serverless-webhook17

これでWebhookの追加ができました。

送信元認証機能の追加

Azure Portalに戻って、index.jsを以下のように編集して保存します。

以下のソースコードは、Webhookの送信元を認証するための機能となります。認証の仕組みの詳細について弊社の公式ドキュメントをご参照ください。

コードの説明はコメントをご参照ください。

※ダウンロード可能な方はこちらから。

  //crptoモジュールを読み込み(ハッシュ値の計算などに使う) const crypto =require('crypto'); //secret_valueは、Orchestrator画面からWebhookを登録する際に入力した内容 const secret_value ='123456'; //すべてのリクエストのエントリー関数 module.exports =async function(context, req){ //必要に応じてログを出力 context.log('JavaScript HTTP trigger function processed a request.'); //リクエストにbodyが入っているかを確認 if (req.body) { var bodyString = JSON.stringify(req.body); //JSONデータをStringに変換 var expectedSignature = req.headers['x-uipath-signature']; //Orchestratorから送られてきたハッシュ値をHTTPヘッダーから取り出す //ハッシュ値が正しいかを認証 if (auth(bodyString, secret_value, expectedSignature)) {  context.log(req.body); //認証成功した場合、リクエスト内容をログに出す context.res = { //送信元を確認できた場合、200番を返す status: 200, body: 'Succeed.' }; } context.log('Authentication failed.'); //デバッグするためのログ //認証失敗した場合、401エラーを返して終了 context.res = { status: 401, body: "Authentication failed. " }; } else { context.log('Invalid format.'); //デバッグするためのログ //リクエストにbodyが入っていない場合、400エラーを返す context.res = { status: 400, body: "Invalid format. " }; } }; // 送信元を認証するための関数 function auth (body, secret, expectedSignature){ //Orchestratorから送ってきたbodyとWebhook登録する際に入れたシークレットを同じハッシュ関数でハッシュ値を生成します。 var calculatedHash = crypto.createHmac('sha256', secret).update(body).digest('base64'); //生成されハッシュ値をリクエストのヘッダーに格納されたハッシュ値を比較して、trueかfalseを返す return calculatedHash === expectedSignature; }

作成された関数をテストする前に、右側の統合をクリックし、統合画面からHTTPトリガーを定義します。WebhookのHTTPリクエストはPOSTなので、POSTだけにしましょう。この画面からルーティング、出力、変数などの設定もできます。後で、SendGrid機能を追加する際に、この画面から出力を定義します。

serverless-webhook18

これで作成されたAzure Functionに送信元を認証することができました。それを確認するために、何かしらのExceptionを出して失敗するジョブを作成し、Orchestratorから実行してみてください。下図のような結果が出てくるはずです。これは、ソースコードの中に、意図的に出したログです。ログの内容は、WebhookのPayloadです。ログが出てこない場合があるので、ブラウザをリフレッシュしてから試してみてください。

試しに、ソースコードの中のシークレット値123456を10000に変更してみてください。この場合、認証が通らなくて、ログに「Authentication failed」が出ます。

serverless-webhook19

SendGrid機能の追加

以上の実装で、Webhookの送信元の認証と内容をログに表示することができました。これより先、作成されたFunctionにSendGridの機能を追加し、メール送信できるようにしていきましょう。Azure Portalに戻って、「統合」をクリックし、先ほど使った統合の設定画面に戻ります。「新しい出力」をクリックします。

serverless-webhook20

SendGridを選択します。

serverless-webhook21

インストール画面が出来るので、インストールをクリックします。

serverless-webhook22

暫く経つと、下記のように表示されたら、成功にインストールされました。 SendGridのAPIキーを新規作成します。「新規」をクリックし、事前準備で作成した名前とキーを入れて「保存」をクリックします。

ここで、メールの送信先、内容なども定義できますが、カスタマイズしやすいように後でソースコードの中で指定します。

serverless-webhook23

最後に、index.jsの編集画面に戻って、ソースコードを以下のように編集します。実際編集したのが2箇所だけです。「//新規追加」とコメントしたところは今回新規追加したところです。

※ダウンロード可能な方はこちらから。

  //crptoモジュールを読み込み(ハッシュ値の計算などに使う) const crypto = require('crypto'); //secret_valueは、Orchestrator画面からWebhookを登録する際に入力した内容 const secret_value = '123456'; //すべてのリクエストのエントリー関数 module.exports =async function(context, req) { //必要に応じてログを出力 context.log('JavaScript HTTP trigger function processed a request.'); //リクエストにbodyが入っているかを確認 if (req.body) { var bodyString = JSON.stringify(req.body); //JSONデータをStringに変換 var expectedSignature = req.headers['x-uipath-signature']; //Orchestratorから送られてきたハッシュ値をHTTPヘッダーから取り出す //ハッシュ値が正しいかを認証 if (auth(bodyString, secret_value, expectedSignature)) { //secretは、Orchestrator画面からWebhookを登録する際に入力した内容 context.log(req.body); //認証成功した場合、リクエスト内容をログに出す //新規追加 context.done(null, createMailTemplate(bodyString)); //メール送信 } context.log('Authentication failed.'); //デバッグするためのログ //認証失敗した場合、401エラーを返して終了 context.res ={ status: 401, body: "Authentication failed. " }; } else { context.log('Invalid format.'); //デバッグするためのログ //リクエストにbodyが入っていない場合、400エラーを返す context.res = { status: 400, body: "Invalid format. " }; } }; // 送信元を認証するための関数 function auth (body, secret, expectedSignature) { //Orchestratorから送ってきたbodyとWebhook登録する際に入れたシークレットを同じハッシュ関数でハッシュ値を生成します。 var calculatedHash = crypto.createHmac('sha256', secret).update(body).digest('base64'); //生成されハッシュ値をリクエストのヘッダーに格納されたハッシュ値を比較して、trueかfalseを返す return calculatedHash === expectedSignature; } //新規追加 //メールの送信内容を定義する関数 function createMailTemplate (content){ var message = { 'personalizations': [ { 'to': [ {'email': 'test@uipath.com'} // 宛先を適宜に置き換えてください。複数宛ての場合、下に追記できる ] //'cc': [] などの追加もできる } ], from: { email: 'webhook@uipath.com' //送信元、任意のアドレスでもOK }, subject: 'Job Failed', //メールのタイトル content: [ { type: 'text/plain', value: content //メール本文 } ] }; return {message}; }

変更内容を保存してこれで完了しました。早速、先ほどと同じように、Orchestratorから失敗となるジョブを実行し、テストしてください。指定した宛先に失敗したジョブの内容が届くはずです。メールの本文は、WebhookのPayloadで、JSON形式のデータです。ここでは、送信内容を加工せず、メール送信していますが、本番環境では、必要な情報だけを取り出してメール送信することもできます。

終わりに

以上、全部100行も行かないぐらいのソースコードで、失敗したジョブをリアルタイムにメールで通知する仕組みを作れました。社内でサーバインフラも持つ必要がなく、Serverlessアーキテクチャの活用によって、低コストでジョブ監視を実現できます。

この記事では、メール連携を例にしていますが、HTTPクライアントを使って、Slack、Skypeなどのチャットツールとの連携も簡単に実現できます。

さらに、Serverlessアーキテクチャを利用してOrchestratorのAPIを実行することによって、失敗したジョブの再実行の自動化の仕組みも簡単に作れます。

どうしてもコードを書く負担を軽減したい場合は、ハッシュ値認証のみをAzure Functionにし、送信やほかのシステムとの連携を簡単に使えるAzure Logic Appを利用することもできます。

Binyang Liu
Binyang Liu

Senior Sales Engineer, UiPath