AWS GuardDutyアラートのSlack連携を強化！ワークフロー＋メンションでインシデント対応を自動化しよう

# 1. はじめに
この記事は DENSO Advent Calendar 2024 の21日目の記事です。

今年のAdvent Calendarでは、初めてQiita記事執筆に挑戦します。拙い部分もあるかもしれませんが、同じく初心者の方の参考になれば幸いです。

AWS GuardDutyで検知した異常をEventBridge経由で通知し、Slack上で担当者にメンションを飛ばしたい――そんな要望に応える仕組みを導入しました。
カスタムレスポンスの試行などの紆余曲折を経て、最終的にはSlackワークフローを使うことで運用負荷を最小限に抑えました。
本記事では、その試行錯誤と得られたナレッジを紹介します。

# 2. 既存構成と課題
現在、**EventBridge**で**CloudWatch Logs**からのイベントをキャッチし、そのまま**Slack**へメッセージを投稿する仕組みを導入しています。実際にGuardDutyのアラートやシステムのエラーログなどを検知して通知できてはいるのですが、以下の課題が浮上してきました。

1. **特定の個人がメンションされていないため、見逃しリスクがある**
    開発メンバーのチャットルームには投げられるものの、担当者が定まっていないと通知が埋もれてしまうことがあります。
1. **セキュリティインシデントの可能性があるため、開発メンバー以外にも周知したい**
現在は **プロダクト名-alert-prod** や **プロダクト名-alert-dev** というアラート専用のSlackチャンネルに通知が飛ぶ仕組みです。ただし、このチャンネルは主に開発チームが監視しているため、上長やセキュリティ担当者など、開発以外のメンバーが情報を追っていないケースが多いのが実情です。その結果、インシデントの可能性がある情報が流れていても、周知が遅れてしまうリスクがあります。

こうした背景から、**誰に通知を飛ばすかを柔軟に指定**でき、**セキュリティインシデントの疑いを多方面へ同時に伝えられる**仕組みの必要性が高まっていました。

# 3. メンション実装への試行錯誤
特定のメンバーや上長、セキュリティ担当者に即座にインシデントを知らせるためには、**メンション**が不可欠です。まずはSlackの**カスタムレスポンス機能**を使ってみようと考えました。カスタムレスポンスとは、あらかじめ設定したキーワードに反応してSlackが特定のメッセージを返す機能ですが、同じキーワードが日常の会話や他の通知文に含まれていると誤発火してしまうリスクがあります。

そこで、**EventBridge**で起動するLambdaのコードに**ハッシュ値**を仕込んで、そのハッシュをメッセージ内に埋め込み、カスタムレスポンスは「特定のハッシュ値にだけ反応する」という形にしようとしました。たとえば、LambdaがSlackに投げるメッセージ末尾に `#abc123def456` のようなユニークな文字列を付与し、その文字列が含まれていればカスタムレスポンスが動く――というイメージです。

しかし、いざ実装してみると、**Botとして投稿されたメッセージに対してはカスタムレスポンスがうまく反応しない**ことが判明しました。検証を進めても、「Botアカウントの投稿ではカスタムレスポンス機能が効かない可能性が高い」という情報しか得られず、実際に動作もしません。最終的に、このアプローチではメンションの自動化が実現できず、別の方法を模索せざるを得なくなったのです。

[公式ドキュメント](https://slack.com/intl/ja-jp/resources/using-slack/a-guide-to-slackbot-custom-responses)を確認してみると、カスタムレスポンスはあくまで“Slackbot”による簡易的な返信機能を想定しており、Botユーザーが投稿したメッセージを拾って複雑な処理を行うようなケースは、そもそも想定されていないように思われました。今回はカスタムレスポンスの範囲外と判断し、別のアプローチに切り替えています。

# 4. Slackワークフローを活用した解決策
## 4-1. なぜSlackワークフローを使うことにしたのか

次の候補として浮上したのが、**Slackワークフロー**です。

- **ワークフロー機能の概要**  
  - Slack上でノーコード／ローコード的に自動化のフローを組める機能。  
  - 「Webhookでデータを受け取る」「フォーム入力をする」「メッセージを投稿する」などを複数ステップでまとめられる。  

従来のカスタムレスポンスより柔軟な点が多く、**Botユーザーが投稿してもトリガーできる**ところや、**メンションできる**点が大きなメリットでした。

## 4-2.ワークフロー活用で得られたメリット

1. **メンション問題の解決**  
   - Bot投稿でもワークフローが動くため、開発者だけでなく上長やセキュリティ担当者も確実にメンションできる。  

2. **対処手順をワンストップで案内**  
   - インシデント通知と同時に、「確認すべきログ」「実施すべき作業」などを**フローのステップごとに指示**できる。  
     - 例：「1. CloudWatch Logsを確認」「2. セキュリティチームへ状況を報告」「3. 対処結果をSlackフォームに記入」といった一連の行動を順番にガイド可能。  
   - カスタムレスポンスでは別途メッセージが飛ぶため、ログ取りやコメントが通知に反映されない課題があったが、ワークフローなら**同じ流れの中で完結**。  

3. **作業の抜け漏れを防止**  
   - フロー設計を工夫すれば、インシデントが発生してから解決するまでに必要なステップを漏れなく実行できる。  
   - 誰が何を実施したかログを取りやすく、後から検証・レビューする際にも役立つ。  

4. **対応状況の可視化と進捗確認**  
   - Slackワークフローのステップで、**「誰がいつ反応したか」**を記録することが可能。  
   - 一定時間経過したタイミングで「対応は進んでいますか？」と**自動的に確認するステップ**を挟めば、対応漏れを早期に発見できる。  
   - ただし、**Slackワークフローでは 条件分岐には対応していないため**、ステップの組み方を工夫しながら「対応未完了であれば次のステップを促す」などのフロー設計を行う必要がある。

## 4-3. 実現方法

### Lambdaの連携（コード例）

Slackワークフローを活用するにあたり、EventBridgeからのGuardDutyアラートをトリガーにLambdaを起動し、Slackにメッセージを送信する仕組みを採用しました。実際のLambdaコード例を以下に示します（部分的に汎用的な値に置き換えています）。

```
from __future__ import print_function

import json
import os
import logging
import urllib.request
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

# 環境変数（本番運用時はLambdaの設定で実際の値を登録）
SLACK_CHANNEL = os.environ['YOUR_SLACK_CHANNEL']
WEBHOOK_URL_FOR_GUARDDUTY = os.environ['YOUR_WEBHOOK_URL']

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    # GuardDutyのイベントログを受け取り、詳細をログ出力（実際には機微情報に注意）
    logger.info("GuardDuty event is %s", event)

    # Slackに送信するメッセージ本体を作成
    text_creater = TextCreater(event)
    slack_text = text_creater.create_slack_text()

    # Webhookに対してPOSTリクエストを送る
    req = Request(WEBHOOK_URL_FOR_GUARDDUTY, json.dumps(slack_text).encode('utf-8'))
    try:
        response = urlopen(req)
        response.read()
        logger.info("Message posted to %s", SLACK_CHANNEL)
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

class TextCreater:
    def __init__(self, event):
        self.event = event

    def create_slack_text(self):
        self.create_text()
        return self.slack_text

    def create_text(self):
        # GuardDutyのFinding情報をSlackメッセージ用に整形
        self.slack_text = {
            'account_id': self.event['detail']['accountId'],
            'region': self.event['detail']['region'],
            'severity': self.event['detail']['severity'],
            'type': self.event['detail']['type'],
            'description': self.event['detail']['description']
        }
```

- **ポイント**  
  - LambdaでGuardDutyのFinding情報を整形し、ワークフローに必要なパラメータを揃える。  
  - Webhook URLはSlackワークフロー作成時に発行されるものを設定。  
  - 連携確認のため、Lambda側でSlackのレスポンスをログに出力するとデバッグしやすい。

### ワークフロー設定

次に、実際のワークフローのステップ例です。

- **Webhookを受信するトリガーを追加**  
   - 選択肢の欄から「Webhookを使って開始する」を選択し、先ほどのLambdaから受け取るJSONを変数として扱います。具体的には、以下画像のように、変数を取得します。
   - また、ここで発行される**ウェブリクエストの URL** を AWSに登録しましょう！
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957868/782c06a9-aaa8-26f8-83d0-8ecc936bcd52.png" width="300">
   
- **アラート通知投稿ステップ**  
   - 次に、前ステップで受け取ったFinding情報を組み込んでメッセージを送信。 
       - 選択肢の欄から「チャンネルへメッセージを送信する」を選択しましょう。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957868/25fa6113-5d84-2304-146e-1f7f31be1c43.png" width="300">

- **特定のユーザーのメンションおよび追加のフローを追加**  
    - ワークフローの「メッセージを送信」ステップで、**開発メンバー**や**上長**をメンションし、それぞれの役割に応じた行動を促せます。
        - 選択肢の欄から「スレッドでメッセージにを返信する」を選択しましょう。スレッドで対象者をメンションすることで、1連の流れを1つのスレッドでまとめることが出来るのでおすすめです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957868/02dde0b5-4d03-771c-89bf-25d8c708479f.png" width="300">

> **ポイント**  
> - `<@開発メンバー>` の部分に開発担当者のユーザーIDを直接書くか、ワークフローの変数としてユーザーを選択してもらうことが可能です。  
> - `<@上長>` も同様に上長・チームリーダーのIDを記載。実際には正しいSlackユーザーIDを設定してください。  

- **追加のフロー（次のステップ）**

さらに、スレッド内で追加のフローを**分割**して設定することで、ユーザーごとのタスクをワークフローでガイドできます。以下は一例です。

1. **開発メンバー用ステップ**  
   - 「調査完了報告フォーム」や「対応履歴の登録フォーム」をワークフロー内で用意し、開発メンバーがスレッド内でフォームを開いて記入する。  
   - 記入内容（調査結果や原因）を次のステップに引き継ぐ。

2. **上長用ステップ**  
   - 開発メンバーの入力を参照し、「インシデント重大度が高い場合はセキュリティ担当へ報告する」といったボタンやメッセージを出す。  
   - ボタンを押すと「セキュリティチームへの連絡フォーム」へ遷移する、などのフローを追加。

このように**メンション**と**追加のステップ**を組み合わせると、ワークフロー内で「誰が」「いつ」「どのように」行動すべきかが整理しやすくなります。単なる通知で終わらず、**チーム全体が一連のフローを見渡しながら対応**を進められるのが大きな利点です。

### 注意点
#### 1. Lambdaのテストを活用する

Slackワークフローと連携させるためのLambdaコードが、想定どおりに動作しているかを**本番前に検証**しましょう。  
AWS Lambdaの「テスト」機能を利用すれば、擬似的にGuardDutyのイベントを入力データとして与え、**デバッグやログ確認**を行いやすくなります。

**テスト用Eventの例**  
Lambda管理画面で「テスト」→「イベントの設定」を行い、以下のようなJSONを入力すると、GuardDutyが検知したアラートを模擬できます。

```json
{
  "detail": {
    "accountId": "1234567890",
    "region": "ap-northeast-1",
    "severity": 5.0,
    "type": "S3/MaliciousIPCaller.Custom",
    "description": "This is a sample GuardDuty finding for testing."
  }
}
```

- **テストを実行**すると、Lambdaのログ（CloudWatch Logs）に実行結果が出力されます。  
- Slackワークフロー用のWebhookへ正しくPOSTできれば、ワークフロー側がトリガーされるはずです。  
- もしエラーが発生した場合は、`logger.error(...)` のログ内容を参考に、Webhook URLの設定ミスやJSONフォーマットエラーなどを確認してください。  

#### 2. 公開時のコード取り扱い

- **Webhook URL** などの機密情報は、必ず**Lambdaの環境変数**やSSM Parameter Storeなどから取得し、**Git管理や記事には直書きしない**ようにしましょう。  
- GuardDutyイベントのログ内容にはIPアドレスやAWSアカウントIDなどの**機微情報**が含まれる可能性があります。QiitaやGitHubなどにログを貼る際は、マスク処理や必要最低限の範囲に留めることをおすすめします。  

#### 3. ワークフロー側の設定と連携確認

- Lambdaが正しく動いていることを確認したら、**ワークフロー側で「Webhook受信→メッセージ投稿」などのステップが問題なく動くか**テストしましょう。  
- 送ったデータが正しく変数に反映されない場合は、ワークフロー内の**変数名**や**JSONキー**が一致しているかを再度チェックしてください。

このように**Lambdaのテスト**をしっかり行うことで、ワークフローやSlack通知との連携がスムーズに運用できます。

## 5. おわりに

長文になってしまい申し訳ありませんが、最後までお読みいただきありがとうございます。
本記事が今後のセキュリティ対応や運用改善のお役に立てれば嬉しいです。
筆者自身も、ワークフローをさらに精査して、インシデント対応の開始から終了までをワークフロー上で完結できるように改良していきたいと考えています。