# Multi-Account Isolation Floci supports full per-account resource isolation out of the box. Resources created by one account are invisible to all others — no configuration flag required. ## How It Works Every incoming request carries an AWS SigV4 `Authorization` header. Floci reads the **Access Key ID** (AKID) from that header and resolves the account in this order: 1. An exactly 12-digit AKID is used directly as the account ID. 2. An active long-term IAM access key resolves to the account that owns it. 3. A live STS temporary credential resolves to its recorded account. 4. Any other key falls back to `FLOCI_DEFAULT_ACCOUNT_ID`. ``` Authorization: AWS4-HMAC-SHA256 Credential=111111111111/20260510/us-east-1/sqs/aws4_request, ... ^^^^^^^^^^^^ 12-digit AKID → account ID 111111111111 ``` Once the account ID is determined, every storage read and write is transparently namespaced under it. An SQS queue named `orders` created by account `111111111111` is stored and retrieved as `111111111111/orders` — completely separate from the same queue name under account `222222222222`. !!! note "Same convention as LocalStack" This 12-digit AKID → account ID rule matches LocalStack's multi-account behavior, so existing multi-account test setups work without changes. ## Long-Term IAM Credentials Active access keys created through IAM route requests back to the account that owns the key. This lets a client keep using its generated `AKIA…` credentials instead of replacing the access key ID with a synthetic 12-digit account ID. Account selection is AKID-based and is not proof that the caller possesses the corresponding secret. This extends Floci's existing local-emulator trust model for 12-digit AKIDs; do not treat account namespaces as a security boundary between mutually untrusted clients. ## Temporary Credentials (AssumeRole) STS temporary credentials are also routed to the correct account. When you call `AssumeRole` (or `AssumeRoleWithWebIdentity`, `AssumeRoleWithSAML`, `GetFederationToken`), Floci issues a temporary `ASIA…` access key and remembers which account it belongs to: - **`AssumeRole` / web-identity / SAML / federation** → the account in the role (or federated-user) ARN. Assuming `arn:aws:iam::222222222222:role/Deployer` from account `111111111111` yields credentials that resolve to **`222222222222`**, so resources created with them land in account B's namespace. - **`GetSessionToken`** → the caller's account (these credentials carry no role), so they stay in the same account. ``` Account 111111111111 ──AssumeRole arn:aws:iam::222222222222:role/Deployer──▶ ASIA… temp key │ ASIA… temp key ──CreateTable orders──▶ stored as 222222222222/...::orders ◀──┘ ``` This makes the cross-account `AssumeRole`-then-provision pattern (e.g. CloudFormation deploying into a target account) work locally exactly as it does in AWS. ## Default Behavior (Single Account) If a credential is not a 12-digit account ID, an active IAM access key, or a live STS session (for example, `test`), requests resolve to the default account ID: ```bash FLOCI_DEFAULT_ACCOUNT_ID=000000000000 # default ``` All ARNs and URLs use this value: ``` arn:aws:sqs:us-east-1:000000000000:my-queue http://localhost:4566/000000000000/my-queue ``` You can change the default account ID without enabling per-request isolation: ```bash FLOCI_DEFAULT_ACCOUNT_ID=123456789012 ``` ## Enabling Multi-Account Isolation Use 12-digit numeric access key IDs. The secret access key can be any non-empty string — Floci does not validate signatures by default. ### AWS CLI ```bash # Configure two named profiles aws configure --profile account-a # AWS Access Key ID: 111111111111 # AWS Secret Access Key: test aws configure --profile account-b # AWS Access Key ID: 222222222222 # AWS Secret Access Key: test export AWS_ENDPOINT_URL=http://localhost:4566 # Create the same queue name under both accounts aws sqs create-queue --queue-name orders --profile account-a aws sqs create-queue --queue-name orders --profile account-b # Each account sees only its own queue aws sqs list-queues --profile account-a # → .../111111111111/orders aws sqs list-queues --profile account-b # → .../222222222222/orders ``` ### AWS SDK (Java) ```java StaticCredentialsProvider accountA = StaticCredentialsProvider.create( AwsBasicCredentials.create("111111111111", "test")); StaticCredentialsProvider accountB = StaticCredentialsProvider.create( AwsBasicCredentials.create("222222222222", "test")); SqsClient clientA = SqsClient.builder() .endpointOverride(URI.create("http://localhost:4566")) .region(Region.US_EAST_1) .credentialsProvider(accountA) .build(); SqsClient clientB = SqsClient.builder() .endpointOverride(URI.create("http://localhost:4566")) .region(Region.US_EAST_1) .credentialsProvider(accountB) .build(); // Both calls succeed, resources are fully isolated clientA.createQueue(r -> r.queueName("orders")); clientB.createQueue(r -> r.queueName("orders")); ``` ### AWS SDK (Python) ```python import boto3 def client(service, account_id): return boto3.client( service, endpoint_url="http://localhost:4566", region_name="us-east-1", aws_access_key_id=account_id, # 12-digit → account ID aws_secret_access_key="test", ) sqs_a = client("sqs", "111111111111") sqs_b = client("sqs", "222222222222") sqs_a.create_queue(QueueName="orders") sqs_b.create_queue(QueueName="orders") print(sqs_a.list_queues()["QueueUrls"]) # [".../111111111111/orders"] print(sqs_b.list_queues()["QueueUrls"]) # [".../222222222222/orders"] ``` ## ARNs Include the Correct Account ID Floci embeds the resolved account ID in every ARN it generates: ``` arn:aws:sqs:us-east-1:111111111111:orders arn:aws:lambda:us-east-1:222222222222:function:my-fn arn:aws:s3:::my-bucket # S3 ARNs are account-agnostic ``` ## Isolation Scope All services that use `StorageFactory` participate in account isolation automatically. This covers every service in Floci — SQS, SNS, S3, DynamoDB, Lambda, SSM, Secrets Manager, KMS, Kinesis, EventBridge, Cognito, RDS, ElastiCache, OpenSearch, MSK, and more. [AWS Organizations](../services/organizations.md) sits on top of this model: the organization's state lives in the management account's namespace, member accounts created with `CreateAccount` are immediately usable as 12-digit access key IDs, and — with IAM enforcement plus `FLOCI_SERVICES_ORGANIZATIONS_SCP_ENFORCEMENT_ENABLED` — service control policies attached in the organization constrain what member-account identities may do. Background workers (Lambda event-source pollers, DynamoDB TTL sweeper, MSK readiness poller, OpenSearch readiness poller) iterate across all accounts internally and route writes back to the originating account. No cross-account data leaks through these async paths. **S3 exception — global bucket namespace.** S3 buckets are isolated per account by default like every other service, but real S3 bucket names are globally unique. Set `FLOCI_SERVICES_S3_GLOBAL_BUCKET_NAMESPACE=true` to make bucket/object resolution span every account's partition, so a bucket created in one account is visible cross-account — needed when a workload writes a bucket in one account and reads it from another (see [S3](../services/s3.md#global-bucket-namespace)). ## Signature Validation Floci does not currently perform general SigV4 validation for `Authorization` headers. Only the access key ID matters for account resolution, so the secret access key can be any non-empty string. `FLOCI_AUTH_VALIDATE_SIGNATURES` currently applies only to S3 presigned URL validation. It does not authenticate general service requests or protect IAM and STS account routing. To validate S3 presigned URLs: ```bash FLOCI_AUTH_VALIDATE_SIGNATURES=true FLOCI_AUTH_PRESIGN_SECRET=your-secret # for pre-signed URL verification ``` When `validate-signatures` is `false` (the default), S3 presigned URL signatures are not verified. Account routing remains AKID-based regardless of this setting. ## Persistence and Account Isolation Storage keys are namespaced per account at the persistence layer. When using `persistent`, `hybrid`, or `wal` storage modes, each account's data is stored under its own key prefix. Restarting Floci restores each account's resources independently. ## Configuration Reference | Variable | Default | Description | |---|---|---| | `FLOCI_DEFAULT_ACCOUNT_ID` | `000000000000` | Account ID used when the AKID does not resolve directly or through a stored credential | | `FLOCI_DEFAULT_REGION` | `us-east-1` | Region used when not derivable from the `Authorization` header | | `FLOCI_AUTH_VALIDATE_SIGNATURES` | `false` | Verify S3 presigned URL signatures |