diff --git a/packages/docker-reverse-proxy/go.mod b/packages/docker-reverse-proxy/go.mod index 1f91e5dc04..a1666e3411 100644 --- a/packages/docker-reverse-proxy/go.mod +++ b/packages/docker-reverse-proxy/go.mod @@ -11,6 +11,7 @@ require ( github.com/e2b-dev/infra/packages/shared v0.0.0 github.com/google/uuid v1.6.0 github.com/jellydator/ttlcache/v3 v3.4.0 + github.com/launchdarkly/go-server-sdk/v7 v7.13.0 github.com/stretchr/testify v1.11.1 ) @@ -35,16 +36,26 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.10.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.18.6 // indirect + github.com/launchdarkly/ccache v1.1.0 // indirect + github.com/launchdarkly/eventsource v1.10.0 // indirect + github.com/launchdarkly/go-jsonstream/v3 v3.1.0 // indirect + github.com/launchdarkly/go-sdk-common/v3 v3.3.0 // indirect + github.com/launchdarkly/go-sdk-events/v3 v3.5.0 // indirect + github.com/launchdarkly/go-semver v1.0.3 // indirect + github.com/launchdarkly/go-server-sdk-evaluation/v3 v3.0.1 // indirect github.com/lib/pq v1.11.2 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/magiconair/properties v1.8.10 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mfridman/interpolate v0.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.2.0 // indirect @@ -57,6 +68,7 @@ require ( github.com/moby/term v0.5.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/pressly/goose/v3 v3.27.2 // indirect @@ -78,6 +90,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect golang.org/x/crypto v0.53.0 // indirect + golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect golang.org/x/mod v0.37.0 // indirect golang.org/x/net v0.56.0 // indirect golang.org/x/sync v0.21.0 // indirect diff --git a/packages/docker-reverse-proxy/go.sum b/packages/docker-reverse-proxy/go.sum index e492f3623a..782bd12dd0 100644 --- a/packages/docker-reverse-proxy/go.sum +++ b/packages/docker-reverse-proxy/go.sum @@ -54,6 +54,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 h1:D/V0gu4zQ3cL2WKeVNVM4r2gLxGGf6McLwgXzRTo2RQ= @@ -68,18 +70,42 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003 h1:vJ0Snvo+SLMY72r5J4sEfkuE7AFbixEP2qRbEcum/wA= +github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003/go.mod h1:zNBxMY8P21owkeogJELCLeHIt+voOSduHYTFUbwRAV8= github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/launchdarkly/ccache v1.1.0 h1:voD1M+ZJXR3MREOKtBwgTF9hYHl1jg+vFKS/+VAkR2k= +github.com/launchdarkly/ccache v1.1.0/go.mod h1:TlxzrlnzvYeXiLHmesMuvoZetu4Z97cV1SsdqqBJi1Q= +github.com/launchdarkly/eventsource v1.10.0 h1:H9Tp6AfGu/G2qzBJC26iperrvwhzdbiA/gx7qE2nDFI= +github.com/launchdarkly/eventsource v1.10.0/go.mod h1:J3oa50bPvJesZqNAJtb5btSIo5N6roDWhiAS3IpsKck= +github.com/launchdarkly/go-jsonstream/v3 v3.1.0 h1:U/7/LplZO72XefBQ+FzHf6o4FwLHVqBE+4V58Ornu/E= +github.com/launchdarkly/go-jsonstream/v3 v3.1.0/go.mod h1:2Pt4BR5AwWgsuVTCcIpB6Os04JFIKWfoA+7faKkZB5E= +github.com/launchdarkly/go-sdk-common/v3 v3.3.0 h1:kkf78wcKX+DOXzNjG29i+py/P+XMIw8/mXS7eEWGQwU= +github.com/launchdarkly/go-sdk-common/v3 v3.3.0/go.mod h1:mXFmDGEh4ydK3QilRhrAyKuf9v44VZQWnINyhqbbOd0= +github.com/launchdarkly/go-sdk-events/v3 v3.5.0 h1:Yav8Thm70dZbO8U1foYwZPf3w60n/lNBRaYeeNM/qg4= +github.com/launchdarkly/go-sdk-events/v3 v3.5.0/go.mod h1:oepYWQ2RvvjfL2WxkE1uJJIuRsIMOP4WIVgUpXRPcNI= +github.com/launchdarkly/go-semver v1.0.3 h1:agIy/RN3SqeQDIfKkl+oFslEdeIs7pgsJBs3CdCcGQM= +github.com/launchdarkly/go-semver v1.0.3/go.mod h1:xFmMwXba5Mb+3h72Z+VeSs9ahCvKo2QFUTHRNHVqR28= +github.com/launchdarkly/go-server-sdk-evaluation/v3 v3.0.1 h1:rTgcYAFraGFj7sBMB2b7JCYCm0b9kph4FaMX02t4osQ= +github.com/launchdarkly/go-server-sdk-evaluation/v3 v3.0.1/go.mod h1:fPS5d+zOsgFnMunj+Ki6jjlZtFvo4h9iNbtNXxzYn58= +github.com/launchdarkly/go-server-sdk/v7 v7.13.0 h1:ajiZOPBwmWVFFgP+EMdy3oS1Xl9wNDlEd/7Zn/0I2JU= +github.com/launchdarkly/go-server-sdk/v7 v7.13.0/go.mod h1:6krbDWp417H7lIg+3ehh/A/AW5xwHtiUFg06fvNYHAk= +github.com/launchdarkly/go-test-helpers/v3 v3.1.0 h1:E3bxJMzMoA+cJSF3xxtk2/chr1zshl1ZWa0/oR+8bvg= +github.com/launchdarkly/go-test-helpers/v3 v3.1.0/go.mod h1:Ake5+hZFS/DmIGKx/cizhn5W9pGA7pplcR7xCxWiLIo= github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= @@ -110,6 +136,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -131,6 +159,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= @@ -142,6 +171,8 @@ github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYI github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ= +github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -172,6 +203,8 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= diff --git a/packages/docker-reverse-proxy/internal/auth/validate.go b/packages/docker-reverse-proxy/internal/auth/validate.go index 270e4807c1..f427987bc7 100644 --- a/packages/docker-reverse-proxy/internal/auth/validate.go +++ b/packages/docker-reverse-proxy/internal/auth/validate.go @@ -7,6 +7,8 @@ import ( "log" "strings" + "github.com/google/uuid" + "github.com/e2b-dev/infra/packages/db/client" authdb "github.com/e2b-dev/infra/packages/db/pkg/auth" "github.com/e2b-dev/infra/packages/db/queries" @@ -30,20 +32,22 @@ func Validate(ctx context.Context, sqlcDB *client.Client, token, envID string) ( return exists, nil } -func ValidateAccessToken(ctx context.Context, db *authdb.Client, accessToken string) bool { +// ValidateAccessToken verifies the token format and DB presence and returns +// the owning user ID so callers can evaluate per-user feature flags. +func ValidateAccessToken(ctx context.Context, db *authdb.Client, accessToken string) (uuid.UUID, bool) { hashedToken, err := keys.VerifyKey(keys.AccessTokenPrefix, accessToken) if err != nil { - return false + return uuid.UUID{}, false } - _, err = db.Read.GetUserIDFromAccessToken(ctx, hashedToken) + userID, err := db.Read.GetUserIDFromAccessToken(ctx, hashedToken) if err != nil { log.Printf("Error while checking access token: %s\n", err.Error()) - return false + return uuid.UUID{}, false } - return true + return userID, true } func ExtractAccessToken(authHeader, authType string) (string, error) { diff --git a/packages/docker-reverse-proxy/internal/handlers/store.go b/packages/docker-reverse-proxy/internal/handlers/store.go index 9cfb3d9a08..2b5518d84b 100644 --- a/packages/docker-reverse-proxy/internal/handlers/store.go +++ b/packages/docker-reverse-proxy/internal/handlers/store.go @@ -14,14 +14,16 @@ import ( "github.com/e2b-dev/infra/packages/db/pkg/pool" "github.com/e2b-dev/infra/packages/docker-reverse-proxy/internal/cache" "github.com/e2b-dev/infra/packages/shared/pkg/consts" + "github.com/e2b-dev/infra/packages/shared/pkg/featureflags" "github.com/e2b-dev/infra/packages/shared/pkg/utils" ) type APIStore struct { - db *client.Client - authDb *authdb.Client - AuthCache *cache.AuthCache - proxy *httputil.ReverseProxy + db *client.Client + authDb *authdb.Client + AuthCache *cache.AuthCache + proxy *httputil.ReverseProxy + featureFlags *featureflags.Client } func NewStore(ctx context.Context) *APIStore { @@ -38,6 +40,11 @@ func NewStore(ctx context.Context) *APIStore { log.Fatal(err) } + featureFlags, err := featureflags.NewClient() + if err != nil { + log.Fatal(err) + } + targetUrl := &url.URL{ Scheme: "https", Host: fmt.Sprintf("%s-docker.pkg.dev", consts.GCPRegion), @@ -57,10 +64,11 @@ func NewStore(ctx context.Context) *APIStore { } return &APIStore{ - db: database, - authDb: authDatabase, - AuthCache: authCache, - proxy: proxy, + db: database, + authDb: authDatabase, + AuthCache: authCache, + proxy: proxy, + featureFlags: featureFlags, } } diff --git a/packages/docker-reverse-proxy/internal/handlers/token.go b/packages/docker-reverse-proxy/internal/handlers/token.go index ba2dfbc5ed..7d87f93199 100644 --- a/packages/docker-reverse-proxy/internal/handlers/token.go +++ b/packages/docker-reverse-proxy/internal/handlers/token.go @@ -14,6 +14,7 @@ import ( "github.com/e2b-dev/infra/packages/docker-reverse-proxy/internal/auth" "github.com/e2b-dev/infra/packages/shared/pkg/consts" + "github.com/e2b-dev/infra/packages/shared/pkg/featureflags" ) type DockerToken struct { @@ -39,7 +40,8 @@ func (a *APIStore) GetToken(w http.ResponseWriter, r *http.Request) error { return fmt.Errorf("error while extracting access token: %w", err) } - if !auth.ValidateAccessToken(ctx, a.authDb, accessToken) { + userID, ok := auth.ValidateAccessToken(ctx, a.authDb, accessToken) + if !ok { log.Printf("Invalid access token: '%s'\n", accessToken) w.WriteHeader(http.StatusForbidden) @@ -48,6 +50,15 @@ func (a *APIStore) GetToken(w http.ResponseWriter, r *http.Request) error { return errors.New("invalid access token") } + // Access token acceptance is gated after validation so the flag can be + // rolled out per-user via LD targeting during the deprecation cutover. + if a.featureFlags.BoolFlag(ctx, featureflags.DisableE2BAccessTokenAuthFlag, featureflags.UserContext(userID.String())) { + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("E2B_ACCESS_TOKEN is deprecated and no longer accepted. Use an API key (E2B_API_KEY) instead. See https://e2b.dev/docs/migration/access-token-deprecation")) + + return errors.New("access token authentication is disabled") + } + scope := r.URL.Query().Get("scope") hasScope := scope != "" diff --git a/packages/docker-reverse-proxy/internal/handlers/token_test.go b/packages/docker-reverse-proxy/internal/handlers/token_test.go new file mode 100644 index 0000000000..2337bf94c1 --- /dev/null +++ b/packages/docker-reverse-proxy/internal/handlers/token_test.go @@ -0,0 +1,106 @@ +package handlers + +import ( + "encoding/base64" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/google/uuid" + "github.com/launchdarkly/go-server-sdk/v7/testhelpers/ldtestdata" + "github.com/stretchr/testify/require" + + authqueries "github.com/e2b-dev/infra/packages/db/pkg/auth/queries" + "github.com/e2b-dev/infra/packages/db/pkg/testutils" + "github.com/e2b-dev/infra/packages/docker-reverse-proxy/internal/cache" + "github.com/e2b-dev/infra/packages/shared/pkg/featureflags" + "github.com/e2b-dev/infra/packages/shared/pkg/keys" +) + +func newTokenTestStore(t *testing.T, accessTokenAuthDisabled bool) (*APIStore, keys.Key) { + t.Helper() + + td := ldtestdata.DataSource() + td.Update(td.Flag(featureflags.DisableE2BAccessTokenAuthFlag.Key()).VariationForAll(accessTokenAuthDisabled)) + ff, err := featureflags.NewClientWithDatasource(td) + require.NoError(t, err) + t.Cleanup(func() { _ = ff.Close(t.Context()) }) + + db := testutils.SetupDatabase(t) + + accessToken, err := keys.GenerateKey(keys.AccessTokenPrefix) + require.NoError(t, err) + + userID := uuid.New() + require.NoError(t, db.AuthDB.Write.UpsertPublicUser(t.Context(), userID)) + + _, err = db.AuthDB.Write.CreateAccessToken(t.Context(), authqueries.CreateAccessTokenParams{ + ID: uuid.New(), + UserID: userID, + AccessTokenHash: accessToken.HashedValue, + AccessTokenPrefix: accessToken.Masked.Prefix, + AccessTokenLength: int32(accessToken.Masked.ValueLength), + AccessTokenMaskPrefix: accessToken.Masked.MaskedValuePrefix, + AccessTokenMaskSuffix: accessToken.Masked.MaskedValueSuffix, + Name: "Test token", + }) + require.NoError(t, err) + + return &APIStore{ + db: db.SqlcClient, + authDb: db.AuthDB, + AuthCache: cache.New(), + featureFlags: ff, + }, accessToken +} + +func newTokenRequest(t *testing.T, rawAccessToken string) *http.Request { + t.Helper() + + req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/v2/token", nil) + loginInfo := base64.StdEncoding.EncodeToString(fmt.Appendf(nil, "_e2b_access_token:%s", rawAccessToken)) + req.Header.Set("Authorization", "Basic "+loginInfo) + + return req +} + +func TestGetTokenAcceptsAccessTokenWhenAuthEnabled(t *testing.T) { + t.Parallel() + + store, accessToken := newTokenTestStore(t, false) + + recorder := httptest.NewRecorder() + err := store.GetToken(recorder, newTokenRequest(t, accessToken.PrefixedRawValue)) + + require.NoError(t, err) + require.Equal(t, http.StatusOK, recorder.Code) + require.Contains(t, recorder.Body.String(), "token") +} + +func TestGetTokenRejectsAccessTokenWhenAuthDisabled(t *testing.T) { + t.Parallel() + + store, accessToken := newTokenTestStore(t, true) + + recorder := httptest.NewRecorder() + err := store.GetToken(recorder, newTokenRequest(t, accessToken.PrefixedRawValue)) + + require.Error(t, err) + require.Equal(t, http.StatusForbidden, recorder.Code) + require.Contains(t, recorder.Body.String(), "E2B_API_KEY") + require.Contains(t, recorder.Body.String(), "https://e2b.dev/docs/migration/access-token-deprecation") +} + +func TestGetTokenRejectsInvalidAccessTokenRegardlessOfFlag(t *testing.T) { + t.Parallel() + + store, _ := newTokenTestStore(t, true) + + recorder := httptest.NewRecorder() + err := store.GetToken(recorder, newTokenRequest(t, keys.AccessTokenPrefix+"invalid")) + + require.Error(t, err) + require.Equal(t, http.StatusForbidden, recorder.Code) + require.Contains(t, recorder.Body.String(), "invalid access token") +} diff --git a/packages/shared/pkg/featureflags/flags.go b/packages/shared/pkg/featureflags/flags.go index aa7fb89248..1c4f02ffe4 100644 --- a/packages/shared/pkg/featureflags/flags.go +++ b/packages/shared/pkg/featureflags/flags.go @@ -225,11 +225,12 @@ var ( // default so issuance keeps working until the deprecation cutover. DisableE2BAccessTokenProvisioningFlag = NewBoolFlag("disable-e2b-access-token-provisioning", false) - // DisableE2BAccessTokenAuthFlag stops the API from accepting E2B access - // tokens (sk_e2b_) for authentication once enabled. E2B_ACCESS_TOKEN is - // superseded by E2B_API_KEY; existing tokens stop working on the - // deprecation cutover (Aug 1, 2026). Off by default. Evaluated per-user so - // rejection can be rolled out gradually via LD targeting. + // DisableE2BAccessTokenAuthFlag stops the API and docker-reverse-proxy + // (V1 build docker login) from accepting E2B access tokens (sk_e2b_) for + // authentication once enabled. E2B_ACCESS_TOKEN is deprecated in favor of + // E2B_API_KEY; existing tokens stop working on the deprecation cutover + // (Aug 1, 2026). Off by default. Evaluated per-user so rejection can be + // rolled out gradually via LD targeting. DisableE2BAccessTokenAuthFlag = NewBoolFlag("disable-e2b-access-token-auth", false) )