GuardDuty 全リージョン分の検出結果を EventBridge で集約してからメール通知する CloudFormation テンプレートの紹介

2022.05.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

全リージョンで有効化された GuardDuty の検出結果を EventBridge で集約してからメール通知する機会がありました。 メールアドレスの登録とメッセージ内容の整形、検出結果の重要度フィルタリングを一箇所にまとめたかったため、EventBridge で GuardDuty の検出結果を集約してからメール通知するテンプレートを紹介します。

今回構築したい通知構成

検知時に届くメール本文

今回作成した Cloudformation テンプレートは以下に置いてあります。

bigmuramura/cfn-guardduty-findings-sns

従来の一般的な通知構成

各リージョンに SNS の設定が必要でした。また、通知レベルを重要度でフィルタリングしたい場合も各リージョン毎に設定が必要でした。

2021年4月 EventBridge のアップデートにより今回構築したい通知構成を取れるようになりました。

カスタムイベントバスのイベントルールを挟むことで各リージョンに散っていた設定を、単一リージョンに集約することができます。

テンプレートの実装範囲

通知に関する部分のみを構築します。

GuardDuty の有効化は以下のリンクを参考にしてください。

SNS へメールアドレスの登録までは含んでおりませんが、ご利用される環境によっては以下の対応もご検討ください。

GuardDuty の通知類似構成

本構成以外の通知実装例です。Slack 通知など実装したい構成があれば以下のリンクを参考にしてください。

本構成のテンプレート紹介

以下の要素を組み合わせテンプレートになっています。各要素を確認したい場合は以下のリンクを参考にしてください。

テンプレート

1リージョンに展開し EventBridge で集約してから SNS へ通知と、GuardDuty のメッセージの整形、重要度フィルター設定込みテンプレートです。イベントルールに設定する IAM ロールは便宜上、個別に図に記しましたが、実際は同じ IAM ロールを1個を全リージョンで使用します。

折りたたみ

1-eventbridge-receiver-with-sns.yml

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  ProjectName:
    Description: Project Name
    Type: String
    Default: unnamed

Resources:
  # ------------------------------------------------------------#
  # EventBridge
  # ------------------------------------------------------------#
  # Custom Event Bus
  EventsEventBus1:
    Type: "AWS::Events::EventBus"
    Properties:
      Name: !Sub ${ProjectName}-eventbus-receiver

  # Event Rule
  EventsRule1:
    Type: "AWS::Events::Rule"
    Properties:
      Name: !Sub ${ProjectName}-receiver-rule
      EventBusName: !Ref EventsEventBus1
      Targets:
        - Arn: !Ref SNSTopic1
          Id: "NotificationDestinationTopic"
          InputTransformer:
            InputPathsMap:
              Account_ID: "$.detail.accountId"
              Finding_ID: "$.detail.id"
              Finding_Type: "$.detail.type"
              Finding_description: "$.detail.description"
              Resource_Type: "$.detail.resource.resourceType"
              region: "$.region"
              severity: "$.detail.severity"
            InputTemplate: |
              "AWS <Account_ID> has a severity <severity> GuardDuty finding type <Finding_Type> in the <region> region."
              "Resource_Type:<Resource_Type>"
              "Finding Description:"
              "<Finding_description>. "
              "For more details open the GuardDuty console at https://<region>.console.aws.amazon.com/guardduty/home?region=<region>#/findings?search=id%3D<Finding_ID>&macros=current"
      EventPattern: |
        {
            "source": [
                "aws.guardduty"
            ],
            "detail-type": [
                "GuardDuty Finding"
            ],
            "detail": {
                "severity": [
                    { "numeric": [ ">=", 0 ] }
                ]
            }
        }
  # ------------------------------------------------------------#
  # SNS
  # ------------------------------------------------------------#
  SNSTopic1:
    Type: "AWS::SNS::Topic"
    Properties:
      DisplayName: !Sub ${ProjectName}-receiver-topic
      TopicName: !Sub ${ProjectName}-receiver-topic
  SNSTopicPolicy1:
    Type: "AWS::SNS::TopicPolicy"
    Properties:
      Topics:
        - !Ref SNSTopic1
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: "EventBridgeRule"
            Effect: "Allow"
            Principal:
              Service: "events.amazonaws.com"
            Action: "sns:Publish"
            Resource: !Ref SNSTopic1

  # ------------------------------------------------------------#
  # Sender for IAM Role
  # ------------------------------------------------------------#
  IAMRole1:
    Type: "AWS::IAM::Role"
    Properties:
      Path: "/service-role/"
      RoleName: !Sub ${ProjectName}-eventbridge-role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "events.amazonaws.com"
            Action: "sts:AssumeRole"
      MaxSessionDuration: 3600
      ManagedPolicyArns:
        - !Ref IAMManagedPolicy1
  IAMManagedPolicy1:
    Type: "AWS::IAM::ManagedPolicy"
    Properties:
      ManagedPolicyName: !Sub ${ProjectName}-eventbridge-policy
      Path: "/service-role/"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - "events:PutEvents"
            Resource:
              - !GetAtt EventsEventBus1.Arn

Outputs:
  ReceiverCustomEventBusARNOutput:
    Description: "This value is used in the stack set  parameters."
    Value: !GetAtt EventsEventBus1.Arn
    Export:
      Name: !Sub ${AWS::StackName}-ReceiverCustomEventBusARN
  SenderRoleARNOutput:
    Description: "This value is used in the stack set  parameters."
    Value: !GetAtt IAMRole1.Arn
    Export:
      Name: !Sub ${AWS::StackName}-SenderRoleARN

構築されるリソース

GuardDuty が有効な全リージョンに展開し、集約する側の EventBridge へ GuardDuty の検知情報をすべて転送する EventBridge ルールです。

折りたたみ

2-eventbridge-sender.yml

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  ProjectName:
    Description: Project Name
    Type: String
    Default: unnamed
  ReceiverCustomEventBusARN:
    Description: "Enter the value of the output of the receiver stack"
    Type: String
    Default: arn:aws:events:ap-northeast-1:123456789012:event-bus/project-eventbus-receiver
  SenderRoleARN:
    Description: "Enter the value of the output of the receiver stack"
    Type: String
    Default: arn:aws:iam::123456789012:role/service-role/project-eventbridge-role

Resources:
  # ------------------------------------------------------------#
  # EventBridge
  # ------------------------------------------------------------#
  # Event Rule
  EventsRule1:
    Type: "AWS::Events::Rule"
    Properties:
      Name: !Sub ${ProjectName}-sender-rule
      EventBusName: "default"
      Targets:
        - Arn: !Ref ReceiverCustomEventBusARN
          Id: "EventBridgeSender"
          RoleArn: !Ref SenderRoleARN
      EventPattern: |
        {
            "source": [
                "aws.guardduty"
            ],
            "detail-type": [
                "GuardDuty Finding"
            ]
        }

構築されるリソース

全リージョンに EventBridge のルールをデプロイ

全リージョンにデプロイするテンプレート(2-eventbridge-sender.yml)はスタックセットを利用します。実行方法は以下で説明されておりますのでご確認ください。

送信側の環境構築

1個目のテンプレート(1-eventbridge-receiver-with-sns.yml)の実行結果をパラメータとして2個目のテンプレート(2-eventbridge-sender.yml)に与えてスタックセットで実行することを想定しています。

通知結果

SNS にトピックにはプロトコル Eメール指定でメールアドレスをを登録します。

通知テストのために GuardDuty のサンプルイベントを生成しました。以下の様なメールが届けば成功です。

おわりに

GuardDuty + EventBridge 集約してからのメール通知の構成・実装(コード)例が見つからなかったので載せました。参考になる部分があれば幸いです。