Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions components/ws-proxy/pkg/proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,11 @@ func (c *HostBasedIngressConfig) Validate() error {

// WorkspacePodConfig contains config around the workspace pod.
type WorkspacePodConfig struct {
TheiaPort uint16 `json:"theiaPort"`
IDEDebugPort uint16 `json:"ideDebugPort"`
SupervisorPort uint16 `json:"supervisorPort"`
SupervisorDebugPort uint16 `json:"supervisorDebugPort"`
TheiaPort uint16 `json:"theiaPort"`
IDEDebugPort uint16 `json:"ideDebugPort"`
SupervisorPort uint16 `json:"supervisorPort"`
SupervisorDebugPort uint16 `json:"supervisorDebugPort"`
DebugWorkspaceProxyPort uint16 `json:"debugWorkspaceProxyPort"`
// SupervisorImage is deprecated
SupervisorImage string `json:"supervisorImage"`
}
Expand All @@ -90,6 +91,7 @@ func (c *WorkspacePodConfig) Validate() error {
validation.Field(&c.IDEDebugPort, validation.Required),
validation.Field(&c.SupervisorPort, validation.Required),
validation.Field(&c.SupervisorDebugPort, validation.Required),
validation.Field(&c.DebugWorkspaceProxyPort, validation.Required),
)
if len(c.SupervisorImage) > 0 {
log.Warn("config value 'workspacePodConfig.supervisorImage' is deprected, use it only to be backwards compatible")
Expand Down
21 changes: 18 additions & 3 deletions components/ws-proxy/pkg/proxy/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,15 +329,18 @@ func (ir *ideRoutes) HandleRoot(route *mux.Route) {
}

func installForeignRoutes(r *mux.Router, config *RouteHandlerConfig, infoProvider WorkspaceInfoProvider) error {
installWorkspacePortRoutes(r.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
err := installWorkspacePortRoutes(r.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
workspacePathPrefix := rm.Vars[workspacePathPrefixIdentifier]
if workspacePathPrefix == "" || rm.Vars[workspacePortIdentifier] == "" {
return false
}
r.URL.Path = strings.TrimPrefix(r.URL.Path, workspacePathPrefix)
return true
}).Subrouter(), config, infoProvider)
err := installDebugWorkspaceRoutes(r.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
if err != nil {
return err
}
err = installDebugWorkspaceRoutes(r.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
workspacePathPrefix := rm.Vars[workspacePathPrefixIdentifier]
if workspacePathPrefix == "" || rm.Vars[debugWorkspaceIdentifier] != "true" {
return false
Expand Down Expand Up @@ -419,6 +422,12 @@ func installWorkspacePortRoutes(r *mux.Router, config *RouteHandlerConfig, infoP
r.Header.Add("X-Forwarded-Proto", "https")
r.Header.Add("X-Forwarded-Host", r.Host)
r.Header.Add("X-Forwarded-Port", "443")

coords := getWorkspaceCoords(r)
if coords.Debug {
r.Header.Add("X-WS-Proxy-Debug-Port", coords.Port)
}

proxyPass(
config,
infoProvider,
Expand Down Expand Up @@ -449,7 +458,13 @@ func workspacePodResolver(config *Config, infoProvider WorkspaceInfoProvider, re
func workspacePodPortResolver(config *Config, infoProvider WorkspaceInfoProvider, req *http.Request) (url *url.URL, err error) {
coords := getWorkspaceCoords(req)
workspaceInfo := infoProvider.WorkspaceInfo(coords.ID)
return buildWorkspacePodURL(workspaceInfo.IPAddress, coords.Port)
var port string
if coords.Debug {
port = fmt.Sprint(config.WorkspacePodConfig.DebugWorkspaceProxyPort)
} else {
port = coords.Port
}
return buildWorkspacePodURL(workspaceInfo.IPAddress, port)
}

// workspacePodSupervisorResolver resolves to the workspace pods Supervisor url from the given request.
Expand Down
86 changes: 67 additions & 19 deletions components/ws-proxy/pkg/proxy/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ var (
},
}

ideServerHost = "localhost:20000"
workspacePort = uint16(20001)
supervisorPort = uint16(20002)
workspaceDebugPort = uint16(20004)
supervisorDebugPort = uint16(20005)
workspaceHost = fmt.Sprintf("localhost:%d", workspacePort)
portServeHost = fmt.Sprintf("localhost:%d", workspaces[0].Ports[0].Port)
blobServeHost = "localhost:20003"
ideServerHost = "localhost:20000"
workspacePort = uint16(20001)
supervisorPort = uint16(20002)
workspaceDebugPort = uint16(20004)
supervisorDebugPort = uint16(20005)
debugWorkspaceProxyPort = uint16(20006)
workspaceHost = fmt.Sprintf("localhost:%d", workspacePort)
portServeHost = fmt.Sprintf("localhost:%d", workspaces[0].Ports[0].Port)
blobServeHost = "localhost:20003"
Comment on lines +59 to +67
Copy link
Contributor

@utam0k utam0k Jan 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nits: I prefer net.JoinHostPort to fmt.Sprintf

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the hint, I will remember it 💡


config = Config{
TransportConfig: &TransportConfig{
Expand All @@ -82,10 +83,11 @@ var (
Scheme: "http",
},
WorkspacePodConfig: &WorkspacePodConfig{
TheiaPort: workspacePort,
SupervisorPort: supervisorPort,
IDEDebugPort: workspaceDebugPort,
SupervisorDebugPort: supervisorDebugPort,
TheiaPort: workspacePort,
SupervisorPort: supervisorPort,
IDEDebugPort: workspaceDebugPort,
SupervisorDebugPort: supervisorDebugPort,
DebugWorkspaceProxyPort: debugWorkspaceProxyPort,
},
BuiltinPages: BuiltinPagesConfig{
Location: "../../public",
Expand Down Expand Up @@ -203,13 +205,14 @@ func TestRoutes(t *testing.T) {
Body string
}
type Targets struct {
IDE *Target
Blobserve *Target
Workspace *Target
DebugWorkspace *Target
Supervisor *Target
DebugSupervisor *Target
Port *Target
IDE *Target
Blobserve *Target
Workspace *Target
DebugWorkspace *Target
Supervisor *Target
DebugSupervisor *Target
Port *Target
DebugWorkspaceProxy *Target
}
tests := []struct {
Desc string
Expand Down Expand Up @@ -406,6 +409,30 @@ func TestRoutes(t *testing.T) {
Body: "host: v--sr1o1nu24nqdf809l0u27jk5t7.test-domain.com\npath: /test.html\n",
},
},
{
Desc: "port debug foreign resource",
Config: &config,
Request: modifyRequest(httptest.NewRequest("GET", "https://v--sr1o1nu24nqdf809l0u27jk5t7"+wsHostSuffix+"/28080-debug-amaranth-smelt-9ba20cc1/test.html", nil),
addHostHeader,
addOwnerToken(workspaces[0].InstanceID, workspaces[0].Auth.OwnerToken),
),
Targets: &Targets{
DebugWorkspaceProxy: &Target{
Handler: func(w http.ResponseWriter, r *http.Request, requestCount uint8) {
fmt.Fprintf(w, "host: %s\n", r.Host)
fmt.Fprintf(w, "path: %s\n", r.URL.Path)
},
},
},
Expectation: Expectation{
Status: http.StatusOK,
Header: http.Header{
"Content-Length": {"69"},
"Content-Type": {"text/plain; charset=utf-8"},
},
Body: "host: v--sr1o1nu24nqdf809l0u27jk5t7.test-domain.com\npath: /test.html\n",
},
},
{
Desc: "CORS preflight",
Config: &config,
Expand Down Expand Up @@ -588,6 +615,26 @@ func TestRoutes(t *testing.T) {
Body: "host: 28080-amaranth-smelt-9ba20cc1.test-domain.com\n",
},
},
{
Desc: "debug port GET 404",
Request: modifyRequest(httptest.NewRequest("GET", "https://28080-debug-amaranth-smelt-9ba20cc1.test-domain.com/this-does-not-exist", nil),
addHostHeader,
addOwnerToken(workspaces[0].InstanceID, workspaces[0].Auth.OwnerToken),
),
Targets: &Targets{
DebugWorkspaceProxy: &Target{
Handler: func(w http.ResponseWriter, r *http.Request, requestCount uint8) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "host: %s\n", r.Host)
},
},
},
Expectation: Expectation{
Header: http.Header{"Content-Length": {"58"}, "Content-Type": {"text/plain; charset=utf-8"}},
Status: http.StatusNotFound,
Body: "host: 28080-debug-amaranth-smelt-9ba20cc1.test-domain.com\n",
},
},
{
Desc: "port GET unexposed",
Request: modifyRequest(httptest.NewRequest("GET", workspaces[0].Ports[0].Url+"this-does-not-exist", nil),
Expand Down Expand Up @@ -728,6 +775,7 @@ func TestRoutes(t *testing.T) {
controlTarget(test.Targets.IDE, "IDE", ideServerHost, false)
controlTarget(test.Targets.Blobserve, "blobserve", blobServeHost, true)
controlTarget(test.Targets.Port, "port", portServeHost, true)
controlTarget(test.Targets.DebugWorkspaceProxy, "debug workspace proxy", fmt.Sprintf("localhost:%d", debugWorkspaceProxyPort), false)
controlTarget(test.Targets.Workspace, "workspace", workspaceHost, false)
controlTarget(test.Targets.DebugWorkspace, "debug workspace", fmt.Sprintf("localhost:%d", workspaceDebugPort), false)
controlTarget(test.Targets.Supervisor, "supervisor", fmt.Sprintf("localhost:%d", supervisorPort), false)
Expand Down
72 changes: 55 additions & 17 deletions components/ws-proxy/pkg/proxy/workspacerouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type hostHeaderProvider func(req *http.Request) string
func matchWorkspaceHostHeader(wsHostSuffix string, headerProvider hostHeaderProvider, matchPort bool) mux.MatcherFunc {
var regexPrefix string
if matchPort {
regexPrefix = workspacePortRegex + workspaceIDRegex
regexPrefix = workspacePortRegex + debugWorkspaceRegex + workspaceIDRegex
} else {
regexPrefix = debugWorkspaceRegex + workspaceIDRegex
}
Expand All @@ -99,13 +99,26 @@ func matchWorkspaceHostHeader(wsHostSuffix string, headerProvider hostHeaderProv
return false
}
if matchPort {
if len(matches) < 4 {
return false
}
// https://3000-debug-coral-dragon-ilr0r6eq.ws-eu10.gitpod.io/index.html
// debugWorkspace: true
// workspaceID: coral-dragon-ilr0r6eq
// workspacePort: 3000
if matches[2] != "" {
debugWorkspace = "true"
}
// https://3000-coral-dragon-ilr0r6eq.ws-eu10.gitpod.io/index.html
// debugWorkspace:
// workspaceID: coral-dragon-ilr0r6eq
// workspacePort: 3000
workspaceID = matches[2]
workspaceID = matches[3]
workspacePort = matches[1]
} else {
if len(matches) < 3 {
return false
}
// https://debug-coral-dragon-ilr0r6eq.ws-eu10.gitpod.io/index.html
// debugWorkspace: true
// workspaceID: coral-dragon-ilr0r6eq
Expand Down Expand Up @@ -145,40 +158,65 @@ func matchWorkspaceHostHeader(wsHostSuffix string, headerProvider hostHeaderProv
}

func matchForeignHostHeader(wsHostSuffix string, headerProvider hostHeaderProvider) mux.MatcherFunc {
pathPortRegex := regexp.MustCompile("^/" + workspacePortRegex + workspaceIDRegex + "/")
pathPortRegex := regexp.MustCompile("^/" + workspacePortRegex + debugWorkspaceRegex + workspaceIDRegex + "/")
pathDebugRegex := regexp.MustCompile("^/" + debugWorkspaceRegex + workspaceIDRegex + "/")

r := regexp.MustCompile("^(?:v--)?[0-9a-v]+" + wsHostSuffix)
return func(req *http.Request, m *mux.RouteMatch) bool {
return func(req *http.Request, m *mux.RouteMatch) (result bool) {
hostname := headerProvider(req)
if hostname == "" {
return false
return
}

matches := r.FindStringSubmatch(hostname)
if len(matches) < 1 {
return false
return
}

result = true

var pathPrefix, workspaceID, workspacePort, debugWorkspace string
matches = pathPortRegex.FindStringSubmatch(req.URL.Path)
if len(matches) < 3 {
if len(matches) < 4 {
matches = pathDebugRegex.FindStringSubmatch(req.URL.Path)
if len(matches) < 3 {
return
}
// 0 => pathPrefix
pathPrefix = matches[0]
// 1 => debug
if matches[1] != "" {
debugWorkspace = "true"
}
// 2 => workspaceId
workspaceID = matches[2]
} else {
// 0 => pathPrefix
pathPrefix = matches[0]
// 1 => port
workspacePort = matches[1]
// 2 => debug
if matches[2] != "" {
debugWorkspace = "true"
}
// 3 => workspaceId
workspaceID = matches[3]
}
if len(matches) < 3 {
return true

if pathPrefix == "" {
return
}

if m.Vars == nil {
m.Vars = make(map[string]string)
}
m.Vars[workspacePathPrefixIdentifier] = strings.TrimRight(matches[0], "/")
m.Vars[workspaceIDIdentifier] = matches[2]
if matches[1] == "debug-" {
m.Vars[debugWorkspaceIdentifier] = "true"
} else {
m.Vars[workspacePortIdentifier] = matches[1]
}

return true
m.Vars[workspacePathPrefixIdentifier] = strings.TrimRight(pathPrefix, "/")
m.Vars[workspaceIDIdentifier] = workspaceID
m.Vars[debugWorkspaceIdentifier] = debugWorkspace
m.Vars[workspacePortIdentifier] = workspacePort

return
}
}

Expand Down
16 changes: 16 additions & 0 deletions components/ws-proxy/pkg/proxy/workspacerouter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ func TestWorkspaceRouter(t *testing.T) {
URL: "http://1234-amaranth-smelt-9ba20cc1.ws.gitpod.dev/",
},
},
{
Name: "host-based debug port access",
URL: "http://1234-debug-amaranth-smelt-9ba20cc1.ws.gitpod.dev/",
Headers: map[string]string{
forwardedHostnameHeader: "1234-debug-amaranth-smelt-9ba20cc1.ws.gitpod.dev",
},
Router: HostBasedRouter(forwardedHostnameHeader, wsHostSuffix, wsHostRegex),
WSHostSuffix: wsHostSuffix,
Expected: Expectation{
DebugWorkspace: "true",
WorkspaceID: "amaranth-smelt-9ba20cc1",
WorkspacePort: "1234",
Status: http.StatusOK,
URL: "http://1234-debug-amaranth-smelt-9ba20cc1.ws.gitpod.dev/",
},
},
}

for _, test := range tests {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading