Creating AWS Accounts From The Command Line With AWS Organizations

Copy+paste some aws-cli commands to add a new AWS account to your AWS Organization

The AWS Organizations service was introduced at AWS re:Invent 2016. The service has some advanced features, but at a minimum, it is a wonderful way to create new accounts easily, with:

  • all accounts under the consolidated billing

  • no need to enter a credit card

  • no need to enter a phone number, answer a call, and key in a confirmation that you are human

  • automatic, pre-defined cross-account IAM role assumable by master account IAM users

  • no need to pick and securely store a password

I create new AWS accounts at the slightest provocation. They don’t cost anything as long as you aren’t using resources, and they are a nice way to keep unrelated projects separate for security, cost monitoring, and just keeping track of what resources belong where.

I will create an AWS account for a new, independent, side project. I will create an account for a weekend hackathon to keep that mess away from anything else I care about. I will even create an account just to test a series of AWS commands for a blog post, making sure that I am not depending on some earlier configurations that might not be in readers' accounts.

By copying and pasting commands based on the examples in this article, I can create and start using a new AWS account in minutes.

Before You Start

For the purposes of this blog post, I will assume that you have already set up an AWS Organization with a master account. You need to be careful when deciding what your master account will be, and this post is not going to go into the details and tradeoffs of that decision.

The following commands must be run from the AWS Organization master account. I will assume you already have your aws-cli configuration set up with that profile.

I also assume that you use an MFA device with your IAM user in the master account.

Parameters

I create a consistent naming scheme where I have a “tag” that indicates the purpose of the account, or just the date it was created. I inject this tag into the account name and the account email address so I can tie them together later. It’s a bother to get an email about your account and not know which account it is referencing.

You are welcome to set the following environment variables to whatever makes sense for you. The cross-account IAM role will be assumed by privileged IAM user(s) in the master account.

tag=$(date +%Y%m%d) # Or some meaningful alphanumeric string
account_name="YOUR NAME ($tag)"
email="YOUREMAIL+$tag@gmail.com"
role=admin

Note: If you use Gmail or G Suite, you can insert “+ANYTHING” before the “@” and it will be delivered to the same mailbox. If you haven’t done this before, give it a try before using it in a new AWS account.

The following are used to set up a new profile in the aws-cli config:

profile=$tag-$role
mfa_name=YOURMFANAME

Next step: do it!

Create a new AWS account

The following command create the a new AWS account in the AWS Organization. I allow the admin role to view billing information:

create_request_id=$(aws organizations create-account \
  --email "$email" \
  --account-name "$account_name" \
  --role-name "$role" \
  --iam-user-access-to-billing ALLOW \
  --output text \
  --query 'CreateAccountStatus.Id'
)
echo create_request_id=$create_request_id

Monitor the status of the create request:

aws organizations list-create-account-status \
  --output text \
  --query 'CreateAccountStatuses[?Id==`'"$create_request_id"'`].[AccountId,AccountName,State,FailureReason]'

Once this returns values including “SUCCEEDED”, your new account is ready. It is generally available within seconds.

The following describes how I manage access to the account from my master account.

Account Access

Get the account ids of the master account and the new account:

master_account_id=$(aws sts get-caller-identity \
  --output text \
  --query 'Account')
new_account_id=$(aws organizations list-create-account-status \
  --output text \
  --query 'CreateAccountStatuses[?Id==`'"$create_request_id"'`].[AccountId]'
)
echo master_account_id=$master_account_id new_account_id=$new_account_id

Make sure those account ids look correct, or edit the variables by hand.

For each new account, I create an aws-cli profile with the appropriate configuration, including the use of an MFA device (though it is not quite yet required by the new account):

master_profile=default

aws configure set profile.$profile.role_arn \
    arn:aws:iam::$new_account_id:role/$role
aws configure set profile.$profile.mfa_serial \
    arn:aws:iam::$master_account_id:mfa/$mfa_name
aws configure set profile.$profile.source_profile \
    $master_profile
aws configure set profile.$profile.region \
    us-east-1

I have a single, permanent policy in my master account that allows a privileged IAM user to assume a role named “admin” in any account that permits it (including the master account!). This allows for easy access to start setting up new accounts. If I want to remove that access, I can change the permissions, or change the name of the “admin” role to something else that needs different permissions set up explicitly.

{
    "Version": "2012-10-17",
    "Statement": {
        "Sid": "AllowAssumeAllAccountsAdminRole",
        "Effect": "Allow",
        "Action": "sts:AssumeRole",
        "Resource": "arn:aws:iam::*:role/admin"
    }
}

As soon as the aws-cli profile has been created, I can use the new account with the aws-cli:

aws --profile $profile ec2 describe-regions

Account Setup

To improve security, I want the new account’s “admin” role to require MFA before it can be used. The following command adds the MFA requirement condition to the trust policy of the admin role in the new account:

aws iam update-assume-role-policy \
  --profile $profile \
  --role-name $role \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "AWS": "arn:aws:iam::'"$master_account_id"':root"
        },
        "Action": "sts:AssumeRole",
        "Condition": {
          "Bool": {
            "aws:MultiFactorAuthPresent": "true"
  }}}]}'

If the above command goes wrong, it might make it a bit difficult to work with the new account as you are modifying access to the role that you are using to access the account. Check it carefully, and run it very early in the process before you have much to lose in the account.

I have a few additional steps I like to take with every new AWS account, no matter how temporary, including:

What steps do you take in new AWS accounts?

[Update 2017-01-28: Use “new” sts method to get account id instead of old transcoder method.]