GuardDuty をリージョン集約して Slack 通知する方法について考えてみた
GuardDuty は SCP 等で制限していない限りは全リージョンで有効化することが推奨されます。
むしろ、積極的に利用していないリージョンでの検知こそ攻撃者のアクティビティによるものである可能性が高いとも言えます。
ただ、GuardDutyを有効化するだけではあまり意味が無く、重要な検知があった際は迅速に対応できるようにする必要があります。
となると、 GuardDuty 検知時の通知を整備したくなるのですが、 GuardDuty にはリージョン集約機能が無いので上手く設計しないと通知に利用するリソースが多くなってしまいます。
今回は Slack への通知を前提にして、リージョン集約しつつ通知する方法について改めて考えてみました。
Slack を前提に考える理由は、今まで私が Slack 通知を実装することが多かったからというだけです。
ただ、 Chatbot を使えれば条件は同じになると思うので、Microsoft Teams や Chime でも同じ話が言えると思います。
リージョン集約する方法
リージョン集約する方法として 2 パターン考えられます。
- Security Hub 連携を利用して、GuardDuty の検出結果を Security Hub に取り込んだ後に EventBridge で拾って SNS + Chatbot 経由で通知する
- EventBridge カスタムイベントバスに全リージョン分のイベントを集めてから、SNS + Chatbot 経由で通知する
GuardDuty の通知は Security Hub 経由で行うとリージョン集約ができるため、リージョンごとに EventBridge Rules 等を作成しなくて良くなります。
ただ、イベントが GuardDuty Finding
から Security Hub Findings - Imported
に変わるので扱うイベントが少し変わります。
また、 SNS をリージョン数分作成することもできるのですが、であれば EventBridge でリージョン集約した方が良いと思っているので考えないことにします。
前者のパターンについて解説したブログ
後者のパターンについて解説したブログ(通知先はメール)
結論としては Security Hub を利用しているのであれば Security Hub 連携で、使っていなければ EventBridge カスタムイベントバスで集約する形で良いと思います。
ですが、どちらを採用するかによってカスマイズしない場合の通知内容も変わるのでもう少し詳しくみていきます。
Security Hub 連携のパターン
マネージドな仕組みを少しでも多く使っていくならこちらになります。
Security Hub のリージョン集約先に EventBridge Rules + SNS を作成しつつ、 Chatbot の設定をすれば良いのでかなりシンプルですね。
ただ、この方法で一点気になっていたのは、通知文から GuardDuty の通知文であることが分かり辛くなることです。
GuardDuty のイベントを Security Hub に取り込むと、Security Hub Findings - Imported
というイベントになるので当たり前と言えば当たり前ですね。
{ "version": "0", "id": "8e5622f9-d81c-4d81-612a-9319e7ee2506", "detail-type": "Security Hub Findings - Imported", "source": "aws.securityhub", "account": "123456789012", "time": "2019-04-11T21:52:17Z", "region": "us-west-2", "resources": ["arn:aws:securityhub:us-west-2::product/aws/macie/arn:aws:macie:us-west-2:123456789012:integtest/trigger/6294d71b927c41cbab915159a8f326a3/alert/f2893b211841"], "detail": { "findings": [{ "SchemaVersion": "2018-10-08", "Id": "arn:aws:macie:us-west-2:123456789012:integtest/trigger/6214d71b927c41cbab015159a8f316a3/alert/f2893b211841467198cc1201e9031ee4", "ProductArn": "arn:aws:securityhub:us-west-2::product/aws/macie", "GeneratorId": "arn:aws:macie:us-west-2:123456789012:integtest/trigger/6214d71b927c41cbab015159a8f316a3", "AwsAccountId": "123456789012", "Types": ["Sensitive Data Identifications/Passwords/Google Suite Two-factor backup codes in S3"], "FirstObservedAt": "2019-04-11T21:52:15.900Z", "LastObservedAt": "2019-04-11T21:52:15.900Z", "CreatedAt": "2019-04-11T21:52:15.900Z", "UpdatedAt": "2019-04-11T21:52:15.900Z", "Severity": { "Product": 6, "Normalized": 15 }, "Confidence": 5, "Title": "Google Suite Two-Factor Backup Codes uploaded to S3", "Description": "Google Suite two-factor backup codes uploaded to S3....", "Remediation": { "Recommendation": { "Text": "v2 Release" } }, "ProductFields": { "rule-arn": "arn:aws:macie:us-west-2:123456789012:trigger/6214d71b927c41cbab015159a8f316a3", "tags:0": "DATA_COMPLIANCE", "tags:1": "BASIC_ALERT", "themes:0/theme": "google_two_factor_backup", "themes:0/count": "1", "dlpRisk:0/risk": "8", "dlpRisk:0/count": "1", "owner:0/name": "vchin", "owner:0/count": "1", "aws/securityhub/FindingId": "arn:aws:securityhub:us-west-2::product/aws/macie/arn:aws:macie:us-west-2:123456789012:integtest/trigger/6214d71b927c41cbab015159a8f316a3/alert/f2893b211841467198cc1201e9031ee4", "aws/securityhub/SeverityLabel": "LOW", "aws/securityhub/ProductName": "Macie", "aws/securityhub/CompanyName": "Amazon" }, "Resources": [{ "Type": "AwsS3Bucket", "Id": "arn:aws:s3:::test-bucket-12", "Partition": "aws", "Region": "us-west-2" }], "RecordState": "ACTIVE", "WorkflowState": "NEW" }] } }
ただし、2023/09 に Chatbot の通知内容をカスタマイズできるようになりました。
この方法を使えば Security Hub 経由の通知でも GuardDuty のものであることを明示できます。
試しに下記のような CloudFormation テンプレートを用意して、カスタマイズした通知を実装してみました。(Chatbot と Slack の連携は済んでいる前提です)
AWSTemplateFormatVersion: "2010-09-09" Parameters: SlackWorkspaceId: Description: "Workspace Id for Slack" Type: String SlackChannelId: Description: "Channel Id in Slack for GuardDuty notification" Type: String Resources: GuardDutyNotificationRule: Type: AWS::Events::Rule Properties: Description: GuardDuty Findings via Security Hub EventBusName: default Name: guardduty-notification-rule EventPattern: source: - "aws.securityhub" detail-type: - "Security Hub Findings - Imported" detail: findings: ProductName: - GuardDuty Workflow: Status: - NEW RecordState: - ACTIVE Targets: - Arn: !Ref GuardDutyNotificationTopic Id: "GuardDutyNotificationTopic" InputTransformer: InputPathsMap: "affectedResource": "$.detail.findings[0].Resources[0].Id" "accountId": "$.detail.findings[0].AwsAccountId" "description": "$.detail.findings[0].Description" "findingId": "$.detail.findings[0].Id" "region": "$.detail.findings[0].Resources[0].Region" "severity": "$.detail.findings[0].Severity.Label" "title": "$.detail.findings[0].Title" "firstObservedAt": "$.detail.findings[0].FirstObservedAt" "lastObservedAt": "$.detail.findings[0].LastObservedAt" InputTemplate: !Sub '{"version" : "1.0", "source": "custom", "content": {"textType": "client-markdown", "title": ":rotating_light: GuardDuty Finding | <region> | Account: <accountId>", "description": "*Title*\n <title>\n *Description*\n <description>\n *Severity*\n <severity>\n *Affected Resource*\n <affectedResource>\n *FirstObservedAt(UTC)*\n <firstObservedAt>\n *LastObservedAt(UTC)*\n <lastObservedAt>\n\n *AWSアカウントにログインして、下記リンクから詳細を確認して下さい*\n https://${AWS::Region}.console.aws.amazon.com/securityhub/home?region=us-east-1#/findings?search=Id%3D%255Coperator%255C%253AEQUALS%255C%253A<findingId>"}}' GuardDutyNotificationTopic: Type: AWS::SNS::Topic Properties: TopicName: guardduty-notification-topic GuardDutyNotificationTopicPolicy: Type: AWS::SNS::TopicPolicy Properties: Topics: - !Ref GuardDutyNotificationTopic PolicyDocument: Version: "2012-10-17" Statement: - Sid: "EventBridgeRule" Effect: "Allow" Principal: Service: "events.amazonaws.com" Action: "sns:Publish" Resource: !Ref GuardDutyNotificationTopic GuardDutyNotificationChatbotConfiguration: Type: AWS::Chatbot::SlackChannelConfiguration Properties: ConfigurationName: GuardDutyNotification IamRoleArn: !GetAtt ChatbotIamRole.Arn LoggingLevel: INFO SlackChannelId: !Ref SlackChannelId SlackWorkspaceId: !Ref SlackWorkspaceId SnsTopicArns: - !Ref GuardDutyNotificationTopic ChatbotIamRole: Type: AWS::IAM::Role Properties: RoleName: chatbot-role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: chatbot.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: chatbot-policy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - cloudwatch:Describe* - cloudwatch:Get* - cloudwatch:List* Resource: - "*"
こんな感じで通知できます。
一旦、GuardDuty のイベントを直接 Slack 通知した際の通知文に寄せることを意識しました。
必要に応じて通知文をさらにカスタマイズするとより使いやすくなるかと思います。
付けているリンクは Security Hub の検出結果一覧に飛ぶ形ですが、ソース URL を押すことで GuardDuty のコンソールにも遷移可能です。
Security Hub 連携を利用した方法は、GuardDuty 以外のセキュリティ系サービスでもほぼ同じ EventBridge のパターンで通知を実装できるメリットもあります。
Security Hub を有効にしているのであれば Security Hub 経由の通知がおすすめです。
EventBridge カスタムイベントバスに集約するパターン
GuardDuty を利用しているものの、Security Hub を利用していない場合はこちらを使うことになります。
Chatobot をカスタマイズしなくても上記のように通知できるので、こちらの通知が好きな人はこのパターンを採用しても良いと思います。
この場合、 GuardDuty のコンソールへの直接のリンクが提供されます。
ちなみに GuardDuty のイベントを直接扱うと下記のようになります。
{ "version": "0", "id": "c8c4daa7-a20c-2f03-0070-b7393dd542ad", "detail-type": "GuardDuty Finding", "source": "aws.guardduty", "account": "123456789012", "time": "1970-01-01T00:00:00Z", "region": "us-east-1", "resources": [], "detail": { "schemaVersion": "2.0", "accountId": "123456789012", "region": "us-east-1", "partition": "aws", "id": "16afba5c5c43e07c9e3e5e2e544e95df", "arn": "arn:aws:guardduty:us-east-1:123456789012:detector/123456789012/finding/16afba5c5c43e07c9e3e5e2e544e95df", "type": "Canary:EC2/Stateless.IntegTest", "resource": { "resourceType": "Instance", "instanceDetails": { "instanceId": "i-05746eb48123455e0", "instanceType": "t2.micro", "launchTime": 1492735675000, "productCodes": [], "networkInterfaces": [{ "ipv6Addresses": [], "privateDnsName": "ip-0-0-0-0.us-east-1.compute.internal", "privateIpAddress": "0.0.0.0", "privateIpAddresses": [{ "privateDnsName": "ip-0-0-0-0.us-east-1.compute.internal", "privateIpAddress": "0.0.0.0" }], "subnetId": "subnet-d58b7123", "vpcId": "vpc-34865123", "securityGroups": [{ "groupName": "launch-wizard-1", "groupId": "sg-9918a123" }], "publicDnsName": "ec2-11-111-111-1.us-east-1.compute.amazonaws.com", "publicIp": "11.111.111.1" }], "tags": [{ "key": "Name", "value": "ssh-22-open" }], "instanceState": "running", "availabilityZone": "us-east-1b", "imageId": "ami-4836a123", "imageDescription": "Amazon Linux AMI 2017.03.0.20170417 x86_64 HVM GP2" } }, "service": { "serviceName": "guardduty", "detectorId": "3caf4e0aaa46ce4ccbcef949a8785353", "action": { "actionType": "NETWORK_CONNECTION", "networkConnectionAction": { "connectionDirection": "OUTBOUND", "remoteIpDetails": { "ipAddressV4": "0.0.0.0", "organization": { "asn": -1, "isp": "GeneratedFindingISP", "org": "GeneratedFindingORG" }, "country": { "countryName": "United States" }, "city": { "cityName": "GeneratedFindingCityName" }, "geoLocation": { "lat": 0, "lon": 0 } }, "remotePortDetails": { "port": 22, "portName": "SSH" }, "localPortDetails": { "port": 2000, "portName": "Unknown" }, "protocol": "TCP", "blocked": false } }, "resourceRole": "TARGET", "additionalInfo": { "unusualProtocol": "UDP", "threatListName": "GeneratedFindingCustomerListName", "unusual": 22 }, "eventFirstSeen": "2017-10-31T23:16:23Z", "eventLastSeen": "2017-10-31T23:16:23Z", "archived": false, "count": 1 }, "severity": 5, "createdAt": "2017-10-31T23:16:23.824Z", "updatedAt": "2017-10-31T23:16:23.824Z", "title": "Canary:EC2/Stateless.IntegTest", "description": "Canary:EC2/Stateless.IntegTest" } }
CloudFormation で実装してみます。
まず、下記テンプレートを集約先リージョンに展開します。
AWSTemplateFormatVersion: "2010-09-09" Parameters: SlackWorkspaceId: Description: "Workspace Id for Slack" Type: String SlackChannelId: Description: "Channel Id in Slack for GuardDuty notification" Type: String Resources: GuardDutyAggregateEventBus: Type: AWS::Events::EventBus Properties: Name: guardguty-aggregate-eventbus GuardDutyNotificationRule: Type: AWS::Events::Rule Properties: Name: guardduty-notification-rule EventBusName: !Ref GuardDutyAggregateEventBus Targets: - Arn: !Ref GuardDutyNotificationTopic Id: "GuardDutyNotificationTopic" EventPattern: source: - "aws.guardduty" detail-type: - "GuardDuty Finding" GuardDutyNotificationTopic: Type: AWS::SNS::Topic Properties: TopicName: guardduty-notification-topic GuardDutyNotificationTopicPolicy: Type: AWS::SNS::TopicPolicy Properties: Topics: - !Ref GuardDutyNotificationTopic PolicyDocument: Version: "2012-10-17" Statement: - Sid: "EventBridgeRule" Effect: "Allow" Principal: Service: "events.amazonaws.com" Action: "sns:Publish" Resource: !Ref GuardDutyNotificationTopic GuardDutyNotificationRuleRole: Type: AWS::IAM::Role Properties: Path: "/service-role/" RoleName: guardduty-notification-rule-role AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: "events.amazonaws.com" Action: "sts:AssumeRole" ManagedPolicyArns: - !Ref GuardDutyNotificationRulePolicy GuardDutyNotificationRulePolicy: Type: AWS::IAM::ManagedPolicy Properties: ManagedPolicyName: guardduty-notification-rule-policy Path: "/service-role/" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "events:PutEvents" Resource: - !GetAtt GuardDutyAggregateEventBus.Arn GuardDutyNotificationChatbotConfiguration: Type: AWS::Chatbot::SlackChannelConfiguration Properties: ConfigurationName: GuardDutyNotification IamRoleArn: !GetAtt ChatbotIamRole.Arn LoggingLevel: INFO SlackChannelId: !Ref SlackChannelId SlackWorkspaceId: !Ref SlackWorkspaceId SnsTopicArns: - !Ref GuardDutyNotificationTopic ChatbotIamRole: Type: AWS::IAM::Role Properties: RoleName: chatbot-role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: chatbot.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: chatbot-policy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - cloudwatch:Describe* - cloudwatch:Get* - cloudwatch:List* Resource: - "*" Outputs: GuardDutyAggregateEventBusARN: Description: "This value is used in the stack set parameters." Value: !GetAtt GuardDutyAggregateEventBus.Arn Export: Name: GuardDutyAggregateEventBusARN GuardDutyNotificationRuleRoleARN: Description: "This value is used in the stack set parameters." Value: !GetAtt GuardDutyNotificationRuleRole.Arn Export: Name: GuardDutyNotificationRuleRole
次に下記 EventBridge rules を展開します。
こちらは 各リージョンに展開する必要があるので StackSets を利用するのが便利です。
AWSTemplateFormatVersion: "2010-09-09" Parameters: GuardDutyAggregateEventBusARN: Description: "Enter the value of the output of the receiver stack" Type: String GuardDutyNotificationRuleRoleARN: Description: "Enter the value of the output of the receiver stack" Type: String Resources: GuardDutyNotificationRule: Type: "AWS::Events::Rule" Properties: Name: guardduty-notification-rule EventBusName: "default" Targets: - Arn: !Ref GuardDutyAggregateEventBusARN Id: "GuardDutyAggregateEventBus" RoleArn: !Ref GuardDutyNotificationRuleRoleARN EventPattern: source: - "aws.guardduty" detail-type: - "GuardDuty Finding"
そこまで構成が複雑になるわけではないので、無理に Security Hub を有効化するくらいであればこちらを採用すると良さそうです。
まとめ
どっちのパターンも便利です。
Security Hub を有効化しているかが一つ大きな判断基準になると思いますが、要件に合わせて選んでみて下さい。