AWS Cloud Operations & Migrations Blog

Deploy and Customize AWS accounts using Account Factory for Terraform in AWS Control Tower

Customers use AWS Control Tower Account Factory to create a new AWS account or enroll existing AWS accounts in their AWS Organizations. Customers launch Account Factory from the AWS Control Tower console or via AWS Service Catalog API. We hear from customers that they want to manage their AWS accounts in the same way that they manage their AWS infrastructure using Terraform. We launched AWS Control Tower Account Factory for Terraform (AFT) at re:Invent 2021, a new Terraform module to provision and customize AWS accounts using Terraform. In this post, we’ll walk you through the steps to deploy a new AWS account using AFT, and then we’ll show you how to customize the account created via AFT.

AFT follows a GitOps model and sets up a Terraform pipeline to provision and customize AWS accounts in AWS Control Tower. You create an account request in Terraform and commit to the repository that triggers the AFT workflow for Account Factory. After Account Factory execution is complete, AFT runs additional customization steps automatically.

AFT uses four separate repositories to manage this entire workflow. Let’s review each repository and its function:

  • aft-account-request – handles placing or updating account requests. This is where you place a request for a new AWS account.
  • aft-global-customizations – manages Terraform-based customizations that are applicable to all of the accounts created by and managed with AFT.
  • aft-account-customizations – manages Terraform-based customizations that are applicable to specific accounts created by and managed with AFT.
  • aft-account-provisioning-customizations – manages customizations using AWS Step Functions. The state machines can use AWS Lambda functions as an example to communicate with external applications. This stage runs before the global and account level customizations stage.

Deployment overview

Before we go into the walkthrough details, let’s discuss what we’ll be deploying. First, we’ll create a new AWS account by submitting a request to the aft-account-request repository. AWS Control Tower Account Factory provisions the new AWS account, and the AFT pipeline starts to apply the customizations. We add Terraform customization in the aft-global-customizations repository to apply Amazon S3 Block public access for all accounts managed by AFT. We also add Terraform customization in aft-account-customizations for two categories: PRODUCTION and SANDBOX. For PRODUCTION, we add a custom guardrail using an AWS Config Rule to check if VPC Flow Logs is enabled. In SANDBOX customization, we add Terraform configuration to setup monthly AWS Budget. Inside the account request, we specify whether or not to use PRODUCTION or SANDBOX category for account-level customization.

In this diagram, the AFT management account has the AWS Codepipeline used for new account requests via AFT. It also shows the codepipeline workflow for global and account customizations.

Figure 1. AFT diagram describing gitops workflow of new account request, AWS Control Tower Account Factory and AFT pipeline to deploy account and global customizations.

Prerequisites

Here are the prerequisites before you start:

  • You should have an AWS Control Tower environment deployed and available. Follow the getting started guide if you need to launch AWS Control Tower.
  • You should have an AFT deployed on a dedicated AFT Management account. Follow the guide on this post to set up AFT.
  • A new root email address for a new vended AWS account that you’ll submit through AFT.
  • A new or existing Organizational Units (OU) governed by AWS Control Tower, which is needed as part of new account request parameter in AFT.
  • Integrated development environment (IDE) with Git, Terraform, and AWS Command Line Interface (AWS CLI) installed. Your IDE environment must be configured with AWS credentials to your AFT Management account.
  • Make sure to specify the AWS Control Tower home region in the commands where applicable

In this post, we use AWS CodeCommit as the version control system (VCS). To learn more about third-party VCS and details for each prerequisite, see the AFT getting started documentation.

Account customizations repo

You can target a specific set of customizations during the new account request by specifying the parameter account_customizations_name inside the aft-account-request module. We’ll discuss in-depth about the aft-account-request in a later section. For now, it’s sufficient to just be aware of this parameter. We’ll create two categories of customizations: PRODUCTION and SANDBOX. When we request a new account, we specify which customization template to choose from: PRODUCTION or SANDBOX.

  1. From the IDE terminal, run the following commands. These commands will clone the sample repository and push it to your aft-account-customizations repository. Replace the placeholder AWS_REGION value with your Control Tower home region.
cd ~/environment/
AWS_REGION=<<enter your Control Tower home region>>
AFT_ACCOUNT_CUSTOMIZATIONS_REPO=`aws ssm get-parameter --name /aft/config/account-customizations/repo-name --region $AWS_REGION | jq -r ".Parameter.Value"`
AFT_ACCOUNT_CUSTOMIZATIONS_BRANCH=`aws ssm get-parameter --name /aft/config/account-customizations/repo-branch --region $AWS_REGION | jq -r ".Parameter.Value"`
AFT_ACCOUNT_CUSTOMIZATIONS_HTTP=`aws codecommit get-repository --repository-name $AFT_ACCOUNT_CUSTOMIZATIONS_REPO --region $AWS_REGION | jq -r ".repositoryMetadata.cloneUrlHttp"`
git clone --branch $AFT_ACCOUNT_CUSTOMIZATIONS_REPO https://github.com/aws-samples/aft-workshop-sample $AFT_ACCOUNT_CUSTOMIZATIONS_REPO
cd $AFT_ACCOUNT_CUSTOMIZATIONS_REPO
rm -rf .git
git init
git remote add origin $AFT_ACCOUNT_CUSTOMIZATIONS_HTTP
git add .
git commit -m 'first commit'
git branch -m $AFT_ACCOUNT_CUSTOMIZATIONS_BRANCH
git push --set-upstream origin $AFT_ACCOUNT_CUSTOMIZATIONS_BRANCH
  1. Open the main.tf file on the aft-account-customizations/PRODUCTION/terraform directory. This is a Terraform configuration which creates an AWS Config guardrail to check if VPC Flow Logs are enabled.
  2. Open the main.tf file on the aft-account-customizations/SANDBOX/terraform directory. This is a Terraform configuration which sets a monthly budget of $100 on the account that you’re creating with this customization.
  3. Open the api_helpers directory, which is where you add bash/python script to further customize your account.

To add additional account level customization, create a new directory following the same structure. You specify the account_customization_name when creating a new account request, and AFT applies all of the Terraform configurations specified in the specific customization directory.

To use custom variables in your Terraform configuration, you pass the custom_fields value during the account request. These custom fields will be available as the AWS Systems Manager (SSM) Parameter Store in the vended AWS account under path /aft/account-request/custom-fields/{field-name}. In your Terraform configuration, you can retrieve these custom fields using data source. We’ll discuss in depth about custom_fields in a later section.

Global customizations repo

You can apply customizations to all of the accounts managed by AFT. For example, in this post, we’ll configure Amazon S3 Block Public Access to block public access to every Amazon Simple Storage Service (Amazon S3) bucket in all of the accounts managed by AFT.

  1. From the IDE terminal, run the following command. These commands will clone the sample repository and push it to your aft-global-customizations repository. Replace the placeholder AWS_REGION value with your Control Tower home region.
cd ~/environment/
AWS_REGION=<<enter your Control Tower home region>>
AFT_GLOBAL_CUSTOMIZATIONS_REPO=`aws ssm get-parameter --name /aft/config/global-customizations/repo-name --region $AWS_REGION | jq -r ".Parameter.Value"`
AFT_GLOBAL_CUSTOMIZATIONS_BRANCH=`aws ssm get-parameter --name /aft/config/global-customizations/repo-branch --region $AWS_REGION | jq -r ".Parameter.Value"`
AFT_GLOBAL_CUSTOMIZATIONS_HTTP=`aws codecommit get-repository --repository-name $AFT_GLOBAL_CUSTOMIZATIONS_REPO --region $AWS_REGION | jq -r ".repositoryMetadata.cloneUrlHttp"`
git clone --branch $AFT_GLOBAL_CUSTOMIZATIONS_REPO https://github.com/aws-samples/aft-workshop-sample $AFT_GLOBAL_CUSTOMIZATIONS_REPO
cd $AFT_GLOBAL_CUSTOMIZATIONS_REPO
rm -rf .git
git init
git remote add origin $AFT_GLOBAL_CUSTOMIZATIONS_HTTP
git add .
git commit -m 'first commit'
git branch -m $AFT_GLOBAL_CUSTOMIZATIONS_BRANCH
git push --set-upstream origin $AFT_GLOBAL_CUSTOMIZATIONS_BRANCH
  1. Open the main.tf file on the aft-global-customizations/terraform directory. This is a Terraform configuration that blocks all public Amazon S3 access when deployed in the account.

Both account level and global customizations will be executed inside of the account-specific Code Pipeline. Global customizations will be applied ahead of the account level customizations. You won’t see this pipeline yet, since we haven’t submitted a new account request.

Provisioning customizations repo

Provisioning customizations utilize Step Functions State Machine to execute custom logic, and non-Terraform configuration as part of account provisioning. For example, you may require integration with external systems, such as ticketing system, security, identity, network, or cost management. Provisioning customization are implemented prior to the account and global customization stages.

In this post, we’ll use a simple state machine that performs pass operations to demonstrate the basic functionality.

  1. From the IDE terminal, run the following command to copy example account customization. Replace the placeholder AWS_REGION value with your Control Tower home region.
cd ~/environment/
AWS_REGION=<<enter your Control Tower home region>>
AFT_PROVISIONING_CUSTOMIZATIONS_REPO=`aws ssm get-parameter --name /aft/config/account-provisioning-customizations/repo-name --region $AWS_REGION | jq -r ".Parameter.Value"`
AFT_PROVISIONING_CUSTOMIZATIONS_BRANCH=`aws ssm get-parameter --name /aft/config/account-provisioning-customizations/repo-branch --region $AWS_REGION | jq -r ".Parameter.Value"`
AFT_PROVISIONING_CUSTOMIZATIONS_HTTP=`aws codecommit get-repository --repository-name $AFT_PROVISIONING_CUSTOMIZATIONS_REPO --region $AWS_REGION | jq -r ".repositoryMetadata.cloneUrlHttp"`
git clone --branch $AFT_PROVISIONING_CUSTOMIZATIONS_REPO https://github.com/aws-samples/aft-workshop-sample $AFT_PROVISIONING_CUSTOMIZATIONS_REPO
cd $AFT_PROVISIONING_CUSTOMIZATIONS_REPO
rm -rf .git
git init
git remote add origin $AFT_PROVISIONING_CUSTOMIZATIONS_HTTP
git add .
git commit -m 'first commit'
git branch -m $AFT_PROVISIONING_CUSTOMIZATIONS_BRANCH
git push --set-upstream origin $AFT_PROVISIONING_CUSTOMIZATIONS_BRANCH
  1. Open the terraform/states/customizations.asl.json file to examine the State Machine definition.
  2. This State Machine has a single state with type Pass. This state passed its input to the output without doing any work.
  3. Open the aft-account-provisioning-customizations/terraform /states.tf file to examine the Terraform code which builds the Step Function State Machine using the template file .asl.json earlier.
  4. Updating to this repository triggers the provisioning customization pipeline. Navigate to AWS CodePipeline and locate the ct-aft-account-provisioning-customizations pipeline. This pipeline runs after you commit to the repository and build the state machine.
  5. After the pipeline has completed successfully, navigate to Step Functions and locate the aft-account-provisioning-customizations state machine.

Account request repo

To create and update the AWS account using AFT, use the aft-account-request Terraform module. Provide the mandatory input, such as account root email address and the OU. Each time that you add or modify the account request, the Terraform file is committed to the repository and will trigger the AFT pipeline.

  1. From the IDE terminal, run the following command to copy the example account customization. Replace the placeholder AWS_REGION value with your Control Tower home region.
cd ~/environment/
AWS_REGION=<<enter your Control Tower home region>>
AFT_ACCOUNT_REQUEST_REPO=`aws ssm get-parameter --name /aft/config/account-request/repo-name --region $AWS_REGION | jq -r ".Parameter.Value"`
AFT_ACCOUNT_REQUEST_BRANCH=`aws ssm get-parameter --name /aft/config/account-request/repo-branch --region $AWS_REGION | jq -r ".Parameter.Value"`
AFT_ACCOUNT_REQUEST_HTTP=`aws codecommit get-repository --repository-name $AFT_ACCOUNT_REQUEST_REPO --region $AWS_REGION | jq -r ".repositoryMetadata.cloneUrlHttp"`
git clone --branch $AFT_ACCOUNT_REQUEST_REPO https://github.com/aws-samples/aft-workshop-sample $AFT_ACCOUNT_REQUEST_REPO
cd $AFT_ACCOUNT_REQUEST_REPO
rm -rf .git
git init
git remote add origin $AFT_ACCOUNT_REQUEST_HTTP
git add .
git commit -m 'first commit'
git branch -m $AFT_ACCOUNT_REQUEST_BRANCH
git push --set-upstream origin $AFT_ACCOUNT_REQUEST_BRANCH
  1. Updating to this repository triggers the account request pipeline.
  2. Navigate to CodePipeline and confirm that your ct-aft-account-request pipeline status is Succeeded. It may take a few minutes for the pipeline to complete.
  3. So far, we only populate the boilerplate without yet submitting a real account request. In the next section, we’ll submit a new account request.

Provision new account

Now we’ll provision a new AWS account via AFT using the SANDOX customization that we configured earlier.

  1. From your IDE, navigate to the aft-account-request/terraform directory.
  2. Create a new file, and give it a name account-requests.tf
  3. Add the following Terraform code and replace the placeholder {{PLACEHOLDER NAME}} with your own value, for example for the account email, name, OU, and SSO. Make sure that you set account_customizations_name with either PRODUCTION or SANDBOX. You can modify other values such as tags and custom fields.
module "account_request_01" {
  source = "./modules/aft-account-request"

  control_tower_parameters = {
    AccountEmail              = "{{ACCOUNT EMAIL}}"
    AccountName               = "{{ACCOUNT NAME}}"
    ManagedOrganizationalUnit = "{{ACCOUNT OU}}" 
    SSOUserEmail              = "{{ACCOUNT SSO EMAIL}}"
    SSOUserFirstName          = "{{ACCOUNT SSO FIRST NAME}}"
    SSOUserLastName           = "{{ACCOUNT SSO LAST NAME}}"
  }

  account_tags = {
    "ABC:Owner"       = "myname@mycompany.com"
    "ABC:Division"    = "ENT"
    "ABC:Environment" = "Dev"
    "ABC:CostCenter"  = "123456"
    "ABC:Vended"      = "true"
    "ABC:DivCode"     = "102"
    "ABC:BUCode"      = "ABC003"
    "ABC:Project"     = "123456"
  }

  change_management_parameters = {
    change_requested_by = "AWS Control Tower Lab"
    change_reason       = "Learn AWS Control Tower Account Factory for Terraform (AFT)"
  }

  custom_fields = {
    custom1 = "a"
    custom2 = "b"
  }

  account_customizations_name = "SANDBOX"
}
  1. Save the file and run the following code to commit to the repository. Replace the placeholder AWS_REGION value with your Control Tower home region.
cd ~/environment/
AWS_REGION=<<enter your Control Tower home region>>
AFT_ACCOUNT_REQUEST_REPO=`aws ssm get-parameter --name /aft/config/account-request/repo-name --region $AWS_REGION | jq -r ".Parameter.Value"`
AFT_ACCOUNT_REQUEST_BRANCH=`aws ssm get-parameter --name /aft/config/account-request/repo-branch --region $AWS_REGION | jq -r ".Parameter.Value"`
cd ~/environment/$AFT_ACCOUNT_REQUEST_REPO
git add . 
git commit -m 'first account request' 
git push origin $AFT_ACCOUNT_REQUEST_BRANCH
  1. Once you commit the account request to the repository, AFT will trigger the Account Factory process in the AWS Control Tower management account.

This process will take some time. You can monitor the Account Factory process from the Service Catalog console in the AWS Control Tower Management account. After AWS Control Tower Account Factory is complete, the AFT pipeline continues with the customizations. You can monitor the AFT customization status by reviewing the execution of the Step function aft-account-provisioning-framework from within the AFT Management account.

Validate the result

To recap what we’ve completed so far, the newly provisioned AWS account will have global customization, which blocks Amazon S3 public access and SANDBOX customization. This sets a $100 spending Budget on the account. Follow these steps to validate the result:

  1. Sign in to the new AWS account that you provision from AFT.
  2. Open the Amazon S3 console here.
  3. Choose Block Public Access settings for this account.
  4. Observe that all of the settings should be ON.
  5. If you set account customizations name as SANDBOX, then go to AWS Budgets here.
  6. Locate the newly created budget called ‘budget-monthly-total’ set for $100.00.
  7. If you set the account customizations name as PRODUCTION, then navigate to AWS Config and locate the AWS Config rule vpc_flow_log_guardrail.
  8. Navigate to SSM, and select Parameter Store. Locate two SSM Parameter custom1 and custom2 (parameter name may vary if you modify the example provided earlier).
  9. These parameters originated from the custom_fields attributes that you specify in the aft-account-request repository. You can use it as a data source in your global/account level customizations.

This completes the validation steps. You have successfully setup AFT repository with sample customizations and launched a new AWS account via AFT.

Other considerations

By using AFT, you can submit multiple account requests simultaneously. AFT will queue each individual account request, and AWS Control Tower Account Factory will process it sequentially. AFT also lets you import existing AWS accounts governed by AWS Control Tower. To use this feature, make sure that you update your AFT module to the latest version. Importing an existing AWS account is similar to requesting a new AWS account. You define the existing AWS account in the aft-account-request repository and trigger the pipeline to run.

To automatically store custom_fields as SSM parameters in the vended account, make sure that you upgrade your AFT module to the latest version. Note that if a custom field is removed from the account request, then the corresponding SSM Parameter Store will also be removed. At the time of writing this post, AFT only supports the standard string value for the SSM Parameter Store. If you must retrieve sensitive secrets, then consider using the pre- or post-API helpers to retrieve it from SSM.

AFT global customizations run before the account level customizations. Note that the update to aft-global-customizations or aft-account-customizations repository won’t automatically trigger the pipeline for each vended account. To re-baseline each vended account after you add new customizations, use the aft-invoke-customizations state machine. See the example event schema here. AFT can invoke a maximum of five customizations at a time. The step function waits and loops until all of the accounts matching the event criteria are complete.

Conclusion

In this post, you learned how to create a new AWS account in AFT, and how to apply global and account level customizations. If you are interested in diving deeper into AFT setup and repository, then see the AFT lab series in AWS Control Tower workshop. If you’re looking for an additional example tutorial, then see the tutorial in HashiCorp Terraform learn portal and additional samples in AFT GitHub repository. You can also report any issues or questions by visiting the AFT GitHub repository. Don’t forget to check our future post where we’ll dive deeper into account provisioning customization using Step Functions.

Authors:

Gaurav Gupta

Gaurav Gupta is a Sr. Product Manager with specialized skills in AWS Control Tower, AWS Service Catalog and AWS Marketplace. His passion is to drive adoption of AWS Cloud services for diverse customer workloads. In his spare time, he plays competitive pool, reads, and travel.

Welly Siauw

Welly Siauw is a Sr. Partner Solution Architect at Amazon Web Services (AWS). He spends his day working with customers and partners, solving architectural challenges. He is passionate about service integration and orchestration, serverless and artificial intelligence (AI) and machine learning (ML). He authored several AWS blogs and actively leading AWS Immersion Days and Activation Days. Welly spends his free time tinkering with espresso machine and outdoor hiking.