The first time you stand up an AWS Organization, a new account is a manual exercise — log into the console, click through, name the OU, attach the SCP, set up the IAM Identity Center permission sets, configure CloudTrail, drop in the VPC. By the fifth account you have built either a runbook or a backlog of inconsistent accounts. Control Tower Account Factory is the AWS-managed answer; CfCT (Customizations for Control Tower) and AFT (Account Factory for Terraform) extend it for the items the base factory does not cover. This article is the procedure we follow when a client asks for a new payer-managed account, and the post-vending checklist that closes the gap.
How to verify
What the landing zone actually thinks is configured:
aws controltower get-landing-zone --landing-zone-identifier <arn>
aws controltower list-enabled-baselines
aws controltower get-enabled-baseline --enabled-baseline-identifier <id>
aws organizations describe-organization
aws sso-admin list-instances
For a vended account specifically, the most useful single check is the AWSControlTowerExecution role assumption — if you can sts:AssumeRole into it from the management account, the account is wired into the landing zone:
aws sts assume-role \
--role-arn arn:aws:iam::111122223333:role/AWSControlTowerExecution \
--role-session-name verify
A failure here usually means the account was created outside the factory (a “plain” Organizations account) and is missing the trust relationship. The fix is to enrol it via the Control Tower console — there is no API for retroactive enrolment as of writing.
What’s happening
Control Tower’s mental model has three pieces:
- Landing Zone — the org-level configuration: the OU layout, the org-wide CloudTrail, the centralized log archive and audit accounts, the IAM Identity Center setup. There is one landing zone per organization.
- Account Factory — the per-account vending machine. Creates a new account in a chosen OU, applies the enabled baseline (a set of mandatory controls), and registers it with Identity Center.
- Controls (formerly Guardrails) — preventive (SCPs) and detective (Config rules) policies attached at the OU level. Some are mandatory; many are elective.
The base factory hands you an account with CloudTrail enabled, a Config aggregator wired up, Identity Center access for a configured group, and the mandatory SCPs attached. It does not give you a VPC, an SCP baseline you wrote yourself, a Terraform state bucket, an organization-wide GuardDuty enablement, or any of the per-account tagging you actually want.
That gap is what CfCT and AFT exist to close. CfCT is a CloudFormation-stack-set framework that runs after Account Factory vends. AFT is a Terraform-based alternative that does the same thing with HCL. Either way, you commit account-customization code to a Git repo, and the pipeline runs it against new accounts as they vend.
The trap most teams hit: they treat Control Tower as a one-time setup and never come back. Two years later, the landing zone version is out of date, the controls list has grown, and a CloudFormation drift in the log-archive account means the org-wide trail has been silently dropping events for a quarter. The repaint pattern is “update the landing zone every quarter, repair drift on schedule” — not “set and forget.”
The procedure
-
Vend the account from the management account. From the AWS console under Control Tower → Account Factory → Create account. You provide an account name, an email address (must be unique across all AWS — disposable plus-address tricks work), the target OU, and a display name for Identity Center. The vending process takes 25-40 minutes; the new account email arrives once it completes.
Via the API, if you have set up Service Catalog to expose the Account Factory:
aws servicecatalog provision-product \ --product-id prod-xxxx \ --provisioning-artifact-id pa-xxxx \ --provisioned-product-name "client-prod-acct" \ --provisioning-parameters \ Key=AccountEmail,Value=aws+client-prod@example.com \ Key=AccountName,Value=client-prod \ Key=ManagedOrganizationalUnit,Value="Workloads/Prod" \ Key=SSOUserEmail,Value=ops-team@example.com \ Key=SSOUserFirstName,Value=Ops \ Key=SSOUserLastName,Value=Team -
Wait for the vending to complete, then assume into the new account. The factory provisions the
AWSControlTowerExecutionrole automatically; this is your foothold for the post-vending customization:aws sts assume-role \ --role-arn arn:aws:iam::<new-acct-id>:role/AWSControlTowerExecution \ --role-session-name post-vending -
Run the post-vending checklist. The items below are not covered by the base factory:
- Set the account alias (improves IAM login pages):
aws iam create-account-alias --account-alias client-prod-acct - Enable cost-allocation tags at the org level if not already on.
- Set the support level if higher than Basic — Business / Enterprise must be enabled per account.
- Apply your SCP baseline if you have one attached to the OU but the new account moved into a wrong OU.
- Deploy the standard VPC (or confirm “no VPC, this is a serverless account”). The default VPC AWS creates should be deleted in nearly every case.
- Enable GuardDuty / Security Hub / Inspector if your detective control list says so. These are per-region, per-account, and the org admin can enable them centrally.
- Register the account in Backup organizational policies if you use AWS Backup.
- Tag the account at the Organizations level:
cost-center,environment,owner,data-classification.
- Set the account alias (improves IAM login pages):
-
Wire the account into CfCT or AFT. If you use AFT, add an entry to your
account-requestTerraform module with the new account ID and the customization profile you want applied. The pipeline picks it up on the next run and applies the per-account Terraform.module "client_prod_request" { source = "./modules/aft-account-request" control_tower_parameters = { AccountEmail = "aws+client-prod@example.com" AccountName = "client-prod" ManagedOrganizationalUnit = "Workloads/Prod" SSOUserEmail = "ops-team@example.com" SSOUserFirstName = "Ops" SSOUserLastName = "Team" } account_customizations_name = "workload-baseline" account_tags = { cost-center = "12345" environment = "prod" owner = "team-payments" } } -
Verify the new account is fully baseline-compliant before handing over the credentials:
aws configservice describe-conformance-pack-status \ --conformance-pack-names baseline-pack aws securityhub get-findings \ --filters '{"AwsAccountId":[{"Value":"111122223333","Comparison":"EQUALS"}],"SeverityLabel":[{"Value":"HIGH","Comparison":"EQUALS"}]}' \ --max-results 50
Operational notes
- The
AWSControlTowerExecutionrole is the only path the factory uses for cross-account work. Do not delete it from a vended account, and do not modify its trust policy — both will break future landing-zone updates. - Landing-zone version updates require a manual “Update” button click in the console; the system does not auto-update. Track the current version against the latest published and schedule the update inside a maintenance window.
- Account closure is asynchronous and effectively permanent after 90 days. A vended account stays in
SUSPENDEDstate and counts toward Organizations limits during the wait. Plan for this when “we’ll just delete the sandbox account” — you can’t, for three months. - CfCT and AFT pipelines run with broad permissions. The pipeline role should have its own SCP-attached OU and an alarm on any policy drift.
- An account moved between OUs picks up the destination OU’s controls immediately but does not retroactively clean up resources that violated the new controls — a
t2instance launched in an OU that did not denyt2does not get terminated when the deny moves in.
Stack Harbor runs Control Tower for clients whose new-account vending is a 30-minute pipeline run, not a half-day of console clicks, and whose landing-zone updates land on a published quarterly schedule. If your team currently builds new AWS accounts by following a wiki page, the customer-vending pipeline is one of the first improvements that pays back the engineering hours under AWS account management.