Deploying AWS GuardDuty with CloudFormation for Master and Member accounts

May 1, 2018 by Paulina BudzoƄ

AWS GuardDuty analyses various events happening on your AWS account and can notify you when suspicious activity takes place. Right now, GuardDuty is specific to a region and needs to be enabled in each region you want to monitor (though AWS recommends you enable it in all regions to ensure global actions are monitored). Going through GuardDuty console in every AWS region can be a daunting task, and quite time consuming if you have multiple AWS accounts which you’d like to connect into Master-Member setup. Luckily, CloudFormation supports enabling and setting up GuardDuty detectors, so you can use it to make it a little bit less painful.

Master-member setup in GuardDuty

GuardDuty allows you to connect multiple accounts together, so that you can see findings from all accounts in one place

  • on the account you choose as “Master”. You still need to monitor each region separately, but at least you can see everything on one account. Each account that sends its findings to the Master account is called a “Member”. You can add multiple member accounts into one master, and unfortunately you have to do it in each region as well.

CloudFormation for Master-Member setup

To make the process of enabling GuardDuty and connecting the Members to Master you can use CloudFormation (you’ll need to create the stack in each region you want to monitor). On every account you need to create GuardDuty " Detector" (AWS::GuardDuty::Detector), which essentially enables GuardDuty. On each Member account, you’ll also need to create a “Master” (AWS::GuardDuty::Master), to tell this account who the Master account is (yes, a master on the member account, it can be confusing at first). On Master account you’ll need to create " Members" (AWS::GuardDuty::Member) to tell it which members will be reporting to it. When you invite a member from a master account, the member account is sent a notification, to confirm the setup (similar to SNS subscriptions). The invitation will be sent to an email address you provide and will also show up in Personal Health Dashboard (PHD) in AWS Console.

A simple troposphere template for a master with single member account can look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
MASTER_ACCOUNT_ID = "1234"
MEMBER_ACCOUNT_ID = "5678"
MEMBER_ACCOUNT_EMAIL = "user@example.com"

t = Template()

t.add_description("GuardDuty example deployment for master and member accounts")

member_invitation = t.add_parameter(Parameter(
    "MemberInvitation",
    Type="String",
    Description="Invitation ID for member account, leave empty on master account"
))

t.add_condition("IsMaster", Equals(Ref(AWS_ACCOUNT_ID), MASTER_ACCOUNT_ID))
t.add_condition("IsMember", Not(Condition("IsMaster")))

detector = t.add_resource(guardduty.Detector(
    "Detector",
    Enable=True
))

master = t.add_resource(guardduty.Master(
    "Master",
    Condition="IsMember",
    DetectorId=Ref(detector),
    MasterId=MASTER_ACCOUNT_ID,
    InvitationId=Ref(member_invitation),
))

# You can create multiple members if you have multiple members accounts
member = t.add_resource(guardduty.Member(
    "Member",
    Condition="IsMaster",
    Status="Invited",
    MemberId=MEMBER_ACCOUNT_ID,
    Email=MEMBER_ACCOUNT_EMAIL,
    DetectorId=Ref(detector)
))

Fill up the MASTER_ACCOUNT_ID with the account ID of the master account, MEMBER_ACCOUNT_ID with the account ID of the member account, and MEMBER_ACCOUNT_EMAIL with the email address to send the invitation email to.

You have to first create the stack on the master account, which will enable GuardDuty and send the invitation to the member account. You then have to find the email with the invite or go to PHD and find the invitation ID - it’s required to enable GuardDuty on the member account and confirm the membership. In PHD you can find the ID in the event as below: guardduty phd invite

The ID is located in the confirmation URL - don’t use the URL, but copy the ID (which looks like SHA hash) and paste it into MemberInvitation parameter when creating the stack on member account. Again, you do have to go through this in each region you want to enable GuardDuty in.

If you want to have multiple member accounts, create more guardduty.Member objects in the template, which will send out multiple invites (you can get rid of MEMBER_ACCOUNT_ID or have multiple of those if you want).

Notifications in one place

Once you have GuardDuty enabled and member accounts setup, all findings will be streamed into your master account, but still be region-specific. To make monitoring and notifying about new findings easier, you can use CloudWatch Events to send all findings to SNS - which can then forward them all to an email address, a Lambda function (cross-account works here, so you only need Lambda in one region), etc.

Sending events from GuardDuty into SNS can also be done as part of your CloudFormation stack:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
snstopic = t.add_resource(sns.Topic(
    "SNSTopic",
    Condition="IsMaster",
    Subscription=[
        # put any subscriptions here
    ]
))

event = t.add_resource(events.Rule(
    "EventsRule",
    Condition="IsMaster",
    EventPattern={
        "source": [
            "aws.guardduty"
        ]
    },
    State="ENABLED",
    Targets=[
        events.Target(
            Arn=Ref(snstopic),
            Id="sns",
        )
    ]
))

# Allow events to send notifications to SNS
t.add_resource(sns.TopicPolicy(
    "SNSTopicPolicy",
    Condition="IsMaster",
    PolicyDocument=aws.Policy(
        Statement=[
            aws.Statement(
                Effect=aws.Allow,
                Action=[
                    aws.Action("sns", "Publish"),
                ],
                Principal=aws.Principal("Service", "events.amazonaws.com"),
                Resource=[Ref(snstopic)],
            ),
        ]
    ),
    Topics=[Ref(snstopic)]
))

The event pattern "source": [ "aws.guardduty" ] will match all events touching GuardDuty - that includes all findings and any configuration changes made to the detectors (changing members, adding trusted IP/threat lists, etc). If you want to limit the events only to the findings, use the following event pattern:

1
2
3
4
5
6
7
8
{
  "source": [
    "aws.guardduty"
  ],
  "detail-type": [
    "GuardDuty Finding"
  ]
}

Full template

You can find the complete troposphere template in our github repository, as well as the JSON Cloudformation template.

Posted in: CloudFormation AWS