Dify(Bedrock) + StepFunctionでGuardDutyからの監視通知を要約してSlackに投げるノーコードツールを作ってみよう

technologies

  • HOME
  • BLOG
  • technologies
  • Dify(Bedrock) + StepFunctionでGuardDutyからの監視通知を要約してSlackに投げるノーコードツールを作ってみよう

おはようございます!ベンジャミンの木村です!

今回、Dify(Bedock) + StepFunctionを使って下記のようなGuardDutyからの監視通知を要約して、通知内容から、「考えられる影響」や、「必要なアクション」を通知してくれるようなノーコードツールを作りましたので、その作り方を紹介したいと思います。

何も施していないGuardDuty → EventBridge → SNSから飛んでくる通知文

これだと、発生した事象しかわかりません…

今回Bedrockによって要約した通知文

Bedrockで監視通知を要約することで、発生した事象に対する調査工数の削減につながります。

目次

  • 構成図
  • Difyとは?
  • ノーコードAIツールを作ってみよう!
    • 1. Difyの設定(Bedrockとの連携)
    • 2. Difyの設定(Start~Answer)
    • 3. Difyの設定(Slack連携 / アプリのデプロイ)
    • 4. StepFunctionの設定
    • 5. GuardDuty / EventBridgeの設定
  • まとめ

構成図

Difyとは?

DifyはAIツールをノーコードで簡単に作るための開発プラットフォームです。RAGや、エージェントから複雑なAIワークフローまでLLMアプリの開発をサポートするだけでなく、プラグインを通じて外部のサービス(GoogleスプレッドシートやGitHubなど)とのAI連携も簡単にすることができます。

下記はDify上でプロンプトを設定、Bedrockとの連携をして、それに合わせて回答するAIチャットの例です。このコードで一から実装すると時間がかかることは、生成AIを使った開発を普段からしている人は理解できるのではないでしょうか…

さらに今回はワークフローを用いて、複雑な処理も簡単に実装できる方法を見ていきたいと思います。

ノーコードAIツールを作ってみよう!

1. Difyの設定(Bedrockとの連携)

1-1. AWS IAMアクセスキーを取得する

AWSアカウントにログインして、IAMアクセスキーを取得してください。

こちらは、下記記事が大変わかりやすかったのでご参考にしていただければと思います。

権限はAmazonBedrockFullAccessで作成していただければ大丈夫です。

[初心者向け] IAMユーザーにアクセスキーを払い出す手順を確認してみた

https://dev.classmethod.jp/articles/iam_user_create_access_keys_2023/

1-2. Difyアカウントの登録

こちらは、下記記事が大変わかりやすかったのでご参考にしていただければと思います。

ノーコードツール「Dify」の登録方法を分かりやすく解説します!

https://note.com/nocode_dev/n/ne03de4beb66b

1-3. DifyとBedrockの連携

1-3-1. Difyアカウントにログイン後、右上の「アイコン」→「設定」をクリックする

1-3-2. 左ペインの「モバイルプロバイダー」→ Amazon Bedrockの「インストール」をクリックする

1-3-3. 「インストール」をクリックする

1-3-4. Amazon Bedrockの「セットアップ」をクリックする

1-3-5. 下記のように値を記載して、「保存」をクリックする

  • Access Key1-1で取得したアクセスキーを設定する
  • Secret Access Key1-1で取得したシークレットキーを設定する
  • AWS Region:US West (Oregon)
  • Bedrock Endpoint URL:bedrock.us-west-2.amazonaws.com
  • Available Model Name:us.anthropic.claude-3-5-haiku-20241022-v1:0
  • Bedrock Proxy URL:記載不要

Available Model Nameの記載ですが、下記の通りBedrockの「Cross-region inference」に「Inference profile ID」を参照していただければ、他のモデルも指定可能です。

1-3-6. Amazon BedrockのAPI-KEYの色が緑に変わっていたら完了

1-4. Bedrockのモデルアクセスの設定

Bedrock側でモデルへのアクセス許可を出す必要があります。下記ブログ記事が大変わかりやすかったので、参照してください。

Amazon Bedrockを試してみた

https://qiita.com/zumax/items/9b0f9e0ed1b57474a72f

2. Difyの設定(ワークフローの設定)

2-1. ワークフローの作成

2-1-1. 「スタジオ」→「最初から作成」をクリックする

2-1-2. 「チャットフロー」を選択 → 任意のアプリ名を入力 → 「作成する」ボタンをクリックする

2-2. 「開始フィールド」の設定

開始フィールドに設定する項目は、Difyにリクエストが飛んできた時、そのリクエスト値を入れる変数を用意します。今回はStepfunctionから、下記のようなリクエストが来る想定で値を入力します。

{
  "inputs": {
    "resource.$": "States.JsonToString($.detail.resource)",
    "severity.$": "$.detail.severity",
    "time.$": "$.time",
    "source.$": "$.source",
    "account.$": "$.account"
  },
  "query.$": "$.detail.description",
  "response_mode": "streaming",
  "user.$": "$.account"
}

※用意する変数は上記リクエストのinputsの中の項目だけです。queryuserはデフォルトで値が入っています。

2-2-1. 下記画像の通り、開始フィールドの「+」ボタンをクリックする

2-2-2. 下記の通りそれぞれの値を入力し「保存」ボタンをクリックする

  • 変数名:resource
    • フィールドタイプ:段落
    • ラベル名:resource
    • 最大長:20000
  • 変数名:severity
    • フィールドタイプ:数値
    • ラベル名:severity
  • 変数名:time
    • フィールドタイプ:短文
    • ラベル名:time
    • 最大長:100
  • 変数名:source
    • フィールドタイプ:短文
    • ラベル名:source
    • 最大長:48
  • 変数名:account
    • フィールドタイプ:短文
    • ラベル名:account
    • 最大長:48

※必須かどうかは、その値が絶対に必要かどうかで判断してください。今回はどちらでも大丈夫です。

2-3. LLMフィールドの設定

リクエストを元にAI(今回だとBedrock)に指示を出すフィールドになります。

2-3-1. AIモデルを「Anthropic Claude」を選択→Bedrock Modelを「Claude 3.5 Haiku」を選択する

2-3-2. プロンプトを設定する

下記画像箇所にAIに指示を出すプロンプトを記載してください。

私が記載したプロンプトは下記に記載しますのでご覧いただけますと幸いです。

プロンプト例文

SYSTEM

あなたはAWSセキュリティの専門家です。{{#sys.query#}}のGuardDuty Finding データを解析してください。

USER

解析結果は以下の形式で分かりやすく説明してください。

[アイコン] *重要度:[重要度] -{{#start.source#}}-[AlertType] - [リソース種別]*

*対象:* {{#start.resource#}}の内容から対象リソースがわかれば記載してください。EC2ならinstanceDetailsに対象の詳細が記載されています。わからない場合は記載しないでください
*重要度:* [重要度]({{#start.severity#}})
*アカウント:* {{#start.account#}}
*発生時間:* {{#start.time#}}

:question:*何が起こったか: *GuardDutyのアラート内容を簡潔に説明してください。技術的な詳細と影響を受けるリソースを明記してください。

:cyclone:*考えられる影響: *このセキュリティイベントがもたらす可能性のあるビジネスへの影響や、セキュリティ上の危険性について説明してください。

:boom:*必要なアクション: *このアラートに対処するために推奨される具体的な手順を箇条書きで3-5項目記載してください。

*原文:*
 リクエスト文を引用形式にしてそのまま出力してください

ASSISTANT

[重要度] は{{#start.severity#}}の値が9.0-10.0の時は「クリティカル」、7.0-8.9の時は「高」、4.0-6.9の時は「中」、1.0~3.9の時は「低」と表現してください。

ASSISTANT

[アイコン]には重要度に合わせて下記の通りアイコンを設定してください
クリティカル::rotating_light: 
高::rotating_light: 
中::warning: 
低::hammer_and_wrench:

ASSISTANT

回答は日本語で行い、専門用語は必要に応じて簡潔に説明を加えてください。

また、SYSTEMやUSERはAIへの指示の役割になります。下記記事が大変わかりやすかったので、ご参照いただけますと幸いです。

OpenAI APIにおける「role」とは

https://zenn.dev/atusi/articles/b67a3f1a499880

3. Difyの設定(Slack連携 / アプリのデプロイ)

3-1. Slackプラグインのインストール

3-1-1. プラグインをクリックする

3-1-2. 「マーケットプレイスを探索する」→Slackの「インストール」をクリックする

3-1-3. Slackの「インストール」をクリックする

3-2. Slackをワークフローに組み込む

3-2-1. ワークフローの画面に戻り、「回答」フィールドの「+」ボタン→「ツール」→「Incoming Webhook to send message」をクリックする

3-2-2. 「承認するには」のボタンをクリックする

3-2-3. SlackのWebhook URLを入力し、「保存」ボタンをクリックする

SlackのWebhook取得手順は下記記事が大変わかりやすかったので、ご参照ください。

SlackのWebhook URL取得手順

https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008

3-2-4. 入力変数にtext、もう一度Webhookを入力する

3-3. アプリケーションのデプロイ

3-3-1. 「公開する」→「更新を公開」ボタンをクリックする

3-3-2. 左ペインより「監視」→「APIキー」ボタンをクリックする

3-3-3. 「新しいシークレットキーを作成」をクリックする

3-3-4. APIシークレットキーが生成されるので、控える

※こちらStepFunctionの設定で使用します。

4. StepFunctionの設定

4-1. EventBridge Connectを設定する

EventBridge ConnectはStepFunctionがシークレット情報(今回だとAPIキー)を取得するときに使うものです。

4-1-1. EventBridgeのコンソール画面へ移動する

https://ap-northeast-1.console.aws.amazon.com/events/home?region=ap-northeast-1#

4-1-2. 左ペインより「接続」→「接続を作成」ボタンをクリックする

4-1-3. 下記の通り値を入力し、「作成」ボタンをクリックする

  • 接続名:任意の名前
  • APIタイプ:パブリック
  • 認証タイプ:APIキー
  • APIキー
    • APIキー名:Authorization
    • Bearer <項番3-3-4で取得したAPIキーの値>

4-2. StepFunctionの設定を行う

4-2-1. 下記URLよりStepFunctionのコンソール画面へ移動する

https://ap-northeast-1.console.aws.amazon.com/states/home?region=ap-northeast-1#/statemachines

4-2-2. 「ステートマシンの作成」をクリックする

4-2-3. 任意の「ステートマシン名」をつけ、「続行」ボタンをクリックする

4-2-4. クエリ言語は「JSONPath」を指定する

4-2-5. Call HTTPS APIsをドラッグ&ドロップで画面に置く

4-2-6. 「Call HTTPS APIs」をクリックし、下記の通り設定を行う

  • 状態名:任意の名前
  • APIエンドポイント:https://api.dify.ai/v1/chat-messages
  • メソッド:POST
  • 接続:項番4-1で作成したEventBridge ConnectのARN
  • リクエスト本文
    • {
        "inputs": {
          "resource.$": "States.JsonToString($.detail.resource)",
          "severity.$": "$.detail.severity",
          "time.$": "$.time",
          "source.$": "$.source",
          "account.$": "$.account"
        },
        "query.$": "$.detail.description",
        "response_mode": "streaming",
        "user.$": "$.account"
      }
  • 詳細パラメータ
    • ヘッダー
      • {
          "Content-Type": "application/json"
        }
  • 次の状態:最後に移動

4-3. Catch処理を記載する

4-3-1. Failブロックをドラッグ&ドロップでフローに置き、下記のように設定する

  • 状態名:Fail
  • Error:APIRequestFailed
  • Cause:外部API呼び出しに失敗しました。

4-3-2. Call HTTPS APIsブロックをクリック→エラー処理→キャッチャー#1を下記のように設定する

  • Errors:States.ALL
  • Fallback State:項番4-3-1で作ったブロック

4-3-3. 「設定」タブ→追加設定の次状態を「最後に移動」を選択し、「作成」ボタンをクリックする

4-4. テストを実施する

4-4-1. 作成したステートマシンから「編集」ボタンをクリックする

4-4-2. Call HTTPS APIsブロックをクリック→「テストの状態」をクリックする

4-4-3. 「状態の入力」にテストコードを入力し、「テストの開始」をクリックする

テストコード
{
  "version": "0",
  "id": "35b188f8-13f6-017d-87e7-ab03f7f8f60a",
  "detail-type": "GuardDuty Finding",
  "source": "aws.guardduty",
  "account": "123456789000",
  "time": "2025-05-12T09:35:33Z",
  "region": "ap-northeast-1",
  "resources": [],
  "detail": {
    "schemaVersion": "2.0",
    "accountId": "1234567890",
    "region": "ap-northeast-1",
    "partition": "aws",
    "id": "7ac8c909458a456db24822e7c1765c1e",
    "arn": "arn:aws:guardduty:ap-northeast-1:123456789000:detector/e6b79099a51bab28aa0cc56fb2c73854/finding/7ac8c909458a456db24822e7c1765c1e",
    "type": "Impact:Kubernetes/SuccessfulAnonymousAccess",
    "resource": {
      "resourceType": "EKSCluster",
      "eksClusterDetails": {
        "name": "GeneratedFindingEKSClusterName",
        "arn": "arn:aws:eks:us-east-1:123456789000:cluster/clusterName",
        "createdAt": 1636625755.218,
        "vpcId": "vpc-0123456789",
        "status": "ACTIVE",
        "tags": [
          {
            "key": "GeneratedFindingEKSClusterTag1",
            "value": "GeneratedFindingEKSClusterTagValue1"
          },
          {
            "key": "GeneratedFindingEKSClusterTag2",
            "value": "GeneratedFindingEKSClusterTagValue2"
          },
          {
            "key": "GeneratedFindingEKSClusterTag3",
            "value": "GeneratedFindingEKSClusterTagValue3"
          },
          {
            "key": "GeneratedFindingEKSClusterTag4",
            "value": "GeneratedFindingEKSClusterTagValue4"
          }
        ]
      },
      "kubernetesDetails": {
        "kubernetesWorkloadDetails": null,
        "kubernetesUserDetails": {
          "username": "system:anonymous",
          "uid": "GeneratedFindingUID",
          "groups": [
            "GeneratedFindingUserGroup1",
            "GeneratedFindingUserGroup2",
            "GeneratedFindingUserGroup3",
            "GeneratedFindingUserGroup4"
          ],
          "sessionName": [
            "GeneratedFindingSessionName1",
            "GeneratedFindingSessionName2",
            "GeneratedFindingSessionName3",
            "GeneratedFindingSessionName4"
          ]
        }
      }
    },
    "service": {
      "serviceName": "guardduty",
      "detectorId": "e6b79099a51bab28aa0cc56fb2c73854",
      "action": {
        "actionType": "KUBERNETES_API_CALL",
        "kubernetesApiCallAction": {
          "requestUri": "GeneratedFindingRequestURI",
          "verb": "get",
          "sourceIPs": [
            "10.0.0.23",
            "10.0.0.24",
            "10.0.0.25",
            "10.0.0.26"
          ],
          "userAgent": "",
          "remoteIpDetails": {
            "ipAddressV4": "198.51.100.0",
            "organization": {
              "asn": "0",
              "asnOrg": "GeneratedFindingASNOrg",
              "isp": "GeneratedFindingISP",
              "org": "GeneratedFindingORG"
            },
            "country": {
              "countryName": "GeneratedFindingCountryName"
            },
            "city": {
              "cityName": "GeneratedFindingCityName"
            },
            "geoLocation": {
              "lat": 0,
              "lon": 0
            },
            "ipAddressV6": "1234:5678:90ab:cdef:1234:5678:90ab:cde0"
          },
          "statusCode": 201,
          "parameters": "GeneratedFindingActionParameters"
        }
      },
      "resourceRole": "TARGET",
      "additionalInfo": {
        "sample": true,
        "value": "{\"sample\":true}",
        "type": "default"
      },
      "evidence": null,
      "eventFirstSeen": "2025-05-09T09:47:11.000Z",
      "eventLastSeen": "2025-05-12T09:29:55.000Z",
      "archived": false,
      "count": 5
    },
    "severity": 8,
    "createdAt": "2025-05-09T09:47:11.983Z",
    "updatedAt": "2025-05-12T09:29:55.583Z",
    "title": "A Kubernetes API commonly used in Impact tactics invoked by the anonymous user.",
    "description": "A Kubernetes API commonly used in Impact tactics was invoked on cluster GeneratedFindingEKSClusterName by the anonymous user system:anonymous."
  }
}

※テストコードはGuardDutyから送られてくるリクエストJSONを記載しています。

4-4-4. Slackに下記のような通知が飛んでくれば完了

5. GuardDuty / EventBridgeの設定

こちらは下記記事が大変わかりやすかったので、こちらの記事のSNS作成部分を除いて実行してください。

また、今回はEventBridgeのターゲットはSNSではなくStepFunctionを指定していただければと思います。

GuardDutyの結果をEvent Bridge→SNSで通知する

https://qiita.com/Y-Suzaki/items/72ffb2b94fb2faed3ae1

GuardDutyから通知が来た際に、Slackに要約文が通知されれば成功です!

まとめ

いかがでしたでしょうか?

今回社内のGuardDutyからの監視通知をAIで改善してみました!

Difyはプロンプト調整がしやすいので、この記事を元に、プロンプトを調整して、社内の通知改善に取り組んでいただければと思います!

Related posts