How To Manage Amazon GuardDuty in AWS Organizations Using Terraform
About the use case
Amazon GuardDuty is a managed threat detection service
that continuously monitors AWS accounts and workloads for malicious or
unauthorized activity using machine learning, anomaly detection, and integrated
threat intelligence.
GuardDuty
supports managing multiple accounts with AWS Organizations via
the delegated administrator feature, with which you designate an AWS account in
the organization to centrally manage GuardDuty for all members. This is great
for managing a multi-account landing zone by centralizing management of
GuardDuty settings in a consistent manner.
Since it is
increasingly common to establish an AWS landing zone using AWS Control Tower, we will use the standard account structure in a Control Tower landing
zone to demonstrate how to configure GuardDuty in Terraform:
The relevant
accounts for our use case in the landing zone are:
- The Management account
for the organization where AWS Organizations is configured. For details,
refer to Managing GuardDuty accounts with AWS Organizations.
- The Audit account where
security and compliance services are typically centralized in a Control
Tower landing zone.
The
objective is to delegate GuardDuty administrative duties from the Management account
to the Audit account, after which all organization configurations are
managed in the Audit account. With that said, let's see how we can
achieve this using Terraform!
Designating
a GuardDuty administrator account
GuardDuty
delegated administrator is configured in the Management account, so
we need a provider associated with it in Terraform. To keep things simple, we
will take a multi-provider approach by defining two providers, one for
the Management account and another for the Audit account,
using AWS CLI profiles as follows:
provider "aws"
{
alias
= "management"
# Use "aws configure" to create the
"management" profile with the Management account credentials
profile = "management"
}
provider "aws"
{
alias
= "audit"
# Use "aws configure" to create the
"audit" profile with the Audit account credentials
profile = "audit"
}
⚠
Since
GuardDuty is a regional service, you must apply this Terraform configuration on
each region that you are using. Consider using the region argument in
your provider definition and a variable to make your Terraform configuration
rerunnable in other regions.
We can then
use the aws_guardduty_organization_admin_account resource to
set the delegated administrator. However, I noticed the following in the Audit account:
- After this resource is created,
GuardDuty will be enabled with both the foundational data sources and all
protection plans enabled.
- When the resource is deleted,
GuardDuty remains enabled.
These side
effects are not desirable since we would ideally want full control over the
lifecycle and configuration of GuardDuty in Terraform. To address this issue,
we will preemptively enable GuardDuty in the Audit account using
the aws_guardduty_detector resource. We will also manage
the protection plans using the aws_guardduty_detector_feature resource in
subsequent steps after we define the org-wide settings.
The
resulting Terraform configuration should be defined as follows (pay special
attention to the provider argument in each resource):
data "aws_caller_identity"
"audit" {
provider = aws.audit
}
resource "aws_guardduty_detector"
"audit" {
provider = aws.audit
}
resource "aws_guardduty_organization_admin_account"
"this" {
provider = aws.management
admin_account_id =
data.aws_caller_identity.audit.account_id
depends_on = [aws_guardduty_detector.audit]
}
With
the Audit account designated as the GuardDuty administrator, we can
now manage the organization configuration.
Configuring
organization auto-enable preferences
GuardDuty
distinguishes the foundational data sources settings from the protection plans
settings. The former is managed using the aws_guardduty_organization_configuration resource. In
our case, we want to manage GuardDuty for all accounts (i.e. both new and
existing accounts). The resulting Terraform configuration should thus look like
the following:
resource "aws_guardduty_organization_configuration"
"this" {
provider = aws.audit
auto_enable_organization_members = "ALL"
detector_id =
aws_guardduty_detector.audit.id
depends_on =
[aws_guardduty_organization_admin_account.this]
}
Next, let's
manage the protection plan configuration. For illustration, let's assume that
we only want to enable only EKS Audit Log Monitoring. To ensure full configurability,
we will define the setting for all protection plans using a variable:
# Terraform
configuration (.tf)
variable "guardduty_features"
{
description = "An object map that
defines the GuardDuty organization configuration."
type = map(object({
auto_enable = string
name
= string
additional_configuration =
optional(list(object({
auto_enable = string
name = string
})))
}))
}
# Variable
definition (.tfvars)
guardduty_features
= {
s3 = {
auto_enable = "NONE"
name
= "S3_DATA_EVENTS"
}
eks = {
auto_enable = "ALL"
name
= "EKS_AUDIT_LOGS"
}
eks_runtime_monitoring = {
# EKS_RUNTIME_MONITORING is deprecated and
should thus be explicitly disabled
auto_enable = "NONE"
name
= "EKS_RUNTIME_MONITORING"
additional_configuration = [
{
auto_enable = "NONE"
name = "EKS_ADDON_MANAGEMENT"
},
]
}
runtime_monitoring = {
auto_enable = "NONE"
name
= "RUNTIME_MONITORING"
additional_configuration = [
{
auto_enable = "NONE"
name = "EKS_ADDON_MANAGEMENT"
},
{
auto_enable = "NONE"
name = "ECS_FARGATE_AGENT_MANAGEMENT"
},
{
auto_enable = "NONE"
name = "EC2_AGENT_MANAGEMENT"
}
]
}
malware = {
auto_enable = "NONE"
name
= "EBS_MALWARE_PROTECTION"
}
rds = {
auto_enable = "NONE"
name
= "RDS_LOGIN_EVENTS"
}
lambda = {
auto_enable = "NONE"
name
= "LAMBDA_NETWORK_LOGS"
}
}
⚠
The EKS_RUNTIME_MONITORING feature
has been superseded by the RUNTIME_MONITORING feature, but to avoid
perpetual differences in Terraform configuration, we must set its enablement
state to NONE.
We can then
use this variable with the for_each meta-argument with the aws_guardduty_organization_configuration_feature resource as
follows:
resource "aws_guardduty_organization_configuration_feature"
"this" {
provider
= aws.audit
for_each
= var.guardduty_features
auto_enable = each.value.auto_enable
detector_id = aws_guardduty_detector.audit.id
name
= each.value.name
dynamic "additional_configuration"
{
for_each =
try(each.value.additional_configuration, [])
content {
auto_enable =
additional_configuration.value.auto_enable
name = additional_configuration.value.name
}
}
depends_on =
[aws_guardduty_organization_admin_account.this]
}
Lastly, we
will circle back to recalibrating the protection plan settings for the Audit account
itself. Let's piggyback on the same variable and use the aws_guardduty_detector_feature resource to
achieve this:
resource "aws_guardduty_detector_feature"
"audit" {
provider
= aws.audit
for_each
= var.guardduty_features
detector_id = aws_guardduty_detector.audit.id
name
= each.value.name
status
= each.value.auto_enable == "NONE" ? "DISABLED" : "ENABLED"
dynamic "additional_configuration"
{
for_each =
try(each.value.additional_configuration, [])
content {
status =
additional_configuration.value.auto_enable == "NONE" ? "DISABLED"
: "ENABLED"
name
= additional_configuration.value.name
}
}
}
With the
complete Terraform configuration, you can now apply it to establish the Audit account
as the delegated administrator and apply organization settings to all accounts
in the target region. Note that it will take up to 24 hours for GuardDuty to automatically
enable it in all accounts. YMMV, but it took about 3 hours in the evening in
the Eastern time zone.
⚠
There is
currently an issue where the additional_configuration block
order causes differences when applying the Terraform configuration without
making any changes.
Caveats
about suspending GuardDuty in member accounts
Due to
limitations with the GuardDuty Terraform resources, GuardDuty is unfortunately
not automatically disabled when you run terraform destroy. Normally
this wouldn't be a problem for a production landing zone. However, if you are
only testing, this could lead to unexpected costs especially when GuardDuty is
a somewhat costly service.
As a
workaround, I would recommend using the AWS CLI or AWS SDK to at least suspend
GuardDuty for all members using the StopMonitoringMembers API. For your
convenience, you can use the following shell script to do so before
running terraform destroy:
#!/bin/bash
# Note: Make
sure that you set the AWS_PROFILE environment variable to "audit"
before running the script
# Get the
GuardDuty detector ID
DETECTOR_ID=$(aws
guardduty list-detectors --query DetectorIds[0] --output text)
# Disable
auto-enable organization members
aws guardduty
update-organization-configuration --detector-id $DETECTOR_ID
--auto-enable-organization-member NONE
# Loop through
each member account and disable GuardDuty
MEMBER_ACCOUNTS=$(aws
guardduty list-members --detector-id $DETECTOR_ID --query Members[*].AccountId
--output text)
for
MEMBER_ACCOUNT in $MEMBER_ACCOUNTS
do
echo "Suspending GuardDuty for account $MEMBER_ACCOUNT"
aws guardduty stop-monitoring-members
--account-ids $MEMBER_ACCOUNT --detector-id $DETECTOR_ID
done
Thant Zin Phyo@Cracky (MCT, MCE, MVP, AWS CB)
Comments
Post a Comment