SecureXの承認ワークフローをチャットで実現する仕組みを考えてみた

この記事はシスコの有志による Cisco Systems Japan Advent Calendar 2022の一つとして投稿しています。

2022年版: https://qiita.com/advent-calendar/2022/cisco

以下、昨年までのものです。

2021年版(1枚目): https://qiita.com/advent-calendar/2021/cisco
2021年版(2枚目): https://qiita.com/advent-calendar/2021/cisco2
2020年版(1枚目): https://qiita.com/advent-calendar/2020/cisco
2020年版(2枚目): https://qiita.com/advent-calendar/2020/cisco2
2019年版: https://qiita.com/advent-calendar/2019/cisco
2018年版: https://qiita.com/advent-calendar/2018/cisco
2017年版: https://qiita.com/advent-calendar/2017/cisco

## やったこと

ワークフローを開始すると、承認を求めるメッセージがWebex Messagingに届き、`Wait For Event`のブロックでワークフローがいったん停止します。承認することでワークフローが再開し、後続する処理が実行されます。

![securex-approval.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176968/0dcb4176-b8d9-082d-0253-c77918bbee2f.gif)

## モチベーション

既存のサンプルだとSecureXのWebインターフェイス上で承認するものがあります。
承認者にいちいちSecureXにログインしてもらうのも何なんでチャットで承認できる方法を考えてみました。

https://github.com/CiscoSecurity/sxo-05-security-workflows/tree/Main/Workflows/Sample-Tasks-ApprovalTask__definition_workflow_01N3JZPFZE2BA67aIDznZOSPLHY5kK5sH8H

## ワークフローの全体像

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176968/5029f62f-8ed1-1459-aa4e-d350ddfe156f.png)

1. Create Approval Request
2. HTTP Request
3. Wait For Event
4. Completed

以下、SecureXのワークフローだけでなくWebexクラウドや外部Webサービスとの連携も加えて、順をおって解説します。

## 1. Create Approval Request

ワークフローを開始するとSecureX上に承認リクエストが作成されます。リクエスト毎にランダムなTask IDが生成されます。各サービス間で連携する際にこのTask IDを引き回すことで、どのリクエストへの承認もしくは拒否がなされたのかの判断できるようにします。

## 2と3で行われる処理の全体像

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176968/d2746985-0b8f-eaa4-2ac7-099f39ae8571.png)

① HTTPリクエストが呼ばれるとWebex API経由でWebex MessagingクライアントにAdaptive Cards形式のメッセージが送信されます。

② 承認者がWebex Messagingクライアント上で「承認する」もしくは「拒否する」のボタンを押すと、WebexクラウドからWebhookが呼ばれます。

③ 承認者によるアクションの詳細を得るためにWebhookの中のIDをみてWebex API経由でアクションの詳細を取得します。

④ SecureXのワークフローが承認待ちで停止状態なので、`Wait For Event`に紐づいているSecureX Webhookを呼びだし、ワークフローを再開させます。

⑤ 最後に、承認者をメンションして承認したのかもしくは拒否したのかのメッセージをスレッドに残します。

## Webexの前準備

Webex MessagingのAPIを通じて、チャットベースの承認リクエストを送ったり、承認者から承認もしくは拒否のアクションを得ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176968/0ca1f77a-72a3-43ed-c246-aaf9e0dd5168.png)

そのためWebex MessagingのBotとWebhookを作成しておきます。
WebhookはBotのWebex Tokenで作成します。

Webex MessagingにおけるBotとWebhookの作成には以下の記事をご参考にしてください。

https://qiita.com/tetsusat/items/6cd2234fecd5af9441c4

Webhookのパラメータは以下のとおりです。

name: Webhook for attachment actions（任意）
targetUrl: https://{my_app}.{my_subdomain}.workers.dev
resourace: attachmentActions
event: created

## 2. HTTP Request

主な設定項目は以下のとおりです。

### Target

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176968/6234f186-6cc5-7388-c281-2928250cb734.png)

#### Target

Webex Teams

### HTTP Request

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176968/cc5b2bae-f6ce-8fec-6610-60a9f108f68e.png)

#### Relative Url

/v1/messages

#### Method

POST

#### Request Body

`roomId`はWebexのルームIDで変数で渡してあげます。
どういったリクエストへの承認なのか分かるように`Create Approval Request`で生成されたTask IDを`data`に埋め込んでおきます。

```
{
  "roomId": "<webex-roomId>",
  "text": "Give me your approval",
  "attachments": [
    {
      "contentType": "application/vnd.microsoft.card.adaptive",
      "content": {
        "type": "AdaptiveCard",
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "version": "1.2",
        "body": [
          {
            "type": "Image",
            "url": "https://4.bp.blogspot.com/-gKPdnJWscyI/VCIkF3Po4DI/AAAAAAAAmjo/fAKkTMyf8hM/s150/monster01.png",
            "horizontalAlignment": "Center",
"width": "150px",
            "height": "150px"
          },
          {
            "type": "Container",
            "style": "emphasis",
            "items": [
              {
                "type": "TextBlock",
                "wrap": true,
                "text": "承認欲求モンスターが承認を求めています",
                "spacing": "None",
                "size": "Large",
                "weight": "Bolder",
                "color": "Accent",
                "isSubtle": false
              }
            ]
          },
          {
            "type": "ActionSet",
            "actions": [
              {
                "type": "Action.Submit",
                "title": "承認する",
                "id": "1",
                "data": {
                  "taskId": "<task-id>",
                  "action": "Approved"            
                }
              },
              {
                "type": "Action.Submit",
                "title": "拒否する",
                "id": "2",
                "data": {
                  "taskId": "<task-id>",
                  "action": "Rejected"
                }
              }
            ]
          }
        ]
      }
    }
  ]
}
```

### Headers

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176968/b1456451-efac-2afb-5d3a-768f47972dd7.png)

#### Content Type

Application JSON

#### Custom headers

HEADER: Authorization
VALUE: Bearer \<webex-bot-token\>

Webex MessagingでAdaptive Cards形式のメッセージを送ったり、カードへのアクションを取得する方法については、以下のサイトを参考にしてください。

**[Webex公式] Buttons and Cards**
https://developer.webex.com/docs/api/guides/cards

## WebexからのWebhookを受けるWebサービスの作成

Adaptive Cards形式のメッセージにボタンを押した際にWebhookが呼ばれます。
そのWebhookを受けるのに今回はCloudflare Workersを使いました。
インターネット上にあってWebex Cloudから到達性があれば、AWS Lambdaとかで用意しても構いません。

Cloudflare Workersの使い方は以下の投稿を参照ください。

https://qiita.com/tetsusat/items/732f0e059af133465b34

Cloudflare Workersで動作させるアプリケーションの雛型を作成します。

```
$ wrangler generate securex-approval
```

```
$ cd securex-approval
```

Webhookが呼ばれたときの処理を`index.js`に編集します。

Webexクラウドから呼び出されるWebhookのリクエストBodyからIDを取得して、Webex API経由でアクションの詳細を取得します。
Task IDと承認もしくは拒否が分かりますので、以下のようにその内容を埋めて、今度はSecureXのWebhookを呼び出します。

```
{
    "taskId": "021RPLBIJIKPO1mfxQsiRySzeViIdnstv6p",
    "action":"Approved"
} 
```

最後に、承認者をメンションして承認したのかもしくは拒否したのかをスレッドに残します。

```index.js
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})
/**
 * Respond with hello worker text
 * @param {Request} request
 */
async function handleRequest(request) {
  const body = await request.json();
  const resp = await fetch(`https://webexapis.com/v1/attachment/actions/${body.data.id}`, {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${WEBEX_TOKEN}`
    }
  })
  const data = await resp.json()
  console.log(data)

  // call securex webhook

  await fetch(SECUREX_WEBHOOK, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ taskId: data.inputs.taskId, action: data.inputs.action }),
  })

  await fetch('https://webexapis.com/v1/messages/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${WEBEX_TOKEN}`
    },
    body: JSON.stringify({ parentId: data.messageId ,roomId: data.roomId, markdown: `<@personId:${body.data.personId}> You ${data.inputs.action}!` }),
  })

  return new Response('{}', {
    headers: { 'content-type': 'application/json' },
  })
}
```

Webex Bot Tokenをシークレット変数として登録します。

```
$ wrangler secret put WEBEX_TOKEN
```

SecureX Webhookをシークレット変数として登録します。

```
$ wrangler secret put SECUREX_WEBHOOK
```

アプリケーションを公開します。

```
$ wrangler publish
```

## SecureX Webhookの作成

SecureXオーケストレーションのトップ画面に戻り、左ナビの`Events & Webhooks`から`Webhook`タブに遷移して、`New Webhook`ボタンをクリックします。

下のようなWebhookの作成画面がでてきたら、`Display Name`を入力して、右下の`Submit`ボタンをクリックします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176968/e7b11796-269b-41f5-9cc7-cd65eaf5ba90.png)


## 3. Wait For Event

主な設定項目は以下のとおりです。

### Event Info

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176968/e02d1bf6-9b05-9cea-f802-d1aa855d3e08.png)

#### Event Type

直前に作成したWebhookを指定

#### Conditions

Cloudflare Workers上のWebサービスからのSecureXのWebhookへのリクエストBodyにTask IDと承認（Approved）もしくは拒否（Rejected）の情報が含まれるので、Task IDと承認（Approved）の文字列が正規表現マッチすることを条件にします。

## 4. Completed

本来であれば、承認後になんらかの処理を実行するはずですが、今回の例では何もせずにワークフローを終了させてます。

## 課題

~~面倒くさいので~~ 過度に複雑な実装にするより分かりやすさを優先して以下の点は妥協しました。

- Create Approal Taskを使ったため、SecureX上にもタスクができてしまいます。仮にSecureX上で承認してもワークフローは再開しません。
- 承認リクエストがタイムアウトした後もチャットから承認できてしまいます。この場合もワークフローは再開しません。

- いったん拒否した後に承認してもワークフローが再開します。

おしまい