Skip to main content

GuardDuty for Organizations

GuardDuty is an anomaly detection system that can alert you via SNS when it detects unusual activity within your AWS accounts, such as someone using it from an unusual IP.

Setting up GuardDuty through an organization can be done with a relatively small amount of Terraform, but there are some issues you may run into as you do so. This document is intended to help you get GuardDuty set up in your organization and work around these pitfalls.

Initial configuration

To get GuardDuty set up within your organization, the first task will be to make some changes in your org-root account to get it started. First, you will need to make sure that GuardDuty is set up as a service principal for your AWS organization like so:

resource "aws_organizations_organization" "main" {
enabled_policy_types = ["SERVICE_CONTROL_POLICY"]
feature_set = "ALL"
aws_service_access_principals = [
"config.amazonaws.com",
"cloudtrail.amazonaws.com",
"guardduty.amazonaws.com" # Make sure this is present
]
}

Using GuardDuty via organizations means that you can consolidate all your findings in one account, known as the "GuardDuty admin account". AWS best practice is to not have this in your org-root account (because we want to use that account for as little as possible). As a result, you will want to set your admin account to be another account, usually the infra account. The code below will make the spacecats-infra account the GuardDuty admin account for our organization:

resource "aws_guardduty_organization_admin_account" "main" {
depends_on = [aws_organizations_organization.main]

admin_account_id = aws_organizations_account.spacecats_infra.id
}

Once we've done that, we can then go over to the spacecats-infra account and configure GuardDuty there. There are two components you'll need to set it up: a GuardDuty detector (which is the basic component for GuardDuty that generates your findings), and a GuardDuty organization configuration. The code for these looks like this:

resource "aws_guardduty_detector" "main" {
enable = true
}

resource "aws_guardduty_organization_configuration" "main" {
auto_enable = true
detector_id = aws_guardduty_detector.main.id
}

Once you've created this, GuardDuty is set up and ready to go for the organization. New accounts added the the organization after this point will automatically have a detector created that is connected to the detector in the GuardDuty admin account. Accounts that already exist will not have GuardDuty set up in them yet, however.

Adding GuardDuty to existing accounts

To add an account to an organization's GuardDuty configuration, in the GuardDuty admin account, you'll need to add an aws_guardduty_member resource for the account. The code for that looks like this:

locals {
spacecats_sandbox_id = "123456789000"
spacecats_sandbox_email = "aws+spacecats-sandbox@example.com"
}

resource "aws_guardduty_member" "spacecats_sandbox" {
account_id = local.spacecats_sandbox_id
detector_id = aws_guardduty_detector.main.id
email = local.spacecats_sandbox_email
invite = false
disable_email_notification = true
}

Unfortunately, due to a bug, once you apply this to fix it, subsequent runs will try to destroy and recreate this resource. However, if we comment out this resource and apply, despite Terraform claiming the resource was destroyed, the membership will still be there. You can confirm this using these commands:

$ aws guardduty list-detectors
{
"DetectorIds": [
"1234567890abcdef0000000000000000"
]
}

$ aws guardduty list-members --detector-id 1234567890abcdef0000000000000000
{
"Members": [
{
"AccountId": "123456789000",
"DetectorId": "deadbeef627C78CEBDeE47FAC9CdA7Cd",
"MasterId": "000987654321",
"RelationshipStatus": "Enabled",
"UpdatedAt": "2020-06-23T14:31:35.220Z"
},

...

You will still see the detector for the account you added in this list of members, even though Terraform will claim to have destroyed it.

Adding more regions

GuardDuty is a regional service -- creating the detectors and members as above will only enable GuardDuty in a single region. Our best practice uses SCPs to prevent AWS actions in regions we're not using, so you don't need to turn on GuardDuty in say, eu-central-1 probably, but at the very least, most commercial organizations will be using us-west-2 and us-east-1, simply because some resources (like Route53) can only work in us-east-1. So at the very least, you'll want to have GuardDuty set up in both of those.

If we assume we've set up our original infrastructure in us-west-2, we want to set up GuardDuty for us-east-1 as well. To do this, we need to add duplicate resources to the GuardDuty admin account.

To start off, we'll need to define a second provider for the other region in our providers.tf file (this may already be done if the account already has resources in the us-east-1 region:

provider "aws" {
version = "~> 2.67"
alias = "us-east-1"
region = "us-east-1"
}

Then we will need to define a new GuardDuty detector and organization GuardDuty configuration for the new region like so:

resource "aws_guardduty_detector" "main_useast1" {
provider = aws.us-east-1

enable = true
}

resource "aws_guardduty_organization_configuration" "main_useast1" {
provider = aws.us-east-1
auto_enable = true
detector_id = aws_guardduty_detector.main_useast1.id
}

As with the originals, this should take care of any new accounts; any accounts that already exist that need to be connected to this detector, however, will need to be created according to the same method described in the previous section, with the addition of the provider argument and pointing to the new detector, like so:

resource "aws_guardduty_member" "spacecats_sandbox_useast1" {
provider = aws.us-east-1
account_id = local.spacecats_sandbox_id
detector_id = aws_guardduty_detector.main_useast1.id
email = local.spacecats_sandbox_email
invite = false
disable_email_notification = true
}