1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWSのセキュリティサービスからの通知をAmazon Bedrockで要約してから通知してみた

Last updated at Posted at 2025-12-19

はじめに

Amazon GuardDutyAWS Security Hubなどのセキュリティサービスから異常やセキュリティリスクを検知した際に、とりあえずメール通知を設定されている方は多いのではないでしょうか。しかし、検知内容をそのまま送信する設定にしていると、以下のような生の JSON データが届いてしまい、内容を理解するのに苦労した経験はありませんか?

{"version":"0","id":"8f403a70-f6a8-aa6a-bc89-3a0f2e1538de","detail-type":"Security Hub Findings - Imported","source":"aws.securityhub","account":"123456789012","time":"2025-12-18T02:45:01Z","region":"ap-northeast-1","resources":["arn:aws:securityhub:ap-northeast-1::product/aws/securityhub/arn:aws:securityhub:ap-northeast-1:123456789012:security-control/Redshift.1/finding/1e73ecde-9dc4-420b-b80a-dadeb21b8171"],"detail":{"findings":[{"ProductArn":"arn:aws:securityhub:ap-northeast-1::product/aws/securityhub","Types":["Software and Configuration Checks/Industry and Regulatory Standards"],"Description":"This control checks whether Amazon Redshift clusters are publicly accessible. It evaluates the publiclyAccessible field in the cluster configuration item.","Compliance":{"Status":"PASSED","StatusReasons":[{"Description":"AWS Config evaluated your resources against the rule. The rule did not apply to the AWS resources in its scope, the specified resources were deleted, or the evaluation results were deleted.","ReasonCode":"CONFIG_EVALUATIONS_EMPTY"}],"SecurityControlId":"Redshift.1","AssociatedStandards":[{"StandardsId":"standards/aws-foundational-security-best-practices/v/1.0.0"}]},"ProductName":"Security Hub","FirstObservedAt":"2025-12-18T02:44:52.098Z","CreatedAt":"2025-12-18T02:44:52.098Z","LastObservedAt":"2025-12-18T02:44:52.098Z","CompanyName":"AWS","FindingProviderFields":{"Types":["Software and Configuration Checks/Industry and Regulatory Standards"],"Severity":{"Normalized":0,"Label":"INFORMATIONAL","Original":"INFORMATIONAL"}},"ProductFields":{"RelatedAWSResources:0/name":"securityhub-redshift-cluster-public-access-check-07c5773e","RelatedAWSResources:0/type":"AWS::Config::ConfigRule","aws/securityhub/ProductName":"Security Hub","aws/securityhub/CompanyName":"AWS","aws/securityhub/annotation":"AWS Config evaluated your resources against the rule. The rule did not apply to the AWS resources in its scope, the specified resources were deleted, or the evaluation results were deleted.","Resources:0/Id":"arn:aws:iam::123456789012:root","aws/securityhub/FindingId":"arn:aws:securityhub:ap-northeast-1::product/aws/securityhub/arn:aws:securityhub:ap-northeast-1:123456789012:security-control/Redshift.1/finding/1e73ecde-9dc4-420b-b80a-dadeb21b8171"},"Remediation":{"Recommendation":{"Text":"For information on how to correct this issue, consult the AWS Security Hub controls documentation.","Url":"https://docs.aws.amazon.com/console/securityhub/Redshift.1/remediation"}},"SchemaVersion":"2018-10-08","GeneratorId":"security-control/Redshift.1","RecordState":"ACTIVE","Title":"Amazon Redshift clusters should prohibit public access","Workflow":{"Status":"RESOLVED"},"Severity":{"Normalized":0,"Label":"INFORMATIONAL","Original":"INFORMATIONAL"},"UpdatedAt":"2025-12-18T02:44:52.098Z","WorkflowState":"NEW","AwsAccountId":"123456789012","Region":"ap-northeast-1","Id":"arn:aws:securityhub:ap-northeast-1:123456789012:security-control/Redshift.1/finding/1e73ecde-9dc4-420b-b80a-dadeb21b8171","Resources":[{"Partition":"aws","Type":"AwsAccount","Region":"ap-northeast-1","Id":"AWS::::Account:123456789012"}],"ProcessedAt":"2025-12-18T02:44:57.365Z"}]}}

正直上記通知を受け取っても即座に何が起きたか判断できず、マネージメントコンソールにアクセスして通知元のサービスにたどり着くまでもやもやすることが多いです。
そこで本記事では、これらのセキュリティアラートをAmazon Bedrockの生成 AI で要約し、分かりやすいメール通知に変換するシステムを構築に挑戦してみた内容を書いてみます。

AWS 構成

本記事で実施する構成はいたってシンプルで、Amazon GuardDutyAWS Security Hubからの検知内容を生成 AI(Amazon Bedrock)に要約させてメール送信させるだけです。

AWS構成図.drawio.png

構成説明

  1. GuardDuty/Security Hubがセキュリティイベントを検知
  2. EventBridgeがイベントをキャッチして SNS に送信
  3. Lambdaが SNS メッセージを受信
  4. Bedrockでアラート内容を要約
  5. SNS経由で要約されたメールを送信

使用する AWS サービス紹介

  • Amazon GuardDuty: 脅威検知サービス。機械学習とアノマリー検出を使用して AWS アカウントとワークロードを継続的に監視し、悪意のあるアクティビティや不正な動作を自動で検出。

  • AWS Security Hub: セキュリティ統合管理サービス。複数の AWS セキュリティサービスからの検出結果を一元的に収集・整理し、セキュリティ態勢の包括的な可視化と優先順位付けされる。

  • Amazon EventBridge: イベントルーティングサービス。AWS サービス、SaaS アプリケーション、カスタムアプリケーション間でリアルタイムイベントを配信し、疎結合なイベント駆動アーキテクチャを構築可能。

  • Amazon SNS: 通知サービス。メッセージの発行と配信を行うフルマネージドな pub/sub メッセージングサービスで、メール、SMS、HTTP エンドポイントなど複数の配信先に同時通知が可能。

  • AWS Lambda: サーバーレス実行環境。サーバーの管理なしでコードを実行できるコンピューティングサービスで、イベント駆動型の処理やマイクロサービスアーキテクチャの実装に最適。

  • Amazon Bedrock: 生成 AI サービス。Claude、Llama、Titan など複数の基盤モデルに API でアクセスでき、テキスト生成、要約、翻訳などの AI 機能をアプリケーションに簡単に統合可能。

実装:CDK を使った構築

前提条件

以下の内容は含めておらず、事前に設定されていることを前提にコーディングしています。

  • Amazon GuardDutyの有効化
  • AWS Security Hubの有効化
  • AWS Configの有効化(Security Hub 要件)

CDK スタックコード

以下 CDK のコードは特別なコーディングは特にしておらず、おおむね以下の内容を記載しているだけなので詳細は割愛します。

  • cdk.jsonからメールアドレスを取得
  • AI 要約済み通知用の SNS トピック作成とメール購読設定
  • セキュリティアラート処理用 Lambda 関数の作成(Python 3.13、5 分タイムアウト)
  • Lambda に Bedrock 呼び出し権限と SNS 発行権限を付与
  • EventBridge からの生通知受信用 SNS トピック作成
  • GuardDuty 検知イベント用 EventBridge ルール作成
  • Security Hub 検知イベント用 EventBridge ルール作成
  • 各 EventBridge ルールを生通知用 SNS トピックにターゲット設定
CDKスタック全体のコード(security_notification_stack.py)
from aws_cdk import (
    Stack,
    aws_lambda as _lambda,
    aws_sns as sns,
    aws_sns_subscriptions as subscriptions,
    aws_iam as iam,
    aws_events as events,
    aws_events_targets as targets,
    Duration,
    RemovalPolicy
)
from constructs import Construct

class SecurityNotificationStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # Get email from cdk.json context
        email = self.node.try_get_context("email")
        if not email:
            raise ValueError("Email address must be specified in cdk.json context")

        # SNS Topic for processed notifications
        processed_topic = sns.Topic(
            self, "ProcessedSecurityNotifications",
            topic_name="processed-security-notifications",
            display_name="Processed Security Notifications"
        )

        # Email subscription for processed notifications
        processed_topic.add_subscription(
            subscriptions.EmailSubscription(email)
        )

        # Lambda function for processing notifications
        processor_function = _lambda.Function(
            self, "SecurityNotificationProcessor",
            runtime=_lambda.Runtime.PYTHON_3_13,
            handler="lambda_function.lambda_handler",
            code=_lambda.Code.from_asset("../lambda"),
            timeout=Duration.minutes(5),
            memory_size=512,
            environment={
                "OUTPUT_TOPIC_ARN": processed_topic.topic_arn,
                "BEDROCK_MODEL_ID": "amazon.titan-text-express-v1"
            }
        )

        # IAM permissions for Lambda
        processor_function.add_to_role_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=[
                    "bedrock:InvokeModel"
                ],
                resources=[
                    f"arn:aws:bedrock:{self.region}::foundation-model/amazon.titan-text-express-v1"
                ]
            )
        )

        # Allow Lambda to publish to SNS
        processed_topic.grant_publish(processor_function)

        # SNS Topic for raw GuardDuty/SecurityHub notifications
        raw_topic = sns.Topic(
            self, "RawSecurityNotifications",
            topic_name="raw-security-notifications",
            display_name="Raw Security Notifications"
        )

        # Subscribe Lambda to raw notifications
        raw_topic.add_subscription(
            subscriptions.LambdaSubscription(processor_function)
        )

        # EventBridge rules for GuardDuty
        guardduty_rule = events.Rule(
            self, "GuardDutyRule",
            event_pattern=events.EventPattern(
                source=["aws.guardduty"],
                detail_type=["GuardDuty Finding"]
            )
        )
        guardduty_rule.add_target(targets.SnsTopic(raw_topic))

        # EventBridge rules for Security Hub
        securityhub_rule = events.Rule(
            self, "SecurityHubRule",
            event_pattern=events.EventPattern(
                source=["aws.securityhub"],
                detail_type=["Security Hub Findings - Imported"]
            )
        )
        securityhub_rule.add_target(targets.SnsTopic(raw_topic))

        # Output the SNS topic ARN for manual configuration if needed
        self.raw_topic_arn = raw_topic.topic_arn

Lambda 関数の実装

以下Lambda関数のコードでは、主に受け取ったセキュリティ情報を生成AIに要約させてメール送信を行う処理となっていますが、プロンプトの内容やそれ以外に行っている処理を一部だけ紹介します。

Lambda関数全体のコード(lambda_function.py)
import json
import boto3
import os
from datetime import datetime
from typing import Dict, Any

def lambda_handler(event, context):
    """
    GuardDuty/SecurityHubの通知をBedrockで要約してSNSに送信
    """

    # 環境変数から設定を取得
    output_topic_arn = os.environ['OUTPUT_TOPIC_ARN']
    bedrock_model_id = os.environ['BEDROCK_MODEL_ID']

    # AWSクライアントを初期化
    bedrock_runtime = boto3.client('bedrock-runtime')
    sns = boto3.client('sns')

    try:
        # デバッグ用:受信したイベントをログ出力
        print(f"Received event: {json.dumps(event, indent=2)}")

        # SNSメッセージからイベントデータを抽出
        for record in event['Records']:
            sns_message = json.loads(record['Sns']['Message'])
            print(f"SNS Message: {json.dumps(sns_message, indent=2)}")

            # GuardDutyまたはSecurity Hubのイベントを処理
            if 'source' in sns_message and sns_message['source'] in ['aws.guardduty', 'aws.securityhub']:
                processed_message = process_security_event(sns_message, bedrock_runtime, bedrock_model_id)

                # 処理済みメッセージをSNSに送信
                sns.publish(
                    TopicArn=output_topic_arn,
                    Subject=processed_message['subject'],
                    Message=processed_message['body']
                )

        return {
            'statusCode': 200,
            'body': json.dumps('Successfully processed security notifications')
        }

    except Exception as e:
        print(f"Error processing notification: {str(e)}")
        return {
            'statusCode': 500,
            'body': json.dumps(f'Error: {str(e)}')
        }

def process_security_event(event_data: Dict[Any, Any], bedrock_runtime, model_id: str) -> Dict[str, str]:
    """
    セキュリティイベントをBedrockで要約処理
    """

    # イベントの基本情報を抽出
    source = event_data.get('source', 'Unknown')
    detail = event_data.get('detail', {})

    # GuardDuty固有の処理
    if source == 'aws.guardduty':
        finding_type = detail.get('type', 'Unknown')
        severity = detail.get('severity', 0)
        title = detail.get('title', 'GuardDuty Finding')
        description = detail.get('description', 'No description available')

        raw_content = f"""
        GuardDuty検知情報:
        - タイプ: {finding_type}
        - 重要度: {severity}
        - タイトル: {title}
        - 詳細: {description}
        - アカウントID: {detail.get('accountId', 'Unknown')}
        - リージョン: {detail.get('region', 'Unknown')}
        - 検知時刻: {detail.get('createdAt', 'Unknown')}
        """

    # Security Hub固有の処理
    elif source == 'aws.securityhub':
        findings = detail.get('findings', [])
        if findings:
            finding = findings[0]  # 最初の検知結果を使用
            title = finding.get('Title', 'Security Hub Finding')
            description = finding.get('Description', 'No description available')
            severity = finding.get('Severity', {}).get('Label', 'Unknown')

            raw_content = f"""
            Security Hub検知情報:
            - タイトル: {title}
            - 重要度: {severity}
            - 詳細: {description}
            - リソース: {finding.get('Resources', [])}
            - コンプライアンス: {finding.get('Compliance', {})}
            """
    else:
        raw_content = f"不明なセキュリティイベント: {json.dumps(event_data, indent=2)}"

    # Bedrockで要約を生成
    summary = generate_summary_with_bedrock(raw_content, bedrock_runtime, model_id)

    # メール用のフォーマットを作成
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S JST')
    subject = f"[AWS Security Alert] {source.upper()} - {timestamp}"

    body = f"""
AWS セキュリティアラート通知

{summary}

---
詳細情報:
{raw_content}

---
このメッセージは自動生成されました。
生成時刻: {timestamp}
    """

    return {
        'subject': subject,
        'body': body
    }

def generate_summary_with_bedrock(content: str, bedrock_runtime, model_id: str) -> str:
    """
    BedrockのTitan Text Expressを使用してセキュリティアラートを要約
    """

    prompt = f"""
以下のAWSセキュリティアラートを日本語で分かりやすく要約してください。
技術者でない人にも理解できるよう、以下の点を含めて説明してください:

1. 何が検知されたか(脅威の種類)
2. 影響の程度(重要度)
3. 推奨される対応アクション
4. 緊急度

セキュリティアラート内容:
{content}

要約は200文字以内で簡潔にまとめてください。
    """

    try:
        # Titan Text Express用のリクエスト形式
        request_body = {
            "inputText": prompt,
            "textGenerationConfig": {
                "maxTokenCount": 300,
                "temperature": 0.1,
                "topP": 0.9
            }
        }

        response = bedrock_runtime.invoke_model(
            modelId=model_id,
            body=JSON.dumps(request_body)
        )

        response_body = JSON.loads(response['body'].read())
        summary = response_body['results'][0]['outputText']

        return summary.strip()

    except Exception as e:
        print(f"Bedrock要約生成エラー: {str(e)}")
        return f"要約生成に失敗しました。元の内容を確認してください。\nエラー: {str(e)}"

各セキュリティサービスの通知内容の整理

生成AIの要約した情報だけでは物足りないので、一部受信した情報を記載するようにしています。
EventBridgeで受信するイベントの内容は決まったJSONフォーマットで送られてい来るため、以下のコードのように検知情報を細分化することが可能です。

    # GuardDuty固有の処理
    if source == 'aws.guardduty':
        finding_type = detail.get('type', 'Unknown')
        severity = detail.get('severity', 0)
        title = detail.get('title', 'GuardDuty Finding')
        description = detail.get('description', 'No description available')

        raw_content = f"""
        GuardDuty検知情報:
        - タイプ: {finding_type}
        - 重要度: {severity}
        - タイトル: {title}
        - 詳細: {description}
        - アカウントID: {detail.get('accountId', 'Unknown')}
        - リージョン: {detail.get('region', 'Unknown')}
        - 検知時刻: {detail.get('createdAt', 'Unknown')}
        """
    # Security Hub固有の処理
    elif source == 'aws.securityhub':
        findings = detail.get('findings', [])
        if findings:
            finding = findings[0]  # 最初の検知結果を使用
            title = finding.get('Title', 'Security Hub Finding')
            description = finding.get('Description', 'No description available')
            severity = finding.get('Severity', {}).get('Label', 'Unknown')

            raw_content = f"""
            Security Hub検知情報:
            - タイトル: {title}
            - 重要度: {severity}
            - 詳細: {description}
            - リソース: {finding.get('Resources', [])}
            - コンプライアンス: {finding.get('Compliance', {})}
            """

生成 AI による要約生成処理

生成 AI にアラート内容をわかりやすく要約してもらうためのプロンプトを以下のコードの通りにしてみました。
長すぎないように文字制限を付けています。
また、今回は Amazon の生成 AI であるTitan Text Expressを利用しています(理由は後ほど説明します)。

def generate_summary_with_bedrock(content: str, bedrock_runtime, model_id: str) -> str:
    prompt = f"""
以下のAWSセキュリティアラートを日本語で分かりやすく要約してください。
技術者でない人にも理解できるよう、以下の点を含めて説明してください:

1. 何が検知されたか(脅威の種類)
2. 影響の程度(重要度)
3. 推奨される対応アクション
4. 緊急度

セキュリティアラート内容:
{content}

要約は200文字以内で簡潔にまとめてください。
    """

    try:
        # Titan Text Express用のリクエスト形式
        request_body = {
            "inputText": prompt,
            "textGenerationConfig": {
                "maxTokenCount": 300,
                "temperature": 0.1,
                "topP": 0.9
            }
        }

        response = bedrock_runtime.invoke_model(
            modelId=model_id,
            body=JSON.dumps(request_body)
        )

        response_body = JSON.loads(response['body'].read())
        summary = response_body['results'][0]['outputText']

        return summary.strip()

    except Exception as e:
        print(f"Bedrock要約生成エラー: {str(e)}")
        return f"要約生成に失敗しました。元の内容を確認してください。\nエラー: {str(e)}"

実際の通知結果

ちょっと違和感がある(不要な回答も含まれるような気がする)のですが、改善後の通知では、メールを開いて何が起きているかすぐにわかるようにはなったのではないでしょうか。

従来の通知(改善前)

image.png

改善後の通知

image.png

補足

今回採用した Amazon Bedrock のモデルについて

本システムではAmazon Titan Textモデル(amazon.titan-text-express-v1)を採用しました。
理由はなぜか私のAWSアカウントでは他のモデルを利用できなかったから、というだけです。
なお現在はモデルアクセスに対する事前申請は不要になっており、申請画面も存在していないため自由に使えるものと思っていたのですが、なぜか権限エラーになってしまう状況です。
そのため今回は申請不要なモデルを利用することにしましたが、この問題が解決したら別モデルでも試してみたいと思います。

GuardDuty や Security Hub の通知量について

今回設定したイベントルールは重要度の絞り込みを行っていないのですが、絞り込みを行わないとかなりの通知量になってしまい生成 AI のリクエストコストが上がりそうなので、本格的に利用する場合は重要度の絞り込みをお勧めします。

おまけ

動作確認していたところ、突然生成AIがおかしな動作をすることがあり、面白さと恐怖さがありましたので共有しておきます。
image.png

まとめ

本記事では、AWS GuardDutySecurity Hub のアラートを、Amazon Bedrock の生成 AI で要約して分かりやすいメール通知に変換するシステムを構築してみました。
AWSの通知はそのまま受け取ると中身の解析が面倒になりがちですが、そういうものこそ生成AIを使えば改善できるので、他にも流用して運用効率が上がるようにしていきたいです。

また、今回はAWS re:Invent 2025で発表された新機能やサービスを利用していないのですが、おそらくより良いサービスが発表されていたはずなので、次はそれらも利用した運用効率向上の記事を書きたい所存です。

こちらからは以上です。

1
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?