How to enable AWS GuardDuty in a multi-account environment using Cloudformation?
During the development of a cloud native application running on Kubernetes we we’re asked to perform implement a security mechanism that pro-actively scans the complete cloud native infrastructure for security threads.
We came up with the idea to implement AWS GuardDuty since is a threat detection service that continuously monitors for malicious activity and unauthorized behavior to protect your AWS accounts and workloads.
Because we have a multi-account strategy setting the whole thing up from an infra-as-code perspective appeared a bit challenging since documentation was not always on-par.
So basically the first thing to understand it that you need to promote 1 account to be the AWS GuardDuty Master and invite numerous accounts to become members. This can be done through the console but we off course want scripts.
So first we define the Master:
Resources: ## Adds the detector and activates the Master account Detector: Type: AWS::GuardDuty::Detector Properties: Enable: True FindingPublishingFrequency: SIX_HOURS #default
Then we can invite the numerous members of our centralized GuardDuty implementation. What is remarkable here is that you do off course need the AWS account ID, but instead of the usual IAM Role cross-account authentication you need to verify you are familiar with the account by defining the root account it’s email-address:
MemberEksPrd: Type: AWS::GuardDuty::Member Properties: DetectorId: !Ref Detector DisableEmailNotification: true Email: email@example.com MemberId: 1234567890 Message: "You are invited to enable Amazon Guardduty." Status: Invited
So when logging in to the AWS console of the member account(s) and check the GuardDuty – Accounts menu we are able to accept the invitation.
Since monitoring is good, but proper alerting is even better we decided to implement a Cloudwatch Event and an SNS Topic to alert. So first we create the SNS Topic that in below example basically sends an e-mail.
GuardDutyFindingTopic: Type: AWS::SNS::Topic Properties: Subscription: - Endpoint: firstname.lastname@example.org Protocol: email TopicName: "GuardDutyFinding"
Then we define the Cloudwatch Event rule that will be triggered on any GuardDuty Finding. In below example we filter on the severity so that only Medium and High GuardDuty findings will trigger the alert.
GuardDutyFindingEventRule: Type: AWS::Events::Rule Properties: Description: "GuardDutyFindingEventRule" EventPattern: source: - "aws.guardduty" detail-type: - "GuardDuty Finding" detail: # high=[7 until 8.9] # medium=[4 until 6.9] severity: [ 4, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 6, 6.0, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9, 7, 7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9 ] State: "ENABLED" Targets: - Arn: Ref: "GuardDutyFindingTopic" Id: "GuardDutyFindingTopic"
As soon as we had GuardDuty running we got some false positives on a corporate security scanner that scans all our VPC’s. So naturally we tried to whitelist this probe to prevent all the Low findings of port scans.
So first we tried with the AWS::GuardDuty::IPSet option. Which basically allows you to create a file with trusted IP’s, store them in a S3 bucket and let GuardDuty use it. This does sound a bit like an over-engineered hassle and it is.
myipset: Type: AWS::GuardDuty::IPSet Properties: Activate: True DetectorId: "12abc34d567e8f4912ab3d45e67891f2" Format: "TXT" Location: "https://s3-eu-west-1.amazonaws.com/mybucket/myipset.txt" Name: "MyIPSet"
But sadly after doing this, it did not work. So eventually this thread on the AWS Forum answered the question why.
The Trusted IP list will not accept RFC1918 addresses, this is due to the ephemeral nature of IP addresses in VPCs. For your use case where you’d like to suppress findings from your scanner you should use the Filter and Auto-Archive feature to create a filter that is defined by the AMI-ID of the scanner, you can also scope the suppression down even further by specifying the finding types you expect.
Our probe is running in our VPC and therefor has a 10.0.0.0/8 (RFC1918) address. So this will not work and we started looking at the AWS GuardDuty filter finding documentation we got it working by adding Suppression Rules filter so the findings were automatically archived.
GuardDutySuppressFilterProbe: Type: AWS::GuardDuty::Filter Properties: Action: ARCHIVE Description: ProbeSuppressFilter DetectorId: !Ref Detector FindingCriteria: Criterion: "resource.instanceDetails.networkInterfaces.privateIpAddresses.privateIpAddress": Eq: - 10.10.20.200 Rank : 2 Name : ProbeSuppressFilter
Hope it helps.