diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 7d665dc01f..cf26529478 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -39,6 +39,8 @@ flowchart TB
LB["Load balancer
api.* | *.domain wildcard | docker.*"]
+ VC["volume-content API (belt)
api.<domain>"]
+
subgraph controlplane["Control plane (API node pool)"]
API["API
REST :80, gRPC :5009/:5109"]
DashAPI["dashboard-api :3010"]
@@ -68,6 +70,8 @@ flowchart TB
SDK -->|REST| LB --> API
Browser -->|"port-sandboxid.domain"| LB --> CP
Docker --> LB --> DRP
+ API -.->|"mint content token + domain"| SDK
+ SDK -->|"volume content (token-authed)
api.<BYOC or default domain>"| VC
API -->|"gRPC Create/Delete/Pause"| ORCH
API -->|"gRPC TemplateCreate"| TM
CP -->|"lookup sandbox → node"| RD
@@ -306,6 +310,43 @@ sequenceDiagram
E-->>U: response
```
+### Volume content
+
+Persistent volumes (`packages/orchestrator/pkg/volumes/`) are managed through the control-plane
+API (`POST/GET /volumes`), but their **content** — reading and writing files — is served by a
+separate volume-content API (belt, `e2b-dev/belt`) that the SDK talks to directly, not through the
+control-plane API. The API's role is to mint the credential and tell the SDK where to send content
+traffic.
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant U as SDK
+ participant API as API
+ participant PG as PostgreSQL
+ participant VC as volume-content API (belt)
+
+ U->>API: POST /volumes (create) or GET /volumes/{id}
+ API->>PG: persist / load volume row
+ API->>API: mint JWT (aud = https://api.<domain>)
resolve domain
+ API-->>U: { volumeID, name, token, domain? }
+ Note over U: domain is returned only for BYOC teams;
SDK stores it and falls back to api.<E2B_DOMAIN> otherwise
+ U->>VC: /volumecontent/{id}/... at api.<domain>
Authorization: Bearer token
+ VC->>VC: verify token (audience must match its own origin)
+ VC-->>U: file content
+```
+
+- **Domain selection.** The token's audience and the content host are the same origin,
+ `https://api.`. For teams on a **custom (BYOC) cluster** (`team.ClusterID` set), the API
+ returns that cluster's domain (`cluster.SandboxDomain`, resolved in
+ `handlers.volumeContentDomain`) so content traffic goes to the BYOC cluster's edge instead of the
+ control-plane host. For teams on the default cluster the response omits `domain` and the SDK uses
+ its configured default (`api.`); the audience then uses the deployment's `DOMAIN_NAME`.
+- **Token.** A short-lived JWT (`handlers.generateVolumeContentToken`, config in
+ `cfg.VolumesTokenConfig`) signed by the API, scoped to the team and volume, presented as a bearer
+ token on every content request. Its `aud` claim is `https://api.`, so a token minted for
+ one cluster's origin is not accepted by another.
+
### Pause and resume
- **Pause**: API records a snapshot row in Postgres, then gRPC `Pause` to the node. The
diff --git a/packages/api/internal/api/api.gen.go b/packages/api/internal/api/api.gen.go
index 7d7b9b83e0..be2b029a79 100644
--- a/packages/api/internal/api/api.gen.go
+++ b/packages/api/internal/api/api.gen.go
@@ -1481,6 +1481,12 @@ type Volume struct {
// VolumeAndToken defines model for VolumeAndToken.
type VolumeAndToken struct {
+ // Domain Domain to use as the destination for volume content requests,
+ // replacing the default `api.`. Only returned when the
+ // team is connected to a custom (BYOC) cluster; absent otherwise, in
+ // which case the default domain is used.
+ Domain *string `json:"domain,omitempty"`
+
// Name Name of the volume
Name string `json:"name"`
@@ -14037,210 +14043,212 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options
// const string: with thousands of chunks the chained `+` fold is several
// times slower for the Go compiler than parsing a slice literal.
var swaggerSpec = []string{
- "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",
- "/i4Ens83/xsAAP//",
+ "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",
+ "uxB4Pt/+vwAAAP//",
}
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
diff --git a/packages/api/internal/clusters/mock.go b/packages/api/internal/clusters/mock.go
new file mode 100644
index 0000000000..375ee6a244
--- /dev/null
+++ b/packages/api/internal/clusters/mock.go
@@ -0,0 +1,29 @@
+package clusters
+
+import (
+ "github.com/google/uuid"
+
+ "github.com/e2b-dev/infra/packages/shared/pkg/smap"
+)
+
+// NewTestPool builds a Pool pre-populated with the given clusters, for use in
+// tests that need cluster lookups (e.g. GetClusterById) without spinning up a
+// full synchronization loop.
+func NewTestPool(clusters ...*Cluster) *Pool {
+ m := smap.New[*Cluster]()
+ for _, c := range clusters {
+ m.Insert(c.ID.String(), c)
+ }
+
+ return &Pool{clusters: m}
+}
+
+// NewTestCluster builds a minimal Cluster carrying just an ID and sandbox
+// domain, for tests. Other collaborators (instances, synchronization,
+// resources) are left nil.
+func NewTestCluster(id uuid.UUID, sandboxDomain *string) *Cluster {
+ return &Cluster{
+ ID: id,
+ SandboxDomain: sandboxDomain,
+ }
+}
diff --git a/packages/api/internal/handlers/volume_create.go b/packages/api/internal/handlers/volume_create.go
index 200080ba64..e202e4c050 100644
--- a/packages/api/internal/handlers/volume_create.go
+++ b/packages/api/internal/handlers/volume_create.go
@@ -89,6 +89,16 @@ func (a *APIStore) PostVolumes(c *gin.Context) {
clusterID := clustershared.WithClusterFallback(team.ClusterID)
+ // Resolve the BYOC domain up front so we fail before allocating any
+ // resources if the team's cluster can't be found.
+ domain, err := a.volumeContentDomain(team)
+ if err != nil {
+ a.sendAPIStoreError(c, http.StatusServiceUnavailable, "Cluster not found")
+ telemetry.ReportError(ctx, "cluster not found", err)
+
+ return
+ }
+
// The volume identity we intend to create. We generate the ID up front so we
// can hand it to the orchestrator, which is given the chance to adjust these
// values (e.g. resolve a placeholder volume type) and returns the definitive
@@ -164,7 +174,7 @@ func (a *APIStore) PostVolumes(c *gin.Context) {
Set("volume_type", volume.VolumeType),
)
- token, apiErr := generateVolumeContentToken(a.config.VolumesToken, volume, team)
+ token, apiErr := generateVolumeContentToken(a.config.VolumesToken, volume, team, a.volumeTokenAudience(domain))
if apiErr != nil {
a.sendAPIStoreError(c, apiErr.Code, apiErr.ClientMsg)
telemetry.ReportCriticalError(ctx, apiErr.ClientMsg, apiErr.Err)
@@ -176,6 +186,7 @@ func (a *APIStore) PostVolumes(c *gin.Context) {
VolumeID: volume.ID.String(),
Name: volume.Name,
Token: token,
+ Domain: domain,
}
c.JSON(http.StatusCreated, result)
diff --git a/packages/api/internal/handlers/volume_get.go b/packages/api/internal/handlers/volume_get.go
index 01af912c8c..f750975aac 100644
--- a/packages/api/internal/handlers/volume_get.go
+++ b/packages/api/internal/handlers/volume_get.go
@@ -15,7 +15,15 @@ func (a *APIStore) GetVolumesVolumeID(c *gin.Context, volumeID api.VolumeID) {
return
}
- token, err := generateVolumeContentToken(a.config.VolumesToken, volume, team)
+ domain, domainErr := a.volumeContentDomain(team)
+ if domainErr != nil {
+ a.sendAPIStoreError(c, http.StatusServiceUnavailable, "Cluster not found")
+ telemetry.ReportError(c.Request.Context(), "cluster not found", domainErr)
+
+ return
+ }
+
+ token, err := generateVolumeContentToken(a.config.VolumesToken, volume, team, a.volumeTokenAudience(domain))
if err != nil {
a.sendAPIStoreError(c, http.StatusInternalServerError, "failed to sign token")
telemetry.ReportCriticalError(c.Request.Context(), "failed to sign token", err)
@@ -27,6 +35,7 @@ func (a *APIStore) GetVolumesVolumeID(c *gin.Context, volumeID api.VolumeID) {
VolumeID: volume.ID.String(),
Name: volume.Name,
Token: token,
+ Domain: domain,
}
c.JSON(http.StatusOK, result)
diff --git a/packages/api/internal/handlers/volume_get_test.go b/packages/api/internal/handlers/volume_get_test.go
index ff1a6b561c..898256824d 100644
--- a/packages/api/internal/handlers/volume_get_test.go
+++ b/packages/api/internal/handlers/volume_get_test.go
@@ -45,7 +45,8 @@ func TestGenerateVolumeContentToken_SetsTokidHeader(t *testing.T) {
}
// Act: generate token
- tokenStr, apiErr := generateVolumeContentToken(config, volume, team)
+ audience := "https://api.custom.example.com"
+ tokenStr, apiErr := generateVolumeContentToken(config, volume, team, audience)
require.Nil(t, apiErr)
require.NotEmpty(t, tokenStr)
@@ -64,4 +65,11 @@ func TestGenerateVolumeContentToken_SetsTokidHeader(t *testing.T) {
tokidVal, ok := parsed.Header["tokid"].(string)
require.True(t, ok, "expected custom header 'tokid' to be present and a string")
require.Equal(t, config.SigningKeyName, tokidVal)
+
+ // Assert: the audience claim is the origin we passed, not the cluster ID.
+ claims, ok := parsed.Claims.(jwt.MapClaims)
+ require.True(t, ok)
+ aud, err := claims.GetAudience()
+ require.NoError(t, err)
+ require.Equal(t, jwt.ClaimStrings{audience}, aud)
}
diff --git a/packages/api/internal/handlers/volume_token.go b/packages/api/internal/handlers/volume_token.go
index e5e84b04d3..2a0ed978d2 100644
--- a/packages/api/internal/handlers/volume_token.go
+++ b/packages/api/internal/handlers/volume_token.go
@@ -13,12 +13,14 @@ import (
"github.com/e2b-dev/infra/packages/api/internal/cfg"
"github.com/e2b-dev/infra/packages/auth/pkg/types"
"github.com/e2b-dev/infra/packages/db/queries"
- "github.com/e2b-dev/infra/packages/shared/pkg/clusters"
)
var ErrVolumesTokenNotConfigured = errors.New("volumes content token signing is not supported by this deployment")
-func generateVolumeContentToken(config cfg.VolumesTokenConfig, volume queries.Volume, team *types.Team) (string, *api.APIError) {
+// generateVolumeContentToken mints the JWT the SDK presents to the volume
+// content API. audience is the origin the token is valid for
+// (`https://api.`, see volumeTokenAudience).
+func generateVolumeContentToken(config cfg.VolumesTokenConfig, volume queries.Volume, team *types.Team, audience string) (string, *api.APIError) {
if !config.IsConfigured() {
return "", &api.APIError{
Err: ErrVolumesTokenNotConfigured,
@@ -27,14 +29,12 @@ func generateVolumeContentToken(config cfg.VolumesTokenConfig, volume queries.Vo
}
}
- clusterID := clusters.WithClusterFallback(team.ClusterID)
-
now := time.Now()
expiration := now.Add(config.Duration)
claims := jwt.MapClaims{
// registered
- "aud": clusterID.String(),
+ "aud": audience,
"exp": jwt.NewNumericDate(expiration),
"iat": jwt.NewNumericDate(now),
"iss": config.Issuer,
diff --git a/packages/api/internal/handlers/volume_util.go b/packages/api/internal/handlers/volume_util.go
index e26f281108..59cd0aca4f 100644
--- a/packages/api/internal/handlers/volume_util.go
+++ b/packages/api/internal/handlers/volume_util.go
@@ -80,6 +80,38 @@ func (a *APIStore) getVolume(c *gin.Context, volumeID string) (queries.Volume, *
var ErrClusterNotFound = errors.New("cluster not found")
+// volumeContentDomain resolves the domain the SDK should use as the destination
+// for volume content requests. Teams connected to a custom (BYOC) cluster get
+// that cluster's domain, which the SDK uses in place of the default
+// api. host. Teams on the default cluster get nil, leaving the SDK
+// on its configured default.
+func (a *APIStore) volumeContentDomain(team *types.Team) (*string, error) {
+ if team.ClusterID == nil {
+ return nil, nil
+ }
+
+ cluster, ok := a.clusters.GetClusterById(*team.ClusterID)
+ if !ok {
+ return nil, fmt.Errorf("%w: %s", ErrClusterNotFound, team.ClusterID.String())
+ }
+
+ return cluster.SandboxDomain, nil
+}
+
+// volumeTokenAudience builds the audience claim for a volume content token: the
+// origin the SDK targets for content requests, i.e. `https://api.`. The
+// domain is the team's BYOC cluster domain when set (as resolved by
+// volumeContentDomain) and the deployment default otherwise, matching the host
+// the token is actually presented to.
+func (a *APIStore) volumeTokenAudience(domain *string) string {
+ host := a.config.DomainName
+ if domain != nil && *domain != "" {
+ host = *domain
+ }
+
+ return fmt.Sprintf("https://api.%s", host)
+}
+
var ErrNoHealthyOrchestratorFound = errors.New("no healthy orchestrator found")
var ErrUnknownVolumeType = errors.New("unknown volume type")
diff --git a/packages/api/internal/handlers/volume_util_test.go b/packages/api/internal/handlers/volume_util_test.go
index 3ef5453f39..2acb3eeb09 100644
--- a/packages/api/internal/handlers/volume_util_test.go
+++ b/packages/api/internal/handlers/volume_util_test.go
@@ -3,12 +3,78 @@ package handlers
import (
"testing"
+ "github.com/google/uuid"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"github.com/e2b-dev/infra/packages/api/internal/api"
+ "github.com/e2b-dev/infra/packages/api/internal/cfg"
+ "github.com/e2b-dev/infra/packages/api/internal/clusters"
"github.com/e2b-dev/infra/packages/api/internal/orchestrator/nodemanager"
+ "github.com/e2b-dev/infra/packages/auth/pkg/types"
+ authqueries "github.com/e2b-dev/infra/packages/db/pkg/auth/queries"
)
+func TestVolumeTokenAudience(t *testing.T) {
+ t.Parallel()
+
+ store := &APIStore{config: cfg.Config{DomainName: "e2b.app"}}
+
+ t.Run("falls back to the deployment domain", func(t *testing.T) {
+ t.Parallel()
+
+ assert.Equal(t, "https://api.e2b.app", store.volumeTokenAudience(nil))
+ })
+
+ t.Run("uses the BYOC domain when set", func(t *testing.T) {
+ t.Parallel()
+
+ domain := "custom.example.com"
+ assert.Equal(t, "https://api.custom.example.com", store.volumeTokenAudience(&domain))
+ })
+}
+
+func TestVolumeContentDomain(t *testing.T) {
+ t.Parallel()
+
+ clusterID := uuid.New()
+ domain := "custom.example.com"
+
+ store := &APIStore{
+ clusters: clusters.NewTestPool(clusters.NewTestCluster(clusterID, &domain)),
+ }
+
+ teamWith := func(id *uuid.UUID) *types.Team {
+ return &types.Team{Team: &authqueries.Team{ID: uuid.New(), ClusterID: id}}
+ }
+
+ t.Run("no cluster returns nil domain", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := store.volumeContentDomain(teamWith(nil))
+ require.NoError(t, err)
+ assert.Nil(t, got)
+ })
+
+ t.Run("BYOC cluster returns its domain", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := store.volumeContentDomain(teamWith(&clusterID))
+ require.NoError(t, err)
+ require.NotNil(t, got)
+ assert.Equal(t, domain, *got)
+ })
+
+ t.Run("unknown cluster returns error", func(t *testing.T) {
+ t.Parallel()
+
+ unknown := uuid.New()
+ got, err := store.volumeContentDomain(teamWith(&unknown))
+ require.ErrorIs(t, err, ErrClusterNotFound)
+ assert.Nil(t, got)
+ })
+}
+
func TestFindNodesByVolumeLabel(t *testing.T) {
t.Parallel()
diff --git a/spec/openapi.yml b/spec/openapi.yml
index e3822f2dae..39a344ccd9 100644
--- a/spec/openapi.yml
+++ b/spec/openapi.yml
@@ -2058,6 +2058,13 @@ components:
token:
type: string
description: Auth token to use for interacting with volume content
+ domain:
+ type: string
+ description: |
+ Domain to use as the destination for volume content requests,
+ replacing the default `api.`. Only returned when the
+ team is connected to a custom (BYOC) cluster; absent otherwise, in
+ which case the default domain is used.
required:
- volumeID
- name
diff --git a/tests/integration/internal/api/generated.go b/tests/integration/internal/api/generated.go
index 9aea20f7af..924f97841c 100644
--- a/tests/integration/internal/api/generated.go
+++ b/tests/integration/internal/api/generated.go
@@ -1476,6 +1476,12 @@ type Volume struct {
// VolumeAndToken defines model for VolumeAndToken.
type VolumeAndToken struct {
+ // Domain Domain to use as the destination for volume content requests,
+ // replacing the default `api.`. Only returned when the
+ // team is connected to a custom (BYOC) cluster; absent otherwise, in
+ // which case the default domain is used.
+ Domain *string `json:"domain,omitempty"`
+
// Name Name of the volume
Name string `json:"name"`