Terraform
Use AssumeRole to provision AWS resources across accounts
AWS AssumeRole allows you to grant temporary credentials with additional privileges to users as needed, following the principle of least privilege. To configure AssumeRole access, you must define an IAM role that specifies the privileges that it grants and which entities can assume it. AssumeRole can grant access within or across AWS accounts. If you are administering multiple AWS accounts, you can use AssumeRole configuration to enable scoped access across accounts without having to manage individual users in each account they may need to interact with resources in.
The AWS Terraform provider can use AssumeRole credentials to authenticate against AWS. In this tutorial, you will use Terraform to define an IAM role that allows users in one account to assume a role in a second account and provision AWS instances there. You will then configure an AWS provider to use the AssumeRole credentials and deploy an EC2 instance across accounts.
Prerequisites
This tutorial assumes that you are familiar with the standard Terraform workflow. If you are new to Terraform, complete the Get Started tutorials first.
For this tutorial, you will need:
- Terraform v0.15+ installed locally
- two AWS accounts
Configure AWS credentials
For this tutorial, you will provision resources across two AWS accounts. Since you are working with multiple accounts, use the shared credentials file method to authenticate the AWS provider.
When using AssumeRole credentials, you must first create the IAM role that grants privileges to resources within that same account, and specifies which entities can access it (whether in that account or in another AWS account). The trusted IAM entities can then assume that role and manage the allowed resources.
In this tutorial, the source account refers to the account you will work in
when you run terraform apply
. The destination account refers to the account
in which you will create the IAM role for AssumeRole access and ultimately
provision your EC2 instance.
Add credentials for the two accounts to your ~/.aws/credentials
file. Note
that the user in the source account cannot be the root user for the account
since AWS will not allow the root user to assume a role.
~/.aws/credentials
[source]
aws_access_key_id=<ACCOUNT 1 KEY>
aws_secret_access_key=<ACCOUNT 1 SECRET KEY>
[destination]
aws_access_key_id=<ACCOUNT 2 KEY>
aws_secret_access_key=<ACCOUNT 2 SECRET KEY>
If your AWS accounts are configured to use session tokens, you will need to add those to the credentials file as well.
To ensure that you are using the keys defined in the credentials file, unset any environment variables containing your AWS credentials.
$ unset AWS_SESSION_TOKEN AWS_SECRET_ACCESS_KEY AWS_ACCESS_KEY_ID
Clone example repositories
This tutorial uses two example repositories: one that defines IAM roles for cross-account AssumeRole access and one defines an EC2 instance that you provision using the role you create.
Clone the IAM configuration repository for this tutorial. It defines an IAM role in your destination account that you can assume from your source account.
$ git clone https://github.com/hashicorp-education/learn-terraform-aws-assume-role-iam
Clone the EC2 instance repository that assumes a role from the source account to manage the EC2 instances in the destination account.
$ git clone https://github.com/hashicorp-education/learn-terraform-aws-assume-role-ec2
Review IAM configuration
Change into the IAM configuration repository directory.
$ cd learn-terraform-aws-assume-role-iam
Open the main.tf
file. This configuration defines two instances of the AWS
provider, one for the source
and one for the destination
profile in your
~/.aws/credentials
file. The provider alias
allows Terraform to
differentiate the two AWS providers.
main.tf
provider "aws" {
alias = "source"
profile = "source"
region = "us-east-2"
}
provider "aws" {
alias = "destination"
profile = "destination"
region = "us-east-2"
}
To allow users in a different AWS account to assume a role, you must define an
AssumeRole policy for that account. This configuration uses the
aws_caller_identity
data source to access the source account's ID. The
aws_iam_policy_document.assume_role
defines a policy that allows all users of
the source account to use any role with the policy attached.
main.tf
data "aws_caller_identity" "source" {
provider = aws.source
}
data "aws_iam_policy_document" "assume_role" {
provider = aws.destination
statement {
actions = ["sts:AssumeRole"]
principals {
type = "AWS"
identifiers = ["arn:aws:iam::${data.aws_caller_identity.source.account_id}:root"]
}
}
}
The aws_iam_role.assume_role
resource references the
aws_iam_policy_document.assume_role
for its assume_role_policy
argument,
allowing the entities specified in that policy to assume this role. It defines
the granted privileges in the destination account through the
managed_policy_arns
argument. In this case, the role grants users in the
source account full EC2 access in the destination account by referencing the
aws_iam_policy.ec2
data source.
main.tf
data "aws_iam_policy" "ec2" {
provider = aws.destination
name = "AmazonEC2FullAccess"
}
resource "aws_iam_role" "assume_role" {
provider = aws.destination
name = "assume_role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
managed_policy_arns = [data.aws_iam_policy.ec2.arn]
}
Create IAM role
Initialize your Terraform configuration.
$ terraform init
Then, apply your configuration to create the IAM role. Respond yes
to the
prompt to confirm the apply.
$ terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_iam_role.assume_role will be created
##...
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ role_arn = (known after apply)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_iam_role.assume_role: Creating...
aws_iam_role.assume_role: Creation complete after 0s [id=assume_role]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
role_arn = "arn:aws:iam::<ACCOUNT_ID>:role/assume_role"
Note the role_arn
output value. You will reference this value in your
configuration to assume the new IAM role in the destination account.
Review EC2 instance configuration
In a new terminal window, navigate to the example EC2 configuration repository directory.
$ cd ../learn-terraform-aws-assume-role-ec2
Open main.tf
and replace <ROLE_ARN>
with the role_arn
output value from
the previous step and save the file in the aws
provider block.
main.tf
provider "aws" {
region = "us-east-2"
profile = "source"
assume_role {
role_arn = "<ROLE_ARN>"
}
}
Notice that this configuration does not reference the destination
profile
from your AWS credentials file. That means that this configuration does not
have access to the second AWS account's credentials to provision the EC2
instance.
Provision resource across AWS accounts
Though the Terraform configuration does not reference the destination
profile
in your shared AWS credentials file, in a production scenario, you likely would
not have credentials to the second account at all.
To simulate this, comment out the destination
profile in your
~/.aws/credentials
file by prefixing the lines with a #
. Save the file.
~/.aws/credentials
[source]
aws_access_key_id=<ACCOUNT 1 KEY>
aws_secret_access_key=<ACCOUNT 1 SECRET KEY>
# [destination]
# aws_access_key_id=<ACCOUNT 2 KEY>
# aws_secret_access_key=<ACCOUNT 2 SECRET KEY>
Working in your learn-terraform-aws-assume-role-ec2
directory, initialize
your Terraform configuration.
$ terraform init
Now apply your configuration to create an EC2 instance in the "destination" account.
$ terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.example will be created
+ resource "aws_instance" "example" {
##...
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_instance.example: Creating...
aws_instance.example: Still creating... [10s elapsed]
aws_instance.example: Still creating... [20s elapsed]
aws_instance.example: Still creating... [30s elapsed]
aws_instance.example: Creation complete after 34s [id=i-0b57844e1e0225c3c]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
instance_id = "i-0b57844e1e0225c3c"
Now log on to your "destination" AWS account console and search for the instance with the ID from the output value to confirm that Terraform created the instance in the expected account.
Destroy infrastructure
Now that you have completed the tutorial, destroy the resources provisioned to avoid incurring unnecessary costs.
First, destroy the EC2 instance defined in the
learn-terraform-aws-assume-role-ec2
directory. Respond yes
to the prompt to
confirm.
$ terraform destroy
aws_instance.example: Refreshing state... [id=i-0b57844e1e0225c3c]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# aws_instance.example will be destroyed
##...
Plan: 0 to add, 0 to change, 1 to destroy.
Changes to Outputs:
- instance_id = "i-0b57844e1e0225c3c" -> null
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_instance.example: Destroying... [id=i-0b57844e1e0225c3c]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 10s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 20s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 30s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 40s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 50s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 1m0s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 1m10s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 1m20s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 1m30s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 1m40s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 1m50s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 2m0s elapsed]
aws_instance.example: Still destroying... [id=i-0b57844e1e0225c3c, 2m10s elapsed]
aws_instance.example: Destruction complete after 2m13s
Destroy complete! Resources: 1 destroyed.
Next, uncomment the credentials for the destination
AWS profile in your
shared credentials account by removing the #
prefix. Save the file.
~/.aws/credentials
[source]
aws_access_key_id=<ACCOUNT 1 KEY>
aws_secret_access_key=<ACCOUNT 1 SECRET KEY>
[destination]
aws_access_key_id=<ACCOUNT 2 KEY>
aws_secret_access_key=<ACCOUNT 2 SECRET KEY>
Then, navigate to the directory that defines the AssumeRole IAM resources.
$ cd ../learn-terraform-aws-assume-role-iam
Destroy the IAM resources. Respond yes
to the prompt to confirm.
$ terraform destroy
aws_iam_role.assume_role: Refreshing state... [id=assume_role]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
##...
Plan: 0 to add, 0 to change, 1 to destroy.
Changes to Outputs:
- role_arn = "arn:aws:iam::<ACCOUNT_ID>:role/assume_role" -> null
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_iam_role.assume_role: Destroying... [id=assume_role]
aws_iam_role.assume_role: Destruction complete after 1s
Destroy complete! Resources: 1 destroyed.
Next steps
You now know how to configure and manage cross-account AssumeRole access for the AWS Terraform provider. To learn more about Terraform configuration, review the following tutorials:
- Review managing provider versions.
- Learn how to define AWS IAM policies.
- Review the AWS provider documentation for more information on available configuration options.