Security Hub イベントを見やすく加工して Eメール通知してみる

2020.07.06

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

はじめに

Security Hubで出てきた検出結果イベントを SNS, CloudWatch Events ルール (EventBridge) を介してメールや Slackチャンネルに投稿ができます。

  • Security Hub イベント → SNS → Eメール
  • Security Hub イベント → SNS → Chatbot

のような構成です。 Chatbot の場合は重要度に応じて色分け、いい感じに表示してくれます。

img

Eメールは Security Hub イベントの JSONがそのまま本文に入ります。Chatbotの通知に比べると見づらいですね。

img

本ブログでは Eメール通知内容を見やすく してみます。 Security Hub イベント → Lambda → SNS → Eメール といった構成を作ってみます。 Lambdaで Security Hub イベントを加工、メッセージを SNS Publish します。

img

2020/08/20 追記

Lambda を挟まなくても EventBridge の ターゲット入力の変換 を利用することで、SNSに渡す文章をカスタマイズできます。

ターゲット入力の変換 | AWSドキュメント

計算処理などを行いたい場合は、本ブログのように Lambdaを介する構成で良いと思います。

構成内容

作成したAWSリソースを説明していきます。 (※ Security Hub を有効化していることは前提です)

SNS

適当な名前( securityhub-finding-topic など ) のSNSトピックを作成します。 送信先のEメールアドレスを指定したサブスクリプションを作成します。

img

Lambda(IAMロール)

Lambdaのサービスロールを作成して、 以下のようなインラインポリシーを付けます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "sns:Publish"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}

Lambdaの基本的な実行ポリシー ( AWSLambdaBasicExecutionRole )に sns:Publish 許可を追加したポリシーになります。

Lambda(Function)

Python3.7 で Lambda Functionを作成します。 コードは以下です。

import json
import os
import boto3

SNS_TOPIC_ARN = os.environ['SNS_TOPIC_ARN']
client = boto3.client("sns")


def lambda_handler(event, context):
    subject = "Security Hub Findings Notification"
    message = parse_event(event)

    client.publish(
        TopicArn=SNS_TOPIC_ARN,
        Subject=subject,
        Message=message
    )


def parse_event(event):
    texts = []
    # Top
    texts.append("# Security Hub Findings")
    texts += [
        "- Region :: {}".format(event['region']),
        "- Account :: {}".format(event['account']),
        "- Time :: {}".format(event['time'])
    ]
    # Findings
    for index, fd in enumerate(event['detail']['findings']):
        texts.append("## Finding #{}".format(index + 1))
        texts += [
            "- Title :: {}".format(fd['Title']),
            "- Severity :: {}".format(fd['Severity']['Label']),
            "- Compliance :: {}".format(fd['Compliance']['Status']),
            "- RecordState :: {}".format(fd['RecordState']),
            "- AwsAccountId :: {}".format(fd['AwsAccountId']),
        ]
        # Resources
        texts.append("- Resources ::")
        for rs in fd['Resources']:
            texts.append("    - Id :: {}".format(rs['Id']))
        # Remediation
        texts.append("### Remediation Recommendation")
        texts += [
            fd['Remediation']['Recommendation']['Text'],
            fd['Remediation']['Recommendation']['Url']
        ]
    # Raw Content
    texts.append("# Raw Content")
    texts.append("{}".format(event))
    return "\n".join(texts)

環境変数に SNS_TOPIC_ARN を作成、さきほど作成したSNSトピックのARNを指定します。

img

EventBridge(ルール)

EventBridgeに新規ルールを作成します。 パターンは イベントパターン 、以下のようなカスタムパターンを今回は使用します。

{
  "source": [
    "aws.securityhub"
  ],
  "detail-type": [
    "Security Hub Findings - Imported"
  ],
  "detail": {
    "findings": {
      "Compliance": {
        "Status": [
          {
            "anything-but": "PASSED"
          }
        ]
      },
      "RecordState": [
        "ACTIVE"
      ],
      "Resources": {
        "Type": [
          {
            "anything-but": "AwsAccount"
          }
        ]
      }
    }
  }
}

このパターンは「(AwsAccount リソースタイプの結果は除外して) セキュリティチェックに合格しなかった結果を取得する」 ものです。

※Security Hub 検出結果のフィルタリング例は下記にまとめているので参照ください。

ターゲットにさきほど作成した Lambda関数を指定します。

img

確認

構築してしばらくすると以下のようなメールが届きました。

img

  • # Security Hub Findings
    • Region … イベントのリージョン
    • Account … イベントのAWSアカウント
    • Time … イベントの時刻
    • ## Finding #X … 取得した各検出結果
      • Title … セキュリティチェック項目のタイトル
      • Severity … 重要度
      • Compliance … チェック結果
      • RecordState … チェック結果のステータス
      • AwsAccountId … チェック対象の AWSアカウントID
      • Resources … チェック対象の各リソースID
      • Remediation Recommendation … 修復アクションの内容、参考リンク
  • # Raw Content … イベントのJSONそのままの内容

JSONそのままの内容よりかは、見やすくなったと思います。

(参考) SAMで構築してみた

上記リソースは AWS サーバーレスアプリケーションモデル (AWS SAM)を使って構築してました。 SAMのプロジェクト構成内容を記します。

プロジェクト

sam init を使って新規プロジェクトを作成します。

sam init --runtime python3.7 --name securityhub-sns-notification

template.yaml

template.yaml は以下です。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: securityhub-sns-notification

Parameters:
  EmailAddress:
    Type: String

Resources:
  # SNSトピック、サブスクリプション
  SnsTopic:
    Type: AWS::SNS::Topic
    Properties: 
      TopicName: securityhub-finding-topic
  SnsSubscription:
    Type: AWS::SNS::Subscription
    Properties: 
      Protocol: email
      Endpoint: !Ref EmailAddress
      TopicArn: !Ref SnsTopic
      # 
  # IAMロール
  IamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: "LambdaPolicy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                  - "sns:Publish"
                Resource: "*"
  # Lambda Function
  Function:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: app.lambda_handler
      Runtime: python3.7
      Environment:
        Variables:
          SNS_TOPIC_ARN: !Ref SnsTopic
      Role: !GetAtt IamRole.Arn
      Events:
        EventBridgeRule:
          Type: CloudWatchEvent
          Properties:
            Pattern:
              source:
                - "aws.securityhub"
              detail-type:
                - "Security Hub Findings - Imported"
              detail:
                findings:
                  Compliance:
                    Status:
                      - anything-but: "PASSED"
                  RecordState: 
                    - "ACTIVE"
                  Resources:
                    Type:
                      - anything-but: "AwsAccount"

src/app.py (Lambda関数)

前述のLambda関数のコードを src/app.py に格納します。

ビルド、デプロイ

sam build
sam deploy --guided

デプロイの設定は下記のとおりです。パラメータに指定した EmailAddress に送信先メールアドレス を記入します。

> sam deploy --guided

Configuring SAM deploy
======================

    Setting default arguments for 'sam deploy'
    =========================================
    Stack Name [securityhub-sns-notification]: securityhub-sns-notification
    AWS Region [ap-northeast-1]: ap-northeast-1
    Parameter EmailAddress []: sample@example.co.jp

おわりに

Security Hub イベントを見やすくしてメール通知してみました。 ただ、 SNS + Chatbotの構成がお手軽なので、Slack(もしくは Chime) を利用できる場合は そちら検討したほうが良さそうです。

少しでもどなたかのお役に立てば幸いです。

参考

▼ CloudFormation, AWS SAM 周り