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:
- *12-digit access key (recommended). The access key IS the account ID. Pass
AWS_ACCESS_KEY_ID=111122223333and every call from that client lands in account111122223333. - *20-character LocalEmu key (
LKIA...long-term,LSIA...session). The account ID is encoded inside the key itself.
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:
- *
GET /_localemu/api/accounts. list every account known to this instance - *
POST /_localemu/api/accounts. create an account out of band (body:{"account_id":"...","name":"...","email":"..."}) - *
GET /_localemu/api/accounts/<id>. single account record - *
DELETE /_localemu/api/accounts/<id>. remove from the registry (resources in other services are kept; the default account cannot be deleted) - *
GET /_localemu/api/accounts/<id>/summary. per-service resource counts in that account
$ 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
- *AWS Organizations. org tree, member accounts, the auto-seeded OrganizationAccountAccessRole
- *Cross-Account Access. resource policies, condition keys, delegation patterns
- *IAM Enforcement. the enforcement modes and how policies are evaluated