Hey, it's been a while, right?

Lately, I'm leading some big changes in our infrastrure since I'm wrapping a Blue/Green deployment into another one. Uh?

Long story short: our product runs on AWS ECS Docker containers, and because we have one sub-domain per app instance, we use Apache wildcard sub-domains. Meaning a single container handles traffic from many clients/instances. We already have a Blue/Green deployment process in place but at the Docker container level, not at the client level. The idea is to entirely duplicate the whole stack (with all its flaws) and a sub-domain pointing to given stack by upserting its DNS CNAME so we can actually move client/instance one by one to the other deployment pool.

Anyway, I'm jungling quite a lot between Go microservices, ansible and Terraform these days. While also doing some AWS "resource cleaning", my last struggle was to handle shared cross-region and cross-account AWS resources in multiple Terraform plans.

Our example is a S3 bucket that needs to be accessible from different regions and accounts!
My S3 bucket eexit-bucket is in account A and region us-east-1.

  • How do I access my bucket from account B and region us-east-1?
  • How do I access my bucket from account B and region eu-west-1?

Cross-account access

Let's say you create this resource in the account A using aws_s3_bucket resource:

provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "eexit_bucket" {
  bucket = "eexit-bucket"
  lifecycle_rule {
    enabled = true
    expiration {
      days = 14
    }
  }
}

In order to allow account B to access it, so you need to set a principal policy to your bucket:

resource "aws_s3_bucket_policy" "eexit_bucket_policy" {
  bucket = "${aws_s3_bucket.eexit_bucket.id}"
  policy = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Allows account B",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::xxxxxxxx:root"
            },
            "Action": "s3:*",
            "Resource": [
                "${aws_s3_bucket.eexit_bucket.arn}",
                "${aws_s3_bucket.eexit_bucket.arn}/*"
            ]
        }
    ]
}
POLICY
}

And that's it. From your account B, you can access to the bucket as long as you're using the same region.

In your account B plan, beware not to use a resource but a data source to refer to the S3 bucket (it's not managed by this Terraform plan):

provider "aws" {
  region = "us-east-1"
}

resource "aws_iam_role" "account_b_role" {
  name = "my-role-name"
}

resource "aws_iam_role_policy" "account_b_role_policy" {
  name = "${aws_iam_role.account_b_role.name}"
  role = "${aws_iam_role.account_b_role.id}"
  policy = "${data.aws_iam_policy_document.account_b_policy_document.json}"
}

data "aws_iam_policy_document" "account_b_policy_document" {
  statement {
    actions = [
      "s3:GetObject",
      "s3:GetObjectAcl",
      "s3:PutObject",
      "s3:PutObjectAcl",
      "s3:DeleteObject"
    ]
    resources = [
      "${data.aws_s3_bucket.eexit_bucket.arn}",
      "${data.aws_s3_bucket.eexit_bucket.arn}/*"
    ]
  }
}

data "aws_s3_bucket" "eexit_bucket" {
  bucket = "eexit-bucket"
}

Cross-region access

This is where I struggled. My account B has multiple Terraform plans, for several regions. We deploy our apps in different regions and yet we sometimes share the same resources.

In this example, my S3 bucket is in account A and region us-east-1 but I wish to access it from account B and region eu-west-1. The trick is to leverage TF provider aliases.

You need to declare another aliased provider and change its region:

// Default provider
provider "aws" {
  region = "eu-west-1"
}

// Provider configured to work in us-east-1
provider "aws" {
  alias = "us"
  region = "us-east-1"
}

// Role + role policy definitions...

data "aws_s3_bucket" "eexit_bucket" {
  provider = "aws.us"
  bucket = "eexit-bucket"
}

In the S3 bucket data source, you specify which provider to use and it compiles auto-magically.

Thanks for reading.


Joris Berthelot