From a507241dc82c436b9a7ad43bfcca51806abc08e1 Mon Sep 17 00:00:00 2001 From: Austin Cormier Date: Thu, 11 Sep 2025 10:48:44 -0400 Subject: [PATCH] Do not enforce websocket restriction on proxy configuration An HTTP Proxy with Connect can proxy any tcp connection not just http. Remove the restriction that the leafnode connection must be websocket. Signed-off-by: Your Name --- server/config_check_test.go | 8 +++--- server/leafnode.go | 34 ++++++++-------------- server/leafnode_proxy_test.go | 54 +++++++++++++++++++++++++++++++---- server/opts.go | 2 +- 4 files changed, 65 insertions(+), 33 deletions(-) diff --git a/server/config_check_test.go b/server/config_check_test.go index 03a61b0e119..d0d0047b542 100644 --- a/server/config_check_test.go +++ b/server/config_check_test.go @@ -2404,7 +2404,7 @@ func TestConfigCheck(t *testing.T) { leafnodes { remotes = [ { - url: "ws://127.0.0.1:7422" + url: "nats://127.0.0.1:7422" proxy { url: "ftp://proxy.example.com:8080" } @@ -2422,7 +2422,7 @@ func TestConfigCheck(t *testing.T) { leafnodes { remotes = [ { - url: "ws://127.0.0.1:7422" + url: "nats://127.0.0.1:7422" proxy { url: "http://" } @@ -2440,7 +2440,7 @@ func TestConfigCheck(t *testing.T) { leafnodes { remotes = [ { - url: "ws://127.0.0.1:7422" + url: "nats://127.0.0.1:7422" proxy { url: "http://proxy.example.com:8080" username: "testuser" @@ -2459,7 +2459,7 @@ func TestConfigCheck(t *testing.T) { leafnodes { remotes = [ { - url: "ws://127.0.0.1:7422" + url: "nats://127.0.0.1:7422" proxy { url: "http://proxy.example.com:8080" password: "testpass" diff --git a/server/leafnode.go b/server/leafnode.go index b7eaa996e83..c4bd0fa10aa 100644 --- a/server/leafnode.go +++ b/server/leafnode.go @@ -399,26 +399,12 @@ func validateLeafNodeProxyOptions(remote *RemoteLeafOpts) ([]string, error) { } if len(remote.URLs) > 0 { - hasWebSocketURL := false - hasNonWebSocketURL := false - for _, remoteURL := range remote.URLs { - if remoteURL.Scheme == wsSchemePrefix || remoteURL.Scheme == wsSchemePrefixTLS { - hasWebSocketURL = true - if (remoteURL.Scheme == wsSchemePrefixTLS) && - remote.TLSConfig == nil && !remote.TLS { - return warnings, fmt.Errorf("proxy is configured but remote URL %s requires TLS and no TLS configuration is provided. When using proxy with TLS endpoints, ensure TLS is properly configured for the leafnode remote", remoteURL.String()) - } - } else { - hasNonWebSocketURL = true + if (remoteURL.Scheme == wsSchemePrefixTLS) && + remote.TLSConfig == nil && !remote.TLS { + return warnings, fmt.Errorf("proxy is configured but remote URL %s requires TLS and no TLS configuration is provided. When using proxy with TLS endpoints, ensure TLS is properly configured for the leafnode remote", remoteURL.String()) } } - - if !hasWebSocketURL { - warnings = append(warnings, "proxy configuration will be ignored: proxy settings only apply to WebSocket connections (ws:// or wss://), but all configured URLs use TCP connections (nats://)") - } else if hasNonWebSocketURL { - warnings = append(warnings, "proxy configuration will only be used for WebSocket URLs: proxy settings do not apply to TCP connections (nats://)") - } } return warnings, nil @@ -752,15 +738,19 @@ func (s *Server) connectToRemoteLeafNode(remote *leafNodeCfg, firstConnect bool) } else { s.Debugf("Trying to connect as leafnode to remote server on %q%s", rURL.Host, ipStr) - // Check if proxy is configured first, then check if URL supports it - if proxyURL != _EMPTY_ && isWSURL(rURL) { - // Use proxy for WebSocket connections - use original hostname, resolved IP for connection + // Check if proxy is configured + if proxyURL != _EMPTY_ { + // Use proxy for connection - use original hostname, resolved IP for connection targetHost := rURL.Host // If URL doesn't include port, add the default port for the scheme if rURL.Port() == _EMPTY_ { - defaultPort := "80" - if rURL.Scheme == wsSchemePrefixTLS { + defaultPort := "4222" // Default NATS port + if rURL.Scheme == "tls" || rURL.Scheme == "nats+tls" { + defaultPort = "4222" // NATS TLS still uses 4222 by default + } else if rURL.Scheme == wsSchemePrefixTLS { defaultPort = "443" + } else if rURL.Scheme == wsSchemePrefix { + defaultPort = "80" } targetHost = net.JoinHostPort(rURL.Hostname(), defaultPort) } diff --git a/server/leafnode_proxy_test.go b/server/leafnode_proxy_test.go index aab6e81211c..82e6f10fc63 100644 --- a/server/leafnode_proxy_test.go +++ b/server/leafnode_proxy_test.go @@ -259,7 +259,7 @@ func TestLeafNodeHttpProxyConfigWarnings(t *testing.T) { warningMatch string }{ { - name: "proxy with only TCP URLs", + name: "proxy with only TCP URLs - now supported", config: ` leafnodes { remotes = [ @@ -272,11 +272,10 @@ func TestLeafNodeHttpProxyConfigWarnings(t *testing.T) { ] } `, - expectWarning: true, - warningMatch: "proxy configuration will be ignored", + expectWarning: false, // No longer generates warnings }, { - name: "proxy with mixed TCP and WebSocket URLs", + name: "proxy with mixed TCP and WebSocket URLs - now supported", config: ` leafnodes { remotes = [ @@ -289,8 +288,7 @@ func TestLeafNodeHttpProxyConfigWarnings(t *testing.T) { ] } `, - expectWarning: true, - warningMatch: "proxy configuration will only be used for WebSocket URLs", + expectWarning: false, // No longer generates warnings }, { name: "proxy with only WebSocket URLs", @@ -388,6 +386,50 @@ func TestLeafNodeHttpProxyConnection(t *testing.T) { checkLeafNodeConnected(t, hub) } +func TestLeafNodeHttpProxyConnectionTCP(t *testing.T) { + // Create a hub server with regular TCP leafnode support + hubConfig := createConfFile(t, []byte(` + listen: "127.0.0.1:-1" + leafnodes { + listen: "127.0.0.1:-1" + } + `)) + + hub, hubOpts := RunServerWithConfig(hubConfig) + defer hub.Shutdown() + + // Create HTTP proxy + proxy := createTestHTTPProxy(_EMPTY_, _EMPTY_) + proxy.start() + defer proxy.stop() + + // Create spoke server with proxy configuration for TCP connection via config file + configContent := fmt.Sprintf(` + listen: "127.0.0.1:-1" + leafnodes { + reconnect_interval: "50ms" + remotes = [ + { + url: "nats://127.0.0.1:%d" + proxy { + url: "%s" + timeout: 5s + } + } + ] + } + `, hubOpts.LeafNode.Port, proxy.url()) + + configFile := createConfFile(t, []byte(configContent)) + + spoke, _ := RunServerWithConfig(configFile) + defer spoke.Shutdown() + + // Verify leafnode connections are established + checkLeafNodeConnected(t, spoke) + checkLeafNodeConnected(t, hub) +} + func TestLeafNodeHttpProxyWithAuthentication(t *testing.T) { // Create a hub server with WebSocket support using config file hubConfig := createConfFile(t, []byte(` diff --git a/server/opts.go b/server/opts.go index 4f2d69c4f6f..d343757ef0c 100644 --- a/server/opts.go +++ b/server/opts.go @@ -241,7 +241,7 @@ type RemoteLeafOpts struct { NoMasking bool `json:"-"` } - // HTTP Proxy configuration for WebSocket connections + // HTTP Proxy configuration for leafnode connections Proxy struct { // URL of the HTTP proxy server (e.g., "http://proxy.example.com:8080") URL string `json:"-"`