Skip to content

feat: add single-tenant API key authentication - #123

Draft
yingdi-shan wants to merge 1 commit into
kvcache-ai:mainfrom
yingdi-shan:feat/single-tenant-auth
Draft

feat: add single-tenant API key authentication#123
yingdi-shan wants to merge 1 commit into
kvcache-ai:mainfrom
yingdi-shan:feat/single-tenant-auth

Conversation

@yingdi-shan

@yingdi-shan yingdi-shan commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

What

  • Require one shared AENV_API_KEY on the AgentENV server and gateway.
  • Authenticate control-plane requests with an exact X-API-Key match and sandbox data-plane requests with either that key or an automatically derived, sandbox-scoped access token.
  • Generate and persist a strong API key in the native, Docker, Compose, and Kubernetes setup paths while allowing operators to supply their own key.
  • Update the OpenAPI definition, generated server, tests, and deployment documentation for the single-tenant authentication contract.

Why

AgentENV previously accepted any non-empty supported credential, so an exposed server did not provide a meaningful authentication boundary. Single-tenant deployments need a secure default without adding accounts, a database, or a second user-managed token, and without changing the existing aenv auth or E2B SDK workflows.

Related issue

N/A. This draft PR is the design and implementation review vehicle.

Scope and non-goals

Included:

  • Shared-key authentication for direct node and gateway deployments.
  • E2B-compatible sandbox access tokens derived from the shared key and sandbox ID.
  • Automatic key generation and protected storage in supported installation helpers.
  • Documentation and coverage for native, Docker, Compose, Kubernetes, manual, and static multi-node deployments.

Not included:

  • HTTPS/TLS termination, certificate issuance, or CA distribution.
  • Multi-tenant identity, users, RBAC, key lookup, or key persistence in the server binary.
  • Unrelated API or control-plane refactors.

Design and behavior changes

The server and gateway fail fast when AENV_API_KEY is missing or empty. All non-health API requests require exactly one matching X-API-Key; missing, incorrect, duplicate, bearer, admin-token, and team-ID credentials are rejected. Comparisons use constant-time primitives.

GET /health remains public for load balancers and container health checks. A sandbox response carries an HMAC-SHA256 token derived from the shared key and sandbox ID, allowing current E2B SDKs to access that sandbox without a user-provided E2B_ACCESS_TOKEN. The same derivation is implemented by the Rust node and Go gateway.

Client -- X-API-Key --> gateway/node control API
Client -- derived X-Access-Token --> one sandbox data-plane route

The proxy removes X-API-Key before forwarding traffic into a sandbox and preserves application Authorization headers. Rotating the shared key invalidates previously derived sandbox tokens.

Installers generate a 256-bit e2b_-prefixed key once and preserve it across upgrades. Native installs store it in /etc/default/aenv, Docker setup stores it in /etc/aenv/auth.env, and Kubernetes stores it in Secret/agentenv-auth. The gateway and all runtime nodes in a multi-node deployment must use the same value.

Compatibility and operations

  • Public API or generated protocol: OpenAPI now declares global X-API-Key authentication with /health explicitly anonymous. Existing routes and request shapes are unchanged; sandbox responses populate existing access-token fields.
  • Configuration or defaults: AENV_API_KEY is required at server and gateway startup. Installation helpers generate it automatically; manual and multi-node deployments must provide the shared value.
  • Snapshot manifest, artifact layout, or storage format: N/A; no persistence formats change.
  • Upgrade and rollback: Re-running the native, Docker, or Kubernetes setup preserves an existing generated key. Hand-managed deployments must set AENV_API_KEY before upgrading. Rollback can leave the unused environment variable or secret in place.
  • Host requirements, permissions, ports, or dependencies: Ports and host privileges are unchanged. The key files use restricted permissions. This PR does not encrypt HTTP, so remote traffic still requires a trusted network, VPN, or external TLS termination.

The end-user flow remains one key: aenv auth still stores the server URL and API key, and E2B SDK users set the same value as E2B_API_KEY. Users do not create or copy a separate sandbox token.

Validation

  • make fmt
  • make clippy
  • make test-unit
  • Relevant Rust integration tests
  • make -C services test (required when services/ changes)
  • Generated clients/server regenerated with the documented make target
  • Documentation updated
  • Benchmarks or performance comparison completed

Commands and results:

GitHub Actions on 90e845b:
make fmt                                                    passed
make clippy                                                 passed
make test-unit PROFILE=debug                                passed
sg kvm -c "make test-agent-integration PROFILE=debug"       passed
sg kvm -c "make test-e2e PROFILE=debug"                     passed
SKIP_BUILD=1 make test-e2e-compose PROFILE=debug            passed
(cd services && make build && make test && make fmt-check && make vet)  passed
envd-tests                                                  passed
ublk-tests                                                  passed

Skipped checks and reasons:

  • Benchmarks were not run because authentication is request gating and setup wiring, not a sandbox runtime or storage-path change.

Risks and reviewer notes

  • The API key is a deployment-wide secret. A compromise grants control-plane access until operators rotate the key everywhere and restart services.
  • Authentication does not provide transport encryption. Review the warnings and deployment guidance before exposing a service outside a trusted boundary.
  • Key rotation is intentionally single-key and disruptive; there is no overlap window in this basic single-tenant implementation.
  • The feature branch is currently 17 commits behind main and should be rebased before the PR is marked ready.
  • The most security-sensitive code is the request classification and credential handling in src/api/impls/auth.rs, src/api/proxy.rs, and services/gateway/internal/server.go.

Checklist

  • The PR contains one coherent change and no unrelated formatting or refactoring.
  • New behavior is covered by tests, or I explained why testing is impractical.
  • Logs and examples contain no credentials, tokens, or private registry information.
  • I did not manually edit generated code without updating its source and regenerating it.

@sunkencity999

Copy link
Copy Markdown

Adding a production deployment data point, since this is a design-review vehicle and it may be useful to know how the current "no auth" state actually gets handled in the field.

We run AgentENV as the isolation layer under a multi-user internal agent platform — each end user gets one persistent sandbox, and an application-side shim maps users to sandbox IDs. Because there is no authentication today, we follow the README's guidance literally: the API is bound to 127.0.0.1:8000, never exposed, and the only thing that talks to it is a local process. Everything reachable by a human sits behind a reverse proxy with its own authentication in front of the app, not in front of AgentENV.

Three observations that bear on the design:

1. Loopback-only is a real deployment posture, not just a stopgap — please keep it viable. For us the shared key would be defence in depth behind the loopback bind, not the primary boundary. If the implementation ends up requiring a key even for a loopback-only single-node install, that is a small friction on a config that is already safe; an "auth required unless bound to loopback" default, or simply generating the key automatically as you describe, avoids making people opt out of security to keep a working setup. The automatic generation in your Scope section reads like it already handles this well.

2. The derived sandbox-scoped token is the part we would use most. Our shim already knows which user owns which sandbox; AgentENV does not, and deliberately so. A token scoped to a sandbox ID means a compromised component can only reach the sandbox it was issued for, rather than the whole node. That is a meaningful reduction in blast radius for anyone running this multi-tenant on top, and it is the piece a shared key alone does not give you.

3. Key rotation is the operational question we would ask first. With a single shared key persisted at setup time, what does rotation look like for a running node with live sandboxes — is the expectation a restart, or can the key be re-read? If derived sandbox tokens are a function of the shared key, does rotating it invalidate every in-flight sandbox token? For long-lived sandboxes (ours persist across a working session and pause/resume rather than being recreated per command) that distinction matters quite a bit. Worth documenting whichever way it lands.

One smaller note: if the key ends up in a config file written by the setup paths, it would be helpful for the docs to state the expected file mode and owner explicitly. It is the kind of thing that is obvious to whoever writes it and non-obvious to whoever inherits the box.

Happy to test a branch against a real multi-user deployment if that is useful — we have a node running this pattern daily and can report back on anything that breaks under pause/resume or long-lived sandboxes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants