Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

インターン生がGuardDutyのSlack通知を改善してみた!

f:id:otyamurao:20210929103151p:plain

はじめに

長袖と半袖の人が行き交う季節になりましたが皆様いかがお過ごしでしょうか?

初めまして、ヤプリで1ヶ月間SREでインターンをさせていただいた静岡大学の鈴木(@otyamura)と申します。

最近はポケモンユナイトでゴールを守っている鈴木ですが、インターンではヤプリのインフラを守る業務に携わらせていただきました。

f:id:otyamurao:20210929142208j:plain

(攻撃は最大の防御の図)

行ったこと

インターン期間中に行わせていただいたことは主に以下の三つです。

  • GuardDutyのSlack通知の改善
  • GuardDutyのS3 Export設定
  • GuardDutyのログをNew Relicに取り込む

使用技術

また、本記事で使用した主な技術は以下の通りです

  • Amazon GuardDuty
  • Amazon EventBridge
  • AWS Chatbot
  • AWS Cloudformation
  • New Relic

背景

今までGuardDutyのFindingsの通知はDatadogから送信しており、以下の画像のように重要度の情報のみでした。

f:id:otyamurao:20210929142210j:plain

Findingsの詳細を見るためにはAWSへアクセスしてGuardDutyを見てどのFindingsか探して…といった手順を踏んでおり、飛んできた通知のみを見ればわかるようにしたいよね、ということで改善に取り組みました。

Slackへの通知は以下の二つのNew Relicにログを取り込んでalertを出す方法とChatbotを用いる方法の二つを実装し、良い方を採用するということになりました。

結果

最終的にEventBridge + SNS + Chatbotを用いて以下のような通知にしました。

f:id:otyamurao:20210928120831j:plain

f:id:otyamurao:20210928120843j:plain

今までのよりも圧倒的に情報量が増えていることがわかると思います。

また、情報量だけでなくFindingsのseverityによって送信先チャンネルを分けており、早急に対処が必要かどうかもすぐに判断できるようになりました。

New Relicはどうだったのか

New Relicからの通知は以下のようになりました。

f:id:otyamurao:20210928134535p:plain

New Relicからの通知はグラフが描写されるため、ログの件数をトリガーにしている場合などはとても見やすい通知です。しかしながら、Findingsをトリガーにした場合はあまり向いていないことがわかったためChatbotの方式を採用することにしました。

今回はNew RelicにFindingsのログを取り込むことでダッシュボードなどに活かせると判断し、ログ取り込みも行うことにしました。

実装方法

EventBridge + SNS + Chatbot

こちらの記事を参考にCloudformationのテンプレートを作成しました。

ポイントとしては、脅威度によって送信するチャンネルを変更できるようにした点です。

テンプレート内のSeverityThresholdという値によって変更ができ、デフォルトの7ではFindingsの脅威度が高以上か未満かで分かれるようになっています。

このテンプレートを、Stacksetを用いてGuardDutyが有効な全リージョンにEventBridgeのルールとSNSの通知設定を展開します。

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  SeverityThreshold:
    Type: Number
    Default: 7
Resources:
  # SNSトピック
  SnsTopicOverThreshold:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: guardduty-over-threshold
  # SNSトピックポリシー
  EventTopicPolicyOverThreshold:
    Type: AWS::SNS::TopicPolicy
    Properties:
      PolicyDocument:
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "events.amazonaws.com"
            Action: "sns:Publish"
            Resource: "*"
      Topics:
        - !Ref SnsTopicOverThreshold
  # EventBridgeルール
  EventBridgeRuleOverThreshold:
    Type: AWS::Events::Rule
    Properties:
      Name: guardduty-over-threshold-rule
      EventPattern: !Sub |
        {
          "source": ["aws.guardduty"],
          "detail-type": ["GuardDuty Finding"],
          "detail": {
            "severity": [
              {"numeric": [">=", ${SeverityThreshold}]}
            ]
          }
        }
      Targets:
        - Arn: !Ref SnsTopicOverThreshold
          Id: sns-topic-over-threshold
  # 脅威度が低いもの
  # SNSトピック
  SnsTopicUnderThreshold:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: guardduty-under-threshold
  # SNSトピックポリシー
  EventTopicPolicyUnderThreshold:
    Type: AWS::SNS::TopicPolicy
    Properties:
      PolicyDocument:
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "events.amazonaws.com"
            Action: "sns:Publish"
            Resource: "*"
      Topics:
        - !Ref SnsTopicUnderThreshold
  # EventBridgeルール
  EventBridgeRuleUnderThreshold:
    Type: AWS::Events::Rule
    Properties:
      Name: guardduty-under-threshold-rule
      EventPattern: !Sub |
        {
          "source": ["aws.guardduty"],
          "detail-type": ["GuardDuty Finding"],
          "detail": {
            "severity": [
              {"numeric": ["<", ${SeverityThreshold}]}
            ]
          }
        }
      Targets:
        - Arn: !Ref SnsTopicUnderThreshold
          Id: sns-topic-under-threshold

その後、以下のCFnテンプレートからChatbotを作成します。

SlackWorkspaceIdとSlackChannelIdの取得方法はこちらの記事を参考にしました。

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  SnsTopicNameOverThreshold:
    Type: String
    Default: guardduty-over-threshold
  SnsTopicNameUnderThreshold:
    Type: String
    Default: guardduty-under-threshold
  SlackWorkspaceId:
    Type: String
    Default: xxxxxxxx
  ChannelId1:
    Type: String
    Default: xxxxxxxx
  ChannelId2:
    Type: String
    Default: xxxxxxxx
Resources:
  # Chatbot用 IAMロール
  ChatbotIamRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: ChatbotIamRole
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service: "chatbot.amazonaws.com"
      Policies:
        - PolicyName: AWS-Chatbot-NotificationsOnly-Policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Action:
                  - "cloudwatch:Describe*"
                  - "cloudwatch:Get*"
                  - "cloudwatch:List*"
                Effect: "Allow"
                Resource: "*"
  # Chatbot 設定
  # 脅威度が高いもの
  ChatbotConfigurationOverThreshold:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties:
      ConfigurationName: slack-channel-1
      IamRoleArn: !GetAtt ChatbotIamRole.Arn
      LoggingLevel: NONE
      SlackChannelId: !Ref ChannelId1
      SlackWorkspaceId: !Ref SlackWorkspaceId
      SnsTopicArns:
        - !Sub 'arn:aws:sns:ap-northeast-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:ap-northeast-2:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:ap-south-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:ap-southeast-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:ap-southeast-2:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:ca-central-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:eu-central-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:eu-north-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:eu-west-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:eu-west-2:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:eu-west-3:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:sa-east-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:us-east-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:us-east-2:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:us-west-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
        - !Sub 'arn:aws:sns:us-west-2:${AWS::AccountId}:${SnsTopicNameOverThreshold}'
  # 脅威度が低いもの
  ChatbotConfigurationUnderThreshold:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties:
      ConfigurationName: slack-channel-2
      IamRoleArn: !GetAtt ChatbotIamRole.Arn
      LoggingLevel: NONE
      SlackChannelId: !Ref ChannelId2
      SlackWorkspaceId: !Ref SlackWorkspaceId
      SnsTopicArns:
        - !Sub 'arn:aws:sns:ap-northeast-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:ap-northeast-2:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:ap-south-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:ap-southeast-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:ap-southeast-2:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:ca-central-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:eu-central-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:eu-north-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:eu-west-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:eu-west-2:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:eu-west-3:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:sa-east-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:us-east-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:us-east-2:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:us-west-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
        - !Sub 'arn:aws:sns:us-west-2:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'

以上で設定が完了です。

New Relicへのログ取り込み

S3 export設定

New Relicへログを取り込むためには、あらかじめGuardDutyのログをS3へ保管するように設定する必要があります。

マネジメントコンソールからの設定は、手間がかかるためこちらの記事を参考にさせていただき、シェルスクリプトでS3への保管設定を行いました。

各リージョンの検知結果を一つのS3バケットに保存することができたら、Lambdaを設定します。

Lambda設定

Serverless Application Repositoryから"カスタムIAMロールまたはリソースポリシーを作成するアプリを表示する"にチェックをつけNewRelic-log-ingestion-s3を検索します。

f:id:otyamurao:20210928145611p:plain

デプロイの画面でNRLicenseKeyという環境変数に、New Relicで発行したLicense keyを入力します。

NRLogTypeではログのフォーマット指定ができますが、GuardDutyの結果はjson形式のため指定せずに解釈を行なってくれます。

f:id:otyamurao:20210929111430p:plain

デプロイを行った後はトリガーを設定します。

S3を選択し、GuardDutyの結果が集約されるバケットを選択します。

f:id:otyamurao:20210928154005p:plain

f:id:otyamurao:20210928154037p:plain

トリガーを設定したら、S3の読み取り権限とKMSの復号権限のポリシーを以下のロールに与えます。

f:id:otyamurao:20210929142212j:plain

  • S3のバケットポリシーのStatementに以下の記述を追加します。
{
    "Sid": "Allow GetObject from lambda",
    "Effect": "Allow",
    "Principal": {
    "AWS": "arn:aws:iam::account-id:role/serverlessrepo-NewRelic-l-NewRelicLogIngestionFunc-xxxxxxxxx"
    },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::your-bucket-name/*"
}
  • KMSの復号ポリシーを作成し、ロールに割り当てます。
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowDecryptFromLambda",
            "Effect": "Allow",
            "Action": "kms:Decrypt",
            "Resource": "arn:aws:kms:ap-northeast-1:account-id:key/xxxxxxxxxxxx"
        }
    ]
}

以上で、New Relicにログが取り込まれます。

通知サンプル生成のTips

GuardDutyの通知のテストを行いたいときにサンプルを生成しますが、以下の画面からサンプル生成を行うと大量のサンプルが生成されて使い勝手が悪いです。

f:id:otyamurao:20210928163956p:plain

そこで、通知のテストを行うときに便利な方法を紹介します。

通知のサンプルを指定して作成する方法

以下のスクリプトを用いることで、重要度が高と低のサンプルを生成することが可能です。

--finding-typesに渡すものによって生成されるサンプルを変更することができ、どのような種類があるのかはこちらをご確認ください。

#!/bin/sh
REGION=ap-northeast-1
PROFILE_NAME=your-profile-name

id=`aws guardduty list-detectors --query 'DetectorIds' --region $REGION --output text --profile $PROFILE_NAME`
echo $id
aws guardduty create-sample-findings \
    --detector-id $id \
    --finding-types UnauthorizedAccess:EC2/TorClient Recon:EC2/PortProbeUnprotectedPort \
    --profile $PROFILE_NAME

おわりに

インシデントは発生しないことが一番ですがいつかは発生してしまうことでもあるので、発生した後の処理を迅速に行うための環境を整備することも重要です。

本記事ではインシデントの処理を行いやすくするために、Chatbotを用いたGuardDutyの検知結果通知やそれらをNew Relicに取り込むといったことをしました。

今回のインターンで実施したことが役に立つ日が来てほしいと思いつつも、インシデントは発生しないのが一番なので通知が来ないことを願ってたり願ってなかったりしています:smile: