Multi-Account support

By default, Moto processes all requests in a default account: 12345678910. The exact credentials provided are usually ignored to make the process of mocking requests as hassle-free as possible.

If you want to mock resources in multiple accounts, or you want to change the default account ID, there are multiple ways to achieve this.

Configure the default account

It is possible to configure the default account ID that will be used for all incoming requests, by setting the environment variable MOTO_ACCOUNT_ID.

Here is an example of what this looks like in practice:

# Create a bucket in the default account
client = boto3.client("s3", region_name="us-east-1")
client.create_bucket(Bucket="bucket-default-account")

# Configure another account - all subsequent requests will use this account ID
os.environ["MOTO_ACCOUNT_ID"] = "111111111111"
client.create_bucket(Bucket="bucket-in-account-2")

assert [b["Name"] for b in client2.list_buckets()["Buckets"]] == ["bucket-in-account-2"]

# Now revert to the default account, by removing the environment variable
del os.environ["MOTO_ACCOUNT_ID"]
assert [b["Name"] for b in client2.list_buckets()["Buckets"]] == ["bucket-default-account"]

Configure the account ID using a request header

If you are using Moto in ServerMode you can add a custom header to a request, to specify which account should be used.

Note

Moto will only look at the request-header if the environment variable is not set.

As an example, this is how you would create an S3-bucket in another account:

headers ={"x-moto-account-id": "333344445555"}
requests.put("http://bucket.localhost:5000/", headers=headers)

# This will return a list of all buckets in account 333344445555
requests.get("http://localhost:5000", headers=headers)

# This will return an empty list, as there are no buckets in the default account
requests.get("http://localhost:5000")

Configure an account using STS

The STS.assume_role()-feature is useful if you want to temporarily use a different set of access credentials. Passing in a role that belongs to a different account will return a set of credentials that give access to that account.

Note

To avoid any chicken-and-egg problems trying to create roles in non-existing accounts, these Roles do not need to exist. Moto will only extract the account ID from the role, and create access credentials for that account.

Note

Moto will only look at the access credentials if the environment variable and request header is not set.

Let’s look at some examples.

# Create a bucket using the default access credentials
client1 = boto3.client("s3", region_name="us-east-1")
client1.create_bucket(Bucket="foobar")

# Assume a role in our account
# Note that this Role does not need to exist
default_account = "123456789012"
sts = boto3.client("sts")
response = sts.assume_role(
    RoleArn=f"arn:aws:iam::{default_account}:role/my-role",
    RoleSessionName="test-session-name",
    ExternalId="test-external-id",
)

# These access credentials give access to the default account
client2 = boto3.client(
    "s3",
    aws_access_key_id=response["Credentials"]["AccessKeyId"],
    aws_secret_access_key=response["Credentials"]["SecretAccessKey"],
    aws_session_token=response["Credentials"]["SessionToken"],
    region_name="us-east-1",
)
client2.list_buckets()["Buckets"].should.have.length_of(1)

Because we assumed a role within the same account, we can see the bucket that we’ve just created.

Things get interesting when assuming a role within a different account.

# Create a bucket with default access credentials
client1 = boto3.client("s3", region_name="us-east-1")
client1.create_bucket(Bucket="foobar")

# Assume a role in a different account
# Note that the Role does not need to exist
sts = boto3.client("sts")
response = sts.assume_role(
    RoleArn="arn:aws:iam::111111111111:role/role-in-another-account",
    RoleSessionName="test-session-name",
    ExternalId="test-external-id",
)

# Retrieve all buckets in this new account - this will be completely empty
client2 = boto3.client(
    "s3",
    aws_access_key_id=response["Credentials"]["AccessKeyId"],
    aws_secret_access_key=response["Credentials"]["SecretAccessKey"],
    aws_session_token=response["Credentials"]["SessionToken"],
    region_name="us-east-1",
)
client2.list_buckets()["Buckets"].should.have.length_of(0)

Because we’ve assumed a role in a different account, no buckets were found. The foobar-bucket only exists in the default account, not in 111111111111.