AWS Security Hubのカスタムアクションではリアルタイム通知はできない
AWS 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
カスタムアクション自体の作成は簡単です。
Secuirty Hub コンソールのSettingsからCreate custom actionを選択します。
アクション名と詳細、アクションIDを入力して、OKを選択します。
例えばSlack通知用のアクションを作成するとして以下のように入力します。
作成したカスタムアクションのARNは以下のような形式になります。
カスタムアクションはCloudWatch Eventsにイベントを送信するだけになりますので
何かしらのアクションを実行するにはCloudWatch Eventsルールの作成が必要です。
ルールの作成ではイベントパターンを以下のようにJSONで直接編集します。
ターゲットに任意のLambda関数やSNSトピックを指定することで
通知等の各種アクションを実行することができます。
イベントルールの作成後、Security Hub コンソールで任意でのFindingを選択し、
Actionsから作成したカスタムアクションを実行することができます。
例えばイベントターゲットでSlack通知を行うLambda関数を設定した場合は
以下のような結果になります。
繰り返しになってしますが、今のところSecurity Hubで確認したFindingやInsightに対して
手動でカスタムアクションを起動する形になります。
上記のSlack通知は、GitHubのaws-samplesにあがっている以下のサンプルをもとに
個人的に通知内容を一部変更&Python/YAMLで書き直しました。
https://github.com/aws-samples/aws-securityhub-to-slack
参考までにこちらに貼っておきます。
以下はCloudWatch Events、Lambdaのデプロイ、IAMの設定をまとめておこなう
CloudFormationテンプレートです。Stack Setでも動作します。
現状Security HubがCloudFormationに対応していないため、カスタムアクションは
手動で作成する必要があります(Custom action ID: SendToSlack)
以上です。
参考になれば幸いです。
タイトルのままなのですが、
それだけではあんまりですので以下に詳細を残します。
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を選択します。
アクション名と詳細、アクションIDを入力して、OKを選択します。
例えばSlack通知用のアクションを作成するとして以下のように入力します。
作成したカスタムアクションの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" ] }
通知等の各種アクションを実行することができます。
イベントルールの作成後、Security Hub コンソールで任意でのFindingを選択し、
Actionsから作成したカスタムアクションを実行することができます。
参考: カスタムアクションを使用したSlackへの通知
例えばイベントターゲットでSlack通知を行うLambda関数を設定した場合は以下のような結果になります。
繰り返しになってしますが、今のところ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
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
参考になれば幸いです。
コメント
コメントを投稿