[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 <ivan@synadia.com>
This commit is contained in:
Ivan Kozlovic
2021-11-22 10:32:58 -07:00
parent ea48105526
commit ede8124fb2
9 changed files with 64 additions and 27 deletions

2
go.mod
View File

@@ -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

4
go.sum
View File

@@ -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=

View File

@@ -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)
}

View File

@@ -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,8 +5208,12 @@ func (c *client) connectionTypeAllowed(acts map[string]struct{}) bool {
want = jwt.ConnectionTypeMqtt
}
case LEAF:
if c.isWebsocket() {
want = jwt.ConnectionTypeLeafnodeWS
} else {
want = jwt.ConnectionTypeLeafnode
}
}
_, ok := acts[want]
return ok
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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"])
}

View File

@@ -26,7 +26,9 @@ const (
ConnectionTypeStandard = "STANDARD"
ConnectionTypeWebsocket = "WEBSOCKET"
ConnectionTypeLeafnode = "LEAFNODE"
ConnectionTypeLeafnodeWS = "LEAFNODE_WS"
ConnectionTypeMqtt = "MQTT"
ConnectionTypeMqttWS = "MQTT_WS"
)
type UserPermissionLimits struct {

2
vendor/modules.txt vendored
View File

@@ -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