How To Manage AWS Security Hub in AWS Organizations Using Terraform

How To Manage AWS Security Hub in AWS Organizations Using Terraform

Learn how to manage multiple accounts in AWS Security Hub using the central configuration feature with Terraform.


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:

Control Tower standard OU and account structure

The relevant accounts for our use case in the landing zone are:

  1. The Management account for the organization where AWS Organizations is configured. For details, refer to Integrating Security Hub with AWS Organizations.

  2. 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         =
  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 = [
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:${}::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 =

resource "aws_securityhub_configuration_policy_association" "org" {
  provider = aws.audit
  target_id = data.aws_organizations_organization.this.roots[0].id
  policy_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.

While cleaning up my environment, I encountered the following state-related error when attempting to destroy the aws_securityhub_configuration_policy resource: Destroying... [id=r-lzgl] 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  =
  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.


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!