How To Manage AWS Security Hub in AWS Organizations Using Terraform
About the use case
AWS Security Hub is a security service that helps you
manage security posture by collecting security data from AWS and third-party
sources, and enabling analysis and remediation of security issues that are
found.
Late last
year, AWS introduced new central configuration capabilities in AWS
Security Hub in the form of Security Hub configuration policies
(SHCPs). With SHCPs, we can customize many aspects of the Security Hub
configuration which can be consistently applied to all members of the
organization. This addresses many challenges with managing Security Hub across
an organization which I experienced first hand last year. It was practically
futile to build Security Hub enablement into AWS Control Tower Account Factory for Terraform (AFT)! As
this is the new best practice, we'll be using this feature.
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 Security Hub 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 Integrating Security Hub 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 Security Hub 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 Security Hub administrator account
Security Hub
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"
}
We can then
use the aws_securityhub_organization_admin_account resource to
set the delegated administrator. However, I noticed the following in the Audit account:
- After this resource is created,
Security Hub will be enabled with the default standards (AWS Foundational
Security Best Practices v1.0.0 and CIS AWS Foundations Benchmark v1.2.0).
- When the resource is deleted,
Security Hub remains enabled.
These side
effects are undesirable since ideally, we want full control over the lifecycle
and configuration of Security Hub in Terraform. To address this issue, we will
preemptively enable Security Hub in the Audit account using the aws_securityhub_account resource. Later we will also
apply the same configuration policy that will be associated to the
organization.
data "aws_caller_identity"
"audit" {
provider = aws.audit
}
resource "aws_securityhub_account"
"audit" {
provider = aws.audit
enable_default_standards = false
}
resource "aws_securityhub_organization_admin_account"
"this" {
provider = aws.management
admin_account_id =
data.aws_caller_identity.audit.account_id
depends_on = [aws_securityhub_account.audit]
}
With
the Audit account designated as the Security Hub administrator, we
can now manage the organization configuration.
Configuring
cross-region aggregation
Security Hub
provides a cross-region aggregation feature that centralizes
findings, finding updates, insights, control compliance statuses, and security
scores from multiple regions into a single region. Being able to review all
findings in one place is incredibly useful for security analysts. We can enable
this feature for all regions using the aws_securityhub_finding_aggregator resource in
Terraform as follows:
resource "aws_securityhub_finding_aggregator"
"this" {
provider
= aws.audit
linking_mode = "ALL_REGIONS"
depends_on
= [aws_securityhub_account.audit]
}
Enabling
central configuration
First, we
need to apply the organization configuration to enable central configuration.
Since the settings are defined in an configuration policy, we need to disable
all settings that are related to local configuration. We will achieve this
using the aws_securityhub_organization_configuration resource:
resource "aws_securityhub_organization_configuration"
"this" {
provider = aws.audit
auto_enable = false
auto_enable_standards = "NONE"
organization_configuration {
configuration_type = "CENTRAL"
}
depends_on = [
aws_securityhub_organization_admin_account.this,
aws_securityhub_finding_aggregator.this
]
}
⚠
If you have
enabled delegated administrator at some point prior to November 2023 when the central configuration feature was
released, you may encounter a DataUnavailableException indicating
that the organization data is still syncing when you create the organization
configuration. To resolve this error, open an AWS support case to have them fix
the data in the backend.
Creating and
associating a configuration policy
With the
organization configuration primed, we can now create and associate a
configuration policy. This can be done with the aws_securityhub_configuration_policy resource and
the aws_securityhub_configuration_policy_association resource.
For
illustration, let's assume that we want to enable only the CIS AWS Foundations Benchmark v1.4.0 standard across
the organization. We also want to disable the control [IAM.6] Hardware MFA should be enabled for the root user.
The
configuration policy can be defined in Terraform as follows:
data "aws_region"
"audit" {
provider = aws.audit
}
data "aws_partition"
"audit" {
provider = aws.audit
}
resource "aws_securityhub_configuration_policy"
"this" {
provider
= aws.audit
name
= "ExamplePolicy"
description = "This is an example
SHCP."
configuration_policy {
service_enabled = true
enabled_standard_arns = ["arn:${data.aws_partition.audit.partition}:securityhub:${data.aws_region.audit.name}::standards/cis-aws-foundations-benchmark/v/1.4.0"]
security_controls_configuration {
disabled_control_identifiers = ["IAM.6"]
}
}
depends_on =
[aws_securityhub_organization_configuration.this]
}
💡
You can find
the ARN format for the Security Hub standards here. Note that all standards are regional except for CIS
AWS Foundations Benchmark v1.2.0.
Lastly, we
will associate this configuration policy to the entire organization:
data "aws_organizations_organization"
"this" {
provider = aws.management
}
resource "aws_securityhub_configuration_policy_association"
"org" {
provider = aws.audit
target_id =
data.aws_organizations_organization.this.roots[0].id
policy_id =
aws_securityhub_configuration_policy.this.id
}
Before you
apply the Terraform configuration, there is one issue which I found while
cleaning up my environment that should be addressed in the Terraform
configuration.
Addressing a
state-related issue which causes policy deletion to fail
While
cleaning up my environment, I encountered the following state-related error
when attempting to destroy the aws_securityhub_configuration_policy resource:
aws_securityhub_configuration_policy_association.org:
Destroying... [id=r-lzgl]
aws_securityhub_configuration_policy_association.org:
Destruction complete after 2s
aws_securityhub_configuration_policy.this:
Destroying... [id=f7bf343f-af38-4b1d-9116-73f43cfb5d61]
╷
│ Error:
deleting Security Hub Configuration Policy
(f7bf343f-af38-4b1d-9116-73f43cfb5d61): operation error SecurityHub:
DeleteConfigurationPolicy, https response error StatusCode: 409, RequestID:
06f4448f-4133-412a-b89b-bda896f7fa08, ResourceConflictException: Policy
f7bf343f-af38-4b1d-9116-73f43cfb5d61 is associated with one or more accounts or
organizational units. You must disassociate the policy before you can delete
it.
However, you
can see in the first two lines in the output that the configuration policy
association is already destroyed before the attempt to destroy the policy.
After
examining the Terraform resource code and the AWS API contract, I found that
the StartConfigurationPolicyDisassociation API action does
not report the disassociation status, nor is there another API action that can
query the status. So this is not a Terraform AWS Provider bug per se and having
the issue addressed upstream seems unlikely.
As a
workaround, I turned to the time_sleep resource that can add a wait time for
resource destruction. Through trial and error, I learned that 10 seconds is
sufficient for the state to be updated. So we can update the Terraform
configuration as follows:
# Some wait
time is needed to account for state changes after the configuration policy is
disassociated
resource "time_sleep"
"aws_securityhub_configuration_policy_this" {
destroy_duration = "10s"
depends_on =
[aws_securityhub_configuration_policy.this]
}
resource "aws_securityhub_configuration_policy_association"
"org" {
provider
= aws.audit
target_id
= data.aws_organizations_organization.this.roots[0].id
policy_id
= aws_securityhub_configuration_policy.this.id
depends_on =
[time_sleep.aws_securityhub_configuration_policy_this]
}
With this
change, the full Terraform configuration can be destroyed successfully.
With the
complete Terraform configuration, you can now apply it to establish the Audit account
as the delegated administrator and apply the SHCP to all accounts and all
regions (as per the finding aggregator settings).
Caveats
about disabling Security Hub in member accounts
Due to the
design of the Security Hub API and the Terraform resources, Security Hub will
not be disabled in the member accounts 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 left
running in all accounts and all regions.
Since it would
be difficult to disable Security Hub in individual account, a smarter way would
be to disable Security Hub using the SHCP. This can be done by changing
the aws_securityhub_configuration_policy.this resource
definition to the following:
resource "aws_securityhub_configuration_policy"
"this" {
provider
= aws.audit
name
= "ExamplePolicy"
description = "This is an example
SHCP."
configuration_policy {
service_enabled = false
}
depends_on =
[aws_securityhub_organization_configuration.this]
}
After you
re-apply the Terraform configuration, Security Hub should be disabled in all
accounts and all regions. Then you can safely run terraform destroy to
remove the remaining Security Hub resources and configuration.
Thant Zin Phyo@Cracky (MCT, MCE, MVP, AWS CB)
Comments
Post a Comment