Terraform and AWS Multi account Setup?
This article is focused on how to use AWS multiple account setup with Terraform. Terraform is a great tool for provisioning your cloud infrastructure but as you start using Terraform for managing your cloud infrastructure you will always feel a need for multiple AWS accounts that can cater to some specific environment such as - development, test, stage, and production.
If you are following DevOps practices for managing the AWS account then it is also recommended by AWS to use multiple accounts for managing your cloud infrastructure.
Benefits of Using Multiple AWS Accounts
- Granular Security - With multiple AWS accounts, you can create an AWS account for each environment (development, test, stage, production), organization level, or department.
- No need for one big AWS account - The other big benefit of using multiple AWS accounts is you do not need to maintain a single AWS account for managing your complete cloud infrastructure. Having a single AWS account poses more security risks for your overall cloud setup.
- Multiple AWS accounts are independent of each other - If you have a multiple AWS account setup then it is really easy for cloud practitioners to keep the AWS accounts separate from each other which eases the Creation and Deletion of AWS accounts.
- AWS Organizations - The other good reason for opting for a multi-account AWS setup is AWS Organization. It allows you to create AWS accounts with pre-sets of resources.
- Least Privileges - When you have multiple accounts then the administrator of one AWS account can not view or access another AWS account which by default implements the principle of least privileges.
But how to implement the multiple AWS Account concept using Terraform? -
There are a few ways to achieve this:
- By creating provider aliases
- Using Terraform workspaces
- AWS Organizations and Assume Role
1. By creating provider aliases
Let’s take an example to implement provider aliases for AWS multi-account setup using Terraform
1. First, define the provider -
1terraform {
2 required_providers {
3 aws = {
4 source = "hashicorp/aws"
5 version = "~> 4.11.0"
6 }
7 }
8}
Each public cloud has a separate provider configuration which is used for API interactions and authentication. If you are using different public cloud providers such as Google Cloud or Azure then change the configuration by referring to their documentation.
2. Multiple provider configuration for multiple users -
Now after defining the required provider configuration we are going to write the terraform configuration for two accounts "dev" and "prod" using the same main.tf file.
1# Development account
2provider "aws" {
3 profile = "dev"
4 region = var.region_dev
5}
6
7#Production account
8provider "aws" {
9 profile = "prod"
10 region = var.region_prod
11 alias = "prod"
12}
In the above configuration we are using two terraform variables “var.region_dev” and “var.region_prod” both the variables we are going to define separately into the “variables.tf” file.
Variables.tf
Here are variables.tf file which you need for this example -
1variable "region_dev" {
2 type = string
3 default = "eu-central-1"
4}
5
6variable "region_prod" {
7 type = string
8 default = "eu-central-1"
9}
As you can see we have defined two string type terraform variables that contain the region where terraform is going to apply the configuration.
3. Mapping AWS resources to the respective AWS account
In the previous step we have created the aliases for DEV and PROD environments, let’s try to map resources that we want to provision in DEV as well as in PROD environments.
To make the example easy we will create two S3 Bucket one in DEV and another in the PROD environment.
1resource "aws_s3_bucket" "dev_s3_bucket" {
2 bucket = "S3-Dev-Bucket"
3 acl = "public-read"
4 lifecycle {
5 prevent_destroy = true
6 }
7}
8
9resource "aws_s3_bucket" "prod_s3_bucket" {
10 provider = aws.prod
11 bucket = "S3-Prod-Bucket"
12 acl = "public-read"
13 lifecycle {
14 prevent_destroy = true
15 }
16}
The only difference between DEV and PROD S3 resource buckets is - In the second S3 bucket for PROD we are defining one more provider which refers to the alias aws.prod.
The same approach can be taken to provision other AWS resources in the multiple AWS accounts using the aliases.
2. Using Terraform workspaces
Terraform provides workspaces for managing the Development, Test, Stage, and production environment. Terraform workspaces is a command-line utility that can be used after installing the terraform.
Use Case
To get a better understanding of Terraform workspace for managing the multiple accounts we will again take the same environments -
- DEV
- PROD
And in both environments, we are going to create an EC2 instance.
1. Let’s create DEV and PROD workspaces
Before we start writing the terraform configuration let’s first create terraform workspaces for DEV as well as PROD.
Run the following terraform command for DEV workspace creation
1$ terraform workspace new dev
Run the following terraform command for PROD workspace creation
1$ terraform workspace new prod
2. Select and Use workspace After creating the workspaces the next challenge would be how to use the workspaces. The most important rule over here is - “You can only use one active workspace at a time”. To do that use the following commands -
For using DEV workspace -
1$ terraform workspace select dev
For using the PROD workspace -
1$ terraform workspace select prod
3. Create EC2 instance in DEV as well as PROD using workspace Here is our basic Terraform configuration for a provisioning EC2 instances in AWS using terraform workspace -
1provider "aws" {
2 region = "eu-central-1"
3}
4
5locals {
6 instance_name = "${terraform.workspace}-instance"
7}
8
9resource "aws_instance" "ec2_example" {
10 ami = "ami-0767046d1677be5a0"
11 instance_type = var.instance_type
12 tags = {
13 Name = local.instance_name
14 }
15}
The important part of the above terraform configuration is the locals block -
1locals {
2 instance_name = "${terraform.workspace}-instance"
3}
When you create and use terraform workspace then you can use the name of the workspace inside your terraform configuration using the interpolation - ${terraform.workspace}
.
The above-mentioned terraform interpolation will assign the workspace name to the EC2 instance name so that it will be easy for you to identify the DEV and PROD resources.
(*Note - Always keep the AWS credentials for DEV and PROD accounts separate from each other)
3. AWS Organizations and Assume Role
1. Create AWS organization
AWS has a really good feature known as AWS organization which can be used for multi-account setup using terraform.
Let's create an "AWS Organization" -
1provider "aws" {
2 region = "eu-central-1"
3}
4
5resource "aws_organizations_organization" "organization" {
6}
2. Create DEV and PROD account
Now we will create two accounts DEV(Development environment) and PROD(Production environment) but you can create as many as you can be based on your needs. Apart from that, we are also going to create a USERS account for managing the users.
1resource "aws_organizations_account" "USERS" {
2 name = "spacelift-users"
3 email = "users@test.com"
4 role_name = "admin"
5}
6
7resource "aws_organizations_account" "DEV" {
8 name = "spacelift-dev"
9 email = "dev@test.com"
10 role_name = "admin"
11}
12
13resource "aws_organizations_account" "PROD" {
14 name = "spacelift-production"
15 email = "prod@test.com"
16 role_name = "admin"
17}
(*Note - You need to use separate email ids for each account because AWS Organization does not allow you have the same email id associated with other accounts)
3. Create provider alias for USERS, DEV and PROD
As we aim to implement a multi-account AWS setup using terraform so we need to define provider blocks for each account using an alias.
1provider "aws" {
2 assume_role {
3 role_arn = "arn:aws:iam::${aws_organizations_account.USERS.id}:role/admin"
4 }
5
6 alias = "USERS"
7 region = "eu-central-1"
8}
9
10provider "aws" {
11 assume_role {
12 role_arn = "arn:aws:iam::${aws_organizations_account.DEV.id}:role/admin"
13 }
14
15 alias = "DEV"
16 region = "eu-central-1"
17}
18
19provider "aws" {
20 assume_role {
21 role_arn = "arn:aws:iam::${aws_organizations_account.PROD.id}:role/admin"
22 }
23
24 alias = "PROD"
25 region = "eu-central-1"
26}
4. IAM Groups, Roles, and Policy for the Accounts
As we have created USERS, DEV, and PROD accounts we are going to use USERS account for managing the users, and for that, we need to create some IAM Roles, groups, and Policies.
Let's first create IAM Group, Policy, and user for self-managing user so that after onboarding users should be able to manage their security credentials, password, and MFA(Multi-Factor Authentication) Secrets
1resource "aws_iam_group" "manage_user_group" {
2 name = "ManageUsers"
3
4 provider = aws.USERS
5}
6
7resource "aws_iam_group_policy_attachment" "iam_read_only_access_policy" {
8 group = aws_iam_group.manage_user_group.name
9 policy_arn = "arn:aws:iam::aws:policy/IAMReadOnlyAccess"
10
11 provider = aws.USERS
12}
13
14resource "aws_iam_group_policy_attachment" "iam_self_manage_service_specific_credentials" {
15 group = aws_iam_group.manage_user_group.name
16 policy_arn = "arn:aws:iam::aws:policy/IAMSelfManageServiceSpecificCredentials"
17
18 provider = aws.USERS
19}
20
21resource "aws_iam_group_policy_attachment" "iam_user_change_password" {
22 group = aws_iam_group.manage_user_group.name
23 policy_arn = "arn:aws:iam::aws:policy/IAMUserChangePassword"
24
25 provider = aws.USERS
26}
27
28resource "aws_iam_policy" "self_manage_vmfa" {
29 name = "SelfManageVMFA"
30 policy = file("${path.module}/data/mfa_policy.json")
31
32 provider = aws.USERS
33}
34
35resource "aws_iam_group_policy_attachment" "self_manage_vmfa" {
36 group = aws_iam_group.manage_user_group.name
37 policy_arn = aws_iam_policy.self_manage_vmfa.arn
38
39 provider = aws.USERS
40}
Here is a policy file used in the above terraform block. i.e. mfa_policy.json
1{
2 "Version": "2020-12-05",
3 "Statement": [
4 {
5 "Effect": "Allow",
6 "Action": [
7 "iam:CreateVirtualMFADevice",
8 "iam:EnableMFADevice",
9 "iam:ResyncMFADevice",
10 "iam:DeleteVirtualMFADevice"
11 ],
12 "Resource": [
13 "arn:aws:iam::*:mfa/${aws:username}",
14 "arn:aws:iam::*:user/${aws:username}"
15 ]
16 },
17 {
18 "Sid": "AllowUsersToDeactivateTheirOwnVirtualMFADevice",
19 "Effect": "Allow",
20 "Action": [
21 "iam:DeactivateMFADevice"
22 ],
23 "Resource": [
24 "arn:aws:iam::*:mfa/${aws:username}",
25 "arn:aws:iam::*:user/${aws:username}"
26 ],
27 "Condition": {
28 "Bool": {
29 "aws:MultiFactorAuthPresent": "true"
30 }
31 }
32 },
33 {
34 "Effect": "Allow",
35 "Action": [
36 "iam:ListMFADevices",
37 "iam:ListVirtualMFADevices",
38 "iam:ListUsers"
39 ],
40 "Resource": "*"
41 }
42 ]
43}
5. Setup Administrator Role for production environment
In the previous step, we have set up the self-managed IAM policy, Group, and Role. Now we need to set up some PRODUCTION level access and it should be catered to developers who are working on the development and maintenance of the application.
First, create a developer role and for that, we are gonna put the following terraform configuration inside a [module] 11 names developer
Path - modules/developer-role/main.tf
1variable "trusted_entity" {
2 type = string
3}
4
5resource "aws_iam_role" "this" {
6 name = "Developer"
7
8 assume_role_policy = data.template_file.trust_relationship.rendered
9}
10
11data "template_file" "trust_relationship" {
12 template = <<TEMPLATE
13{
14 "Version": "2012-10-17",
15 "Statement": [
16 {
17 "Effect": "Allow",
18 "Principal": {
19 "AWS": "${trusted_entity}"
20 },
21 "Action": "sts:AssumeRole",
22 "Condition": {
23 "Bool": {
24 "aws:MultiFactorAuthPresent": "true"
25 }
26 }
27 }
28 ]
29}
30TEMPLATE
31
32 vars = {
33 trusted_entity = var.trusted_entity
34 }
35}
36
37resource "aws_iam_role_policy_attachment" "administrator_access" {
38 policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
39 role = aws_iam_role.this.name
40}
41
42output "role_arn" {
43 value = aws_iam_role.this.arn
44}
Now create one more module named developer-group and users of this group are allowed to assume the roles.
Path - modules/developer-group/main.tf
1variable "group_name" {
2 type = string
3}
4
5variable "assume_role_arns" {
6 type = list(string)
7}
8
9resource "aws_iam_group" "this" {
10 name = var.group_name
11}
12
13resource "aws_iam_policy" "assume_role" {
14 name = "${var.group_name}AssumeRole"
15 policy = data.template_file.assume_role.rendered
16}
17
18data "template_file" "assume_role" {
19 template = <<TEMPLATE
20{
21 "Version": "2012-10-17",
22 "Statement": {
23 "Effect": "Allow",
24 "Action": "sts:AssumeRole",
25 "Resource": ${roles_arns_json}
26 }
27}
28TEMPLATE
29
30 vars = {
31 roles_arns_json = jsonencode(var.assume_role_arns)
32 }
33}
34
35resource "aws_iam_group_policy_attachment" "assume_role" {
36 group = aws_iam_group.this.name
37 policy_arn = aws_iam_policy.assume_role.arn
38}
39
40output "group_name" {
41 value = aws_iam_group.this.name
42}
6. IAM user account
So till now, we have set up aws_organizations_account, aws_provider aliases, IAM group, IAM policy, and IAM roles. Now we will create actual users with the help of the terraforming module. Here is a terraform configuration for the user module -
Path - modules/user/main.tf
1variable "name" {
2 type = string
3}
4
5variable "pgp_key" {
6 type = string
7}
8
9variable "groups" {
10 type = list(string)
11}
12
13resource "aws_iam_user" "this" {
14 name = var.name
15}
16
17resource "aws_iam_user_login_profile" "this" {
18 user = aws_iam_user.this.name
19 pgp_key = var.pgp_key
20}
21
22resource "aws_iam_access_key" "this" {
23 user = aws_iam_user.this.name
24 pgp_key = var.pgp_key
25}
26
27resource "aws_iam_user_group_membership" "this" {
28 user = aws_iam_user.this.name
29
30 groups = var.groups
31}
32
33output "summary" {
34 value = {
35 name = var.name
36 password = aws_iam_user_login_profile.this.encrypted_password
37 access_key_id = aws_iam_access_key.this.id
38 secret_access_key = aws_iam_access_key.this.encrypted_secret
39 }
40}
7. Adding a User In the previous step we have defined the user module, let’s use the same module to create a user with production access-
1module "test_user" {
2 source = "modules/user"
3 name = "test.user"
4 pgp_key = "lkjsdfuLKASDEGBDEOPNBSG..." # public key
5
6 groups = [
7 aws_iam_group.self_managing.name,
8 module.developer_group_production.group_name
9 ]
10
11 providers = {
12 aws = aws.users
13 }
14}
15
16output "user_details" {
17 value = [
18 module.test_user.summary
19 ]
20}
Once you apply the above user configuration you should see the following information of the generated user -
1user_details = [
2 {
3 "access_key_id" = "AKIDLSJDFGLKANS324"
4 "name" = "test.user"
5 "password" = "asldjfOIUASDHF7823HJDG..."
6 "secret_access_key" = "KLSDFJO84GNVBSU..."
7 },
8]
Note - Click here to read more on How to manage AWS IAM user, Roles and Policies with Terraform
Conclusion
I hope the above steps would help you to set up multiple AWS accounts using terraform. Also you might have some different steps and procedures for implementing the multiple AWS accounts because there is no strict rules on handling the AWS account with terraform.
Read More - Terragrunt -
Posts in this Series
- Securing Sensitive Data in Terraform
- Boost Your AWS Security with Terraform : A Step-by-Step Guide
- How to Load Input Data from a File in Terraform?
- Can Terraform be used to provision on-premises infrastructure?
- Fixing the Terraform Error creating IAM Role. MalformedPolicyDocument Has prohibited field Resource
- In terraform how to handle null value with default value?
- Terraform use module output variables as inputs for another module?
- How to Reference a Resource Created by a Terraform Module?
- Understanding Terraform Escape Sequences
- How to fix private-dns-enabled cannot be set because there is already a conflicting DNS domain?
- Use Terraform to manage AWS IAM Policies, Roles and Users
- How to split Your Terraform main.tf File into Multiple Files
- How to use Terraform variable within variable
- Mastering the Terraform Lookup Function for Dynamic Keys
- Copy files to EC2 and S3 bucket using Terraform
- Troubleshooting Error creating EC2 Subnet InvalidSubnet Range The CIDR is Invalid
- Troubleshooting InvalidParameter Security group and subnet belong to different networks
- Managing strings in Terraform: A comprehensive guide
- How to use terraform depends_on meta argument?
- What is user_data in Terraform?
- Why you should not store terraform state file(.tfstate) inside Git Repository?
- How to import existing resource using terraform import comand?
- Terraform - A detailed guide on setting up ALB(Application Load Balancer) and SSL?
- Testing Infrastructure as Code with Terraform?
- How to remove a resource from Terraform state?
- What is Terraform null Resource?
- In terraform how to skip creation of resource if the resource already exist?
- How to setup Virtual machine on Google Cloud Platform
- How to use Terraform locals?
- Terraform Guide - Docker Containers & AWS ECR(elastic container registry)?
- How to generate SSH key in Terraform using tls_private_key?
- How to fix-Terraform Error acquiring the state lock ConditionalCheckFiledException?
- Terraform Template - A complete guide?
- How to use Terragrunt?
- Terraform and AWS Multi account Setup?
- Terraform and AWS credentials handling?
- How to fix-error configuring S3 Backend no valid credential sources for S3 Backend found?
- Terraform state locking using DynamoDB (aws_dynamodb_table)?
- Managing Terraform states?
- Securing AWS secrets using HashiCorp Vault with Terraform?
- How to use Workspaces in Terraform?
- How to run specific terraform resource, module, target?
- How Terraform modules works?
- Secure AWS EC2s & GCP VMs with Terraform SSH Keys!
- What is terraform provisioner?
- Is terraform destroy needed before terraform apply?
- How to fix terraform error Your query returned no results. Please change your search criteria and try again?
- How to use Terraform Data sources?
- How to use Terraform resource meta arguments?
- How to use Terraform Dynamic blocks?
- Terraform - How to nuke AWS resources and save additional AWS infrastructure cost?
- Understanding terraform count, for_each and for loop?
- How to use Terraform output values?
- How to fix error configuring Terraform AWS Provider error validating provider credentials error calling sts GetCallerIdentity SignatureDoesNotMatch?
- How to fix Invalid function argument on line in provider credentials file google Invalid value for path parameter no file exists
- How to fix error value for undeclared variable a variable named was assigned on the command line?
- What is variable.tf and terraform.tfvars?
- How to use Terraform Variables - Locals,Input,Output
- Terraform create EC2 Instance on AWS
- How to fix Error creating service account googleapi Error 403 Identity and Access Management (IAM) API has not been used in project before or it is disabled
- Install terraform on Ubuntu 20.04, CentOS 8, MacOS, Windows 10, Fedora 33, Red hat 8 and Solaris 11