Tooling to deploy an AWS backend for Terraform state, using Ansible and CloudFormation.
This sets up the AWS resources that Terraform needs, in a way that does not depend on Terraform.
The CloudFormation templates deploy a set of replicated S3 buckets and a DynamoDB table. Each bucket has object versioning enabled, so that previous versions of the state files can be restored. All storage is encrypted with KMS customer-managed keys.
To increase the safety of the Terraform state, the policy on the main S3 bucket prevents objects from being deleted.
Ansible provides a convenient way to deploy sets of CloudFormation templates together. You may use the CloudFormation templates without Ansible.
This project deliberately does not follow a standard directory structure for Ansible. By design it completely separates the files for Ansible and CloudFormation.
It assumes that you have at least two AWS accounts:
- A managing AWS account that will contain the Terraform backend
- One or more managed AWS accounts that will contain resources that will be managed by Terraform
You should always use more than one AWS account in your infrastructure. AWS accounts are free, because you are only charged for the resources that you use.
If you wish, you can use Terraform to control resources in the managing AWS account. The aim of this tooling is to set up just the AWS resources that Terraform itself needs.
The deployed resources include an IAM user account. This user account can assume the roles to access the Terraform storage, and execute Terraform on each AWS account. Your automation can then use this account to run Terraform on AWS.
The org_prefix should be a short string that uniquely identifies the current organization. S3 bucket names must be globally unique across all AWS customers, so we must prefix the name of each bucket with a string that is unique to our accounts. The playbooks also use the org_prefix to namespace the AWS tags that they apply.
Ansible requires Python 3. You may run Ansible on Linux, macOS or WSL.
Run these commands to install Ansible:
pip3 --user pipx
pipx install ansible-core
pipx inject ansible-core boto3
ansible-galaxy install -r requirements.yml
First decide an org_prefix, a short string that uniquely identifies your organization. This is used to ensure consistent and unique naming.
Choose appropriate regions for your resources. The examples use eu-west-2 as the AWS region for the main bucket, with replica buckets in the eu-west-1 and eu-central-1 regions.
The examples below take variables from the command-line. You may create YAML or JSON files for the variables instead. This example reads variables from a YAML file called tf-identities-vars.yml:
ansible-playbook --connection=local ./ansible/deploy-tf-identities-playbook.yml --extra-vars "@tf-identities-vars.yml"
Run the playbook ./ansible/deploy-tf-exec-role-playbook.yml to create the IAM role that Terraform will use to run on each AWS account.
To run the Ansible playbook for Terraform execution role on an AWS account:
ansible-playbook --connection=local ./ansible/deploy-tf-exec-role-playbook.yml --extra-vars "managing_account_id=MANAGING-AWS-ACCOUNT-ID org_prefix=YOUR-ORG-PREFIX stack_prefix=tf-exec"
Run the playbook deploy-tf-storage-playbook.yml for Terraform storage.
ansible-playbook --connection=local ./ansible/deploy-tf-storage-playbook.yml --extra-vars "org_prefix=YOUR-ORG-PREFIX stack_prefix=tf-state primary_region=eu-west-2 replica_region_001=eu-west-1 replica_region_002=eu-central-1"
Get the ARNs of the resources that were generated by this playbook.
Run the playbook deploy-tf-backend-access-playbook.yml to deploy access to the Terraform backend storage
ansible-playbook --connection=local ./ansible/deploy-tf-backend-access-playbook.yml --extra-vars "org_prefix=YOUR-ORG-PREFIX stack_prefix=tf-access-svc managing_account_id=MANAGING-AWS-ACCOUNT-ID kms_key_arn=PRIMARY_KMS_KEY_ARN s3_bucket_arn=arn:aws:s3:::YOUR-ORG-PREFIX-tf-state-primary-MANAGING-AWS-ACCOUNT-ID-eu-west-2 ddb_table_arn=arn:aws:dynamodb:eu-west-2:MANAGING-AWS-ACCOUNT-ID:table/YOUR-ORG-PREFIX-tf-state-lock-eu-west-2"
Run the playbook deploy-tf-identities-playbook.yml to deploy IAM users and groups.
ansible-playbook --connection=local ./ansible/deploy-tf-identities-playbook.yml --extra-vars "org_prefix=YOUR-ORG-PREFIX stack_prefix=tf-access managing_account_id=MANAGING-AWS-ACCOUNT-ID iam_role_arns=arn:aws:iam::MANAGING-AWS-ACCOUNT-ID:role/YOUR-ORG-PREFIX-tf-backend-access-role,arn:aws:iam::MANAGING-AWS-ACCOUNT-ID:role/YOUR-ORG-PREFIX-tf-exec-role,arn:aws:iam::FIRST-MANAGED-AWS-ACCOUNT-ID:role/YOUR-ORG-PREFIX-tf-exec-role,arn:aws:iam::SECOND-MANAGED-AWS-ACCOUNT-ID:role/YOUR-ORG-PREFIX-tf-exec-role"
Once this playbook has completed, you will have a new IAM user account. This account can assume the roles to access the Terraform storage, and execute Terraform on each AWS account.
Create an AWS access key for the user, and configure your CI system to use it. Your CI system can then deploy to the AWS accounts with Terraform.
- Terraform documentation for state storage with AWS
- Terragrunt requirements for AWS
- CloudFormation for a Terraform backend, by Tibor Hercz - An existing implementation, using just CloudFormation
- Managing Terraform Remote State with CloudFormation, by Chris Kent - Part of a series on setting up Terraform