【AWS】GuardDutyイベント通知の構成をCDKで実装する

# はじめに
こんにちは。
AWSアカウントでGuardDutyを有効化した際、イベント発生時にメールなどで通知させる構成を構築することが最近多かったのですが、設定する内容はそれぞれのAWSアカウントで変わるものでもないので、CDKでコード化してしまえたら今後楽になるのではないかな？と思いました。

ということで今回はAWSアカウント上でGuardDutyを有効にし、GuardDutyで発生したイベントをEventBridge経由でSNSにメッセージを送り、Eメール通知させる構成をCDKで実装していこうと思います。

## GuardDutyとは
以下公式ドキュメントから引用します。

https://docs.aws.amazon.com/ja_jp/guardduty/latest/ug/what-is-guardduty.html

> Amazon GuardDuty は継続的なセキュリティモニタリングサービスで、以下を分析して処理します。データソース: VPC フローログ、AWS CloudTrail管理イベントログ、CloudTrail S3 データイベントログ、DNS ログ。悪意のある IP アドレスやドメインのリストなどの脅威インテリジェンスフィードおよび Machine Learning を使用して、お客様の AWS 環境内での予期しない、および潜在的に未許可で悪意のあるアクティビティを識別します。

セキュリティに関するAWSサービスとなっているので、様々な案件で有効にしている場合が多いように感じます。

# 構成
以下の構成で実装を行っていきます。
![構成図.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/d3c191ec-3942-0c30-13e7-d4c8fc97b1a7.jpeg)
①GuardDutyから発行されたイベントをEventBridgeにて検知。
②GuardDutyからのイベント発行をトリガーにSNSトピックを実行。
③SNSトピックで登録したメールアドレス宛にGuardDutyで発行されたイベントのメール通知を行う。

構成に関しては以下のサイトを参考にしました。
参考1：[Amazon CloudWatch Events を使用した GuardDuty の結果に対するカスタムレスポンスの作成](https://docs.aws.amazon.com/ja_jp/guardduty/latest/ug/guardduty_findings_cloudwatch.html)
参考2：[GuardDutyの通知が重要度でフィルター可能になりました](https://dev.classmethod.jp/articles/guardduty-event-filter/)

# 実装
## CDKコード作成
CDK実行環境の用意については[以前の記事](https://qiita.com/risa_someya/items/8d87853c00c02fbb8bb1)を参考にご用意お願いします。

実行環境の用意が完了したら、作業フォルダ内で以下のファイルの作成を行います。

```python:app.py
#!/usr/bin/env python3

from aws_cdk import core

from cdk_workshop.guardduty_notifications import GuardDutyNotifications

app = core.App()
GuardDutyNotifications(app, "guardduty-notifications")
app.synth()
```

```python:/cdk_workshop/guardduty_notifications.py
from aws_cdk import (
    core,
    aws_sns as sns,
    aws_sns_subscriptions as subs,
    aws_events as events,
    aws_events_targets as targets,
    aws_guardduty as guardduty,
    aws_iam as iam,
    aws_kms as kms,
)

class GuardDutyNotifications(core.Stack):
    """
    GuardDuty有効化、snsトピック・EventBridgeルールの作成
    """

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

        ### ---GuardDuty有効化---
        guardduty_enable = guardduty.CfnDetector(self, "GuardDuty",
            enable=True
        )


        ### ---KMSカスタマー管理型キー作成---
        ### ※SNSトピック暗号化する場合は作成
        kms_custom_policy = iam.PolicyDocument(
            statements=[
                iam.PolicyStatement(
                    actions=["kms:*"],
                    principals=[iam.AccountRootPrincipal()],
                    resources=["*"]),
                iam.PolicyStatement(
                    actions=["kms:Decrypt", "kms:GenerateDataKey"],
                    principals=[iam.ServicePrincipal("events.amazonaws.com")],
                    resources=["*"])
            ]
        )

        sns_key = kms.Key(self, "Key",
            policy=kms_custom_policy,
            removal_policy=core.RemovalPolicy.DESTROY   #スタックが削除されたときにKMSキーも削除されるようにする
        )

        key_alias = kms.Alias(self, "Alias",
            alias_name="alias/guardduty-notifications-key",
            target_key=sns_key
        )


        ### ---SNSトピック作成---
        sns_topic=sns.Topic(self, "guardduty-notifications-snstopic",
            display_name="guardduty-notifications-snstopic",
            topic_name="guardduty-notifications-snstopic",
            master_key=sns_key
        )
        sns_topic.add_subscription(subs.EmailSubscription('{メールアドレス}'))
        #複数のメールアドレスを設定する場合は上の1行を下にコピーしてそれぞれ設定するアドレスを入力する。


        ### ---EventBridgeルール作成---
        event_bridge_rule=events.Rule(self,"guardduty-notifications-eventrule",
            event_pattern={
                "source": ["aws.guardduty"],
                "detail_type": ["GuardDuty Finding"],
                "detail": {
                    "severity": [
                        { "numeric": [ ">=", 4 ] }  #重要度中・高のみ通知するようにフィルタリング
                    ]
                }
            },
            rule_name="guardduty-notifications-eventrule"
        )

        event_bridge_rule.add_target(target=targets.SnsTopic(sns_topic,
                message=events.RuleTargetInput.from_multiline_text(
                    f"AWS {events.EventField.from_path('$.detail.accountId')} has a severity {events.EventField.from_path('$.detail.severity')} GuardDuty finding type {events.EventField.from_path('$.detail.type')} in the {events.EventField.from_path('$.region')} region.\n"
                    f"Resource_Type:{events.EventField.from_path('$.detail.resource.resourceType')}\n"
                    f"Finding Description:{events.EventField.from_path('$.detail.description')}\n"
                    f"For more details open the GuardDuty console at https://{events.EventField.from_path('$.region')}.console.aws.amazon.com/guardduty/home?region={events.EventField.from_path('$.region')}#/findings?search=id%3D{events.EventField.from_path('$.detail.id')}&macros=current\n"
                )
            )
        )
```
※SNSトピックを暗号化する場合のコードとなります。
※※GuardDutyのイベントの重要度中・高のみ通知するようにEventBridgeの方で設定しています。

## デプロイ
```cdk ls```コマンドを実行し、コマンドプロンプト上でスタック名を確認しておきます。

```
(.venv) C:\Users\user\Documents\cdk-workshop>cdk ls
guardduty-notifications
```

```cdk synth```コマンドを実行し、デプロイされるテンプレートが想定通りになっているか確認します。

```
(.venv) C:\Users\user\Documents\cdk-workshop>cdk synth guardduty-notifications
Resources:
  GuardDuty:
    Type: AWS::GuardDuty::Detector
    Properties:
      Enable: true
    Metadata:
      aws:cdk:path: guardduty-notifications/GuardDuty
  Key961B73FD:
    Type: AWS::KMS::Key
    Properties:
      KeyPolicy:
        Statement:
          - Action: kms:*
            Effect: Allow
            Principal:
              AWS:
                Fn::Join:
                  - ""
                  - - "arn:"
                    - Ref: AWS::Partition
                    - ":iam::"
                    - Ref: AWS::AccountId
                    - :root
            Resource: "*"
          - Action:
              - kms:Decrypt
              - kms:GenerateDataKey
            Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Resource: "*"
        Version: "2012-10-17"
    UpdateReplacePolicy: Delete
    DeletionPolicy: Delete
    Metadata:
      aws:cdk:path: guardduty-notifications/Key/Resource
  Alias325C5727:
    Type: AWS::KMS::Alias
    Properties:
      AliasName: alias/guardduty-notifications-key
      TargetKeyId:
        Fn::GetAtt:
          - Key961B73FD
          - Arn
    Metadata:
      aws:cdk:path: guardduty-notifications/Alias/Resource
  guarddutynotificationssnstopic72E6F5F9:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: guardduty-notifications-snstopic
      KmsMasterKeyId:
        Fn::GetAtt:
          - Key961B73FD
          - Arn
      TopicName: guardduty-notifications-snstopic
    Metadata:
      aws:cdk:path: guardduty-notifications/guardduty-notifications-snstopic/Resource
  guarddutynotificationssnstopic{メールアドレス}C3B8772B:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: email
      TopicArn:
        Ref: guarddutynotificationssnstopic72E6F5F9
      Endpoint: {メールアドレス}
    Metadata:
      aws:cdk:path: guardduty-notifications/guardduty-notifications-snstopic/{メールアドレス}/Resource
  guarddutynotificationssnstopicPolicy062F9F6E:
    Type: AWS::SNS::TopicPolicy
    Properties:
      PolicyDocument:
        Statement:
          - Action: sns:Publish
            Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Resource:
              Ref: guarddutynotificationssnstopic72E6F5F9
            Sid: "0"
        Version: "2012-10-17"
      Topics:
        - Ref: guarddutynotificationssnstopic72E6F5F9
    Metadata:
      aws:cdk:path: guardduty-notifications/guardduty-notifications-snstopic/Policy/Resource
  guarddutynotificationseventrule3F18B821:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        detail:
          severity:
            - numeric:
                - ">="
                - 4
        detail-type:
          - GuardDuty Finding
        source:
          - aws.guardduty
      Name: guardduty-notifications-eventrule
      State: ENABLED
      Targets:
        - Arn:
            Ref: guarddutynotificationssnstopic72E6F5F9
          Id: Target0
          InputTransformer:
            InputPathsMap:
              detail-accountId: $.detail.accountId
              detail-severity: $.detail.severity
              detail-type: $.detail.type
              region: $.region
              detail-resource-resourceType: $.detail.resource.resourceType
              detail-description: $.detail.description
              detail-id: $.detail.id
            InputTemplate: |-
              "AWS <detail-accountId> has a severity <detail-severity> GuardDuty finding type <detail-type> in the <region> region."
              "Resource_Type:<detail-resource-resourceType>"
              "Finding Description:<detail-description>"
              "For more details open the GuardDuty console at https://<region>.console.aws.amazon.com/guardduty/home?region=<region>#/findings?search=id%3D<detail-id>&macros=current"
              ""
    Metadata:
      aws:cdk:path: guardduty-notifications/guardduty-notifications-eventrule/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/1VPQQ7CMAx7C/fSgRAHbiC4cZk2PjCyAGFbi9oUNFX9O80mIXGyHcdWstbrzVavFvvm45fQdkUE61DHmhvo1NEazy4Aq+PNVOhtcIDCs9ESkzVJSTDeQ+PaNvCos3lCRmDrVDd4Hc84SkLg0FPjRUwkKW+yf7EvAhnOpA5XD45eUi7TPz2tlLYnGH+JWSaFbzSc+6rQTycKppRUOfLDmmKjd/nNpydaumCYBtTVjF8Py9wrAwEAAA==
    Metadata:
      aws:cdk:path: guardduty-notifications/CDKMetadata/Default
    Condition: CDKMetadataAvailable
Conditions:
  CDKMetadataAvailable:
{~~中略~~}
```

テンプレートが想定通り出力されることが確認できたため、```cdk deploy```コマンドを実行してAWS環境にデプロイします。

```
(.venv) C:\Users\user\Documents\cdk-workshop>cdk deploy guardduty-notifications

?  Synthesis time: 23.18s

This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬──────────────────────────────┬────────┬──────────────────────────────┬──────────────────────────────┬───────────┐
│   │ Resource                     │ Effect │ Action                       │ Principal                    │ Condition │
├───┼──────────────────────────────┼────────┼──────────────────────────────┼──────────────────────────────┼───────────┤
│ + │ ${Key.Arn}                   │ Allow  │ kms:*                        │ AWS:arn:${AWS::Partition}:ia │           │
│   │                              │        │                              │ m::${AWS::AccountId}:root    │           │
│ + │ ${Key.Arn}                   │ Allow  │ kms:Decrypt                  │ Service:events.amazonaws.com │           │
│   │                              │        │ kms:GenerateDataKey          │                              │           │
├───┼──────────────────────────────┼────────┼──────────────────────────────┼──────────────────────────────┼───────────┤
│ + │ ${guardduty-notifications-sn │ Allow  │ sns:Publish                  │ Service:events.amazonaws.com │           │
│   │ stopic}                      │        │                              │                              │           │
└───┴──────────────────────────────┴────────┴──────────────────────────────┴──────────────────────────────┴───────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)? y
guardduty-notifications: deploying...
guardduty-notifications: creating CloudFormation changeset...
guardduty-notifications | 0/9 | 19:38:24 | REVIEW_IN_PROGRESS   | AWS::CloudFormation::Stack | guardduty-notifications User Initiated
guardduty-notifications | 0/9 | 19:38:29 | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | guardduty-notifications User Initiated
guardduty-notifications | 0/9 | 19:38:34 | CREATE_IN_PROGRESS   | AWS::KMS::Key            | Key (Key961B73FD)
guardduty-notifications | 0/9 | 19:38:34 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata       | CDKMetadata/Default (CDKMetadata)
guardduty-notifications | 0/9 | 19:38:35 | CREATE_IN_PROGRESS   | AWS::GuardDuty::Detector | GuardDuty
guardduty-notifications | 0/9 | 19:38:36 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata       | CDKMetadata/Default (CDKMetadata) Resource creation Initiated
guardduty-notifications | 1/9 | 19:38:36 | CREATE_COMPLETE      | AWS::CDK::Metadata       | CDKMetadata/Default (CDKMetadata)
guardduty-notifications | 1/9 | 19:38:36 | CREATE_IN_PROGRESS   | AWS::KMS::Key            | Key (Key961B73FD) Resource creation Initiated
guardduty-notifications | 1/9 | 19:39:03 | CREATE_IN_PROGRESS   | AWS::GuardDuty::Detector | GuardDuty Resource creation Initiated
guardduty-notifications | 2/9 | 19:39:03 | CREATE_COMPLETE      | AWS::GuardDuty::Detector | GuardDuty
2/9 Currently in progress: guardduty-notifications, Key961B73FD
guardduty-notifications | 3/9 | 19:40:38 | CREATE_COMPLETE      | AWS::KMS::Key            | Key (Key961B73FD)
guardduty-notifications | 3/9 | 19:40:41 | CREATE_IN_PROGRESS   | AWS::KMS::Alias          | Alias (Alias325C5727)
guardduty-notifications | 3/9 | 19:40:42 | CREATE_IN_PROGRESS   | AWS::SNS::Topic          | guardduty-notifications-snstopic (guarddutynotificationssnstopic72E6F5F9)
guardduty-notifications | 3/9 | 19:40:42 | CREATE_IN_PROGRESS   | AWS::SNS::Topic          | guardduty-notifications-snstopic (guarddutynotificationssnstopic72E6F5F9) Resource creation Initiated
guardduty-notifications | 3/9 | 19:40:43 | CREATE_IN_PROGRESS   | AWS::KMS::Alias          | Alias (Alias325C5727) Resource creation Initiated
guardduty-notifications | 4/9 | 19:40:53 | CREATE_COMPLETE      | AWS::SNS::Topic          | guardduty-notifications-snstopic (guarddutynotificationssnstopic72E6F5F9)
guardduty-notifications | 4/9 | 19:40:55 | CREATE_IN_PROGRESS   | AWS::SNS::Subscription   | guardduty-notifications-snstopic/{メールアドレス} (guarddutynotificationssnstopicrisasomeyaarijpcomC3B8772B)
guardduty-notifications | 4/9 | 19:40:55 | CREATE_IN_PROGRESS   | AWS::SNS::TopicPolicy    | guardduty-notifications-snstopic/Policy (guarddutynotificationssnstopicPolicy062F9F6E)
guardduty-notifications | 4/9 | 19:40:55 | CREATE_IN_PROGRESS   | AWS::Events::Rule        | guardduty-notifications-eventrule (guarddutynotificationseventrule3F18B821)
guardduty-notifications | 4/9 | 19:40:55 | CREATE_IN_PROGRESS   | AWS::SNS::TopicPolicy    | guardduty-notifications-snstopic/Policy (guarddutynotificationssnstopicPolicy062F9F6E) Resource creation Initiated
guardduty-notifications | 5/9 | 19:40:55 | CREATE_COMPLETE      | AWS::SNS::TopicPolicy    | guardduty-notifications-snstopic/Policy (guarddutynotificationssnstopicPolicy062F9F6E)
guardduty-notifications | 5/9 | 19:40:56 | CREATE_IN_PROGRESS   | AWS::SNS::Subscription   | guardduty-notifications-snstopic/{メールアドレス} (guarddutynotificationssnstopicrisasomeyaarijpcomC3B8772B) Resource creation Initiated
guardduty-notifications | 5/9 | 19:40:56 | CREATE_IN_PROGRESS   | AWS::Events::Rule        | guardduty-notifications-eventrule (guarddutynotificationseventrule3F18B821) Resource creation Initiated
guardduty-notifications | 6/9 | 19:40:56 | CREATE_COMPLETE      | AWS::SNS::Subscription   | guardduty-notifications-snstopic/{メールアドレス} (guarddutynotificationssnstopicrisasomeyaarijpcomC3B8772B)
6/9 Currently in progress: guardduty-notifications, Alias325C5727, guarddutynotificationseventrule3F18B821
guardduty-notifications | 7/9 | 19:41:44 | CREATE_COMPLETE      | AWS::KMS::Alias          | Alias (Alias325C5727)
guardduty-notifications | 8/9 | 19:41:56 | CREATE_COMPLETE      | AWS::Events::Rule        | guardduty-notifications-eventrule (guarddutynotificationseventrule3F18B821)
guardduty-notifications | 9/9 | 19:41:58 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | guardduty-notifications

 ?  guardduty-notifications

?  Deployment time: 218.23s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:{アカウントID}:stack/guardduty-notifications/37e08ef0-9ba7-11ec-a9fa-0e64e1439df3

?  Total time: 241.41s


(.venv) C:\Users\user\Documents\cdk-workshop>
```

正常に実行が完了しました。次に動作確認を行っていきます。

# 動作確認
デプロイしたことで、SNSサブスクリプション登録時の認証メールが、設定したメールアドレス宛に届きますので認証しておきます。
![GuardDuty_002.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/19901031-db51-673c-d2ef-a7ea99747f14.jpeg)
![GuardDuty_003.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/0297cd0e-38ef-19be-52ce-4f8e6c9f8181.jpeg)
認証が完了しました。

GuardDutyでサンプルイベントを作成して、登録したメールアドレス宛に通知が届くかどうか確認してみます。
![GuardDuty_004.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/a1873408-3e95-55d9-06de-194021cb9b42.jpeg)
![GuardDuty_005.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/9a685205-dccc-4821-f279-11ea6e89752d.jpeg)
サンプルイベントが作成できました。

メールの方も確認してみます。
![GuardDuty_018.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/a9d1abc1-414e-bd5f-661d-e393a6d6c454.jpeg)
EventBridgeで設定した通りの文面でメールが届いていることが確認できました。
以上で動作確認は完了です。

# さいごに
今回はGuardDutyのイベント通知に関しての構成をCDKで作成してみました。
テンプレート化してしまえば複数の案件で同様の構成を構築する際の工数が削減できるので、ご入用の方は是非使ってみてください！