Introduction
Earlier I've published the blog post How To Manage Amazon GuardDuty in AWS Organizations Using Terraform which is essential in establishing threat detection as part of a security baseline, such as the AWS Security Baseline which I covered extensively in the blog series How to implement the AWS Startup Security Baseline (SSB) using Terraform.
Similarly, a good security baseline must include the means to manage the security posture, achieved using Security Hub in AWS. In this blog post, I will walk you through the steps to configuring Security Hub with central configuration in 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.
✅ You can find the complete Terraform in the GitHub repository that accompanies this blog post.
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.
Summary
In this blog post, you learned how to implement central configuration to manage AWS Security Hub in AWS Organizations using Terraform. By consolidating all management work of all accounts in an organization and all regions into a delegated administrator account, you now have a single pane of glass to review and manage your cloud security posture.
For more tips and walkthroughs on AWS, Terraform, and more, please check out the Avangards Blog. Thanks for reading!
Top comments (2)
Excellent level of detail. :-)
"Leverage Terraform's AWS provider to automate Security Hub configuration and compliance across multiple accounts."
Thank you for the insightful information on managing AWS Security Hub within AWS Organizations using Terraform!