Docs / Multi-Account

Multi-Account

LocalEmu supports multiple AWS accounts in a single instance. Each account has its own isolated namespace of resources, ARNs are account-scoped, and sts:AssumeRole works across accounts the same way as on real AWS. No flag has to be flipped: multi-account is on by default.

How an access key becomes an account ID

LocalEmu derives the AWS account ID from the access key on every inbound request. There are two supported forms:

Any other access key (the standard example values, freshly-minted STS sessions, or test placeholders) falls through to the default account 000000000000. This is the account used by awsemu and by the example credentials shipped in the [runtime] extra.

Two accounts in one instance

Run two clients side by side with different 12-digit access keys. Each has a fully isolated resource namespace:

# Account A: the access key IS the account ID
$ AWS_ACCESS_KEY_ID=111122223333 \
  AWS_SECRET_ACCESS_KEY=any awsemu s3 mb s3://account-a-bucket
make_bucket: account-a-bucket

# Account B: different access key, fully isolated namespace
$ AWS_ACCESS_KEY_ID=444455556666 \
  AWS_SECRET_ACCESS_KEY=any awsemu s3 mb s3://account-b-bucket
make_bucket: account-b-bucket

# Each account only sees its own buckets
$ AWS_ACCESS_KEY_ID=111122223333 awsemu s3 ls
2026-06-06 12:00:00 account-a-bucket

The same shape works for every service. SQS queue URLs and SNS topic ARNs carry the owning account:

$ AWS_ACCESS_KEY_ID=111122223333 \
  AWS_SECRET_ACCESS_KEY=any awsemu sqs create-queue --queue-name q
http://sqs.us-east-1.localhost:4566/111122223333/q

$ AWS_ACCESS_KEY_ID=444455556666 \
  AWS_SECRET_ACCESS_KEY=any awsemu sqs create-queue --queue-name q
http://sqs.us-east-1.localhost:4566/444455556666/q

Verifying the active account

sts:GetCallerIdentity returns the account derived from the access key. It is the cheapest way to confirm which account a script is operating in:

$ AWS_ACCESS_KEY_ID=111122223333 \
  AWS_SECRET_ACCESS_KEY=any awsemu sts get-caller-identity
{
  "UserId": "AKIAIOSFODNN7EXAMPLE",
  "Account": "111122223333",
  "Arn": "arn:aws:sts::111122223333:user/moto"
}

Cross-account AssumeRole

sts:AssumeRole across accounts works the AWS way: the target role's trust policy must name the caller's account, and the returned session credentials route subsequent calls to the target account. The session AccessKeyId starts with ASIA.

# Account B creates a role trusting account A
$ AWS_ACCESS_KEY_ID=444455556666 awsemu iam create-role \
    --role-name CrossAccountRead \
    --assume-role-policy-document '{
  "Version":"2012-10-17",
  "Statement":[{
    "Effect":"Allow",
    "Principal":{"AWS":"arn:aws:iam::111122223333:root"},
    "Action":"sts:AssumeRole"
  }]
}'

# Account A assumes the role; returned credentials act as account B
$ AWS_ACCESS_KEY_ID=111122223333 awsemu sts assume-role \
    --role-arn arn:aws:iam::444455556666:role/CrossAccountRead \
    --role-session-name demo
{
  "AssumedRoleUser": {
    "Arn": "arn:aws:sts::444455556666:assumed-role/CrossAccountRead/demo"
  },
  "Credentials": {
    "AccessKeyId": "ASIA...",
    "SecretAccessKey": "...",
    "SessionToken": "..."
  }
}

Account registry and admin endpoints

Every account LocalEmu sees gets recorded in a central registry. The registry is queryable via four endpoints:

$ curl http://localhost:4566/_localemu/api/accounts
{
  "Accounts": [
    {"Id": "000000000000", "Arn": "arn:aws:organizations::000000000000:account/000000000000",
     "Email": "000000000000@localemu.local", "Name": "account-000000000000",
     "Status": "ACTIVE", "JoinedMethod": "IMPLICIT"},
    {"Id": "111122223333", "Arn": "arn:aws:organizations::111122223333:account/111122223333",
     "Status": "ACTIVE", "JoinedMethod": "IMPLICIT"}
  ]
}

# Create an account explicitly (without going through Organizations)
$ curl -X POST http://localhost:4566/_localemu/api/accounts \
    -H "Content-Type: application/json" \
    -d '{"account_id":"777788889999","name":"prod"}'
{"Id":"777788889999","Name":"prod","Status":"ACTIVE","JoinedMethod":"CREATED"}

Per-account resource counts

The summary endpoint reports how many resources each account holds in every backed service. Useful for spotting which account is the heavy one when you have several open at once:

$ curl http://localhost:4566/_localemu/api/accounts/111122223333/summary
{
  "account_id": "111122223333",
  "resources": {
    "s3": 1,
    "sqs": 1,
    "iam": 2976,
    "lambda": 0
  }
}

IAM enforcement and the root user

With IAM_ENFORCEMENT=1, LocalEmu evaluates the caller's identity policies on every API call. The ROOT_ACCESS_KEYS env var lists access keys to treat as the root user of their respective accounts. Root bypasses identity enforcement the way AWS's root account does:

$ IAM_ENFORCEMENT=1 \
  ROOT_ACCESS_KEYS=AKIAIOSFODNN7EXAMPLE \
  localemu start
IAM policy enforcement: enabled
Ready.

Cross-account requests under IAM enforcement also consult the target service's resource-based policy (S3 bucket policy, KMS key policy, SQS / SNS / Lambda / EventBridge policies). See Cross-Account Access for the condition keys and the delegation patterns.

See also