Create reusable Assume role script for Cross-Account AWS access

Create reusable Assume role script for Cross-Account AWS access

You'll learn how to create an assume role script that allows you to easily access AWS resources cross-account.

ยท

5 min read

This article shows how to create a reusable assume role script that can be used by AWS CodeBuild for example to assume a role in another AWS account.

To accomplish this we need to:

Introduction

In this day and age, we're moving more towards AWS multi-account setups where workloads are being managed separately. With a multi-account setup comes a shared services account that acts as a central hub in which AWS Codepipelines are hosted and deploy infrastructure and services to our workload accounts e.g. development, testing, acceptance, and production AWS accounts.

AWS assume role cross-account diagram example with AWS Codebuild and AWS Codepipeline

The example above displays a simple multi-account setup in which a shared services account acts as a hub to deploy resources to the workload accounts. To make it possible for the shared services account to access the other AWS accounts it needs to assume a role on the target account.

In the next step, you'll be shown how to create an IAM role on the target AWS account.

Create an IAM role on the target AWS Account

On the target AWS account e.g. Test we need to create an IAM role that can be assumed by the source AWS account, in this case, it's the shared services account. As an example, we'll use AWS CloudFormation to create a stack that can be deployed to AWS.

AWSTemplateFormatVersion: "2010-09-09"
Description: A CloudFormation template that creates a cross-account role that can be assumed by the source (shared services) account.
Parameters:
  Environment: { Description: Environment, Type: String }
  SourceAccount: { Description: Source AWS account ID, Type: String }
  S3Bucket: { Description: S3 Bucket that the shared account needs access to, Type: String }
Resources:
  CrossTargetAccountRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub crossaccount-${Environment}-role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Sub arn:aws:iam::${SourceAccount}:root
            Action: sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: S3BucketAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:Get*
                  - s3:List*
                  - s3:PutObject*
                  - s3:DeleteObject*
                Resource:
                  - !Sub arn:aws:s3:::${S3Bucket}/*
                  - !Sub arn:aws:s3:::${S3Bucket}
        - PolicyName: SSMAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ssm:PutParameter
                Resource: "*"

The CloudFormation template shows an example of an IAM role that grants the Source account access to an S3 bucket in the target account and gives the ability to create SSM Parameters.

Once you deploy it you can get the ARN of the newly created role by running the following AWS CLI command:

โžœ aws iam list-roles --output text --query 'Roles[*].[Arn]'|grep crossaccount
arn:aws:iam::012345678910:role/crossaccount-tst-role

Create an IAM role on the source AWS Account

Next up we need to have an IAM role on the shared service account that assumes the role we created previously on the target account, so it can access the S3 bucket.

AWSTemplateFormatVersion: 2010-09-09
Description: A CloudFormation template that creates a role that can assume a role on a target AWS account
Parameters:
  CrossAccountRoleTestARN:
    Type: String
    Description: "The ARN of role on the target account that the source account (AWS Codebuild) assumes to access its services"
Resources:
  CodeBuildServiceRole:
  Type: AWS::IAM::Role
  Properties:
    Path: /
    AssumeRolePolicyDocument:
      Version: 2012-10-17
      Statement:
        - Effect: Allow
          Principal:
            Service: codebuild.amazonaws.com
          Action: sts:AssumeRole
    ManagedPolicyArns:
      - !Ref CrossAccountAssumePolicy

  CrossAccountAssumePolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - sts:AssumeRole
            Resource:
              - !Ref CrossAccountRoleTestARN

Here we create an IAM role the has a managed policy. Within the policy, we allow sts:AssumeRole action on the arn:aws:iam::012345678910:role/crossaccount-tst-role that we created in the previous step.

Now you can use this IAM role for AWS CodeBuild or AWS CodePipeline to automatically access resources on other accounts. But until then we need one more thing and thats the assume role script.

Create an Assume role script

The script that we're going to create assumes the role on the target account and uses Simple Token Service (sts) to create temporary AWS credentials. Then it automatically creates an AWS profile that will be stored in the AWS config so that it can be used in a shell environment e.g. AWS CodeBuild step.

You can copy the code below and paste it in a shell script:

#!/bin/bash
# Usage
#
# ./assume-role.sh $CLIENT_ROLE_ARN client
# aws s3 ls --profile client --region eu-central-1

ROLE_ARN=$1
OUTPUT_PROFILE=$2

echo "Assuming role $ROLE_ARN"
sts=$(aws sts assume-role \
  --role-arn "$ROLE_ARN" \
  --role-session-name "$OUTPUT_PROFILE" \
  --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' \
  --output text)
echo "Converting sts to array"
sts=($sts)
echo "AWS_ACCESS_KEY_ID is ${sts[0]}"
aws configure set aws_access_key_id ${sts[0]} --profile $OUTPUT_PROFILE
aws configure set aws_secret_access_key ${sts[1]} --profile $OUTPUT_PROFILE
aws configure set aws_session_token ${sts[2]} --profile $OUTPUT_PROFILE
echo "credentials stored in the profile named $OUTPUT_PROFILE"

How to use the assume role script

You run the assume role script with 2 arguments. First, you insert the role ARN of the target AWS account that you want to assume e.g. arn:aws:iam::012345678910:role/crossaccount-tst-role. The second argument is how you want to name the AWS profile that you're creating e.g. test-account.

./assume_role.sh arn:aws:iam::012345678910:role/crossaccount-tst-role test-acount

Once the AWS profile is created with the STS credentials, we can run our actions on the target AWS account to run S3 commands for example:

aws s3 ls --profile test-account --region eu-central-1

We can also write SSM parameters to the target test account:

aws ssm put-parameter --name Environment --value Test --type "String" --overwrite --profile test-acount --region eu-central-1

Finished!

Now that you've set up a reusable assume role script, you can reuse it in your CI/CD pipelines and grant cross-account access to AWS resources.

If you want to have access to different AWS resources, then make sure to update the policies that are attached to the IAM role on the target account.


๐Ÿ‘‹ Enjoyed this article? Reach out in the comments below or on Twitter to let me know what you think of it.

If you found some value in reading this, please consider showing your support by sponsoring me. Thanks to your support, I'm able to continue doing what I enjoy the most, which is sharing my learnings with the Cloud Community. Donate here ๐Ÿ‘‡