fix(admin): require an admin token for /_cloudemu under --enforce-auth - #1438
Merged
Merged
Conversation
Every control endpoint except health now needs Authorization: Bearer <token> when serve runs with --enforce-auth. The token comes from --admin-token or CLOUDEMU_ADMIN_TOKEN, or is generated and printed once or written to --admin-token-file. Seed fixtures accept iamUsers with fixed access keys so the first IAM user can be bootstrapped with the token. cloudemu start, the snapshot/net/cost commands and testcontainers send the token.
Write the admin token through an O_EXCL temp file renamed into place, insert imported access keys with SetIfAbsent, require AKIA-shaped key ids and bounded secrets in seed fixtures, and let testcontainers generate the token and append --enforce-auth to an existing command.
…oints-enforce-auth # Conflicts: # providers/aws/iam/iam.go
NitinKumar004
marked this pull request as ready for review
October 4, 2026 10:50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes AUTHN-X3. Under
serve --enforce-auththe/_cloudemu/*control plane needed no credentials, socurl :4566/_cloudemu/snapshotreturned every IAM secret access key, and an anonymous POST tosnapshot,seedorresetcould replace all state, including injecting IAM keys. The SigV4 gate added by--enforce-authonly covered the cloud APIs.What changed
server/admin).Control.RequireTokenmakes every control endpoint excepthealthrequireAuthorization: Bearer <token>and return 401 with aWWW-Authenticate: Bearerchallenge otherwise. Both sides are hashed with SHA-256 and compared withcrypto/subtle, so the check is constant time and doesn't leak the length. Requests outside/_cloudemu/go to the backend untouched.server/serverkit). WithEnforceAuthandAdminboth on,NewtakesConfig.AdminTokenor generates a random 32-byte token. IfConfig.AdminTokenFileis set, the token goes to that file (mode 0600) and only the path is logged. Otherwise a generated token is printed once on stderr. Every listener's Control (AWS, Azure, GCP, OCI, Kubernetes) gets the same token. The non-loopback--adminwarning no longer fires under--enforce-auth, since the plane is now gated.server/serveflags).--admin-token(envCLOUDEMU_ADMIN_TOKEN) and--admin-token-file(envCLOUDEMU_ADMIN_TOKEN_FILE). They're shared, socloudemu serveandcontrib/serverboth get them.-hnever prints the token, even when it comes from the env. The help for--admin-tokendocuments the bootstrap.seed, AWS IAM). Under--enforce-authyou can't callCreateAccessKeywithout a key to sign with, so there was no in-band way to get the first key (earlier e2e runs worked around this by restoring a snapshot). Seed fixtures now takeiamUserswith access keys whose id and secret you choose. An optionaliamdriver.AccessKeyImportercapability (AWS only, likeAccessKeyResolver) registers them, with the same per-user quota and duplicate checks asCreateAccessKey. A user with no policies is unrestricted, so that user can create everyone else through the normal IAM API. This works throughPOST /_cloudemu/seedwith the admin token and through--init-dir.cloudemu startpasses--admin-token-file ~/.cloudemu/admin-token, removes any stale token before launching, anddeleteremoves the file.cloudemu snapshot save|load,netandcostsend the token fromCLOUDEMU_ADMIN_TOKENor from that file, and a 401 turns into a clear error. In testcontainers,WithEnforceAuth(token)starts the container with--enforce-authand waits on/_cloudemu/health(unsignedGET /returns 403 under enforce-auth).ResetandSeedsend the token. Thecontrib/serverenforce-auth tests now bootstrap through the seed path instead of a second auth-off server.contrib/server/README.mdhas a new "Admin token and the first IAM user" section.docs/standalone-server.mdanddocs/persistence.mdand the testcontainers README are updated too.Design choice
I went with a bearer admin token (option a) over SigV4 from an admin principal:
/_cloudemu, and SigV4 only means something on the AWS side.startwrites it to the run dir, the CLI reads it, and testcontainers passes it through the env.Behaviour with
--enforce-authoff is unchanged: the control plane stays open and the token flags are ignored. That's still the documented developer mode. With auth on and a valid token, the snapshot GET still returns secrets, because restore needs them.healthstays open on purpose. There's no DockerfileHEALTHCHECK.cloudemu startuses a TCP probe, and testcontainers waits onGET /by default or on/_cloudemu/healthwithWithEnforceAuth.Testing
TestEnforceAuthAdminEndpointsRequireTokenin serverkit got 200/400 instead of 401 on every gated endpoint.server/admin: missing, wrong, malformed and non-Bearer tokens get 401 and no body leaks. A valid token gets 200.healthis open, cloud API paths pass through, and with no token set the plane stays open.server/serverkit: enforce-auth gating on every listener, a fixed token, a generated token written 0600, auth-off unchanged, a seeded IAM key resolvable by SigV4, and the warning.server/serveflags: the token comes from the env, the flag wins over the env, and-hnever shows the value.cmd/cloudemu:snapshot save/loadandcostagainst an in-process--enforce-authserver, using the run-dir token andCLOUDEMU_ADMIN_TOKEN. Without a token, or with a wrong one, they fail with the 401 error.contrib/server:TestEnforceAuthAdminEndpointsNeedTokenruns the full flow with the real SDK. Unauthenticated calls get 401 and health is open. It seeds the first user, signsCreateUserwith that user's key, then snapshot, reset and restore all work with the token.seedandproviders/aws/iam:iamUsersapply and validation, andImportAccessKeychecks for user existence, duplicates and quota.go build ./..., thengo vetandgo test -raceon admin, serverkit, serveflags, cmd/cloudemu, persist, seed and aws/iam.contrib/serverpasses-run 'Enforce|Admin|Snapshot', and golangci-lint--new-from-revis clean.E2E against a real
serve --enforce-auth --aws-port 45766:snapshotGET/POST,reset,seed,cost,net/can-connectandsnapshot/return 401, andhealthreturns 200.iamUserswith the token, then callingaws iam create-user,sts get-caller-identityands3 mbwith that key, all succeed. A bogus key getsInvalidClientTokenId.cloudemu snapshot save/list/loadandcostwork withCLOUDEMU_ADMIN_TOKENand fail cleanly without it.cloudemu start --enforce-authwritesadmin-token(0600), andsnapshot saveworks with no env set. An unauthenticated reset returns 401.The testcontainers module vets and builds, but its Docker test (
TestEnforceAuthAdminToken) builds the image and the local Docker daemon wasn't responding, so it didn't run here. CI covers it.