Cross account roles with AWS

I recently spent far more time than I would care to admit trying to work out how to use cross account roles to access an S3 bucket that had many files written by different AWS accounts resulting in a *soup* of ACL’s. A large part of this time burnt was because I couldn’t find an idiots guide to cross-account roles, so here is what I hope to be a thorough explanation of how to use cross-account roles to access an S3 bucket in a different account.

In my case I was trying to access billing CSV information so the example role names are themed that way.

In the account that contains the S3 bucket you need to create a role with a policy that gives access to the bucket and allows another account role to assume this role.

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "Billing role",

  "Parameters" : {
    "AccountArn": {
      "Type": "String",
      "Description": "Account ARN that can assume this role e.g. arn:aws:iam::111111111111:root"
    },
    "BillingBucketArn": {
      "Type": "String",
      "Description": "Bucket to grant access to ARN e.g. arn:aws:s3:::my-s3-bucket"
    }
  },

  "Resources": {
    "MyRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version" : "2012-10-17",
          "Statement": [ {
            "Effect": "Allow",
            "Principal": {
              "AWS": [
                { "Ref": "AccountArn" }
              ]
            },
            "Action": "sts:AssumeRole"
          } ]
        }
      }
    },

    "MyRolePolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "BillingPolicy",
        "PolicyDocument": {
          "Version" : "2012-10-17",
          "Statement": [{
            "Resource": { "Ref": "BillingBucketArn" },
            "Action": [
              "s3:ListBucket",
              "s3:GetBucketLocation"
            ],
            "Effect": "Allow"
          },
          {
            "Resource": { "Fn::Join": [ "", [ { "Ref": "BillingBucketArn" }, "/*"] ] },
            "Action": [
              "s3:GetObject"
            ],
            "Effect": "Allow"
          } ]
        },

        "Roles": [ {
          "Ref": "MyRole"
        } ]
      }
    }
  }
}

In the account that contains the app that needs to assume the bucket access role you need to create another role that allows you to assume the role you just created in the other account. In the example below I have also included the cloud formation for deploying an EC2 instance with this role applied to it. Most of the parameters for this are self explanatory except for maybe the RoleToAssumeArn parameter which accepts the ARN of the role you just created above.

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : " billing EC2 instances",

  "Parameters" : {
    "VpcId": {
      "Description": "The VPC id to deploy the EC2 instance into",
      "Type": "String"
    },
    "SubnetId": {
      "Description": "The subnet id to deploy the EC2 instance into",
      "Type": "String"
    },
    "AmiId": {
      "Description": "The AMI id to deploy with this EC2 instance.",
      "Type": "String"
    },
    "SshKeyId": {
      "Description": "The SSH keypair to apply to the EC2 instance",
      "Type": "String"
    },
    "RoleToAssumeArn": {
      "Type": "String",
      "Description": "Name of IAM role in bucket account e.g. arn:aws:iam::222222222222:role/BillingRole"
    }
  },

  "Resources": {
    "InstanceRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version" : "2012-10-17",
          "Statement": [ {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Principal": {
              "Service": "ec2.amazonaws.com"
            }
          } ]
        }
      }
    },

    "InstancePolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "BillingPolicy",
        "PolicyDocument": {
          "Version" : "2012-10-17",
          "Statement": [{
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": { "Ref" : "RoleToAssumeArn" }
          } ]
        },
        "Roles": [ {
          "Ref": "InstanceRole"
        } ]
      }
    },

    "InstanceProfile": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
        "Path": "/",
        "Roles": [
          { "Ref": "InstanceRole" }
        ]
      }
    },

    "Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "SubnetId": { "Ref": "SubnetId" },
        "ImageId": { "Ref": "AmiId" },
        "IamInstanceProfile": { "Ref": "InstanceProfile" },
        "InstanceType": "m3.medium",
        "Monitoring": true,
        "KeyName": { "Ref": "SshKeyId" }
      }
    }
  }
}

The last thing you will need to do is actually assume the role in the bucket account either in your application or by running an AWS CLI command. I happened to be using Python 3 so here is an example of this for Python.

import boto

sts_connection = boto.sts.connection.STSConnection()
assumed_sts_role = sts_connection.assume_role(
  role_arn="arn:aws:iam::222222222222:role/BillingRole",
  role_session_name="BucketSession"
)

s3_connection = boto.connect_s3(
  aws_access_key_id=assumed_sts_role.credentials.access_key,
  aws_secret_access_key=assumed_sts_role.credentials.secret_key,
  security_token=assumed_sts_role.credentials.session_token
)

billing_bucket = s3_connection.get_bucket("BILLING_BUCKET_NAME")
for s3_key in billing_bucket.list():
  print s3_key.name</pre>
comments powered by Disqus