AWS Security Hubのカスタムアクションではリアルタイム通知はできない

AWS Security Hubのカスタムアクションではリアルタイム通知はできない:

タイトルのままなのですが、

それだけではあんまりですので以下に詳細を残します。


Security Hubって何?

AWS re:Invent 2018で発表されたAWS環境全体のセキュリティとコンプライアンスの状況を

確認できるサービスです。GuardDuty の脅威診断結果、Inspector のスキャン結果、Macie

による機密データの検出などAWSサービスはもちろん、パートナーツールからの出力結果も

取り込んで1つのダッシュボードで管理することができます。

https://aws.amazon.com/jp/security-hub/

ちなみに現在はPublic Preview中で無料で使用することができます。


カスタムアクション

カスタムアクションはSecurity Hubで集約された検出結果(Fingings)を

CloudWatch Eventsに送信できる機能です。

これにより従来各サービス毎に設定していたSlackなどの通知についてもSecurity Hubで

集約できると思っていましたが、ここで勘違いをしていました。。。

Findings に結果が集約されるたびに CloudWatch Events にイベントが

送信されるわけではありません。

現状は Security Hub のマネージメントコンソールから選択した Findings を手動で

CloudWatch Events へ送信できる機能であると言えます。

CloudWatchイベントを使用したAWSセキュリティハブの自動化
https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cloudwatch-events.html#securityhub-cwe-configure

現在のリリースでは、選択した Security Hubの検出結果と洞察の結果をCloudWatch Eventsに送信してさらに処理するようにSecurity Hubを設定できます。
現在のリリースでは という記載になっているので、今後のアップデートに期待します。


カスタムアクションの作成と設定

カスタムアクション自体の作成は簡単です。

Secuirty Hub コンソールのSettingsからCreate custom actionを選択します。


image.png


アクション名と詳細、アクションIDを入力して、OKを選択します。

例えばSlack通知用のアクションを作成するとして以下のように入力します。
image.png

作成したカスタムアクションのARNは以下のような形式になります。
arn:aws:securityhub:<region>:<account-id>:action/<custom-action-id>

カスタムアクションはCloudWatch Eventsにイベントを送信するだけになりますので

何かしらのアクションを実行するにはCloudWatch Eventsルールの作成が必要です。

ルールの作成ではイベントパターンを以下のようにJSONで直接編集します。

{ 
  "resources": [ 
    "arn:aws:securityhub:<region>:<account-id>:action/<custom-action-id>" 
  ], 
  "source": [ 
    "aws.securityhub" 
  ] 
} 
ターゲットに任意のLambda関数やSNSトピックを指定することで

通知等の各種アクションを実行することができます。
image.png

イベントルールの作成後、Security Hub コンソールで任意でのFindingを選択し、

Actionsから作成したカスタムアクションを実行することができます。
image.png


参考: カスタムアクションを使用したSlackへの通知

例えばイベントターゲットでSlack通知を行うLambda関数を設定した場合は

以下のような結果になります。
image.png

繰り返しになってしますが、今のところSecurity Hubで確認したFindingやInsightに対して

手動でカスタムアクションを起動する形になります。

上記のSlack通知は、GitHubのaws-samplesにあがっている以下のサンプルをもとに

個人的に通知内容を一部変更&Python/YAMLで書き直しました。

https://github.com/aws-samples/aws-securityhub-to-slack

参考までにこちらに貼っておきます。

lambda_function.py
""" 
This is a sample function to send AWS Security Hub Findings to a slack. 
 
Environment variables: 
    CHANNEL: Slack channel name 
    MIN_SEVERITY_LEVEL: Minimum severity to notify 
    WEBHOOK_URL: Incoming Webhook URL 
""" 
 
import json 
import os 
from datetime import datetime 
from logging import getLogger, INFO 
from urllib.request import Request, urlopen 
from urllib.error import URLError, HTTPError 
from dateutil.parser import parse 
 
logger = getLogger() 
logger.setLevel(INFO) 
 
def get_params(event, properties): 
    """Slack message formatting""" 
    channel = os.environ['CHANNEL'] 
    message = f"*Security Hub finding in {event['region']} for Account: {event['account']}*" 
    title = event['detail']['findings'][0]['Title'] 
    console_url = 'https://console.aws.amazon.com/securityhub/' 
    description = event['detail']['findings'][0]['Description'] 
    product = event['detail']['findings'][0]['ProductFields']['aws/securityhub/ProductName'] 
    last_seen = datetime.strftime( 
        parse(event['detail']['findings'][0]['UpdatedAt']), 
        '%Y-%m-%d %H:%M:%S %Z' 
    ) 
    slack_message = { 
        'username': 'AWS Security Hub', 
        'channels': channel, 
        'icon_emoji': ':securityhub:', 
        'text': message, 
        'attachments': [ 
            { 
                'fallback': 'AWS Security Hub Findings Description.', 
                'color': properties['color'], 
                'title': title, 
                'title_link': f"{console_url}home?region={event['region']}#/findings", 
                'text': description, 
                'fields': [ 
                    {'title': 'Product', 'value': product, 'short': True}, 
                    {'title': 'Severity', 'value': properties['label'], 'short': True}, 
                    {'title': 'Last Seen', 'value': last_seen, 'short': True} 
                ] 
            } 
        ] 
    } 
    return slack_message 
 
def get_properties(severity, min_severity_level): 
    """Returns the label and color setting of severity""" 
    if severity < 4.0: 
        if min_severity_level != 'LOW': 
            logger.info("Skip Notification: Minimum Severity Level is %s.", min_severity_level) 
            return 
        properties = {'label': 'Low', 'color': 'warning'} 
    elif severity < 7.0: 
        if min_severity_level == 'HIGH': 
            logger.info("Skip Notification: Minimum Severity Level is HIGH.") 
            return 
        properties = {'label': 'Medium', 'color': 'warning'} 
    else: 
        properties = {'label': 'High', 'color': 'danger'} 
    return properties 
 
def lambda_handler(event, context): 
    """AWS Lambda Function to send Security Hub Findings to slack""" 
    result = 1 
    properties = get_properties( 
        event['detail']['findings'][0]['Severity']['Product'], 
        os.environ['MIN_SEVERITY_LEVEL'] 
    ) 
    if properties: 
        slack_message = get_params(event, properties) 
        req = Request(os.environ['WEBHOOK_URL'], json.dumps(slack_message).encode('utf-8')) 
        try: 
            with urlopen(req) as res: 
                res.read() 
                logger.info("Message posted.") 
        except HTTPError as err: 
            logger.error("Request failed: %d %s", err.code, err.reason) 
        except URLError as err: 
            logger.error("Server connection failed: %s", err.reason) 
        else: 
            result = 0 
    return result 
以下はCloudWatch Events、Lambdaのデプロイ、IAMの設定をまとめておこなう

CloudFormationテンプレートです。Stack Setでも動作します。

現状Security HubがCloudFormationに対応していないため、カスタムアクションは

手動で作成する必要があります(Custom action ID: SendToSlack)

CFn_SecurityHubToSlack.yaml
AWSTemplateFormatVersion: '2010-09-09' 
Description: AWS Security Hub Findings to Slack 
 
Parameters:  
  IncomingWebHookURL:  
    Default: "https://hooks.slack.com/services/XXXXXX/YYYYY/REPLACE_WITH_YOURS" 
    Description: "Your unique Incoming Web Hook URL from slack service" 
    Type: String 
  SlackChannel:  
    Default: "#alerts" 
    Description: "The slack channel to send findings to" 
    Type: String 
  MinSeverityLevel:  
    Default: LOW 
    Description: "The minimum findings severity to send to your slack channel (LOW, MEDIUM or HIGH)" 
    Type: String 
    AllowedValues:  
      - LOW 
      - MEDIUM 
      - HIGH 
 
Resources: 
# Create IAM Role 
  LambdaExecutionRole: 
    Type: "AWS::IAM::Role" 
    Properties: 
      AssumeRolePolicyDocument: 
        Version: "2012-10-17" 
        Statement: 
          - Effect: Allow 
            Principal: 
              Service: lambda.amazonaws.com 
            Action: 
              - sts:AssumeRole 
      Path: "/" 
      Policies: 
        - PolicyName: LambdaBasicExecution 
          PolicyDocument: 
            Version: '2012-10-17' 
            Statement: 
              - Effect: Allow 
                Action: 
                  - logs:CreateLogGroup 
                  - logs:CreateLogStream 
                  - logs:PutLogEvents 
                Resource: arn:aws:logs:*:*:* 
 
# Create Lambda Function 
  LambdaFunction:  
    Type: "AWS::Lambda::Function" 
    Properties:  
      Handler: "index.lambda_handler" 
      Role: !GetAtt LambdaExecutionRole.Arn 
      Code:  
        ZipFile: | 
          """ 
          This is a sample function to send AWS Security Hub Findings to a slack. 
 
          Environment variables: 
              CHANNEL: Slack channel name 
              MIN_SEVERITY_LEVEL: Minimum severity to notify 
              WEBHOOK_URL: Incoming Webhook URL 
          """ 
 
          import json 
          import os 
          from datetime import datetime 
          from logging import getLogger, INFO 
          from urllib.request import Request, urlopen 
          from urllib.error import URLError, HTTPError 
          from dateutil.parser import parse 
 
          logger = getLogger() 
          logger.setLevel(INFO) 
 
          def get_params(event, properties): 
              """Slack message formatting""" 
              channel = os.environ['CHANNEL'] 
              message = f"*Security Hub finding in {event['region']} for Account: {event['account']}*" 
              title = event['detail']['findings'][0]['Title'] 
              console_url = 'https://console.aws.amazon.com/securityhub/' 
              description = event['detail']['findings'][0]['Description'] 
              product = event['detail']['findings'][0]['ProductFields']['aws/securityhub/ProductName'] 
              last_seen = datetime.strftime( 
                  parse(event['detail']['findings'][0]['UpdatedAt']), 
                  '%Y-%m-%d %H:%M:%S %Z' 
              ) 
              slack_message = { 
                  'username': 'AWS Secuirty Hub', 
                  'channels': channel, 
                  'icon_emoji': ':securityhub:', 
                  'text': message, 
                  'attachments': [ 
                      { 
                          'fallback': 'AWS Security Hub Findings Description.', 
                          'color': properties['color'], 
                          'title': title, 
                          'title_link': f"{console_url}home?region={event['region']}#/findings", 
                          'text': description, 
                          'fields': [ 
                              {'title': 'Product', 'value': product, 'short': True}, 
                              {'title': 'Severity', 'value': properties['label'], 'short': True}, 
                              {'title': 'Last Seen', 'value': last_seen, 'short': True} 
                          ] 
                      } 
                  ] 
              } 
              return slack_message 
 
          def get_properties(severity, min_severity_level): 
              """Returns the label and color setting of severity""" 
              if severity < 4.0: 
                  if min_severity_level != 'LOW': 
                      logger.info("Skip Notification: Minimum Severity Level is %s.", min_severity_level) 
                      return 
                  properties = {'label': 'Low', 'color': 'warning'} 
              elif severity < 7.0: 
                  if min_severity_level == 'HIGH': 
                      logger.info("Skip Notification: Minimum Severity Level is HIGH.") 
                      return 
                  properties = {'label': 'Medium', 'color': 'warning'} 
              else: 
                  properties = {'label': 'High', 'color': 'danger'} 
              return properties 
 
          def lambda_handler(event, context): 
              """AWS Lambda Function to send Security Hub Findings to slack""" 
              result = 1 
              properties = get_properties( 
                  event['detail']['findings'][0]['Severity']['Product'], 
                  os.environ['MIN_SEVERITY_LEVEL'] 
              ) 
              if properties:  
                  slack_message = get_params(event, properties) 
                  req = Request(os.environ['WEBHOOK_URL'], json.dumps(slack_message).encode('utf-8')) 
                  try: 
                      with urlopen(req) as res: 
                          res.read() 
                          logger.info("Message posted.") 
                  except HTTPError as err: 
                      logger.error("Request failed: %d %s", err.code, err.reason) 
                  except URLError as err: 
                      logger.error("Server connection failed: %s", err.reason) 
                  else: 
                      result = 0 
              return result 
 
      Runtime: "python3.6" 
      MemorySize: 128 
      Timeout: 3 
      Environment: 
        Variables: 
          CHANNEL: !Ref SlackChannel 
          MIN_SEVERITY_LEVEL: !Ref MinSeverityLevel 
          WEBHOOK_URL: !Ref IncomingWebHookURL 
 
# Create CloudWatch Events Rule 
  EventRule: 
    Type: "AWS::Events::Rule" 
    Properties:  
      Description: "AWS Security Hub Findings to Slack" 
      EventPattern: 
        source:  
          - aws.securityhub 
        resources:  
          - !Join 
            - "" 
            - - "arn:aws:securityhub:" 
              - !Ref "AWS::Region" 
              - ":" 
              - !Ref "AWS::AccountId" 
              - ":" 
              - "action/custom/SendToSlack" 
      Name: SecurityHub-to-Slack 
      State: "ENABLED" 
      Targets: 
        -  
          Arn: !GetAtt LambdaFunction.Arn 
          Id: "TargetFunctionV1" 
 
# Create Invoke Lambda Permission 
  PermissionForEventsToInvokeLambda:  
    Type: "AWS::Lambda::Permission" 
    Properties:  
      FunctionName: !Ref LambdaFunction 
      Action: "lambda:InvokeFunction" 
      Principal: "events.amazonaws.com" 
      SourceArn: !GetAtt EventRule.Arn 
以上です。

参考になれば幸いです。

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)