From 1361789eec5ad278481ebf2316c3a1fcf51172aa Mon Sep 17 00:00:00 2001 From: Vikas Date: Tue, 3 Jun 2025 18:50:17 +0530 Subject: [PATCH 1/2] Add authorization header handling in proxy middleware - Implemented logic to pass the Authorization header to the target if the proxy URL includes user credentials. - Added unit tests to verify behavior for both scenarios: with and without user credentials in the proxy URL. --- middleware/proxy.go | 11 +++++++++ middleware/proxy_test.go | 53 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/middleware/proxy.go b/middleware/proxy.go index 2744bc4a8..a4b993dde 100644 --- a/middleware/proxy.go +++ b/middleware/proxy.go @@ -430,5 +430,16 @@ func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handle } proxy.Transport = config.Transport proxy.ModifyResponse = config.ModifyResponse + + // Handle authorization from target URL + if tgt.URL.User != nil { + originalDirector := proxy.Director + proxy.Director = func(req *http.Request) { + originalDirector(req) + password, _ := tgt.URL.User.Password() + req.SetBasicAuth(tgt.URL.User.Username(), password) + } + } + return proxy } diff --git a/middleware/proxy_test.go b/middleware/proxy_test.go index dbf07648b..a0489a6b9 100644 --- a/middleware/proxy_test.go +++ b/middleware/proxy_test.go @@ -1040,3 +1040,56 @@ func TestProxyWithConfigWebSocketTLS2NonTLS(t *testing.T) { assert.NoError(t, err) assert.Equal(t, sendMsg, recvMsg) } + +func TestProxyWithAuthorizationHeader(t *testing.T) { + // Scenario: + // A proxy target has user:pass in the url. + // The proxy should pass the Authorization header to the target. + + var receivedAuthHeader string + // Arrange + t1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedAuthHeader = r.Header.Get("Authorization") + fmt.Fprint(w, "target 1") + })) + defer t1.Close() + url1, _ := url.Parse(t1.URL) + url1.User = url.UserPassword("user1", "pass1") + + e := echo.New() + tp := &testProvider{} + tp.target = &ProxyTarget{Name: "target 1", URL: url1} + e.Use(Proxy(tp)) + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + e.ServeHTTP(rec, req) + + // Assert + assert.Equal(t, "target 1", rec.Body.String()) + assert.Equal(t, "Basic dXNlcjE6cGFzczE=", receivedAuthHeader) + + // Scenario: + // A proxy target does not have user:pass in the url. + // The proxy should not pass the Authorization header to the target. + + receivedAuthHeader = "" + // Arrange + t2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedAuthHeader = r.Header.Get("Authorization") + fmt.Fprint(w, "target 2") + })) + defer t2.Close() + url2, _ := url.Parse(t2.URL) + + e = echo.New() + tp = &testProvider{} + tp.target = &ProxyTarget{Name: "target 2", URL: url2} + e.Use(Proxy(tp)) + rec = httptest.NewRecorder() + req = httptest.NewRequest(http.MethodGet, "/", nil) + e.ServeHTTP(rec, req) + + // Assert + assert.Equal(t, "target 2", rec.Body.String()) + assert.Equal(t, "", receivedAuthHeader) +} From 664ceba4d6c898977f708711dd9bf07095e4ec2a Mon Sep 17 00:00:00 2001 From: Vikas Date: Tue, 3 Jun 2025 19:04:55 +0530 Subject: [PATCH 2/2] Move proxy target authorization handling to middleware instead of internals - Add support and test for WebSockect authorization handling - Cleanup --- middleware/proxy.go | 18 +++++------ middleware/proxy_test.go | 65 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/middleware/proxy.go b/middleware/proxy.go index a4b993dde..85e48ca93 100644 --- a/middleware/proxy.go +++ b/middleware/proxy.go @@ -375,6 +375,13 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc { // that Balancer may have replaced with c.SetRequest. req = c.Request() + // Handle authorization from target URL (for both HTTP and WebSocket) + if tgt.URL.User != nil { + username := tgt.URL.User.Username() + password, _ := tgt.URL.User.Password() + req.SetBasicAuth(username, password) + } + // Proxy switch { case c.IsWebSocket(): @@ -430,16 +437,5 @@ func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handle } proxy.Transport = config.Transport proxy.ModifyResponse = config.ModifyResponse - - // Handle authorization from target URL - if tgt.URL.User != nil { - originalDirector := proxy.Director - proxy.Director = func(req *http.Request) { - originalDirector(req) - password, _ := tgt.URL.User.Password() - req.SetBasicAuth(tgt.URL.User.Username(), password) - } - } - return proxy } diff --git a/middleware/proxy_test.go b/middleware/proxy_test.go index a0489a6b9..569742774 100644 --- a/middleware/proxy_test.go +++ b/middleware/proxy_test.go @@ -1093,3 +1093,68 @@ func TestProxyWithAuthorizationHeader(t *testing.T) { assert.Equal(t, "target 2", rec.Body.String()) assert.Equal(t, "", receivedAuthHeader) } + +func TestProxyWithConfigWebSocketAuthorizationHeader(t *testing.T) { + // Capture the authorization header received by the WebSocket server + var receivedAuthHeader string + var authHeaderMutex sync.Mutex + + // Create a WebSocket server that captures the Authorization header + wsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authHeaderMutex.Lock() + receivedAuthHeader = r.Header.Get("Authorization") + authHeaderMutex.Unlock() + + wsHandler := func(conn *websocket.Conn) { + defer conn.Close() + for { + var msg string + err := websocket.Message.Receive(conn, &msg) + if err != nil { + return + } + // Echo message back to the client + websocket.Message.Send(conn, msg) + } + } + websocket.Server{Handler: wsHandler}.ServeHTTP(w, r) + })) + defer wsServer.Close() + + // Create proxy server with target URL containing user:pass credentials + targetURL, _ := url.Parse(wsServer.URL) + targetURL.User = url.UserPassword("wsuser", "wspass") + + e := echo.New() + balancer := NewRandomBalancer([]*ProxyTarget{{URL: targetURL}}) + e.Use(ProxyWithConfig(ProxyConfig{Balancer: balancer})) + + proxyServer := httptest.NewServer(e) + defer proxyServer.Close() + + // Connect to the proxy WebSocket + proxyURL, _ := url.Parse(proxyServer.URL) + proxyURL.Scheme = "ws" + proxyURL.Path = "/" + + wsConn, err := websocket.Dial(proxyURL.String(), "", "http://localhost/") + assert.NoError(t, err) + defer wsConn.Close() + + // Send message to verify WebSocket connection works + sendMsg := "Hello, WebSocket with Auth!" + err = websocket.Message.Send(wsConn, sendMsg) + assert.NoError(t, err) + + // Read response + var recvMsg string + err = websocket.Message.Receive(wsConn, &recvMsg) + assert.NoError(t, err) + assert.Equal(t, sendMsg, recvMsg) + + // Verify authorization header was forwarded + authHeaderMutex.Lock() + expectedAuth := "Basic d3N1c2VyOndzcGFzcw==" // base64 of "wsuser:wspass" + assert.Equal(t, expectedAuth, receivedAuthHeader) + authHeaderMutex.Unlock() +}