Creating IAM trust policy and role in another account

Hi,

I have two AWS accounts:

  • root - 111111111111: Only IAM groups and users are kept
  • stag - 222222222222: No IAM groups or users, only other services such as S3, RDS, SQS etc.

Using the configurations below, this is what I do

In AWS UI I manually:

  1. Create an IAM trust policy devops_stag_perms (e.g. S3 full access) in stag account
  2. Create an IAM role devops in stag account
  3. Attach devops_stag_perms and devops together in stag account

Then I run $ terraform apply to:

  1. Create an IAM group devops in root account
  2. Create an IAM user john in root account
  3. Make john part of devops group in root account
  4. Create an IAM policy devops_assume_stag_role in root account that assumes devops role in stag

So far john can assume devops role in stag account to list S3 buckets (of course after using john’s credentials with role_arn in AWS CLI config). However, please carry on reading …

AWS CLI

# .aws/config
[profile default]
region = eu-west-1
output = json
role_session_name = admin-in-root-account

# .aws/credentials
[default]
aws_access_key_id = ADMIN_IN_ROOT_ACCOUNT_KEY
aws_secret_access_key = ADMIN_IN_ROOT_ACCOUNT_SEC

main.tf

terraform {
  required_version = "~> 1.2.6"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.30.0"
    }
  }
}

provider "aws" {
  profile = "default"
  region  = "eu-west-1"
}

policy.tf

resource "aws_iam_policy" "devops_assume_stag_role" {
  name        = "devops-assume-stag-role"
  description = "Allows devops group to assume role in stag account."

  policy = jsonencode({
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Action" : "sts:AssumeRole",
          "Resource" : "arn:aws:iam::222222222222:role/devops"
        }
      ]
    })
}

resource "aws_iam_group_policy_attachment" "devops_group_stag_role_link" {
  group      = aws_iam_group.devops.name
  policy_arn = aws_iam_policy.devops_assume_stag_role.arn
}

group.tf

resource "aws_iam_group" "devops" {
  name = "devops"
}

resource "aws_iam_user" "john" {
  name          = "john"
  force_destroy = true
}

resource "aws_iam_user_group_membership" "john" {
  user   = aws_iam_user.john.name
  groups = [aws_iam_group.john.name]
}

This is what I need help with please

I need to get rid of the manual steps that I listed above and add them to Terraform configuration. If I do, the trust policy and role (the ones at the bottom) is created in root account, not in the stag account. Due to that, I then get error below when using the AWS CLI config below.

# .aws/config
[profile default]
region = eu-west-1
output = json
role_session_name = john
role_arn = arn:aws:iam::222222222222:role/devops

# .aws/credentials
[default]
aws_access_key_id = JOHN_KEY
aws_secret_access_key = JOHN_SEC
$ aws sts get-caller-identity

An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::111111111111:user/john is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::222222222222:role/devops


I know I will have to add this to the config but as I said I definitely need some sort of refactorisation in the current configs above. Maybe some provider and aws_caller_identity like stuff but I am a beginner in Terraform so lost a bit.

resource "aws_iam_role" "devops" {
  name        = "devops"
  description = "Trusts devops group assume role in stag account."

  assume_role_policy = jsonencode({
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Action" : "sts:AssumeRole",
          "Principal" : {
            "AWS" : "arn:aws:iam::111111111111:root"
          },
          "Condition" : {}
        }
      ]
    })
}

resource "aws_iam_policy" "devops_stag_perms" {
  name        = "devops-stag-perms"
  description = "Grants devops group permissions in stag account."

  policy = jsonencode({
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Action" : "s3:*",
          "Resource" : "arn:aws:s3:::*"
        }
      ]
    })
}

resource "aws_iam_role_policy_attachment" "devops_stag" {
  role       = aws_iam_role.devops.name
  policy_arn = aws_iam_policy.devops_stag_perms.arn
}

There are a couple of places I think there is confusion

As a starting point:
You have two accounts
root: 111111111111
stag: 222222222222

You are looking to provision resources in both accounts.

To accomplish this you need to use provider aliases:

This assumes that you have a configuration on your local machine / terraform execution platform that has access to credentials for both accounts.
ex:

provider "aws" {
  alias   = "root" # you can forgo this alias if you want to not include an explicit `provider = aws.root` on resources
  region  = "eu-west-1"
  profile = "svc-terraform-root"
}
provider "aws" {
  alias   = "stag"
  region  = "eu-west-1"
  profile = "svc-terraform-stag"
}

After that you’ll have to add
provider = aws.root
or
provider = aws.stag

To the resource blocks that are appropriate.

ex:

resource "aws_iam_role_policy_attachment" "devops_stag" {
  provider   = aws.stag # <---- this is new
  role       = aws_iam_role.devops.name
  policy_arn = aws_iam_policy.devops_stag_perms.arn
}

I would configure this and confirm you are generating the resources (previously manual) appropriately and then check if john can’t assume the role then.

Thank you cai for the response. I thing to note again. I don’t have IAM users in the stag account. They all are in the root account.

This is the second place of confusion and is more of an AWS configuration question.

For Terraform to perform actions in an account you must have a user or role in the stag account.
When you manually execute the workflow of creating a role in the stag account, how are you accessing stag ?

Your architecture sounds like you have an account that is an identity store (root) and you are cross-account accessing the destination (stag) account. When you perform this action manually, I would have to assume you are cross account accessing the stag account with an admin role… OR you are using a root account credential and logging into the account.

You are in a chicken and egg situation.

The example I used above was a dedicated role/profile in each account which would be used by Terraform (or you) to perform actions. You cannot perform API calls in an AWS account without having access to the AWS account - either via API keys, or a cross account role.
ex:
~/.aws/credentials

[default]
# These go to the root account
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

~/.aws/config

[default]
region = eu-west-2
output = json

[profile svc-terraform-root]
role_arn=arn:aws:iam:: 111111111111:role/svc-terraform
source_profile = default

[profile svc-terraform-stag]
role_arn=arn:aws:iam:: 222222222222:role/svc-terraform
source_profile = default

You worked it out right :100: So based on my confusion and as far as the best practises go, which one should go with if you don’t mind me asking please (considering I actually have more accounts such as sandbox, test, prod etc).

a. Create a terraform IAM user in stag account and use AWS CLI credentials
b. Create a cross account IAM role in stag account and use role_arn in AWS CLI config (the way you do above)

I have a feeling that b might be the one thats avoids duplicating the user in each account and have multiple credentials lying around. Instead just duplicate role in each accounts and reference role_arn in config. Just thinking out loud!

Sorry, I am still a beginner with Terraform and AWS so questions might sound a bit silly! Also, thank you for taking time to answers those.