Control Tower Account Factory
This is a Terraform module that will trigger the creation of a new AWS account by using Control Tower.
Under the hood, this module uses AWS Service Catalog to trigger Control Tower, as Control Tower does not currently expose any APIs to trigger it directly.
Control Tower Service Catalog YAML
The below YAML is copied from the Control Tower product in AWS Service Catalog. It is useful in knowing what parameters to pass to this Service Catalog product:
Note: some of the data below (e.g., the AllowedValues
for ManagedOrganizationalUnit
) is auto-generated for each
AWS organization, so it will vary from org to org.
AWSTemplateFormatVersion: 2010-09-09
Description: AWS Control Tower Account Factory Template (DO NOT DELETE)
Parameters:
AccountName:
Description: "Account name, the new managed Account will be created with this name."
Type: String
AllowedPattern : ".+"
AccountEmail:
Description: "Account email, must be unique for each AWS Account."
Type: String
AllowedPattern : "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"
SSOUserFirstName:
Description: "SSO user first name."
Type: String
AllowedPattern : ".+"
SSOUserLastName:
Description: "SSO user last name."
Type: String
AllowedPattern : ".+"
SSOUserEmail:
Description: "SSO user email. A new SSO user will be created for this email, if it does not exist. This SSO user will be associated with the new managed Account."
Type: String
AllowedPattern : "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"
ManagedOrganizationalUnit:
Description: "Your account will be added to this registered organizational unit. The list includes top-level and nested OUs registered with AWS Control Tower. You can search for an OU by name or ID. To manage these OUs, go to AWS Control Tower."
Type: String
AllowedValues:
- XXX (ou-abcd-12345678)
- YYY (ou-abcd-91011121)
- ZZZ (ou-abcd-34151617)
Resources:
WaitCondition:
Type: AWS::CloudFormation::WaitCondition
Properties:
Handle: WaitHandle
Timeout: 1
WaitHandle:
Type: AWS::CloudFormation::WaitConditionHandle
Troubleshooting Tips
ResourceInUseException
If you attempt to create/update/remove too many accounts from ControlTower at once, you may encounter an error in Service Catalog that looks like this:
ResourceInUseException: Account Factory cannot complete an operation on this account, because the allowed maximum of 5 concurrent account operations is exceeded. Try again later.
This is usually accompanied by this module returning outputs that look like the following:
"account_email" = "(could not get email associated with account)"
Unfortunately, this is an unrecoverable error from an AWS Provider perspective, as the provider has no insight into the fact that Service Catalog is in a bad state when it fails in this fashion, and retries will not help.
The easiest way to recover from this error is to make a small update to one of the variables that are passed into this module. For example, if you are integrating with this module via the ../control-tower-multi-account-factory module, you could change the value of something in the relevant file in the directory referenced by the account_requests_folder
, then revert your change.
e.g.
sso_user_first_name: "John"
sso_user_last_name: "Doe"
to
sso_user_first_name: "Jane"
sso_user_last_name: "Doe"
Perform an apply, then revert the change for another apply.
This workaround should only be done to correct up to five Service Catalog provisioned products at a time.
Sample Usage
- Terraform
- Terragrunt
# ------------------------------------------------------------------------------------------------------
# DEPLOY GRUNTWORK'S CONTROL-TOWER-ACCOUNT-FACTORY MODULE
# ------------------------------------------------------------------------------------------------------
module "control_tower_account_factory" {
source = "git::git@github.com:gruntwork-io/terraform-aws-control-tower.git//modules/landingzone/control-tower-account-factory?ref=v0.8.1"
# ----------------------------------------------------------------------------------------------------
# REQUIRED VARIABLES
# ----------------------------------------------------------------------------------------------------
# Account email, must be globally unique across all AWS Accounts.
account_email = <string>
# The name to use for the new AWS account
account_name = <string>
# The name of the organizational unit (OU) in which this account should be
# created. Must be one of the OUs in your Control Tower dashboard.
organizational_unit_name = <string>
# The list of organizational units (OUs) in which to look for the specified
# organizational_unit_name. The module will look for the OU with the specified
# name in this list.
ous = <list(object(
id = string
name = string
))>
# The email address of the user who will be granted admin access to this new
# account through AWS SSO.
sso_user_email = <string>
# The first name of the user who will be granted admin access to this new
# account through AWS SSO.
sso_user_first_name = <string>
# The last name of the user who will be granted admin access to this new
# account through AWS SSO.
sso_user_last_name = <string>
# ----------------------------------------------------------------------------------------------------
# OPTIONAL VARIABLES
# ----------------------------------------------------------------------------------------------------
# If specified, this is assumed to be the file path of a YAML file where the
# details of the new account created by this module will be written (if the
# file already exists, the module will merge its data into the file). The
# expected format of this YAML file is that the keys are the account names and
# the values are objects with the following keys: id (the account ID), email
# (the root user email address for the account).
accounts_yaml_path = null
# The amount of time allowed for the create operation to take before being
# considered to have failed.
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
create_operation_timeout = "60m"
# The amount of time allowed for the delete operation to take before being
# considered to have failed.
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
delete_operation_timeout = "60m"
# If set to true, this module will use a Bash script to try to find the
# Control Tower provisioning artifact ID automatically. Due to a Terraform bug
# (https://github.com/hashicorp/terraform-provider-aws/issues/24362), the
# aws_servicecatalog_provisioned_product resource doesn't always find the AWS
# Service Catalog provisioning artifact ID correctly, so using this Bash
# script is our temporary workaround. This way, you don't have to set
# provisioning_artifact_id manually—and update it every time it changes! Note
# that this script requires the AWS CLI to be installed and on the PATH.
find_provisioning_artifact_id_using_script = true
# The ID of the AWS Control Tower Account Factory provisioning artifact in AWS
# Service Catalog to use. If find_provisioning_artifact_id_using_script is set
# to true, we will look up the ID automatically, and you don't need to set
# this parameter. However, if find_provisioning_artifact_id_using_script is
# false, you should set this parameter, as, due to a Terraform bug
# (https://github.com/hashicorp/terraform-provider-aws/issues/24362), the
# aws_servicecatalog_provisioned_product resource fails to look this up
# automatically. You can find the ID manually by going to the Product List in
# the AWS Service Catalog console
# (https://console.aws.amazon.com/servicecatalog/home#admin-products),
# clicking the 'AWS Control Tower Account Factory' product, and grabbing the
# ID of the latest 'active' product version from the Product Versions table at
# the bottom.
provisioning_artifact_id = null
# The amount of time allowed for the read operation to take before being
# considered to have failed.
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
read_operation_timeout = "20m"
# A map of tags to apply to the new account.
tags = {}
# The amount of time allowed for the update operation to take before being
# considered to have failed.
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
update_operation_timeout = "60m"
}
# ------------------------------------------------------------------------------------------------------
# DEPLOY GRUNTWORK'S CONTROL-TOWER-ACCOUNT-FACTORY MODULE
# ------------------------------------------------------------------------------------------------------
terraform {
source = "git::git@github.com:gruntwork-io/terraform-aws-control-tower.git//modules/landingzone/control-tower-account-factory?ref=v0.8.1"
}
inputs = {
# ----------------------------------------------------------------------------------------------------
# REQUIRED VARIABLES
# ----------------------------------------------------------------------------------------------------
# Account email, must be globally unique across all AWS Accounts.
account_email = <string>
# The name to use for the new AWS account
account_name = <string>
# The name of the organizational unit (OU) in which this account should be
# created. Must be one of the OUs in your Control Tower dashboard.
organizational_unit_name = <string>
# The list of organizational units (OUs) in which to look for the specified
# organizational_unit_name. The module will look for the OU with the specified
# name in this list.
ous = <list(object(
id = string
name = string
))>
# The email address of the user who will be granted admin access to this new
# account through AWS SSO.
sso_user_email = <string>
# The first name of the user who will be granted admin access to this new
# account through AWS SSO.
sso_user_first_name = <string>
# The last name of the user who will be granted admin access to this new
# account through AWS SSO.
sso_user_last_name = <string>
# ----------------------------------------------------------------------------------------------------
# OPTIONAL VARIABLES
# ----------------------------------------------------------------------------------------------------
# If specified, this is assumed to be the file path of a YAML file where the
# details of the new account created by this module will be written (if the
# file already exists, the module will merge its data into the file). The
# expected format of this YAML file is that the keys are the account names and
# the values are objects with the following keys: id (the account ID), email
# (the root user email address for the account).
accounts_yaml_path = null
# The amount of time allowed for the create operation to take before being
# considered to have failed.
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
create_operation_timeout = "60m"
# The amount of time allowed for the delete operation to take before being
# considered to have failed.
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
delete_operation_timeout = "60m"
# If set to true, this module will use a Bash script to try to find the
# Control Tower provisioning artifact ID automatically. Due to a Terraform bug
# (https://github.com/hashicorp/terraform-provider-aws/issues/24362), the
# aws_servicecatalog_provisioned_product resource doesn't always find the AWS
# Service Catalog provisioning artifact ID correctly, so using this Bash
# script is our temporary workaround. This way, you don't have to set
# provisioning_artifact_id manually—and update it every time it changes! Note
# that this script requires the AWS CLI to be installed and on the PATH.
find_provisioning_artifact_id_using_script = true
# The ID of the AWS Control Tower Account Factory provisioning artifact in AWS
# Service Catalog to use. If find_provisioning_artifact_id_using_script is set
# to true, we will look up the ID automatically, and you don't need to set
# this parameter. However, if find_provisioning_artifact_id_using_script is
# false, you should set this parameter, as, due to a Terraform bug
# (https://github.com/hashicorp/terraform-provider-aws/issues/24362), the
# aws_servicecatalog_provisioned_product resource fails to look this up
# automatically. You can find the ID manually by going to the Product List in
# the AWS Service Catalog console
# (https://console.aws.amazon.com/servicecatalog/home#admin-products),
# clicking the 'AWS Control Tower Account Factory' product, and grabbing the
# ID of the latest 'active' product version from the Product Versions table at
# the bottom.
provisioning_artifact_id = null
# The amount of time allowed for the read operation to take before being
# considered to have failed.
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
read_operation_timeout = "20m"
# A map of tags to apply to the new account.
tags = {}
# The amount of time allowed for the update operation to take before being
# considered to have failed.
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
update_operation_timeout = "60m"
}
Reference
- Inputs
- Outputs
Required
account_email
stringAccount email, must be globally unique across all AWS Accounts.
Details
AWS requires that the account email is less than or equal to 64 characters
account_name
stringThe name to use for the new AWS account
Details
AWS requires the account name to be at most 50 characters: https://docs.aws.amazon.com/organizations/latest/APIReference/API_Account.htmlorganizations-Type-Account-Name
However, we use the account name as part of an S3 bucket name, along with a -<REGION>-tf-state suffix, which adds up to ~25 characters.
S3 bucket names are limited to 63 characters: https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html
So, 63 - 25 = 38, so to be on the safe side, that's the limit we enforce here.
organizational_unit_name
stringThe name of the organizational unit (OU) in which this account should be created. Must be one of the OUs in your Control Tower dashboard.
ous
list(object(…))The list of organizational units (OUs) in which to look for the specified organizational_unit_name. The module will look for the OU with the specified name in this list.
list(object({
id = string
name = string
}))
sso_user_email
stringThe email address of the user who will be granted admin access to this new account through AWS SSO.
sso_user_first_name
stringThe first name of the user who will be granted admin access to this new account through AWS SSO.
sso_user_last_name
stringThe last name of the user who will be granted admin access to this new account through AWS SSO.
Optional
accounts_yaml_path
stringIf specified, this is assumed to be the file path of a YAML file where the details of the new account created by this module will be written (if the file already exists, the module will merge its data into the file). The expected format of this YAML file is that the keys are the account names and the values are objects with the following keys: id (the account ID), email (the root user email address for the account).
null
create_operation_timeout
stringThe amount of time allowed for the create operation to take before being considered to have failed. https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
"60m"
delete_operation_timeout
stringThe amount of time allowed for the delete operation to take before being considered to have failed. https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
"60m"
If set to true, this module will use a Bash script to try to find the Control Tower provisioning artifact ID automatically. Due to a Terraform bug (https://github.com/hashicorp/terraform-provider-aws/issues/24362), the aws_servicecatalog_provisioned_product resource doesn't always find the AWS Service Catalog provisioning artifact ID correctly, so using this Bash script is our temporary workaround. This way, you don't have to set provisioning_artifact_id manually—and update it every time it changes! Note that this script requires the AWS CLI to be installed and on the PATH.
true
provisioning_artifact_id
stringThe ID of the AWS Control Tower Account Factory provisioning artifact in AWS Service Catalog to use. If find_provisioning_artifact_id_using_script is set to true, we will look up the ID automatically, and you don't need to set this parameter. However, if find_provisioning_artifact_id_using_script is false, you should set this parameter, as, due to a Terraform bug (https://github.com/hashicorp/terraform-provider-aws/issues/24362), the aws_servicecatalog_provisioned_product resource fails to look this up automatically. You can find the ID manually by going to the Product List in the AWS Service Catalog console (https://console.aws.amazon.com/servicecatalog/home#admin-products), clicking the 'AWS Control Tower Account Factory' product, and grabbing the ID of the latest 'active' product version from the Product Versions table at the bottom.
null
read_operation_timeout
stringThe amount of time allowed for the read operation to take before being considered to have failed. https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
"20m"
tags
map(string)A map of tags to apply to the new account.
{}
update_operation_timeout
stringThe amount of time allowed for the update operation to take before being considered to have failed. https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicecatalog_provisioned_product#timeouts
"60m"
The email address of the newly created account
The ID of the newly created account
The ID of the Organizational Unit (OU) this account was created in.
The ARN of the Service Catalog product that was provisioned to trigger Control Tower
The ID of the Service Catalog product that was provisioned to trigger Control Tower
The outputs of the Service Catalog product that was provisioned to trigger Control Tower
The email address of the user that has been granted admin access via AWS SSO in this account
The URL of the AWS SSO login page for this account