Repository: nats-io/nats-server (go)

Issue:
Allow non-secure WebSocket Transport
## Feature Request
Allow non-TLS websocket configuration 

#### Use Case:
in one of the use case i am trying out is publish real time market data to HTML5 apps running under Electron. e.g. volume is 1k msg size per record 1000 records and each record update 4 times per second.
WSS will spend considerable time in decrypting

#### Proposed Change:
Allow flag in webSocket configuration block to bypass TLS

#### Who Benefits From The Change(s)?
HTML5 Clients e..g Electron Dekstop apps running in secure network intranet where TLS may be optional 

#### Alternative Approaches



Pull request description:
Websocket: add option to disable TLS
The new option Websocket.NoTLS would have to be set to true
to disable the server check that enforces TLS configuration.

Resolves #1529

Signed-off-by: Ivan Kozlovic <ivan@synadia.com>


Patch:
diff --git a/server/opts.go b/server/opts.go
index 0869418a..bed56494 100644
--- a/server/opts.go
+++ b/server/opts.go
@@ -281,6 +281,15 @@ type WebsocketOpts struct {
 	// Timeout for the authentication process.
 	AuthTimeout float64
 
+	// By default the server will enforce the use of TLS. If no TLS configuration
+	// is provided, you need to explicitly set NoTLS to true to allow the server
+	// to start without TLS configuration. Note that if a TLS configuration is
+	// present, this boolean is ignored and the server will run the Websocket
+	// server with that TLS configuration.
+	// Running without TLS is less secure since Websocket clients that use bearer
+	// tokens will send them in clear. So this should not be used in production.
+	NoTLS bool
+
 	// TLS configuration is required.
 	TLSConfig *tls.Config
 	// If true, map certificate values for authentication purposes.
@@ -3070,6 +3079,8 @@ func parseWebsocket(v interface{}, o *Options, errors *[]error, warnings *[]erro
 			o.Websocket.Host = mv.(string)
 		case "advertise":
 			o.Websocket.Advertise = mv.(string)
+		case "no_tls":
+			o.Websocket.NoTLS = mv.(bool)
 		case "tls":
 			tc, err := parseTLS(tk)
 			if err != nil {
diff --git a/server/websocket.go b/server/websocket.go
index 54de84a5..3e877395 100644
--- a/server/websocket.go
+++ b/server/websocket.go
@@ -82,9 +82,6 @@ var decompressorPool sync.Pool
 // From https://tools.ietf.org/html/rfc6455#section-1.3
 var wsGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
 
-// Can be set for tests
-var testWebsocketAllowNonTLS = false
-
 type websocket struct {
 	frames     net.Buffers
 	fs         int64
@@ -734,8 +731,8 @@ func validateWebsocketOptions(o *Options) error {
 	if wo.Port == 0 {
 		return nil
 	}
-	// Enforce TLS...
-	if !testWebsocketAllowNonTLS && wo.TLSConfig == nil {
+	// Enforce TLS... unless NoTLS is set to true.
+	if wo.TLSConfig == nil && !wo.NoTLS {
 		return errors.New("websocket requires TLS configuration")
 	}
 	// Make sure that allowed origins, if specified, can be parsed.
@@ -840,6 +837,10 @@ func (s *Server) startWebsocketServer() {
 		s.mu.Unlock()
 		return
 	}
+	// Do not check o.NoTLS here. If a TLS configuration is available, use it,
+	// regardless of NoTLS. If we don't have a TLS config, it means that the
+	// user has configured NoTLS because otherwise the server would have failed
+	// to start due to options validation.
 	if o.TLSConfig != nil {
 		proto = "wss"
 		config := o.TLSConfig.Clone()
@@ -854,6 +855,9 @@ func (s *Server) startWebsocketServer() {
 		return
 	}
 	s.Noticef("Listening for websocket clients on %s://%s:%d", proto, o.Host, port)
+	if proto == "ws" {
+		s.Warnf("Websocket not configured with TLS. DO NOT USE IN PRODUCTION!")
+	}
 
 	s.websocket.tls = proto == "wss"
 	if port == 0 {


Test patch:
diff --git a/server/server_test.go b/server/server_test.go
index e2f4bddd..bb8254e6 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -1229,9 +1229,6 @@ func TestAcceptError(t *testing.T) {
 }
 
 func TestAcceptLoopsDoNotLeaveOpenedConn(t *testing.T) {
-	testWebsocketAllowNonTLS = true
-	defer func() { testWebsocketAllowNonTLS = false }()
-
 	for _, test := range []struct {
 		name string
 		url  func(o *Options) (string, int)
@@ -1258,6 +1255,7 @@ func TestAcceptLoopsDoNotLeaveOpenedConn(t *testing.T) {
 			o.Websocket.Host = "127.0.0.1"
 			o.Websocket.Port = -1
 			o.Websocket.HandshakeTimeout = 1
+			o.Websocket.NoTLS = true
 			s := RunServer(o)
 			defer s.Shutdown()
 
@@ -1321,9 +1319,6 @@ func TestAcceptLoopsDoNotLeaveOpenedConn(t *testing.T) {
 }
 
 func TestServerShutdownDuringStart(t *testing.T) {
-	testWebsocketAllowNonTLS = true
-	defer func() { testWebsocketAllowNonTLS = false }()
-
 	o := DefaultOptions()
 	o.DisableShortFirstPing = true
 	o.Accounts = []*Account{NewAccount("$SYS")}
@@ -1339,6 +1334,7 @@ func TestServerShutdownDuringStart(t *testing.T) {
 	o.Websocket.Host = "127.0.0.1"
 	o.Websocket.Port = -1
 	o.Websocket.HandshakeTimeout = 1
+	o.Websocket.NoTLS = true
 
 	// We are going to test that if the server is shutdown
 	// while Start() runs (in this case, before), we don't