From ede8124fb2bbda0d2ec9e608235bf7e27752c6d0 Mon Sep 17 00:00:00 2001 From: Ivan Kozlovic Date: Mon, 22 Nov 2021 10:32:58 -0700 Subject: [PATCH] [FIXED/CHANGED] Add leafnode websocket connection type This was missing since WEBSOCKET allowed connection type is really used for client connections. If one wants to limit a configured user to leafnode connections, including if the connection is over websocket, but does not want an application to connect over websocket using this user, this would have been impossible to configure. The JWT library has been updated to add LEAFNODE_WS and MQTT_WS for future work. Signed-off-by: Ivan Kozlovic --- go.mod | 2 +- go.sum | 4 +- server/auth.go | 4 +- server/client.go | 10 ++++- server/leafnode_test.go | 41 ++++++++++++++----- vendor/github.com/nats-io/jwt/v2/header.go | 2 +- .../github.com/nats-io/jwt/v2/signingkeys.go | 16 +++++--- .../github.com/nats-io/jwt/v2/user_claims.go | 10 +++-- vendor/modules.txt | 2 +- 9 files changed, 64 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index be6b70e9..7e921fa2 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/golang/protobuf v1.4.2 // indirect github.com/klauspost/compress v1.13.4 github.com/minio/highwayhash v1.0.1 - github.com/nats-io/jwt/v2 v2.1.0 + github.com/nats-io/jwt/v2 v2.2.0 github.com/nats-io/nats.go v1.13.1-0.20211018182449-f2416a8b1483 github.com/nats-io/nkeys v0.3.0 github.com/nats-io/nuid v1.0.1 diff --git a/go.sum b/go.sum index b7b2c150..b5c80979 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEE github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= -github.com/nats-io/jwt/v2 v2.1.0 h1:1UbfD5g1xTdWmSeRV8bh/7u+utTiBsRtWhLl1PixZp4= -github.com/nats-io/jwt/v2 v2.1.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= +github.com/nats-io/jwt/v2 v2.2.0 h1:Yg/4WFK6vsqMudRg91eBb7Dh6XeVcDMPHycDE8CfltE= +github.com/nats-io/jwt/v2 v2.2.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= github.com/nats-io/nats.go v1.13.1-0.20211018182449-f2416a8b1483 h1:GMx3ZOcMEVM5qnUItQ4eJyQ6ycwmIEB/VC/UxvdevE0= github.com/nats-io/nats.go v1.13.1-0.20211018182449-f2416a8b1483/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= diff --git a/server/auth.go b/server/auth.go index 5f5c9298..2e5031d2 100644 --- a/server/auth.go +++ b/server/auth.go @@ -1104,7 +1104,9 @@ func validateAllowedConnectionTypes(m map[string]struct{}) error { for ct := range m { ctuc := strings.ToUpper(ct) switch ctuc { - case jwt.ConnectionTypeStandard, jwt.ConnectionTypeWebsocket, jwt.ConnectionTypeLeafnode, jwt.ConnectionTypeMqtt: + case jwt.ConnectionTypeStandard, jwt.ConnectionTypeWebsocket, + jwt.ConnectionTypeLeafnode, jwt.ConnectionTypeLeafnodeWS, + jwt.ConnectionTypeMqtt: default: return fmt.Errorf("unknown connection type %q", ct) } diff --git a/server/client.go b/server/client.go index 11fe8759..aeb0d6e2 100644 --- a/server/client.go +++ b/server/client.go @@ -5172,7 +5172,9 @@ func convertAllowedConnectionTypes(cts []string) (map[string]struct{}, error) { for _, i := range cts { i = strings.ToUpper(i) switch i { - case jwt.ConnectionTypeStandard, jwt.ConnectionTypeWebsocket, jwt.ConnectionTypeLeafnode, jwt.ConnectionTypeMqtt: + case jwt.ConnectionTypeStandard, jwt.ConnectionTypeWebsocket, + jwt.ConnectionTypeLeafnode, jwt.ConnectionTypeLeafnodeWS, + jwt.ConnectionTypeMqtt: m[i] = struct{}{} default: unknown = append(unknown, i) @@ -5206,7 +5208,11 @@ func (c *client) connectionTypeAllowed(acts map[string]struct{}) bool { want = jwt.ConnectionTypeMqtt } case LEAF: - want = jwt.ConnectionTypeLeafnode + if c.isWebsocket() { + want = jwt.ConnectionTypeLeafnodeWS + } else { + want = jwt.ConnectionTypeLeafnode + } } _, ok := acts[want] return ok diff --git a/server/leafnode_test.go b/server/leafnode_test.go index 5d1e19b5..feb6dc5d 100644 --- a/server/leafnode_test.go +++ b/server/leafnode_test.go @@ -22,6 +22,7 @@ import ( "math/rand" "net" "net/url" + "os" "strings" "sync" "sync/atomic" @@ -3032,12 +3033,12 @@ func TestLeafNodeWSFailedConnection(t *testing.T) { } func TestLeafNodeWSAuth(t *testing.T) { - conf := createConfFile(t, []byte(fmt.Sprintf(` + template := ` port: -1 authorization { users [ {user: "user", pass: "puser", connection_types: ["%s"]} - {user: "leaf", pass: "pleaf", connection_types: ["%s"]} + {user: "leaf", pass: "pleaf", connection_types: ["%s"%s]} ] } websocket { @@ -3047,20 +3048,40 @@ func TestLeafNodeWSAuth(t *testing.T) { leafnodes { port: -1 } - `, jwt.ConnectionTypeStandard, jwt.ConnectionTypeLeafnode))) - defer removeFile(t, conf) - o, err := ProcessConfigFile(conf) - if err != nil { - t.Fatalf("Error processing config file: %v", err) - } - o.NoLog, o.NoSigs = true, true - s := RunServer(o) + ` + s, o, conf := runReloadServerWithContent(t, + []byte(fmt.Sprintf(template, jwt.ConnectionTypeStandard, jwt.ConnectionTypeLeafnode, ""))) + defer os.Remove(conf) defer s.Shutdown() + l := &captureErrorLogger{errCh: make(chan string, 10)} + s.SetLogger(l, false, false) + lo := testDefaultRemoteLeafNodeWSOptions(t, o, false) + u, _ := url.Parse(fmt.Sprintf("ws://leaf:pleaf@127.0.0.1:%d", o.Websocket.Port)) + remote := &RemoteLeafOpts{URLs: []*url.URL{u}} + lo.LeafNode.Remotes = []*RemoteLeafOpts{remote} + lo.LeafNode.ReconnectInterval = 50 * time.Millisecond ln := RunServer(lo) defer ln.Shutdown() + var lasterr string + tm := time.NewTimer(2 * time.Second) + for done := false; !done; { + select { + case lasterr = <-l.errCh: + if strings.Contains(lasterr, "authentication") { + done = true + } + case <-tm.C: + t.Fatalf("Expected auth error, got %v", lasterr) + } + } + + ws := fmt.Sprintf(`, "%s"`, jwt.ConnectionTypeLeafnodeWS) + reloadUpdateConfig(t, s, conf, fmt.Sprintf(template, + jwt.ConnectionTypeStandard, jwt.ConnectionTypeLeafnode, ws)) + checkLeafNodeConnected(t, s) checkLeafNodeConnected(t, ln) diff --git a/vendor/github.com/nats-io/jwt/v2/header.go b/vendor/github.com/nats-io/jwt/v2/header.go index 3dece6a1..198bf306 100644 --- a/vendor/github.com/nats-io/jwt/v2/header.go +++ b/vendor/github.com/nats-io/jwt/v2/header.go @@ -23,7 +23,7 @@ import ( const ( // Version is semantic version. - Version = "2.1.0" + Version = "2.2.0" // TokenTypeJwt is the JWT token type supported JWT tokens // encoded and decoded by this library diff --git a/vendor/github.com/nats-io/jwt/v2/signingkeys.go b/vendor/github.com/nats-io/jwt/v2/signingkeys.go index 87fc951a..0d8a5b93 100644 --- a/vendor/github.com/nats-io/jwt/v2/signingkeys.go +++ b/vendor/github.com/nats-io/jwt/v2/signingkeys.go @@ -120,9 +120,12 @@ func (sk SigningKeys) Validate(vr *ValidationResults) { } // MarshalJSON serializes the scoped signing keys as an array -func (sk SigningKeys) MarshalJSON() ([]byte, error) { +func (sk *SigningKeys) MarshalJSON() ([]byte, error) { + if sk == nil { + return nil, nil + } var a []interface{} - for k, v := range sk { + for k, v := range *sk { if v != nil { a = append(a, v) } else { @@ -132,7 +135,10 @@ func (sk SigningKeys) MarshalJSON() ([]byte, error) { return json.Marshal(a) } -func (sk SigningKeys) UnmarshalJSON(data []byte) error { +func (sk *SigningKeys) UnmarshalJSON(data []byte) error { + if *sk == nil { + *sk = make(SigningKeys) + } // read an array - we can have a string or an map var a []interface{} if err := json.Unmarshal(data, &a); err != nil { @@ -141,7 +147,7 @@ func (sk SigningKeys) UnmarshalJSON(data []byte) error { for _, i := range a { switch v := i.(type) { case string: - sk[v] = nil + (*sk)[v] = nil case map[string]interface{}: d, err := json.Marshal(v) if err != nil { @@ -153,7 +159,7 @@ func (sk SigningKeys) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(d, &us); err != nil { return err } - sk[us.Key] = us + (*sk)[us.Key] = us default: return fmt.Errorf("unknown signing key scope %q", v["type"]) } diff --git a/vendor/github.com/nats-io/jwt/v2/user_claims.go b/vendor/github.com/nats-io/jwt/v2/user_claims.go index 0c8ccbeb..d8a3a6c9 100644 --- a/vendor/github.com/nats-io/jwt/v2/user_claims.go +++ b/vendor/github.com/nats-io/jwt/v2/user_claims.go @@ -23,10 +23,12 @@ import ( ) const ( - ConnectionTypeStandard = "STANDARD" - ConnectionTypeWebsocket = "WEBSOCKET" - ConnectionTypeLeafnode = "LEAFNODE" - ConnectionTypeMqtt = "MQTT" + ConnectionTypeStandard = "STANDARD" + ConnectionTypeWebsocket = "WEBSOCKET" + ConnectionTypeLeafnode = "LEAFNODE" + ConnectionTypeLeafnodeWS = "LEAFNODE_WS" + ConnectionTypeMqtt = "MQTT" + ConnectionTypeMqttWS = "MQTT_WS" ) type UserPermissionLimits struct { diff --git a/vendor/modules.txt b/vendor/modules.txt index a71b7fe2..8a790725 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -6,7 +6,7 @@ github.com/klauspost/compress/s2 # github.com/minio/highwayhash v1.0.1 ## explicit github.com/minio/highwayhash -# github.com/nats-io/jwt/v2 v2.1.0 +# github.com/nats-io/jwt/v2 v2.2.0 ## explicit github.com/nats-io/jwt/v2 # github.com/nats-io/nats.go v1.13.1-0.20211018182449-f2416a8b1483