Integration & Automation
Multiple-account, multiple-Region AWS CloudFormation
AWS CloudFormation nested stacks provide a great way to break down templates into reusable components and logically separate groups of resources. To do this, you can use the AWS::CloudFormation::Stack
resource type, which launches the child stack into the same account, AWS Region, and AWS Identity and Access Management (IAM) identity as the parent.
But what if you don’t want the child stack in the same account or Region as the parent stack? Examples of this use case include disaster-recovery stacks that place backups into a different Region, or CI/CD pipelines that are run centrally and manage resources in dev, QA, and prod accounts. In this post, I will cover a custom resource that behaves similarly to the native resource type but allows the customer to specify a target account, Region, and IAM role for the child stack. There are many more use-cases where multi-account or cross-region CloudFormation stacks can be useful.
The example launches a CloudFormation stack in a central account (CentralAccount) that provisions child stacks, each provisioning an Amazon Simple Storage Service (Amazon S3) bucket, into another account (DevAccount) in two different Regions.
To start using the cross-account custom resource in your own stacks, or to browse the example templates covered in this post, check it out in GitHub.
Getting set up
To complete the steps in the following example walkthrough, you can use the AWS Management Console, AWS Command Line Interface (AWS CLI) or SDKs. I’m going to use the AWS CLI, which I set up with two profiles, one called DevAccount and one called CentralAccount. Ideally, the two profiles should be configured with credentials from two different accounts, but if you do not have access to two different accounts, you can test it all in one account by pointing both profiles at the same account. Instructions on configuring AWS CLI to use profiles are available in the AWS CLI documentation.
I need to create an IAM role in each account. The role in CentralAccount will be granted permission to assume the DevAccount role. The DevAccount role will have a trust policy that trusts the role in CentralAccount, and it will have permissions to manage the CloudFormation stacks and the S3 buckets that the example stack will create. When using this with your own templates, expand the target account (DevAccount) policy to include any resources that your template provisions. For more information on how cross-account IAM works, see the IAM documentation.
To simplify this, I’ve created central-iam.yaml
and dev-iam.yaml
AWS CloudFormation templates to provision the example roles. These templates each require the other’s role name to be provided, so we have what seems like a circular dependency problem. To deal with this, I will hardcode the role names instead of letting AWS CloudFormation autogenerate them. The downside of this approach is that you cannot launch more than one of these templates in a single account, as the name will collide. If you want to have more than one role, you will need to specify a unique name for the RoleName
parameter for each additional stack.
To launch these stacks, I will need the AWS account ID for each account. If you don’t know the account IDs, you can get them from the AWS CLI by using the sts get-caller-identity
command.
To launch the CentralAccount stack and create the role, I use the create-stack
command. Be sure to replace <DEV_ACCOUNT_ID>
with the AWS account ID for DevAccount.
We need to wait for the stack to reach CREATE_COMPLETE
, because when the DevAccount role is created, the IAM service will validate the Role ARN in the trust policy and transform it to a unique ID for the cross-account trust. More information on this is in the IAM documentation.
You can run the describe-stacks command periodically to check the stack status until CREATE_COMPLETE
is shown in the output. Note the CentralAccount ARN
value, as you’ll need it later on.
Now, using the DevAccount profile, I create the DevAccount role. Be sure to replace <CENTRAL_ACCOUNT_ID>
with the AWS account ID of the CentralAccount stack.
We’ll use the same describe-stacks
command against the DevAccount stack to get the ARN that we will need later on. The ARN will be available only when the stack reaches the CREATE_COMPLETE
state.
That completes the prerequisites. In a scenario where you want a central account to create stacks in a group of other accounts, you need to create the central role only once. The target roles can be created to delegate trust to the central account as part of the provisioning process for new accounts.
Where the magic happens
With the needed IAM roles in place, we can start to create AWS CloudFormation templates that use the roles to deploy resources across multiple accounts. Let’s have a look at the cross-account.yaml
template. It contains an AWS CloudFormation custom resource to launch the provided template into the remote account and Region. Here’s a snippet showing a cross-account custom resource declaration:
The TemplateUrl
property is pointed at the template that will be launched, and the CfnParameters
property provides values for the template’s parameters. In this case, we’ve just got a Tag
parameter.
Using this custom resource in your own stacks, you can easily enable cross-account provisioning for your existing template library. You can also have a look at the Quick Start catalog, which provides reference architectures for popular workloads, all of which can be enabled for cross-account provisioning by using this custom resource.
Launching the stack
Let’s go ahead and launch the stack. You will need to replace <CENTRALACCOUNT_ROLE_ARN>
and <DEVACCOUNT_ROLE_ARN>
with the ARNs that you obtained from the outputs in the Getting set up section of this post.
Again, we can keep an eye on progress by using the describe-stacks
command.
After the stack reaches CREATE_COMPLETE
, the buckets in Stockholm and Tokyo are created. Let’s have a look at the CloudFormation stacks in DevAccount to confirm.
In the output, you should see the CloudFormation stack names, and that they are in the CREATE_COMPLETE
state.
Cleanup
Let’s use the delete-stack
command to quickly clean up all the stacks we created in this walkthrough. We’ll need to do the cross-account-buckets stack first, seeing as it needs to use the roles in the other stacks.
You can use the same describe-stacks
command that you used to check on the progress when creating the stack. After delete-stack
has completed, we can delete the two roles that we created in the prerequisites.
Next steps
The AWS Lambda function source code and the examples in this post are available on GitHub in the cloudformation-cross-account folder in the quickstart-examples repository. Check it out to start building your multi-account infrastructure-as-code templates using AWS CloudFormation. You can use GitHub issues for feature requests, and the comments section below to let us know how you’re using this custom resource in your environment.