From 7a85f5176974b5fbce16cd96e2cf75ee1fa67577 Mon Sep 17 00:00:00 2001 From: Salim Kayal Date: Tue, 10 Nov 2026 16:41:04 +0100 Subject: [PATCH] fix: strip leading slash from the remote session wstunnel path prefix WSTUNNEL_PATH_PREFIX was built as RENKU_BASE_URL_PATH + "/__amalthea__/tunnel", and RENKU_BASE_URL_PATH is "/sessions/", so the prefix kept its leading slash. wstunnel's client prepends another one when building the upgrade request (format!("/{}/events")), so the HPC side actually asks for "//sessions//__amalthea__/tunnel/events". That works behind ingress-nginx only because nginx merges repeated slashes when matching locations and still forwards the raw URI. Behind a proxy that matches paths literally (agentgateway/Envoy among others) the request matches no session route, falls through to the UI and returns 404, leaving the remote session frontend unreachable: "failed to do websocket handshake with the server ... Invalid status code: 404". Trim both edges of the base path in a small helper so the emitted -P never starts (or ends) with a slash, and cover it with a test. The remote scratch path is unaffected: it goes through path.Join. Reported against renku/amalthea-sessions 0.31.0 (3ac6997) deployed behind agentgateway; the same code on a nginx-based deployment emits the same doubled path and is only masked by nginx. --- internal/remote/firecrest/controller.go | 10 ++++++++-- internal/remote/firecrest/controller_test.go | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/internal/remote/firecrest/controller.go b/internal/remote/firecrest/controller.go index ffd93c73..87c2a309 100644 --- a/internal/remote/firecrest/controller.go +++ b/internal/remote/firecrest/controller.go @@ -449,8 +449,8 @@ func (c *FirecrestRemoteSessionController) Start(ctx context.Context) error { return err } env["WSTUNNEL_SERVICE_ADDRESS"] = renkuBaseURL.Hostname() - env["WSTUNNEL_SERVICE_PORT"] = fmt.Sprintf("%d", 443) // wss port (same as https) - env["WSTUNNEL_PATH_PREFIX"] = renkuBaseURLPath + "/" + v1alpha1.TunnelIngressPathSuffix // session path with tunnel + env["WSTUNNEL_SERVICE_PORT"] = fmt.Sprintf("%d", 443) // wss port (same as https) + env["WSTUNNEL_PATH_PREFIX"] = wstunnelPathPrefix(renkuBaseURLPath) // session path with tunnel } // Setup environment variables for git repositories repos := []string{} @@ -1055,3 +1055,9 @@ func getPreferredScratch(fileSystems *[]FileSystem) *FileSystem { } return scratch } + +// wstunnelPathPrefix returns the -P value for the remote wstunnel client. wstunnel prepends a "/" to +// it when building the upgrade request, so the prefix must not carry one itself. +func wstunnelPathPrefix(renkuBaseURLPath string) string { + return strings.TrimPrefix(path.Join(renkuBaseURLPath, v1alpha1.TunnelIngressPathSuffix), "/") +} diff --git a/internal/remote/firecrest/controller_test.go b/internal/remote/firecrest/controller_test.go index 2ac1b9b6..9d991513 100644 --- a/internal/remote/firecrest/controller_test.go +++ b/internal/remote/firecrest/controller_test.go @@ -148,3 +148,23 @@ func TestStreamsToFetch(t *testing.T) { }) } } + +func TestWstunnelPathPrefix(t *testing.T) { + tests := []struct { + name string + renkuBaseURLPath string + want string + }{ + {"leading slash", "/sessions/my-session", "sessions/my-session/__amalthea__/tunnel"}, + {"no leading slash", "sessions/my-session", "sessions/my-session/__amalthea__/tunnel"}, + {"trailing slash", "/sessions/my-session/", "sessions/my-session/__amalthea__/tunnel"}, + {"doubled leading slash", "//sessions/my-session", "sessions/my-session/__amalthea__/tunnel"}, + } + // wstunnel's client prepends a "/" when it builds the upgrade request, so a prefix with a leading + // slash produces a "//sessions/..." request path that strict path-matching proxies do not route. + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, wstunnelPathPrefix(tt.renkuBaseURLPath)) + }) + } +}