CloudFormation一撃でGuardDutyを有効にして、通知をLambdaでイイ感じに編集してSNSへ通知するテンプレート作った

2019.09.17

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

GuardDutyまじ優秀を定期的につぶやいているAWS事業本部 梶原@新福岡オフィスです。

完全に 「一発でGuardDutyを全リージョン有効化して通知設定するテンプレート作った」

一発でGuardDutyを全リージョン有効化して通知設定するテンプレート作った

とほぼ内容被ってますが、リージョンごとにSNSを作りたくない。リージョンをまたぐ際の呼び出しコストなどは認識しているけど、まぁ、大丈夫という構成の場合に使用してください。

構成はこんな感じです。

AWS構成図

CloudFormationテンプレートの実行

1つのリージョンだけで、GuardDutyを有効にする場合はSNSのトピックを作成して、下記テンプレートを実行してください。 さくっとGuardDutyを有効にまた通知を作成できるかと思います。

テンプレートはS3に置いてますので、ログイン後に以下ボタンをポチっとしてください。 ※IAMの権限(AWS Lambda用のIAM Roleを作成します)の確認がありますので、AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。にチェックをして進めてください。

パラメータ

  • EnableGuardDutyDetector: は実行するリージョンで有効にする場合はtrueを選択して下さい(すでに有効にしている場合はfalse)
  • MinSeverity: 通知するSeverityを指定します(デフォルトでは5以上を指定していますがすべて通知する場合は0を選択)
  • SnsTopicArn: 通知先のSNSのARNを指定してください。

※複数のリージョンで構成する場合は、Stacksetsを作成して、各リージョンにテンプレートを展開、リソースを作成してください。

StackSetsの作成方法などは

[新機能] CloudFormation StackSetsを試してみた

をご参考ください

S3ののテンプレートURLは https://pub-devio-blog-vtuisp2o.s3.amazonaws.com/template/guardduty-sns-withroll.yml になります

SNS通知

脅威を検知した場合、SNSへ通知が送られます。 通知内容は下記のような通知になります(サンプルです)

Subject:[GuardDuty Finding] EC2 instance i-99999999 is communicating with a Drop Point.

GuardDuty Finding
------------------------------
schemaVersion: 2.0
accountId: XXXXXXXXXXXX
region: ap-southeast-2
partition: aws
id: 9ab66955e7b313cf77fbdbe7d164cc13
arn: arn:aws:guardduty:ap-southeast-2:XXXXXXXXXXXX:detector/c0b66941518c08d9fe004f6de4168aeb/finding/9ab66955e7b313cf77fbdbe7d164cc13
type: Trojan:EC2/DropPoint
severity: 5
createdAt: 2019-08-27T10:42:50.854Z
updatedAt: 2019-08-27T10:42:50.854Z
title: EC2 instance i-99999999 is communicating with a Drop Point.
description: EC2 instance i-99999999 is communicating with a remote host 198.51.100.0 that is known to hold credentials and other stolen data captured by malware.
------------------------------
For details, please refer to the following URL:https://ap-southeast-2.console.aws.amazon.com/guardduty/home?region=ap-southeast-2#/findings?fId=9ab66955e7b313cf77fbdbe7d164cc13
------------------------------

というような内容で通知されますので、文中のURLのリンクに飛ぶと、GuardDutyのコンソールで該当の脅威を表示するようにしています。 (こちらは現時点のコンソールのリンクとなりますので、今後も動作するかどうは未定です。Lambda中で編集をおこなってますので、適時Lambdaを修正してください)

テンプレート

テンプレートを置いておきますので、ご自由にご編集ください。

AWSTemplateFormatVersion: 2010-09-09
Description:
  "enable guardduty and create SnsPublishLambda"

Parameters:
  EnableGuardDutyDetector:
    Type: String
    Default: no
    AllowedValues: [yes, no]
  SnsTopicArn:
    Description: Enter SNS Topic Arn
    Type: String
  MinSeverity:
    Description: Enter Min publish Severity
    Type: Number
    Default: 5

Conditions:
  EnableGuardDutyDetector: !Equals [ !Ref EnableGuardDutyDetector, yes ]

Resources:
  GuardDutyDetector:
    Condition: EnableGuardDutyDetector
    Type: "AWS::GuardDuty::Detector"
    Properties:
      Enable: true

  GuardDutyFindingsEventsRule:
    Type: "AWS::Events::Rule"
    Properties:
      Name: GuardDutyFindingsEventsRule
      Description: "Alert to Lambda when find threats by GuardDuty"
      EventPattern: 
        source:
          - aws.guardduty
        detail-type:
          - GuardDuty Finding
      Targets:
        - Id: GuardDutyFindingsTargets1
          Arn: !GetAtt SnsPublishFunction.Arn

  SnsPublishFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Environment:
        Variables:
          SNS_TOPIC_ARN: !Ref SnsTopicArn
          MIN_SEVERTIY: !Ref MinSeverity
      Code:
        ZipFile: !Sub |
          var SNS_TOPIC_ARN = process.env.SNS_TOPIC_ARN;
          var MIN_SEVERTIY  = process.env.MIN_SEVERTIY;
          
          var AWS = require('aws-sdk');

          exports.handler = async(event) => {
              var severity = event.detail.severity;
              if(severity < MIN_SEVERTIY) {
                  return;
              }
              
              var message = `${!event["detail-type"]}\n`;
              message += "------------------------------\n";
              for (const key in event.detail) {
                  if (typeof(event.detail[key]) != 'object')
                      message += `${!key}: ${!event.detail[key]}\n`;
              }
              message += "------------------------------\n";

              var url = `https://${!event.region}.console.aws.amazon.com/guardduty/home?region=${!event.region}#/findings?fId=${!event.detail.id}`;
              message += `For details, please refer to the following URL:${!url}\n`;

              message += "------------------------------\n";

              var subject = `[${!event["detail-type"]}] ${!event.detail.title}`;
              subject = subject.replace(/[\u{0080}-\u{FFFF}]/gu,"") // remove not ASCII
                  .replace(/\r?\n/g, "") // remove return
                  .slice(0, 100);
                  
              // SNS Publish
              var params = {
                  Message: message,
                  Subject: subject,
                  TopicArn: SNS_TOPIC_ARN
              };

              var SNS_REGION = SNS_TOPIC_ARN.split(":")[3];
              var sns = new AWS.SNS({region: SNS_REGION});
              var result = await sns.publish(params).promise();

              return result;
          }
      Runtime: nodejs12.x
      Timeout: 30
 
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Path: "/"
      Policies:
        - PolicyName: LambdaExecutionRole-SnsPublishPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
                - sns:Publish
              Resource: !Ref SnsTopicArn
 
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt SnsPublishFunction.Arn
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt GuardDutyFindingsEventsRule.Arn

まとめ

先行する小ネタ2つに引っかかりましたが、GuardDuty有効にしたいけど、SNSのトピックあまりつくりたくないんだよねーという要望にはまると幸いです。 Lambda部分などは最適化余地がたくさんあるかとおもうんで、ぜひいい感じの通知内容ができたら教えてください。 StacksSetsの部分はカスタムリソースなどをつくってホントの一撃をしたいなと思ったんですが、要望があればがんばりますので、応援してください。

参考URL

一発でGuardDutyを全リージョン有効化して通知設定するテンプレート作った

一発でGuardDutyを全リージョン有効化して通知設定するテンプレート作った

[新機能] CloudFormation StackSetsを試してみた

[新機能] CloudFormation StackSetsを試してみた