diff --git a/components/ws-proxy/go.mod b/components/ws-proxy/go.mod index 50021149b40729..e1d2817fd4508e 100644 --- a/components/ws-proxy/go.mod +++ b/components/ws-proxy/go.mod @@ -30,8 +30,10 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/configcat/go-sdk/v7 v7.6.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.8.0 // indirect diff --git a/components/ws-proxy/go.sum b/components/ws-proxy/go.sum index 04a8ecf5e176e7..d5fdc8595668f8 100644 --- a/components/ws-proxy/go.sum +++ b/components/ws-proxy/go.sum @@ -6,6 +6,8 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM= @@ -17,6 +19,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/configcat/go-sdk/v7 v7.6.0 h1:CthQJ7DMz4bvUrpc8aek6VouJjisCvZCfuTG2gyNzL4= +github.com/configcat/go-sdk/v7 v7.6.0/go.mod h1:2245V6Igy1Xz6GXvcYuK5z996Ct0VyzyuI470XS6aTw= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -35,6 +39,8 @@ github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.11.2 h1:mjwHjStlXWibxOohM7HYieIViKyh56mmt3+6viyhDDI= +github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gitpod-io/golang-crypto v0.0.0-20231122075959-de838e9cb174 h1:OoCsCV3wxfN5FvbuhwuUgz6rZvsyfPxX3jZ2fKA+9H8= @@ -77,6 +83,7 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= diff --git a/components/ws-proxy/pkg/config/config.go b/components/ws-proxy/pkg/config/config.go index 97e812141f5022..f5a127d9cb9962 100644 --- a/components/ws-proxy/pkg/config/config.go +++ b/components/ws-proxy/pkg/config/config.go @@ -5,14 +5,20 @@ package config import ( + "context" "encoding/json" "os" + "time" "golang.org/x/xerrors" + "github.com/gitpod-io/gitpod/common-go/experiments" + "github.com/gitpod-io/gitpod/common-go/log" "github.com/gitpod-io/gitpod/ws-proxy/pkg/proxy" ) +const experimentsCorsEnabled = "ws_proxy_cors_enabled" + // Config configures this service. type Config struct { Ingress proxy.HostBasedIngressConfig `json:"ingress"` @@ -64,5 +70,31 @@ func GetConfig(fn string) (*Config, error) { return nil, xerrors.Errorf("config validation error: %w", err) } + timeout := time.Minute * 5 + log.WithField("timeout", timeout).Info("waiting for Feature Flag") + experimentsClient := experiments.NewClient(experiments.WithPollInterval(time.Second * 3)) + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + ffValue := waitExperimentsStringValue(ctx, experimentsClient, experimentsCorsEnabled, "nope", experiments.Attributes{}) + corsEnabled := ffValue == "true" + cfg.Proxy.CorsEnabled = corsEnabled + log.WithField("ffValue", ffValue).WithField("corsEnabled", cfg.Proxy.CorsEnabled).Info("feature flag final value") + return &cfg, nil } + +func waitExperimentsStringValue(ctx context.Context, client experiments.Client, experimentName, nopeValue string, attributes experiments.Attributes) string { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return nopeValue + case <-ticker.C: + value := client.GetStringValue(ctx, experimentName, nopeValue, attributes) + if value != nopeValue { + return value + } + } + } +} diff --git a/components/ws-proxy/pkg/proxy/config.go b/components/ws-proxy/pkg/proxy/config.go index 7d452b10e89a42..6c468720478a1e 100644 --- a/components/ws-proxy/pkg/proxy/config.go +++ b/components/ws-proxy/pkg/proxy/config.go @@ -29,6 +29,7 @@ type Config struct { BuiltinPages BuiltinPagesConfig `json:"builtinPages"` SSHGatewayCAKeyFile string `json:"sshCAKeyFile"` + CorsEnabled bool } // Validate validates the configuration to catch issues during startup and not at runtime. diff --git a/components/ws-proxy/pkg/proxy/routes.go b/components/ws-proxy/pkg/proxy/routes.go index 6af4d7bc5efc31..24510f60f44b29 100644 --- a/components/ws-proxy/pkg/proxy/routes.go +++ b/components/ws-proxy/pkg/proxy/routes.go @@ -61,11 +61,10 @@ func WithDefaultAuth(infoprov common.WorkspaceInfoProvider) RouteHandlerConfigOp // NewRouteHandlerConfig creates a new instance. func NewRouteHandlerConfig(config *Config, opts ...RouteHandlerConfigOpt) (*RouteHandlerConfig, error) { - corsHandler, err := corsHandler(config.GitpodInstallation.Scheme, config.GitpodInstallation.HostName) + corsHandler, err := corsHandler(config.CorsEnabled, config.GitpodInstallation.Scheme, config.GitpodInstallation.HostName) if err != nil { return nil, err } - cfg := &RouteHandlerConfig{ Config: config, DefaultTransport: createDefaultTransport(config.TransportConfig), @@ -190,6 +189,7 @@ func (ir *ideRoutes) HandleCreateKeyRoute(route *mux.Route, hostKeyList []ssh.Si r := route.Subrouter() r.Use(logRouteHandlerHandler("HandleCreateKeyRoute")) r.Use(ir.Config.CorsHandler) + r.Use(ir.workspaceMustExistHandler) r.Use(ir.Config.WorkspaceAuthHandler) @@ -655,7 +655,13 @@ func buildWorkspacePodURL(protocol api.PortProtocol, ipAddress string, port stri } // corsHandler produces the CORS handler for workspaces. -func corsHandler(scheme, hostname string) (mux.MiddlewareFunc, error) { +func corsHandler(enabled bool, scheme, hostname string) (mux.MiddlewareFunc, error) { + if !enabled { + // empty handler + return func(h http.Handler) http.Handler { + return h + }, nil + } origin := fmt.Sprintf("%s://%s", scheme, hostname) domainRegex := strings.ReplaceAll(hostname, ".", "\\.") diff --git a/components/ws-proxy/pkg/proxy/routes_test.go b/components/ws-proxy/pkg/proxy/routes_test.go index 187eac00ff894e..1f808c4ff57ec4 100644 --- a/components/ws-proxy/pkg/proxy/routes_test.go +++ b/components/ws-proxy/pkg/proxy/routes_test.go @@ -95,6 +95,7 @@ var ( BuiltinPages: BuiltinPagesConfig{ Location: "../../public", }, + CorsEnabled: false, } ) @@ -450,7 +451,7 @@ func TestRoutes(t *testing.T) { }, }, { - Desc: "CORS preflight", + Desc: "no CORS allow in workspace urls", Config: &config, Request: modifyRequest(httptest.NewRequest("GET", workspaces[0].URL+"somewhere/in/the/ide", nil), addHostHeader, @@ -462,12 +463,9 @@ func TestRoutes(t *testing.T) { Expectation: Expectation{ Status: http.StatusOK, Header: http.Header{ - "Access-Control-Allow-Credentials": {"true"}, - "Access-Control-Allow-Origin": {"test-domain.com"}, - "Access-Control-Expose-Headers": {"Authorization"}, - "Content-Length": {"37"}, - "Content-Type": {"text/plain; charset=utf-8"}, - "Vary": {"Accept-Encoding"}, + "Content-Length": {"37"}, + "Content-Type": {"text/plain; charset=utf-8"}, + "Vary": {"Accept-Encoding"}, }, Body: "workspace hit: /somewhere/in/the/ide\n", }, diff --git a/install/installer/pkg/common/common.go b/install/installer/pkg/common/common.go index b45c128bb8dd6a..b34f362014c745 100644 --- a/install/installer/pkg/common/common.go +++ b/install/installer/pkg/common/common.go @@ -409,6 +409,31 @@ func ConfigcatEnv(ctx *RenderContext) []corev1.EnvVar { } } +func ConfigcatEnvOutOfCluster(ctx *RenderContext) []corev1.EnvVar { + var sdkKey string + _ = ctx.WithExperimental(func(cfg *experimental.Config) error { + if cfg.WebApp != nil && cfg.WebApp.ConfigcatKey != "" { + sdkKey = cfg.WebApp.ConfigcatKey + } + return nil + }) + + if sdkKey == "" { + return nil + } + + return []corev1.EnvVar{ + { + Name: "CONFIGCAT_SDK_KEY", + Value: "gitpod", + }, + { + Name: "CONFIGCAT_BASE_URL", + Value: fmt.Sprintf("https://%s/configcat", ctx.Config.Domain), + }, + } +} + func ConfigcatProxyEnv(ctx *RenderContext) []corev1.EnvVar { var ( sdkKey string diff --git a/install/installer/pkg/common/common_test.go b/install/installer/pkg/common/common_test.go index 8309a193590a62..245258da1618f5 100644 --- a/install/installer/pkg/common/common_test.go +++ b/install/installer/pkg/common/common_test.go @@ -11,9 +11,11 @@ import ( "github.com/gitpod-io/gitpod/common-go/baseserver" "github.com/gitpod-io/gitpod/installer/pkg/common" config "github.com/gitpod-io/gitpod/installer/pkg/config/v1" + "github.com/gitpod-io/gitpod/installer/pkg/config/v1/experimental" "github.com/gitpod-io/gitpod/installer/pkg/config/versions" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" ) func TestKubeRBACProxyContainer_DefaultPorts(t *testing.T) { @@ -73,3 +75,19 @@ func TestServerComponentWaiterContainer(t *testing.T) { require.Equal(t, labels, "app=gitpod,component=server") require.Equal(t, []string{"-v", "component", "--namespace", "test_namespace", "--component", common.ServerComponent, "--labels", labels, "--image", ctx.Config.Repository + "/server:" + "happy_path_server_image"}, container.Args) } + +func TestConfigcatEnvOutOfCluster(t *testing.T) { + ctx, err := common.NewRenderContext(config.Config{ + Domain: "gitpod.io", + Experimental: &experimental.Config{ + WebApp: &experimental.WebAppConfig{ + ConfigcatKey: "foo", + }, + }, + }, versions.Manifest{}, "test_namespace") + require.NoError(t, err) + + envVars := common.ConfigcatEnvOutOfCluster(ctx) + require.Equal(t, len(envVars), 2) + require.Equal(t, envVars, []v1.EnvVar([]v1.EnvVar{{Name: "CONFIGCAT_SDK_KEY", Value: "gitpod"}, {Name: "CONFIGCAT_BASE_URL", Value: "https://gitpod.io/configcat"}})) +} diff --git a/install/installer/pkg/components/ws-proxy/deployment.go b/install/installer/pkg/components/ws-proxy/deployment.go index 783050c1c0d805..46244ee02efe0c 100644 --- a/install/installer/pkg/components/ws-proxy/deployment.go +++ b/install/installer/pkg/components/ws-proxy/deployment.go @@ -142,6 +142,8 @@ func deployment(ctx *common.RenderContext) ([]runtime.Object, error) { common.DefaultEnv(&ctx.Config), common.WorkspaceTracingEnv(ctx, Component), common.AnalyticsEnv(&ctx.Config), + // ws-proxy and proxy may not in the same cluster + common.ConfigcatEnvOutOfCluster(ctx), )), ReadinessProbe: &corev1.Probe{ InitialDelaySeconds: int32(2),