LoginSignup
15
0

More than 1 year has passed since last update.

Cisco SecureX Orchestrationを使ってWebexBOTのWebhookを受けてみる

Last updated at Posted at 2022-12-13

はじめに

この記事はシスコの有志による Cisco Systems Japan Advent Calendar 2022 (1枚目) の 14日目として投稿しています。
2022年版 1/2枚目

過去のカレンダー
2017年版 2018年版 2019年版 2020年版1枚目 2020年版2枚目 2021年版1枚目 2021年版2枚目


SecureX Orchestrationについては、以下の記事にて紹介されていますので、これらを参照ください。
無料のクラウドPython環境としてのSecureX
Cisco SecureX を使って簡易 REST API モジュールを作る

サンプルのWorkflowがやってくれること

サンプルのWorkflowは、Webexに投稿されたBot宛のメッセージを処理するための枠組みを提供しています。
Pythonで処理をさせているので、その中身を拡張することで、コマンドに応じて何らかの処理をさせて、その結果をWebexにPostするというようなアプリが作成できます。
例えば、SecureXで連携しているセキュリティソリューションのステータスを確認したり、何らかの設定変更をする手段を提供する、というようなBOTアプリへの応用が考えられます。

サンプルの動作イメージ

  • Webex
    image.png
  • データフロー
    image.png
  • SecureX内の処理フロー

SecureXでWorkflowを作成する

Step1: Webhookの作成

SecureX Orchestrationで、Webhookの受け口となるエントリを作成します。
image.png

Step2: Webexの準備

1. BOTの作成
以下のページの"Create a New App"から新規にBOTを作成し、Access Tokenxxx@webex.bot形式のUsernameを記録します。
https://developer.webex.com/my-apps

Access Tokenは再表示することができず、わからなくなると再生成となってしまうので、必ず何らかの方法で記録を残しましょう

2. BOTをメンバーに加えたWebexのスペースを作成
WebexのアプリケーションでBOTがメンバーとして含まれるスペースを作成します。

3. Room IDを調べる
Webex APIを使って、作成したスペースのRoom IDを調べます。
https://developer.webex.com/docs/api/v1/rooms/list-rooms

sortBy: createdとすると、新しい順にソートされますので、2で作ったスペースのRoom IDを記録します。

4. BOTのPerson IDを調べる
Webex APIを使って、作成したBOTのPerson IDを調べます。
https://developer.webex.com/docs/api/v1/people/list-people

email: BOTのUsernameでフィルタし、BOTのidを記録します。
(1のBOTの管理画面で確認できるIDと、ここで調べたPerson IDは異なるものです)

5. WebexのWebhookを作成
Webex APIを使って、WebexのWebhookを作成します。
https://developer.webex.com/docs/api/v1/webhooks/create-a-webhook

パラメータ 内容
name 任意の名前
targetUrl Step1で作成したSecureXのWebhook URL
resource messages
event created
filter roomId=<3のRoomID> &mentionedPeople=<4のBotのID>

これらのパラメータを設定すると、BOT宛のメンション付きメッセージが投稿された場合に、SecureXに対してWebhookを通じてイベントの発生を通知するようになります。

今回の例ではsecretは設定しておらず、SecureX側でもメッセージの検証は実施していません。
セキュリティレベルを高めるためには、secretを設定してメッセージに対する署名の検証を実施することを推奨します。
Authenticating Requests

Step3: SecureX workflowのインポート

  • Workflowをインポート
    image.png

  • サンプルのJSONをペーストするか、ファイルにして読み込む
    image.png

Workflow JSON

サンプルJSON (クリックして展開してください)
{
  "workflow": {
    "unique_name": "definition_workflow_021FSI6HRJKRJ2Z8DzviBz0NCS74TcZ3Hhn",
    "name": "Webex Webhook Receiver",
    "title": "Webex Webhook Receiver",
    "type": "generic.workflow",
    "base_type": "workflow",
    "variables": null,
    "properties": {
      "atomic": {
        "is_atomic": false
      },
      "delete_workflow_instance": false,
      "display_name": "Webex Webhook Receiver",
      "runtime_user": {
        "target_default": true
      },
      "target": {
        "target_type": "web-service.endpoint",
        "target_id": "definition_target_01QBNQMLV0JZ60DEPeaVtzSjfEvrJE3QFeh",
        "execute_on_workflow_target": true
      }
    },
    "object_type": "definition_workflow",
    "actions": [
      {
        "unique_name": "definition_activity_021FSI6NZ4XKZ4VFFRGHvZHDtKYWfsfZkrP",
        "name": "JSONPath Query",
        "title": "Get message/room IDs",
        "type": "corejava.jsonpathquery",
        "base_type": "activity",
        "properties": {
          "action_timeout": 180,
          "continue_on_failure": false,
          "description": "Get messageId from body of webhook",
          "display_name": "Get message/room IDs",
          "input_json": "$trigger.triggerevent_021FSI6N0ECFR40XDW449RAQnvNIUM0U9rW.output.request_body$",
          "jsonpath_queries": [
            {
              "jsonpath_query": "$.data.id",
              "jsonpath_query_name": "msgId",
              "jsonpath_query_type": "string",
              "zdate_type_format": "yyyy-MM-dd'T'HH:mm:ssZ"
            },
            {
              "jsonpath_query": "$.data.roomId",
              "jsonpath_query_name": "roomId",
              "jsonpath_query_type": "string",
              "zdate_type_format": "yyyy-MM-dd'T'HH:mm:ssZ"
            }
          ],
          "skip_execution": false
        },
        "object_type": "definition_activity"
      },
      {
        "unique_name": "definition_activity_021FSI6P901D10XSWFKvlKiqdKUH5fNWRpy",
        "name": "HTTP Request",
        "title": "Webex - Get Message",
        "type": "web-service.http_request",
        "base_type": "activity",
        "properties": {
          "action_timeout": 180,
          "allow_auto_redirect": true,
          "allow_headers_redirect": false,
          "content_type": "application/json",
          "continue_on_error_status_code": false,
          "continue_on_failure": false,
          "custom_headers": [
            {
              "name": "Authorization",
              "value": "Bearer $env.variable_021FSRR44GSHJ25067E6CfbyKEzPDoxbbVz.env.variable_021FSRR44GSHJ25067E6CfbyKEzPDoxbbVz$"
            }
          ],
          "description": "Get message content based on messageId which was informed on the body of webhook",
          "display_name": "Webex - Get Message",
          "method": "GET",
          "relative_url": "/v1/messages/$activity.definition_activity_021FSI6NZ4XKZ4VFFRGHvZHDtKYWfsfZkrP.output.jsonpath_queries.msgId$",
          "runtime_user": {
            "target_default": true
          },
          "skip_execution": false,
          "target": {
            "use_workflow_target": true
          }
        },
        "object_type": "definition_activity"
      },
      {
        "unique_name": "definition_activity_021FSI6QU5ZOU1iNjO0616wCUNBw0fafahb",
        "name": "Execute Python Script",
        "title": "Parse command string",
        "type": "python3.script",
        "base_type": "activity",
        "properties": {
          "action_timeout": 180,
          "continue_on_failure": false,
          "display_name": "Parse command string",
          "script": "import sys, json, re\n\nstrHtml = json.loads(sys.argv[1])['html']\nm = re.search('<\\/spark-mention>\\s*(.*)\\s*<\\/p>',strHtml)\nstrCmd = m.group(1)\n\nstrWebexMsg = strCmd",
          "script_arguments": [
            "$activity.definition_activity_021FSI6P901D10XSWFKvlKiqdKUH5fNWRpy.output.response_body$"
          ],
          "script_queries": [
            {
              "script_query": "strWebexMsg",
              "script_query_name": "strWebexMsg",
              "script_query_type": "string"
            }
          ],
          "skip_execution": false
        },
        "object_type": "definition_activity"
      },
      {
        "unique_name": "definition_activity_021FSSTV4PSJB09Q70ZYcJSUY88MDvlFBwh",
        "name": "Webex - Post Message to Room",
        "title": "Webex - Post Message to Room",
        "type": "workflow.atomic_workflow",
        "base_type": "subworkflow",
        "properties": {
          "continue_on_failure": false,
          "display_name": "Webex - Post Message to Room",
          "input": {
            "variable_workflow_01PP78DJH1TI76BYfsu9g0Tqj2S6cUxjtu5": "$activity.definition_activity_021FSI6NZ4XKZ4VFFRGHvZHDtKYWfsfZkrP.output.jsonpath_queries.roomId$",
            "variable_workflow_01PP78DJH1XNQ7gNQ5iZdperRHqrppzARXC": "$env.variable_021FSRR44GSHJ25067E6CfbyKEzPDoxbbVz.env.variable_021FSRR44GSHJ25067E6CfbyKEzPDoxbbVz$",
            "variable_workflow_01PP78DJH1YWL3allalGQbg1VkgKwh9GvCi": "",
            "variable_workflow_01PP78DJH22BB3Ej3I8tJ4OCQur0unYGjj9": "$activity.definition_activity_021FSI6QU5ZOU1iNjO0616wCUNBw0fafahb.output.script_queries.strWebexMsg$",
            "variable_workflow_01SVERQNMKN8N6vqX2djMtAfshphGydGsH8": ""
          },
          "runtime_user": {
            "target_default": true
          },
          "skip_execution": false,
          "target": {
            "target_type": "web-service.endpoint",
            "use_workflow_target": true
          },
          "workflow_id": "definition_workflow_01PP78DJMXS415nTjonujf03ROkr6t2PNyw",
          "workflow_name": "Webex - Post Message to Room"
        },
        "object_type": "definition_activity"
      }
    ],
    "categories": [
      "category_1BMfMXSnJMyt5Ihqi7rWJr5N8cf"
    ]
  },
  "triggers": {
    "triggerevent_021FSI6N0ECFR40XDW449RAQnvNIUM0U9rW": {
      "workflow_id": "definition_workflow_021FSI6HRJKRJ2Z8DzviBz0NCS74TcZ3Hhn",
      "name": "webex message created",
      "title": "",
      "lowercase_name": "event.webex_message_created",
      "type": "event",
      "base_type": "trigger",
      "ref_id": "event_webhook_021C5Q1RM9I7I05ETt1iabQLBUwfAVsYHq9",
      "version": "1.0.0",
      "disabled": false,
      "unique_name": "triggerevent_021FSI6N0ECFR40XDW449RAQnvNIUM0U9rW",
      "object_type": "triggerevent"
    }
  },
  "events": {
    "event_webhook_021C5Q1RM9I7I05ETt1iabQLBUwfAVsYHq9": {
      "name": "Webhook event - Webex BOT",
      "title": "Webhook event - Webex BOT",
      "type": "webhook.event",
      "base_type": "event",
      "object_type": "event_webhook",
      "target_id": "",
      "webhook_id": "webhook_021GC3O4FYSVO00JC8U4MGWwYR8lXllFODJ",
      "version": "1.0.0",
      "properties": {
        "display_name": "Webex BOT",
        "title": "Webhook event - Webex BOT",
        "webhook_id": "webhook_021GC3O4FYSVO00JC8U4MGWwYR8lXllFODJ"
      },
      "unique_name": "event_webhook_021C5Q1RM9I7I05ETt1iabQLBUwfAVsYHq9"
    }
  },
  "targets": {
    "definition_target_01QBNQMLV0JZ60DEPeaVtzSjfEvrJE3QFeh": {
      "unique_name": "definition_target_01QBNQMLV0JZ60DEPeaVtzSjfEvrJE3QFeh",
      "name": "Webex Teams",
      "title": "Webex Teams",
      "type": "web-service.endpoint",
      "base_type": "target",
      "object_type": "definition_target",
      "properties": {
        "description": "Webex Teams",
        "disable_certificate_validation": false,
        "display_name": "Webex Teams",
        "host": "webexapis.com",
        "no_runtime_user": true,
        "port": 443,
        "protocol": "https"
      }
    }
  },
  "variables": {
    "variable_021FSRR44GSHJ25067E6CfbyKEzPDoxbbVz": {
      "unique_name": "variable_021FSRR44GSHJ25067E6CfbyKEzPDoxbbVz",
      "properties": {
        "value": "*****",
        "scope": "env",
        "name": "WebexBOT-Token",
        "type": "datatype.secure_string",
        "is_required": false,
        "is_invisible": false
      },
      "object_type": "variable"
    }
  },
  "atomic_workflows": [
    "definition_workflow_01PP78DJMXS415nTjonujf03ROkr6t2PNyw"
  ],
  "webhooks": {
    "webhook_021GC3O4FYSVO00JC8U4MGWwYR8lXllFODJ": {
      "name": "WebexBOT - post message",
      "title": "WebexBOT - post message",
      "type": "generic.webhook",
      "base_type": "webhook",
      "object_type": "webhook",
      "version": "1.0.0",
      "properties": {
        "display_name": "WebexBOT - post message",
        "request_content_type": "application/json; charset=utf-8"
      },
      "unique_name": "webhook_021GC3O4FYSVO00JC8U4MGWwYR8lXllFODJ"
    }
  },
  "dependent_workflows": [
    "definition_workflow_01PP78DJMXS415nTjonujf03ROkr6t2PNyw"
  ]
}
  • Secure Stringの環境変数、"WebexBOT-Token"の更新を求められるので、内容を更新
    image.png

  • インポートされたWorkflowをクリックして開くと、Variablesが有効化されます
    image.png

Step4: SecureX webhook eventの変更

インポートされたイベントにはStep1で作成したWebhookが関連づいていないため、内容を変更します。
image.png

Workflow 各ステップの処理内容概説

image.png

  1. CORE (JSONPath Query): Get message/room IDs
    WebexのWebhookが送信してきたJSONから、イベントのもととなったメッセージ、ルームのIDを抽出。
    階層としては'data' > 'id', 'data' > 'roomId'が該当するため、JSONPathの記述としては'\$.data.id', '\$.data.roomId'の表記になる。(https://github.com/json-path/JsonPath)
  2. WEB SERVICE (HTTP Request): Webex – Get Message
    1で取得したMessageIdの詳細情報をWebexAPIで取得する。
    ATOMIC actionの定義がないため、HTTP Requestで実行。アクセス先のホストの定義は、Workflow全体のプロパティにあるTargetで"Webex Teams" (Default定義のTarget) を指定。
  3. PYTHON: Parse command string
    2で取得したメッセージの詳細情報にあるHTMLコンテンツから、コマンド部分を抽出。
    "SCRIPT OUTPUTVARIABLES"として定義した"strWebexMsg"に抽出した文字列をセットする。このPythonの内容を改変することで、コマンドの内容に従った動作をさせる事ができる。
  4. ATOMIC: Webex – Post Message to Room
    3で生成したメッセージを、BOTを投稿者として、イベントのもととなったWebexのRoomへ投稿する。Room IDは1で抽出したもの。

テスト

ここまでで、SecureX Orchestration、Webexの設定作業は完了です。
最初に紹介した動作イメージのように、作成したスペースで、BOT宛のメンション付きメッセージを送ってみましょう。
image.png

SecureXにおけるWebhookの制限事項

本記事の作成時点で、以下のような制限事項があります。

  • WebhookのPayloadサイズは1MBまで
  • レートリミット
    • 5回/1分 (イベント単位でカウント)
    • 5000回/1日 (外部イベントをトリガとするすべてのイベントの合算)

このような制限が設けられているため、特にイベント数が多くなるような環境では利用に当あたり注意が必要です。

免責事項

本サイトおよび対応するコメントにおいて表明される意見は、投稿者本人の個人的意見であり、シスコの意見ではありません。本サイトの内容は、情報の提供のみを目的として掲載されており、シスコや他の関係者による推奨や表明を目的としたものではありません。各利用者は、本Webサイトへの掲載により、投稿、リンクその他の方法でアップロードした全ての情報の内容に対して全責任を負い、本Web サイトの利用に関するあらゆる責任からシスコを免責することに同意したものとします。

15
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
0