Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6996c90
test(integration): add §G SDK lifecycle tests (batch 1)
Jun 10, 2026
69eaf13
test(integration): add §G SDK lifecycle tests (batch 2)
Jun 10, 2026
86f5d55
test(terraform): add §H/§O fixtures + document §G/§H/§O progress
Jun 10, 2026
3973005
parity(§P): pass-4 line-level fixes across 12 services
Jun 10, 2026
c3b6b33
dashboard: add §E backend-only service pages (18 services)
Jun 10, 2026
16f14eb
parity(§R): Cognito auth correctness fixes
Jun 10, 2026
57a47b6
parity(§R): CloudFormation error-code & intrinsic fidelity
Jun 10, 2026
ff1e7fc
parity(§Q): RolesAnywhere pagination correctness
Jun 10, 2026
aa4ab25
parity(§Q/§R): OpsWorks error status + VerifiedPermissions desc bound
Jun 10, 2026
05118cd
parity(§R): bound list MaxResults on EMR Serverless & MediaStore Data
Jun 10, 2026
c3bd176
parity(§Q/§R): IdentityStore & Batch list-input validation
Jun 10, 2026
7c9abad
parity(§Q): Polly NextToken omitempty + API GW Mgmt GoneException shape
Jun 10, 2026
7b9e3da
parity(§Q): S3Control priority + Account PutAlternateContact validation
Jun 10, 2026
6153ff4
parity: lint cleanup for pass-5/6 fixes
Jun 11, 2026
03953a7
parity: document §Q/§R pass-5/6 status
Jun 11, 2026
256544a
dashboard: §F UI features for SQS, SNS, KMS, Secrets Manager
Jun 10, 2026
90b4b1e
dashboard: §F UI features for SSM and Lambda
Jun 11, 2026
1067813
dashboard: §F result export for Athena + CloudWatch Logs; parity.md s…
Jun 11, 2026
2c95cdf
parity(§I/§N): seedable Inspector2 findings, NextToken pagination, re…
Jun 11, 2026
5eb5c60
parity(§I): populate Forecast GetAccuracyMetrics with deterministic b…
Jun 11, 2026
0a183c0
parity: document §I/§N pass-7 status (implemented + false-positives +…
Jun 11, 2026
34c2d45
feat(cloudformation): §K pass-1 — 22 new CFN resource types
Jun 11, 2026
ae5e341
dashboard: §F second pass — popular-services UI features
Jun 11, 2026
2650993
feat(platform): opt-in TLS listener + SigV4 validation; §M wiring ver…
Jun 11, 2026
624dd73
dashboard: §F third pass — non-popular per-service UI features
Jun 11, 2026
2318ee0
feat(parity): CFN intrinsic-error propagation + §N structural items
Jun 11, 2026
917ca19
dashboard: §F fourth pass — more non-popular per-service UI features
Jun 11, 2026
a103028
batch: lazy-init CloudWatch Logs client so page renders under test mocks
Jun 11, 2026
c4ec448
ui: §F pass 5 — Polly lexicon, X-Ray annotations, AppSync pipeline, G…
Jun 11, 2026
507de53
ui: §F pass 6 — ML/AI/media group (Bedrock playground, SageMaker A/B,…
Jun 11, 2026
f932cfe
ui: §F pass 6 — Data/Storage/Networking group (FSx, Glue, Athena, Ope…
Jun 11, 2026
bdaa586
ui: §F pass 6 — Security/identity + Messaging service group
Jun 11, 2026
bccb498
fix(dashboard): restore static/spa/.keep so go:embed all:static/spa r…
Jun 11, 2026
1d1e4d8
test(integration): fix Batch ListJobs all-queues for required jobQueue
Jun 11, 2026
18f2c3d
fix(parity): wire §G services + align SDK wire format for integration…
Jun 11, 2026
37ea992
fix(parity): repair remaining §G integration tests
Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions MULTI_ACCOUNT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Multi-Account / Multi-Region Isolation

This document describes gopherstack's current account/region model, why full
multi-account / multi-region isolation is **not yet implemented**, what a faithful
implementation would require, and a migration path. It is a design note, not an
implemented feature.

## Current model: single account, single region

gopherstack runs as a single-tenant simulator with one fixed account ID and one
default region:

- The account ID comes from `--account-id` / `ACCOUNT_ID` (default
`000000000000`) and the region from `--region` / `REGION` / `AWS_REGION` /
`AWS_DEFAULT_REGION` (default `us-east-1`). Both are surfaced through
`pkgs/config/config.go` (`GlobalConfig.GetAccountID`, `GetRegion`).
- Every service backend keys its in-memory state **only by resource name/ID**
(e.g. an SQS queue is keyed by queue name, a DynamoDB table by table name). The
account ID and region embedded in a request are read for two narrow purposes
only:
- **routing** — `httputils.ExtractRegionFromRequest` / `ExtractServiceFromRequest`
parse the SigV4 `Authorization` credential scope to pick the target service;
- **ARN construction** — backends stamp the configured account/region into the
ARNs they return.
- A handful of services thread a per-request region through to a
region-partitioned store (e.g. Firehose's `regionStore(region)`), but this is
not consistent across services and there is **no account dimension** anywhere.

Practical consequence: two clients pointed at different account IDs or regions
share the same underlying state. `arn:aws:sqs:us-east-1:111111111111:q` and
`arn:aws:sqs:eu-west-1:222222222222:q` resolve to the *same* queue if the name
matches. This matches LocalStack's open-tier default historically, but diverges
from real AWS and from LocalStack's account/region-keyed stores.

## What full isolation would require

Real AWS partitions every resource by **(partition, account, region)**. A
faithful implementation in gopherstack would need all of the following:

1. **Request-scoped account+region resolution.** A single middleware that derives
`(accountID, region)` for every request — from the SigV4 credential scope, the
`X-Amz-*` headers, the host/SNI, or an explicit override — and places it on the
`context.Context`. Today only region is partially derived and only for routing.

2. **Account+region-keyed backends.** Every service's in-memory maps would change
from `map[name]*Resource` to `map[accountID]map[region]map[name]*Resource`
(or an equivalent composite key). This touches **every** backend in
`services/*` — dozens of stores — plus their persistence snapshots, janitors,
TTL sweepers, and reset logic.

3. **Cross-service wiring must carry the scope.** Every event/integration path
(S3→SQS/SNS/Lambda, SNS→*, EventBridge→*, CloudWatch Logs subscription filters,
Step Functions, Pipes, Scheduler, ESM pollers) currently passes resource
names/ARNs. Each would need to resolve and propagate the source resource's
`(account, region)` so the target lookup happens in the correct partition. ARNs
already encode account+region, so target resolution can key off the ARN — but
the source-side context and any name-only lookups must be made scope-aware.

4. **ARN parsing as the source of truth.** Where a target is given by ARN, the
account/region must be read from the ARN rather than the global config. Where a
target is given by bare name (many APIs), the *caller's* request scope must be
used.

5. **Persistence format change.** Snapshot files would need to encode the
account/region dimension so restored state lands in the right partition; this
is a breaking change to the on-disk format and requires a migration/versioning
step in `pkgs/persistence`.

6. **DNS, dashboard, health/reset.** Embedded DNS hostname synthesis, the
dashboard's resource views, and `POST /_gopherstack/reset[?service=…]` would all
need an account/region filter to remain coherent.

## Why it is deferred

This is a cross-cutting re-architecture of the state-keying scheme in every
service, the persistence format, and every cross-service wiring path. It is high
risk (touches all stored state and all delivery paths at once), cannot be staged
safely inside an unrelated stacked PR, and would regress existing single-account
clients unless gated. It is intentionally **out of scope** here and tracked as a
standalone effort.

## Migration path (incremental, low-risk)

1. **Introduce request scope (no behavior change).** Add an
`(accountID, region)` value to the request `context.Context` via middleware,
defaulting to the global config when absent. Backends ignore it at first.

2. **Add a keying abstraction.** Introduce a `scopeKey{account, region}` helper
and a generic partitioned-store wrapper. Backends opt in one at a time,
defaulting all reads/writes to the single global scope so behavior is
identical until a backend is migrated.

3. **Migrate backends incrementally**, highest-value first (DynamoDB, S3, SQS,
SNS, Lambda), each behind the default-global-scope shim, with per-service tests
asserting isolation between two scopes.

4. **Make wiring scope-aware** alongside each migrated service: ARN-targeted
deliveries resolve scope from the ARN; name-targeted deliveries inherit the
source request scope.

5. **Version the persistence format** to carry the scope dimension, with a
loader that maps legacy (scopeless) snapshots into the default global scope.

6. **Flip the default** only once every backend and wiring path is scope-aware,
optionally behind a `--isolate-accounts` flag for one release to allow
rollback.
Loading
Loading