Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 68 additions & 2 deletions conf/lex.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2013-2018 The NATS Authors
// Copyright 2013-2024 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand Down Expand Up @@ -263,7 +263,8 @@ func lexTop(lx *lexer) stateFn {

switch r {
case topOptStart:
return lexSkip(lx, lexTop)
lx.push(lexTop)
return lexSkip(lx, lexBlockStart)
case commentHashStart:
lx.push(lexTop)
return lexCommentStart
Expand Down Expand Up @@ -318,6 +319,71 @@ func lexTopValueEnd(lx *lexer) stateFn {
"comment or EOF, but got '%v' instead.", r)
}

func lexBlockStart(lx *lexer) stateFn {
r := lx.next()
if unicode.IsSpace(r) {
return lexSkip(lx, lexBlockStart)
}

switch r {
case topOptStart:
lx.push(lexBlockEnd)
return lexSkip(lx, lexBlockStart)
case commentHashStart:
lx.push(lexBlockEnd)
return lexCommentStart
case commentSlashStart:
rn := lx.next()
if rn == commentSlashStart {
lx.push(lexBlockEnd)
return lexCommentStart
}
lx.backup()
fallthrough
case eof:
if lx.pos > lx.start {
return lx.errorf("Unexpected EOF.")
}
lx.emit(itemEOF)
return nil
}

// At this point, the only valid item can be a key, so we back up
// and let the key lexer do the rest.
lx.backup()
lx.push(lexBlockEnd)
return lexKeyStart
}

// lexBlockEnd is entered whenever a block-level value has been consumed.
// It must see only whitespace, and will turn back to lexTop upon a "}".
func lexBlockEnd(lx *lexer) stateFn {
r := lx.next()
switch {
case r == commentHashStart:
// a comment will read to a new line for us.
lx.push(lexBlockEnd)
return lexCommentStart
case r == commentSlashStart:
rn := lx.next()
if rn == commentSlashStart {
lx.push(lexBlockEnd)
return lexCommentStart
}
lx.backup()
fallthrough
case isNL(r) || isWhitespace(r):
return lexBlockEnd
case r == optValTerm || r == topOptValTerm:
lx.ignore()
return lexBlockStart
case r == topOptTerm:
lx.ignore()
return lx.pop()
}
return lx.errorf("Expected a block-level value to end with a '}', but got '%v' instead.", r)
}

// lexKeyStart consumes a key name up until the first non-whitespace character.
// lexKeyStart will ignore whitespace. It will also eat enclosing quotes.
func lexKeyStart(lx *lexer) stateFn {
Expand Down
77 changes: 77 additions & 0 deletions conf/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -740,3 +740,80 @@ func TestJSONParseCompat(t *testing.T) {
})
}
}

func TestBlocks(t *testing.T) {
for _, test := range []struct {
name string
input string
expected map[string]any
err string
linepos string
}{
{
"inline block",
`{ listen: 0.0.0.0:4222 }`,
map[string]any{
"listen": "0.0.0.0:4222",
},
"", "",
},
{
"newline block",
`{
listen: 0.0.0.0:4222
}`,
map[string]any{
"listen": "0.0.0.0:4222",
},
"", "",
},
{
"newline block with trailing comment",
`
{
listen: 0.0.0.0:4222
}
# wibble
`,
map[string]any{
"listen": "0.0.0.0:4222",
},
"", "",
},
{
"nested newline blocks with trailing comment",
`
{
{
listen: 0.0.0.0:4222 // random comment
}
# wibble1
}
# wibble2
`,
map[string]any{
"listen": "0.0.0.0:4222",
},
"", "",
},
} {
t.Run(test.name, func(t *testing.T) {
f, err := os.CreateTemp(t.TempDir(), "nats.conf-")
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(f.Name(), []byte(test.input), 066); err != nil {
t.Error(err)
}
if m, err := ParseFile(f.Name()); err == nil {
if !reflect.DeepEqual(m, test.expected) {
t.Fatalf("Not Equal:\nReceived: '%+v'\nExpected: '%+v'\n", m, test.expected)
}
} else if !strings.Contains(err.Error(), test.err) || !strings.Contains(err.Error(), test.linepos) {
t.Errorf("expected invalid conf error, got: %v", err)
} else if err != nil {
t.Error(err)
}
})
}
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ go 1.20
require (
github.com/klauspost/compress v1.17.7
github.com/minio/highwayhash v1.0.2
github.com/nats-io/jwt/v2 v2.5.6
github.com/nats-io/jwt/v2 v2.5.7
github.com/nats-io/nats.go v1.34.1
github.com/nats-io/nkeys v0.4.7
github.com/nats-io/nuid v1.0.1
go.uber.org/automaxprocs v1.5.3
golang.org/x/crypto v0.22.0
golang.org/x/crypto v0.23.0
golang.org/x/sys v0.20.0
golang.org/x/time v0.5.0
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLA
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/nats-io/jwt/v2 v2.5.6 h1:Cp618+z4q042sWqHiSoIHFT08OZtAskui0hTmRfmGGQ=
github.com/nats-io/jwt/v2 v2.5.6/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A=
github.com/nats-io/jwt/v2 v2.5.7 h1:j5lH1fUXCnJnY8SsQeB/a/z9Azgu2bYIDvtPVNdxe2c=
github.com/nats-io/jwt/v2 v2.5.7/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A=
github.com/nats-io/nats.go v1.34.1 h1:syWey5xaNHZgicYBemv0nohUPPmaLteiBEUT6Q5+F/4=
github.com/nats-io/nats.go v1.34.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
Expand All @@ -16,8 +16,8 @@ github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
Expand Down
3 changes: 2 additions & 1 deletion server/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1464,7 +1464,8 @@ func validateAllowedConnectionTypes(m map[string]struct{}) error {
switch ctuc {
case jwt.ConnectionTypeStandard, jwt.ConnectionTypeWebsocket,
jwt.ConnectionTypeLeafnode, jwt.ConnectionTypeLeafnodeWS,
jwt.ConnectionTypeMqtt, jwt.ConnectionTypeMqttWS:
jwt.ConnectionTypeMqtt, jwt.ConnectionTypeMqttWS,
jwt.ConnectionTypeInProcess:
default:
return fmt.Errorf("unknown connection type %q", ct)
}
Expand Down
33 changes: 13 additions & 20 deletions server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ type client struct {
trace bool
echo bool
noIcb bool
iproc bool // In-Process connection, set at creation and immutable.

tags jwt.TagList
nameTag string
Expand Down Expand Up @@ -2349,24 +2350,11 @@ func (c *client) generateClientInfoJSON(info Info) []byte {
info.MaxPayload = c.mpay
if c.isWebsocket() {
info.ClientConnectURLs = info.WSConnectURLs
if c.srv != nil { // Otherwise lame duck info can panic
c.srv.websocket.mu.RLock()
info.TLSAvailable = c.srv.websocket.tls
if c.srv.websocket.tls && c.srv.websocket.server != nil {
if tc := c.srv.websocket.server.TLSConfig; tc != nil {
info.TLSRequired = !tc.InsecureSkipVerify
}
}
if c.srv.websocket.listener != nil {
laddr := c.srv.websocket.listener.Addr().String()
if h, p, err := net.SplitHostPort(laddr); err == nil {
if p, err := strconv.Atoi(p); err == nil {
info.Host = h
info.Port = p
}
}
}
c.srv.websocket.mu.RUnlock()
// Otherwise lame duck info can panic
if c.srv != nil {
ws := &c.srv.websocket
info.TLSAvailable, info.TLSRequired = ws.tls, ws.tls
info.Host, info.Port = ws.host, ws.port
}
}
info.WSConnectURLs = nil
Expand Down Expand Up @@ -5745,7 +5733,8 @@ func convertAllowedConnectionTypes(cts []string) (map[string]struct{}, error) {
switch i {
case jwt.ConnectionTypeStandard, jwt.ConnectionTypeWebsocket,
jwt.ConnectionTypeLeafnode, jwt.ConnectionTypeLeafnodeWS,
jwt.ConnectionTypeMqtt, jwt.ConnectionTypeMqttWS:
jwt.ConnectionTypeMqtt, jwt.ConnectionTypeMqttWS,
jwt.ConnectionTypeInProcess:
m[i] = struct{}{}
default:
unknown = append(unknown, i)
Expand All @@ -5772,7 +5761,11 @@ func (c *client) connectionTypeAllowed(acts map[string]struct{}) bool {
case CLIENT:
switch c.clientType() {
case NATS:
want = jwt.ConnectionTypeStandard
if c.iproc {
want = jwt.ConnectionTypeInProcess
} else {
want = jwt.ConnectionTypeStandard
}
case WS:
want = jwt.ConnectionTypeWebsocket
case MQTT:
Expand Down
93 changes: 93 additions & 0 deletions server/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2962,3 +2962,96 @@ func TestRemoveHeaderIfPrefixPresent(t *testing.T) {
t.Fatalf("Expected headers to be stripped, got %q", hdr)
}
}

func TestInProcessAllowedConnectionType(t *testing.T) {
tmpl := `
listen: "127.0.0.1:-1"
accounts {
A { users: [{user: "test", password: "pwd", allowed_connection_types: ["%s"]}] }
}
write_deadline: "500ms"
`
for _, test := range []struct {
name string
ct string
inProcessOnly bool
}{
{"conf inprocess", jwt.ConnectionTypeInProcess, true},
{"conf standard", jwt.ConnectionTypeStandard, false},
} {
t.Run(test.name, func(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, test.ct)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()

// Create standard connection
nc, err := nats.Connect(s.ClientURL(), nats.UserInfo("test", "pwd"))
if test.inProcessOnly && err == nil {
nc.Close()
t.Fatal("Expected standard connection to fail, it did not")
}
// Works if nc is nil (which it will if only in-process are allowed)
nc.Close()

// Create inProcess connection
nc, err = nats.Connect(_EMPTY_, nats.UserInfo("test", "pwd"), nats.InProcessServer(s))
if !test.inProcessOnly && err == nil {
nc.Close()
t.Fatal("Expected in-process connection to fail, it did not")
}
// Works if nc is nil (which it will if only standard are allowed)
nc.Close()
})
}
for _, test := range []struct {
name string
ct string
inProcessOnly bool
}{
{"jwt inprocess", jwt.ConnectionTypeInProcess, true},
{"jwt standard", jwt.ConnectionTypeStandard, false},
} {
t.Run(test.name, func(t *testing.T) {
skp, _ := nkeys.FromSeed(oSeed)
spub, _ := skp.PublicKey()

o := defaultServerOptions
o.TrustedKeys = []string{spub}
o.WriteDeadline = 500 * time.Millisecond
s := RunServer(&o)
defer s.Shutdown()

buildMemAccResolver(s)

kp, _ := nkeys.CreateAccount()
aPub, _ := kp.PublicKey()
claim := jwt.NewAccountClaims(aPub)
aJwt, err := claim.Encode(oKp)
require_NoError(t, err)

addAccountToMemResolver(s, aPub, aJwt)

creds := createUserWithLimit(t, kp, time.Time{},
func(j *jwt.UserPermissionLimits) {
j.AllowedConnectionTypes.Add(test.ct)
})
// Create standard connection
nc, err := nats.Connect(s.ClientURL(), nats.UserCredentials(creds))
if test.inProcessOnly && err == nil {
nc.Close()
t.Fatal("Expected standard connection to fail, it did not")
}
// Works if nc is nil (which it will if only in-process are allowed)
nc.Close()

// Create inProcess connection
nc, err = nats.Connect(_EMPTY_, nats.UserCredentials(creds), nats.InProcessServer(s))
if !test.inProcessOnly && err == nil {
nc.Close()
t.Fatal("Expected in-process connection to fail, it did not")
}
// Works if nc is nil (which it will if only standard are allowed)
nc.Close()
})
}
}
17 changes: 17 additions & 0 deletions server/config_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1817,6 +1817,23 @@ func TestConfigCheck(t *testing.T) {
errorLine: 9,
errorPos: 9,
},
{
name: "invalid duration for remote leafnode first info timeout",
config: `
leafnodes {
port: -1
remotes [
{
url: "nats://127.0.0.1:123"
first_info_timeout: abc
}
]
}
`,
err: fmt.Errorf("error parsing first_info_timeout: time: invalid duration %q", "abc"),
errorLine: 7,
errorPos: 8,
},
{
name: "show warnings on empty configs without values",
config: ``,
Expand Down
Loading