mirror of
https://github.com/hanzoai/pubsub.git
synced 2026-10-30 15:06:26 +00:00
The configuration reload now supports adding and/or removing remote leafnodes. The changes allowed are the same than what was previously supported, that is: TLS, "TLS first", compression and enabled/disabled. Note that the DenyImports/DenyExports list in the leafnode remote configuration was previously ignored because it was changed at runtime, but with the internal changes made to the diff process, and the fact that they are no longer changed at runtime, we no longer ignore them, which means that if they are changed, the configuration reload will now properly reject the changes. A remote is identified with the combination of its URLs list, local account and credentials file name. This is what is used by the server to detect changes for the remote leafnodes list. The list of URLs can contain username/password information, so a redacted version of the URLs list is used, so that if the name appears in the log, it will not show sensitive information. The changes ensure that if a remote is quickly changed from enabled to disabled to enabled again (and same for added and removed) we don't end-up with duplicate leafnode connections. Unrelated: fixed some tests and flappers. Co-authored-by: Waldemar Quevedo <[email protected]> Signed-off-by: Ivan Kozlovic <[email protected]>
2637 lines
60 KiB
Go
2637 lines
60 KiB
Go
// Copyright 2018-2025 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
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package server
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestConfigCheck(t *testing.T) {
|
|
tests := []struct {
|
|
// name is the name of the test.
|
|
name string
|
|
|
|
// config is content of the configuration file.
|
|
config string
|
|
|
|
// warningErr is an error that does not prevent server from starting.
|
|
warningErr error
|
|
|
|
// errorLine is the location of the error.
|
|
errorLine int
|
|
|
|
// errorPos is the position of the error.
|
|
errorPos int
|
|
|
|
// warning errors also include a reason optionally.
|
|
reason string
|
|
|
|
// newDefaultErr is a configuration error that includes source of error.
|
|
err error
|
|
}{
|
|
{
|
|
name: "when unknown field is used at top level",
|
|
config: `
|
|
monitor = "127.0.0.1:4442"
|
|
`,
|
|
err: errors.New(`unknown field "monitor"`),
|
|
errorLine: 2,
|
|
errorPos: 17,
|
|
},
|
|
{
|
|
name: "when default permissions are used at top level",
|
|
config: `
|
|
"default_permissions" {
|
|
publish = ["_SANDBOX.>"]
|
|
subscribe = ["_SANDBOX.>"]
|
|
}
|
|
`,
|
|
err: errors.New(`unknown field "default_permissions"`),
|
|
errorLine: 2,
|
|
errorPos: 18,
|
|
},
|
|
{
|
|
name: "when authorization config is empty",
|
|
config: `
|
|
authorization = {
|
|
}
|
|
`,
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "when authorization config has unknown fields",
|
|
config: `
|
|
authorization = {
|
|
foo = "bar"
|
|
}
|
|
`,
|
|
err: errors.New(`unknown field "foo"`),
|
|
errorLine: 3,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when authorization config has unknown fields",
|
|
config: `
|
|
port = 4222
|
|
|
|
authorization = {
|
|
user = "hello"
|
|
foo = "bar"
|
|
password = "world"
|
|
}
|
|
|
|
`,
|
|
err: errors.New(`unknown field "foo"`),
|
|
errorLine: 6,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when user authorization config has unknown fields",
|
|
config: `
|
|
authorization = {
|
|
users = [
|
|
{
|
|
user = "foo"
|
|
pass = "bar"
|
|
token = "quux"
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`unknown field "token"`),
|
|
errorLine: 7,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "when user authorization permissions config has unknown fields",
|
|
config: `
|
|
authorization {
|
|
permissions {
|
|
subscribe = {}
|
|
inboxes = {}
|
|
publish = {}
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Unknown field "inboxes" parsing permissions`),
|
|
errorLine: 5,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "when user authorization permissions config has unknown fields within allow or deny",
|
|
config: `
|
|
authorization {
|
|
permissions {
|
|
subscribe = {
|
|
allow = ["hello", "world"]
|
|
deny = ["foo", "bar"]
|
|
denied = "_INBOX.>"
|
|
}
|
|
publish = {}
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Unknown field name "denied" parsing subject permissions, only 'allow' or 'deny' are permitted`),
|
|
errorLine: 7,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "when user authorization permissions config has unknown fields within allow or deny",
|
|
config: `
|
|
authorization {
|
|
permissions {
|
|
publish = {
|
|
allow = ["hello", "world"]
|
|
deny = ["foo", "bar"]
|
|
allowed = "_INBOX.>"
|
|
}
|
|
subscribe = {}
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Unknown field name "allowed" parsing subject permissions, only 'allow' or 'deny' are permitted`),
|
|
errorLine: 7,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "when user authorization permissions config has unknown fields using arrays",
|
|
config: `
|
|
authorization {
|
|
|
|
default_permissions {
|
|
subscribe = ["a"]
|
|
publish = ["b"]
|
|
inboxes = ["c"]
|
|
}
|
|
|
|
users = [
|
|
{
|
|
user = "foo"
|
|
pass = "bar"
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`Unknown field "inboxes" parsing permissions`),
|
|
errorLine: 7,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "when user authorization permissions config has unknown fields using strings",
|
|
config: `
|
|
authorization {
|
|
|
|
default_permissions {
|
|
subscribe = "a"
|
|
requests = "b"
|
|
publish = "c"
|
|
}
|
|
|
|
users = [
|
|
{
|
|
user = "foo"
|
|
pass = "bar"
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`Unknown field "requests" parsing permissions`),
|
|
errorLine: 6,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "when user authorization permissions config is empty",
|
|
config: `
|
|
authorization = {
|
|
users = [
|
|
{
|
|
user = "foo", pass = "bar", permissions = {
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "when unknown permissions are included in user config",
|
|
config: `
|
|
authorization = {
|
|
users = [
|
|
{
|
|
user = "foo", pass = "bar", permissions {
|
|
inboxes = true
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`Unknown field "inboxes" parsing permissions`),
|
|
errorLine: 6,
|
|
errorPos: 11,
|
|
},
|
|
{
|
|
name: "when clustering config is empty",
|
|
config: `
|
|
cluster = {
|
|
}
|
|
`,
|
|
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "when unknown option is in clustering config",
|
|
config: `
|
|
# NATS Server Configuration
|
|
port = 4222
|
|
|
|
cluster = {
|
|
|
|
port = 6222
|
|
|
|
foo = "bar"
|
|
|
|
authorization {
|
|
user = "hello"
|
|
pass = "world"
|
|
}
|
|
|
|
}
|
|
`,
|
|
|
|
err: errors.New(`unknown field "foo"`),
|
|
errorLine: 9,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when unknown option is in clustering authorization config",
|
|
config: `
|
|
cluster = {
|
|
authorization {
|
|
foo = "bar"
|
|
}
|
|
}
|
|
`,
|
|
|
|
err: errors.New(`unknown field "foo"`),
|
|
errorLine: 4,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "when unknown option is in tls config",
|
|
config: `
|
|
tls = {
|
|
hello = "world"
|
|
}
|
|
`,
|
|
err: errors.New(`error parsing tls config, unknown field "hello"`),
|
|
errorLine: 3,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when unknown option is in cluster tls config",
|
|
config: `
|
|
cluster {
|
|
tls = {
|
|
foo = "bar"
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`error parsing tls config, unknown field "foo"`),
|
|
errorLine: 4,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "when using cipher suites in the TLS config",
|
|
config: `
|
|
tls = {
|
|
cipher_suites: [
|
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
|
]
|
|
preferences = []
|
|
}
|
|
`,
|
|
err: errors.New(`error parsing tls config, unknown field "preferences"`),
|
|
errorLine: 7,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "when using curve preferences in the TLS config",
|
|
config: `
|
|
tls = {
|
|
curve_preferences: [
|
|
"CurveP256",
|
|
"CurveP384",
|
|
"CurveP521"
|
|
]
|
|
suites = []
|
|
}
|
|
`,
|
|
err: errors.New(`error parsing tls config, unknown field "suites"`),
|
|
errorLine: 8,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "when using curve preferences in the TLS config",
|
|
config: `
|
|
tls = {
|
|
curve_preferences: [
|
|
"CurveP5210000"
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`unrecognized curve preference CurveP5210000`),
|
|
errorLine: 4,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "verify_cert_and_check_known_urls not support for clients",
|
|
config: `
|
|
tls = {
|
|
cert_file: "configs/certs/server.pem"
|
|
key_file: "configs/certs/key.pem"
|
|
verify_cert_and_check_known_urls: true
|
|
}
|
|
`,
|
|
err: errors.New("verify_cert_and_check_known_urls not supported in this context"),
|
|
errorLine: 5,
|
|
errorPos: 10,
|
|
},
|
|
{
|
|
name: "when unknown option is in cluster config with defined routes",
|
|
config: `
|
|
cluster {
|
|
port = 6222
|
|
routes = [
|
|
nats://127.0.0.1:6222
|
|
]
|
|
peers = []
|
|
}
|
|
`,
|
|
err: errors.New(`unknown field "peers"`),
|
|
errorLine: 7,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when used as variable in authorization block it should not be considered as unknown field",
|
|
config: `
|
|
# listen: 127.0.0.1:-1
|
|
listen: 127.0.0.1:4222
|
|
|
|
authorization {
|
|
# Superuser can do anything.
|
|
super_user = {
|
|
publish = ">"
|
|
subscribe = ">"
|
|
}
|
|
|
|
# Can do requests on foo or bar, and subscribe to anything
|
|
# that is a response to an _INBOX.
|
|
#
|
|
# Notice that authorization filters can be singletons or arrays.
|
|
req_pub_user = {
|
|
publish = ["req.foo", "req.bar"]
|
|
subscribe = "_INBOX.>"
|
|
}
|
|
|
|
# Setup a default user that can subscribe to anything, but has
|
|
# no publish capabilities.
|
|
default_user = {
|
|
subscribe = "PUBLIC.>"
|
|
}
|
|
|
|
unused = "hello"
|
|
|
|
# Default permissions if none presented. e.g. susan below.
|
|
default_permissions: $default_user
|
|
|
|
# Users listed with persmissions.
|
|
users = [
|
|
{user: alice, password: foo, permissions: $super_user}
|
|
{user: bob, password: bar, permissions: $req_pub_user}
|
|
{user: susan, password: baz}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`unknown field "unused"`),
|
|
errorLine: 27,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when used as variable in top level config it should not be considered as unknown field",
|
|
config: `
|
|
monitoring_port = 8222
|
|
|
|
http_port = $monitoring_port
|
|
|
|
port = 4222
|
|
`,
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "when used as variable in cluster config it should not be considered as unknown field",
|
|
config: `
|
|
cluster {
|
|
clustering_port = 6222
|
|
port = $clustering_port
|
|
}
|
|
`,
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "when setting permissions within cluster authorization block",
|
|
config: `
|
|
cluster {
|
|
authorization {
|
|
permissions = {
|
|
publish = { allow = ["foo", "bar"] }
|
|
}
|
|
}
|
|
|
|
permissions = {
|
|
publish = { deny = ["foo", "bar"] }
|
|
}
|
|
}
|
|
`,
|
|
warningErr: errors.New(`invalid use of field "authorization"`),
|
|
errorLine: 3,
|
|
errorPos: 5,
|
|
reason: `setting "permissions" within cluster authorization block is deprecated`,
|
|
},
|
|
{
|
|
name: "when write deadline is used with deprecated usage",
|
|
config: `
|
|
write_deadline = 100
|
|
`,
|
|
warningErr: errors.New(`invalid use of field "write_deadline"`),
|
|
errorLine: 2,
|
|
errorPos: 17,
|
|
reason: `write_deadline should be converted to a duration`,
|
|
},
|
|
/////////////////////
|
|
// ACCOUNTS //
|
|
/////////////////////
|
|
{
|
|
name: "when accounts block is correctly configured",
|
|
config: `
|
|
http_port = 8222
|
|
|
|
accounts {
|
|
|
|
#
|
|
# synadia > nats.io, cncf
|
|
#
|
|
synadia {
|
|
# SAADJL5XAEM6BDYSWDTGVILJVY54CQXZM5ZLG4FRUAKB62HWRTPNSGXOHA
|
|
nkey = "AC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL"
|
|
|
|
users [
|
|
{
|
|
# SUAEL6RU3BSDAFKOHNTEOK5Q6FTM5FTAMWVIKBET6FHPO4JRII3CYELVNM
|
|
nkey = "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
|
|
}
|
|
]
|
|
|
|
exports = [
|
|
{ service: "synadia.requests", accounts: [nats, cncf] }
|
|
]
|
|
}
|
|
|
|
#
|
|
# nats < synadia
|
|
#
|
|
nats {
|
|
# SUAJTM55JH4BNYDA22DMDZJSRBRKVDGSLYK2HDIOCM3LPWCDXIDV5Q4CIE
|
|
nkey = "ADRZ42QBM7SXQDXXTSVWT2WLLFYOQGAFC4TO6WOAXHEKQHIXR4HFYJDS"
|
|
|
|
users [
|
|
{
|
|
# SUADZTYQAKTY5NQM7XRB5XR3C24M6ROGZLBZ6P5HJJSSOFUGC5YXOOECOM
|
|
nkey = "UD6AYQSOIN2IN5OGC6VQZCR4H3UFMIOXSW6NNS6N53CLJA4PB56CEJJI"
|
|
}
|
|
]
|
|
|
|
imports = [
|
|
# This account has to send requests to 'nats.requests' subject
|
|
{ service: { account: "synadia", subject: "synadia.requests" }, to: "nats.requests" }
|
|
]
|
|
}
|
|
|
|
#
|
|
# cncf < synadia
|
|
#
|
|
cncf {
|
|
# SAAFHDZX7SGZ2SWHPS22JRPPK5WX44NPLNXQHR5C5RIF6QRI3U65VFY6C4
|
|
nkey = "AD4YRVUJF2KASKPGRMNXTYKIYSCB3IHHB4Y2ME6B2PDIV5QJ23C2ZRIT"
|
|
|
|
users [
|
|
{
|
|
# SUAKINP3Z2BPUXWOFSW2FZC7TFJCMMU7DHKP2C62IJQUDASOCDSTDTRMJQ
|
|
nkey = "UB57IEMPG4KOTPFV5A66QKE2HZ3XBXFHVRCCVMJEWKECMVN2HSH3VTSJ"
|
|
}
|
|
]
|
|
|
|
imports = [
|
|
# This account has to send requests to 'synadia.requests' subject
|
|
{ service: { account: "synadia", subject: "synadia.requests" } }
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "when nkey is invalid within accounts block",
|
|
config: `
|
|
accounts {
|
|
|
|
#
|
|
# synadia > nats.io, cncf
|
|
#
|
|
synadia {
|
|
# SAADJL5XAEM6BDYSWDTGVILJVY54CQXZM5ZLG4FRUAKB62HWRTPNSGXOHA
|
|
nkey = "AC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL"
|
|
|
|
users [
|
|
{
|
|
# SUAEL6RU3BSDAFKOHNTEOK5Q6FTM5FTAMWVIKBET6FHPO4JRII3CYELVNM
|
|
nkey = "SCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
|
|
}
|
|
]
|
|
|
|
exports = [
|
|
{ service: "synadia.requests", accounts: [nats, cncf] }
|
|
]
|
|
}
|
|
|
|
#
|
|
# nats < synadia
|
|
#
|
|
nats {
|
|
# SUAJTM55JH4BNYDA22DMDZJSRBRKVDGSLYK2HDIOCM3LPWCDXIDV5Q4CIE
|
|
nkey = "ADRZ42QBM7SXQDXXTSVWT2WLLFYOQGAFC4TO6WOAXHEKQHIXR4HFYJDS"
|
|
|
|
users [
|
|
{
|
|
# SUADZTYQAKTY5NQM7XRB5XR3C24M6ROGZLBZ6P5HJJSSOFUGC5YXOOECOM
|
|
nkey = "UD6AYQSOIN2IN5OGC6VQZCR4H3UFMIOXSW6NNS6N53CLJA4PB56CEJJI"
|
|
}
|
|
]
|
|
|
|
imports = [
|
|
# This account has to send requests to 'nats.requests' subject
|
|
{ service: { account: "synadia", subject: "synadia.requests" }, to: "nats.requests" }
|
|
]
|
|
}
|
|
|
|
#
|
|
# cncf < synadia
|
|
#
|
|
cncf {
|
|
# SAAFHDZX7SGZ2SWHPS22JRPPK5WX44NPLNXQHR5C5RIF6QRI3U65VFY6C4
|
|
nkey = "AD4YRVUJF2KASKPGRMNXTYKIYSCB3IHHB4Y2ME6B2PDIV5QJ23C2ZRIT"
|
|
|
|
users [
|
|
{
|
|
# SUAKINP3Z2BPUXWOFSW2FZC7TFJCMMU7DHKP2C62IJQUDASOCDSTDTRMJQ
|
|
nkey = "UB57IEMPG4KOTPFV5A66QKE2HZ3XBXFHVRCCVMJEWKECMVN2HSH3VTSJ"
|
|
}
|
|
]
|
|
|
|
imports = [
|
|
# This account has to send requests to 'synadia.requests' subject
|
|
{ service: { account: "synadia", subject: "synadia.requests" } }
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Not a valid public nkey for a user`),
|
|
errorLine: 14,
|
|
errorPos: 11,
|
|
},
|
|
{
|
|
name: "when accounts block has unknown fields",
|
|
config: `
|
|
http_port = 8222
|
|
|
|
accounts {
|
|
foo = "bar"
|
|
}`,
|
|
err: errors.New(`Expected map entries for accounts`),
|
|
errorLine: 5,
|
|
errorPos: 19,
|
|
},
|
|
{
|
|
name: "when accounts has a referenced config variable within same block",
|
|
config: `
|
|
accounts {
|
|
PERMISSIONS = {
|
|
publish = {
|
|
allow = ["foo","bar"]
|
|
deny = ["quux"]
|
|
}
|
|
}
|
|
|
|
synadia {
|
|
nkey = "AC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL"
|
|
|
|
users [
|
|
{
|
|
nkey = "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
|
|
permissions = $PERMISSIONS
|
|
}
|
|
]
|
|
exports = [
|
|
{ stream: "synadia.>" }
|
|
]
|
|
}
|
|
}`,
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "when accounts has an unreferenced config variables within same block",
|
|
config: `
|
|
accounts {
|
|
PERMISSIONS = {
|
|
publish = {
|
|
allow = ["foo","bar"]
|
|
deny = ["quux"]
|
|
}
|
|
}
|
|
|
|
synadia {
|
|
nkey = "AC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL"
|
|
|
|
users [
|
|
{
|
|
nkey = "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
|
|
}
|
|
]
|
|
exports = [
|
|
{ stream: "synadia.>" }
|
|
]
|
|
}
|
|
}`,
|
|
err: errors.New(`unknown field "publish"`),
|
|
errorLine: 4,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when accounts block defines a global account",
|
|
config: `
|
|
http_port = 8222
|
|
|
|
accounts {
|
|
$G = {
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`"$G" is a Reserved Account`),
|
|
errorLine: 5,
|
|
errorPos: 19,
|
|
},
|
|
{
|
|
name: "when accounts block uses an invalid public key",
|
|
config: `
|
|
accounts {
|
|
synadia = {
|
|
nkey = "invalid"
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Not a valid public nkey for an account: "invalid"`),
|
|
errorLine: 4,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "when accounts list includes reserved account",
|
|
config: `
|
|
port = 4222
|
|
|
|
accounts = [foo, bar, "$G"]
|
|
|
|
http_port = 8222
|
|
`,
|
|
err: errors.New(`"$G" is a Reserved Account`),
|
|
errorLine: 4,
|
|
errorPos: 26,
|
|
},
|
|
{
|
|
name: "when accounts list includes a dupe entry",
|
|
config: `
|
|
port = 4222
|
|
|
|
accounts = [foo, bar, bar]
|
|
|
|
http_port = 8222
|
|
`,
|
|
err: errors.New(`Duplicate Account Entry: bar`),
|
|
errorLine: 4,
|
|
errorPos: 25,
|
|
},
|
|
{
|
|
name: "when accounts block includes a dupe user",
|
|
config: `
|
|
port = 4222
|
|
|
|
accounts = {
|
|
nats {
|
|
users = [
|
|
{ user: "foo", pass: "bar" },
|
|
{ user: "hello", pass: "world" },
|
|
{ user: "foo", pass: "bar" }
|
|
]
|
|
}
|
|
}
|
|
|
|
http_port = 8222
|
|
`,
|
|
err: errors.New(`Duplicate user "foo" detected`),
|
|
errorLine: 6,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "when accounts block imports are not a list",
|
|
config: `
|
|
port = 4222
|
|
|
|
accounts = {
|
|
nats {
|
|
imports = true
|
|
}
|
|
}
|
|
|
|
http_port = 8222
|
|
`,
|
|
err: errors.New(`Imports should be an array, got bool`),
|
|
errorLine: 6,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "when accounts block exports are not a list",
|
|
config: `
|
|
port = 4222
|
|
|
|
accounts = {
|
|
nats {
|
|
exports = true
|
|
}
|
|
}
|
|
|
|
http_port = 8222
|
|
`,
|
|
err: errors.New(`Exports should be an array, got bool`),
|
|
errorLine: 6,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "when accounts block imports items are not a map",
|
|
config: `
|
|
port = 4222
|
|
|
|
accounts = {
|
|
nats {
|
|
imports = [
|
|
false
|
|
]
|
|
}
|
|
}
|
|
|
|
http_port = 8222
|
|
`,
|
|
err: errors.New(`Import Items should be a map with type entry, got bool`),
|
|
errorLine: 7,
|
|
errorPos: 23,
|
|
},
|
|
{
|
|
name: "when accounts block export items are not a map",
|
|
config: `
|
|
port = 4222
|
|
|
|
accounts = {
|
|
nats {
|
|
exports = [
|
|
false
|
|
]
|
|
}
|
|
}
|
|
|
|
http_port = 8222
|
|
`,
|
|
err: errors.New(`Export Items should be a map with type entry, got bool`),
|
|
errorLine: 7,
|
|
errorPos: 23,
|
|
},
|
|
{
|
|
name: "when accounts exports has a stream name that is not a string",
|
|
config: `
|
|
port = 4222
|
|
|
|
accounts = {
|
|
nats {
|
|
exports = [
|
|
{
|
|
stream: false
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
http_port = 8222
|
|
`,
|
|
err: errors.New(`Expected stream name to be string, got bool`),
|
|
errorLine: 8,
|
|
errorPos: 25,
|
|
},
|
|
{
|
|
name: "when accounts exports has a service name that is not a string",
|
|
config: `
|
|
accounts = {
|
|
nats {
|
|
exports = [
|
|
{
|
|
service: false
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Expected service name to be string, got bool`),
|
|
errorLine: 6,
|
|
errorPos: 25,
|
|
},
|
|
{
|
|
name: "when accounts imports stream without name",
|
|
config: `
|
|
port = 4222
|
|
|
|
accounts = {
|
|
nats {
|
|
imports = [
|
|
{ stream: { }}
|
|
]
|
|
}
|
|
}
|
|
|
|
http_port = 8222
|
|
`,
|
|
err: errors.New(`Expect an account name and a subject`),
|
|
errorLine: 7,
|
|
errorPos: 25,
|
|
},
|
|
{
|
|
name: "when accounts imports service without name",
|
|
config: `
|
|
port = 4222
|
|
|
|
accounts = {
|
|
nats {
|
|
imports = [
|
|
{ service: { }}
|
|
]
|
|
}
|
|
}
|
|
|
|
http_port = 8222
|
|
`,
|
|
err: errors.New(`Expect an account name and a subject`),
|
|
errorLine: 7,
|
|
errorPos: 25,
|
|
},
|
|
{
|
|
name: "when account trace destination is of the wrong type",
|
|
config: `
|
|
accounts {
|
|
A { trace_dest: 123 }
|
|
}
|
|
`,
|
|
err: errors.New(`Expected account message trace "trace_dest" to be a string or a map/struct, got int64`),
|
|
errorLine: 3,
|
|
errorPos: 23,
|
|
},
|
|
{
|
|
name: "when account trace destination is not a valid destination",
|
|
config: `
|
|
accounts {
|
|
A { trace_dest: "invalid..dest" }
|
|
}
|
|
`,
|
|
err: errors.New(`Trace destination "invalid..dest" is not valid`),
|
|
errorLine: 3,
|
|
errorPos: 23,
|
|
},
|
|
{
|
|
name: "when account trace destination is not a valid publish subject",
|
|
config: `
|
|
accounts {
|
|
A { trace_dest: "invalid.publish.*.subject" }
|
|
}
|
|
`,
|
|
err: errors.New(`Trace destination "invalid.publish.*.subject" is not valid`),
|
|
errorLine: 3,
|
|
errorPos: 23,
|
|
},
|
|
{
|
|
name: "when account message trace dest is wrong type",
|
|
config: `
|
|
accounts {
|
|
A { msg_trace: {dest: 123} }
|
|
}
|
|
`,
|
|
err: errors.New(`Field "dest" should be a string, got int64`),
|
|
errorLine: 3,
|
|
errorPos: 35,
|
|
},
|
|
{
|
|
name: "when account message trace dest is invalid",
|
|
config: `
|
|
accounts {
|
|
A { msg_trace: {dest: "invalid..dest"} }
|
|
}
|
|
`,
|
|
err: errors.New(`Trace destination "invalid..dest" is not valid`),
|
|
errorLine: 3,
|
|
errorPos: 35,
|
|
},
|
|
{
|
|
name: "when account message trace sampling is wrong type",
|
|
config: `
|
|
accounts {
|
|
A { msg_trace: {dest: "acc.dest", sampling: {wront: "type"}} }
|
|
}
|
|
`,
|
|
err: errors.New(`Trace destination sampling field "sampling" should be an integer or a percentage, got map[string]interface {}`),
|
|
errorLine: 3,
|
|
errorPos: 53,
|
|
},
|
|
{
|
|
name: "when account message trace sampling is wrong string",
|
|
config: `
|
|
accounts {
|
|
A { msg_trace: {dest: "acc.dest", sampling: abc%} }
|
|
}
|
|
`,
|
|
err: errors.New(`Invalid trace destination sampling value "abc%"`),
|
|
errorLine: 3,
|
|
errorPos: 53,
|
|
},
|
|
{
|
|
name: "when account message trace sampling is negative",
|
|
config: `
|
|
accounts {
|
|
A { msg_trace: {dest: "acc.dest", sampling: -1} }
|
|
}
|
|
`,
|
|
err: errors.New(`Ttrace destination sampling value -1 is invalid, needs to be [1..100]`),
|
|
errorLine: 3,
|
|
errorPos: 53,
|
|
},
|
|
{
|
|
name: "when account message trace sampling is zero",
|
|
config: `
|
|
accounts {
|
|
A { msg_trace: {dest: "acc.dest", sampling: 0} }
|
|
}
|
|
`,
|
|
err: errors.New(`Ttrace destination sampling value 0 is invalid, needs to be [1..100]`),
|
|
errorLine: 3,
|
|
errorPos: 53,
|
|
},
|
|
{
|
|
name: "when account message trace sampling is more than 100",
|
|
config: `
|
|
accounts {
|
|
A { msg_trace: {dest: "acc.dest", sampling: 101} }
|
|
}
|
|
`,
|
|
err: errors.New(`Ttrace destination sampling value 101 is invalid, needs to be [1..100]`),
|
|
errorLine: 3,
|
|
errorPos: 53,
|
|
},
|
|
{
|
|
name: "when account message trace has unknown field",
|
|
config: `
|
|
accounts {
|
|
A { msg_trace: {wrong: "field"} }
|
|
}
|
|
`,
|
|
err: errors.New(`Unknown field "wrong" parsing account message trace map/struct "msg_trace"`),
|
|
errorLine: 3,
|
|
errorPos: 35,
|
|
},
|
|
{
|
|
name: "when user authorization config has both token and users",
|
|
config: `
|
|
authorization = {
|
|
token = "s3cr3t"
|
|
users = [
|
|
{
|
|
user = "foo"
|
|
pass = "bar"
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`Can not have a token and a users array`),
|
|
errorLine: 2,
|
|
errorPos: 3,
|
|
},
|
|
{
|
|
name: "when user authorization config has both token and user",
|
|
config: `
|
|
authorization = {
|
|
user = "foo"
|
|
pass = "bar"
|
|
token = "baz"
|
|
}
|
|
`,
|
|
err: errors.New(`Cannot have a user/pass and token`),
|
|
errorLine: 2,
|
|
errorPos: 3,
|
|
},
|
|
{
|
|
name: "when user authorization config has both user and users array",
|
|
config: `
|
|
authorization = {
|
|
user = "user1"
|
|
pass = "pwd1"
|
|
users = [
|
|
{
|
|
user = "user2"
|
|
pass = "pwd2"
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`Can not have a single user/pass and a users array`),
|
|
errorLine: 2,
|
|
errorPos: 3,
|
|
},
|
|
{
|
|
name: "when user authorization has duplicate users",
|
|
config: `
|
|
authorization = {
|
|
users = [
|
|
{user: "user1", pass: "pwd"}
|
|
{user: "user2", pass: "pwd"}
|
|
{user: "user1", pass: "pwd"}
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf(`Duplicate user %q detected`, "user1"),
|
|
errorLine: 2,
|
|
errorPos: 3,
|
|
},
|
|
{
|
|
name: "when user authorization has duplicate nkeys",
|
|
config: `
|
|
authorization = {
|
|
users = [
|
|
{nkey: UC6NLCN7AS34YOJVCYD4PJ3QB7QGLYG5B5IMBT25VW5K4TNUJODM7BOX }
|
|
{nkey: UBAAQWTW6CG2G6ANGNKB5U2B7HRWHSGMZEZX3AQSAJOQDAUGJD46LD2E }
|
|
{nkey: UC6NLCN7AS34YOJVCYD4PJ3QB7QGLYG5B5IMBT25VW5K4TNUJODM7BOX }
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf(`Duplicate nkey %q detected`, "UC6NLCN7AS34YOJVCYD4PJ3QB7QGLYG5B5IMBT25VW5K4TNUJODM7BOX"),
|
|
errorLine: 2,
|
|
errorPos: 3,
|
|
},
|
|
{
|
|
name: "when user authorization config has users not as a list",
|
|
config: `
|
|
authorization = {
|
|
users = false
|
|
}
|
|
`,
|
|
err: errors.New(`Expected users field to be an array, got false`),
|
|
errorLine: 3,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when user authorization config has users not as a map",
|
|
config: `
|
|
authorization = {
|
|
users = [false]
|
|
}
|
|
`,
|
|
err: errors.New(`Expected user entry to be a map/struct, got false`),
|
|
errorLine: 3,
|
|
errorPos: 14,
|
|
},
|
|
{
|
|
name: "when user authorization config has permissions not as a map",
|
|
config: `
|
|
authorization = {
|
|
users = [{user: hello, pass: world}]
|
|
permissions = false
|
|
}
|
|
`,
|
|
err: errors.New(`Expected permissions to be a map/struct, got false`),
|
|
errorLine: 4,
|
|
errorPos: 19,
|
|
},
|
|
{
|
|
name: "when user authorization permissions config has invalid fields within allow",
|
|
config: `
|
|
authorization {
|
|
permissions {
|
|
publish = {
|
|
allow = [false, "hello", "world"]
|
|
deny = ["foo", "bar"]
|
|
}
|
|
subscribe = {}
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Subject in permissions array cannot be cast to string`),
|
|
errorLine: 5,
|
|
errorPos: 18,
|
|
},
|
|
{
|
|
name: "when user authorization permissions config has invalid fields within deny",
|
|
config: `
|
|
authorization {
|
|
permissions {
|
|
publish = {
|
|
allow = ["hello", "world"]
|
|
deny = [true, "foo", "bar"]
|
|
}
|
|
subscribe = {}
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Subject in permissions array cannot be cast to string`),
|
|
errorLine: 6,
|
|
errorPos: 17,
|
|
},
|
|
{
|
|
name: "when user authorization permissions config has invalid type",
|
|
config: `
|
|
authorization {
|
|
permissions {
|
|
publish = {
|
|
allow = false
|
|
}
|
|
subscribe = {}
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Expected subject permissions to be a subject, or array of subjects, got bool`),
|
|
errorLine: 5,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "when user authorization permissions subject is invalid",
|
|
config: `
|
|
authorization {
|
|
permissions {
|
|
publish = {
|
|
allow = ["foo..bar"]
|
|
}
|
|
subscribe = {}
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`subject "foo..bar" is not a valid subject`),
|
|
errorLine: 5,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "when cluster config listen is invalid",
|
|
config: `
|
|
cluster {
|
|
listen = "0.0.0.0:XXXX"
|
|
}
|
|
`,
|
|
err: errors.New(`could not parse port "XXXX"`),
|
|
errorLine: 3,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when cluster config includes multiple users",
|
|
config: `
|
|
cluster {
|
|
authorization {
|
|
users = []
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Cluster authorization does not allow multiple users`),
|
|
errorLine: 3,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when cluster routes are invalid",
|
|
config: `
|
|
cluster {
|
|
routes = [
|
|
"0.0.0.0:XXXX"
|
|
# "0.0.0.0:YYYY"
|
|
# "0.0.0.0:ZZZZ"
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`error parsing route url ["0.0.0.0:XXXX"]`),
|
|
errorLine: 4,
|
|
errorPos: 22,
|
|
},
|
|
{
|
|
name: "when setting invalid TLS config within cluster block",
|
|
config: `
|
|
cluster {
|
|
tls {
|
|
}
|
|
}
|
|
`,
|
|
err: nil,
|
|
errorLine: 0,
|
|
errorPos: 0,
|
|
},
|
|
{
|
|
name: "invalid lame_duck_duration type",
|
|
config: `
|
|
lame_duck_duration: abc
|
|
`,
|
|
err: errors.New(`error parsing lame_duck_duration: time: invalid duration`),
|
|
errorLine: 2,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "lame_duck_duration too small",
|
|
config: `
|
|
lame_duck_duration: "5s"
|
|
`,
|
|
err: errors.New(`invalid lame_duck_duration of 5s, minimum is 30 seconds`),
|
|
errorLine: 2,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "invalid lame_duck_grace_period type",
|
|
config: `
|
|
lame_duck_grace_period: abc
|
|
`,
|
|
err: errors.New(`error parsing lame_duck_grace_period: time: invalid duration`),
|
|
errorLine: 2,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "lame_duck_grace_period should be positive",
|
|
config: `
|
|
lame_duck_grace_period: "-5s"
|
|
`,
|
|
err: errors.New(`invalid lame_duck_grace_period, needs to be positive`),
|
|
errorLine: 2,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when only setting TLS timeout for a leafnode remote",
|
|
config: `
|
|
leafnodes {
|
|
remotes = [
|
|
{
|
|
url: "tls://nats:7422"
|
|
tls {
|
|
timeout: 0.01
|
|
}
|
|
}
|
|
]
|
|
}`,
|
|
err: nil,
|
|
errorLine: 0,
|
|
errorPos: 0,
|
|
},
|
|
{
|
|
name: "verify_cert_and_check_known_urls do not work for leaf nodes",
|
|
config: `
|
|
leafnodes {
|
|
remotes = [
|
|
{
|
|
url: "tls://nats:7422"
|
|
tls {
|
|
timeout: 0.01
|
|
verify_cert_and_check_known_urls: true
|
|
}
|
|
}
|
|
]
|
|
}`,
|
|
//Unexpected error after processing config: /var/folders/9h/6g_c9l6n6bb8gp331d_9y0_w0000gn/T/057996446:8:5:
|
|
err: errors.New("verify_cert_and_check_known_urls not supported in this context"),
|
|
errorLine: 8,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when leafnode remotes use wrong type",
|
|
config: `
|
|
leafnodes {
|
|
remotes: {
|
|
url: "tls://nats:7422"
|
|
}
|
|
}`,
|
|
err: errors.New(`Expected remotes field to be an array, got map[string]interface {}`),
|
|
errorLine: 3,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when leafnode remotes url uses wrong type",
|
|
config: `
|
|
leafnodes {
|
|
remotes: [
|
|
{ urls: 1234 }
|
|
]
|
|
}`,
|
|
err: errors.New(`Expected remote leafnode url to be an array or string, got 1234`),
|
|
errorLine: 4,
|
|
errorPos: 18,
|
|
},
|
|
{
|
|
name: "when leafnode min_version is wrong type",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
min_version = 123
|
|
}`,
|
|
err: errors.New(`interface conversion: interface {} is int64, not string`),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "when leafnode min_version has parsing error",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
min_version = bad.version
|
|
}`,
|
|
err: errors.New(`invalid leafnode's minimum version: invalid semver`),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "when leafnode min_version is too low",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
min_version = 2.7.9
|
|
}`,
|
|
err: errors.New(`the minimum version should be at least 2.8.0`),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "when setting latency tracking with a system account",
|
|
config: `
|
|
system_account: sys
|
|
|
|
accounts {
|
|
sys { users = [ {user: sys, pass: "" } ] }
|
|
|
|
nats.io: {
|
|
users = [ { user : bar, pass: "" } ]
|
|
|
|
exports = [
|
|
{ service: "nats.add"
|
|
response: singleton
|
|
latency: {
|
|
sampling: 100%
|
|
subject: "latency.tracking.add"
|
|
}
|
|
}
|
|
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: nil,
|
|
errorLine: 0,
|
|
errorPos: 0,
|
|
},
|
|
{
|
|
name: "when setting latency tracking with an invalid publish subject",
|
|
config: `
|
|
system_account = sys
|
|
accounts {
|
|
sys { users = [ {user: sys, pass: "" } ] }
|
|
|
|
nats.io: {
|
|
users = [ { user : bar, pass: "" } ]
|
|
|
|
exports = [
|
|
{ service: "nats.add"
|
|
response: singleton
|
|
latency: "*"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Error adding service latency sampling for "nats.add" on subject "*": invalid publish subject`),
|
|
errorLine: 3,
|
|
errorPos: 17,
|
|
},
|
|
{
|
|
name: "when setting latency tracking on a stream",
|
|
config: `
|
|
system_account = sys
|
|
accounts {
|
|
sys { users = [ {user: sys, pass: "" } ] }
|
|
|
|
nats.io: {
|
|
users = [ { user : bar, pass: "" } ]
|
|
|
|
exports = [
|
|
{ stream: "nats.add"
|
|
latency: "foo"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Detected latency directive on non-service`),
|
|
errorLine: 11,
|
|
errorPos: 25,
|
|
},
|
|
{
|
|
name: "when setting allow_trace on a stream export (after)",
|
|
config: `
|
|
system_account = sys
|
|
accounts {
|
|
sys { users = [ {user: sys, pass: "" } ] }
|
|
|
|
nats.io: {
|
|
users = [ { user : bar, pass: "" } ]
|
|
exports = [ { stream: "nats.add", allow_trace: true } ]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Detected allow_trace directive on non-service`),
|
|
errorLine: 8,
|
|
errorPos: 55,
|
|
},
|
|
{
|
|
name: "when setting allow_trace on a stream export (before)",
|
|
config: `
|
|
system_account = sys
|
|
accounts {
|
|
sys { users = [ {user: sys, pass: "" } ] }
|
|
|
|
nats.io: {
|
|
users = [ { user : bar, pass: "" } ]
|
|
exports = [ { allow_trace: true, stream: "nats.add" } ]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Detected allow_trace directive on non-service`),
|
|
errorLine: 8,
|
|
errorPos: 35,
|
|
},
|
|
{
|
|
name: "when setting allow_trace on a service import (after)",
|
|
config: `
|
|
accounts {
|
|
A: {
|
|
users = [ {user: user1, pass: ""} ]
|
|
exports = [{service: "foo"}]
|
|
}
|
|
B: {
|
|
users = [ {user: user2, pass: ""} ]
|
|
imports = [ { service: {account: "A", subject: "foo"}, allow_trace: true } ]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Detected allow_trace directive on a non-stream`),
|
|
errorLine: 9,
|
|
errorPos: 76,
|
|
},
|
|
{
|
|
name: "when setting allow_trace on a service import (before)",
|
|
config: `
|
|
accounts {
|
|
A: {
|
|
users = [ {user: user1, pass: ""} ]
|
|
exports = [{service: "foo"}]
|
|
}
|
|
B: {
|
|
users = [ {user: user2, pass: ""} ]
|
|
imports = [ { allow_trace: true, service: {account: "A", subject: "foo"} } ]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Detected allow_trace directive on a non-stream`),
|
|
errorLine: 9,
|
|
errorPos: 35,
|
|
},
|
|
{
|
|
name: "when using duplicate service import subject",
|
|
config: `
|
|
accounts {
|
|
A: {
|
|
users = [ {user: user1, pass: ""} ]
|
|
exports = [
|
|
{service: "remote1"}
|
|
{service: "remote2"}
|
|
]
|
|
}
|
|
B: {
|
|
users = [ {user: user2, pass: ""} ]
|
|
imports = [
|
|
{service: {account: "A", subject: "remote1"}, to: "local"}
|
|
{service: {account: "A", subject: "remote2"}, to: "local"}
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`Duplicate service import subject "local", previously used in import for account "A", subject "remote1"`),
|
|
errorLine: 14,
|
|
errorPos: 71,
|
|
},
|
|
{
|
|
name: "mixing single and multi users in leafnode authorization",
|
|
config: `
|
|
leafnodes {
|
|
authorization {
|
|
user: user1
|
|
password: pwd
|
|
users = [{user: user2, password: pwd}]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New("can not have a single user/pass and a users array"),
|
|
errorLine: 3,
|
|
errorPos: 20,
|
|
},
|
|
{
|
|
name: "duplicate usernames in leafnode authorization",
|
|
config: `
|
|
leafnodes {
|
|
authorization {
|
|
users = [
|
|
{user: user, password: pwd}
|
|
{user: user, password: pwd}
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`duplicate user "user" detected in leafnode authorization`),
|
|
errorLine: 3,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "mqtt bad type",
|
|
config: `
|
|
mqtt [
|
|
"wrong"
|
|
]
|
|
`,
|
|
err: errors.New(`Expected mqtt to be a map, got []interface {}`),
|
|
errorLine: 2,
|
|
errorPos: 17,
|
|
},
|
|
{
|
|
name: "mqtt bad listen",
|
|
config: `
|
|
mqtt {
|
|
listen: "xxxxxxxx"
|
|
}
|
|
`,
|
|
err: errors.New(`could not parse address string "xxxxxxxx"`),
|
|
errorLine: 3,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "mqtt bad host",
|
|
config: `
|
|
mqtt {
|
|
host: 1234
|
|
}
|
|
`,
|
|
err: errors.New(`interface conversion: interface {} is int64, not string`),
|
|
errorLine: 3,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "mqtt bad port",
|
|
config: `
|
|
mqtt {
|
|
port: "abc"
|
|
}
|
|
`,
|
|
err: errors.New(`interface conversion: interface {} is string, not int64`),
|
|
errorLine: 3,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "mqtt bad TLS",
|
|
config: `
|
|
mqtt {
|
|
port: -1
|
|
tls {
|
|
cert_file: "./configs/certs/server.pem"
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`missing 'key_file' in TLS configuration`),
|
|
errorLine: 4,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "connection types wrong type",
|
|
config: `
|
|
authorization {
|
|
users [
|
|
{user: a, password: pwd, allowed_connection_types: 123}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`error parsing allowed connection types: unsupported type int64`),
|
|
errorLine: 4,
|
|
errorPos: 53,
|
|
},
|
|
{
|
|
name: "connection types content wrong type",
|
|
config: `
|
|
authorization {
|
|
users [
|
|
{user: a, password: pwd, allowed_connection_types: [
|
|
123
|
|
WEBSOCKET
|
|
]}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`error parsing allowed connection types: unsupported type in array int64`),
|
|
errorLine: 5,
|
|
errorPos: 32,
|
|
},
|
|
{
|
|
name: "connection types type unknown",
|
|
config: `
|
|
authorization {
|
|
users [
|
|
{user: a, password: pwd, allowed_connection_types: [ "UNKNOWN" ]}
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf("invalid connection types [%q]", "UNKNOWN"),
|
|
errorLine: 4,
|
|
errorPos: 53,
|
|
},
|
|
{
|
|
name: "websocket auth unknown var",
|
|
config: `
|
|
websocket {
|
|
authorization {
|
|
unknown: "field"
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("unknown field %q", "unknown"),
|
|
errorLine: 4,
|
|
errorPos: 25,
|
|
},
|
|
{
|
|
name: "websocket bad tls",
|
|
config: `
|
|
websocket {
|
|
tls {
|
|
cert_file: "configs/certs/server.pem"
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("missing 'key_file' in TLS configuration"),
|
|
errorLine: 3,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "verify_cert_and_check_known_urls not support for websockets",
|
|
config: `
|
|
websocket {
|
|
tls {
|
|
cert_file: "configs/certs/server.pem"
|
|
key_file: "configs/certs/key.pem"
|
|
verify_cert_and_check_known_urls: true
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("verify_cert_and_check_known_urls not supported in this context"),
|
|
errorLine: 6,
|
|
errorPos: 10,
|
|
},
|
|
{
|
|
name: "ambiguous store dir",
|
|
config: `
|
|
store_dir: "foo"
|
|
jetstream {
|
|
store_dir: "bar"
|
|
}
|
|
`,
|
|
err: fmt.Errorf(`Duplicate 'store_dir' configuration`),
|
|
},
|
|
{
|
|
name: "token not supported in cluster",
|
|
config: `
|
|
cluster {
|
|
port: -1
|
|
authorization {
|
|
token: "my_token"
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("Cluster authorization does not support tokens"),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "token not supported in gateway",
|
|
config: `
|
|
gateway {
|
|
port: -1
|
|
name: "A"
|
|
authorization {
|
|
token: "my_token"
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("Gateway authorization does not support tokens"),
|
|
errorLine: 5,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "wrong type for cluster pool size",
|
|
config: `
|
|
cluster {
|
|
port: -1
|
|
pool_size: "abc"
|
|
}
|
|
`,
|
|
err: fmt.Errorf("interface conversion: interface {} is string, not int64"),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "wrong type for cluster accounts",
|
|
config: `
|
|
cluster {
|
|
port: -1
|
|
accounts: 123
|
|
}
|
|
`,
|
|
err: fmt.Errorf("error parsing accounts: unsupported type int64"),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "wrong type for cluster compression",
|
|
config: `
|
|
cluster {
|
|
port: -1
|
|
compression: 123
|
|
}
|
|
`,
|
|
err: fmt.Errorf("field %q should be a boolean or a structure, got int64", "compression"),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "wrong type for cluster compression mode",
|
|
config: `
|
|
cluster {
|
|
port: -1
|
|
compression: {
|
|
mode: 123
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("interface conversion: interface {} is int64, not string"),
|
|
errorLine: 5,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "wrong type for cluster compression rtt thresholds",
|
|
config: `
|
|
cluster {
|
|
port: -1
|
|
compression: {
|
|
mode: "s2_auto"
|
|
rtt_thresholds: 123
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("interface conversion: interface {} is int64, not []interface {}"),
|
|
errorLine: 6,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "invalid durations for cluster compression rtt thresholds",
|
|
config: `
|
|
cluster {
|
|
port: -1
|
|
compression: {
|
|
mode: "s2_auto"
|
|
rtt_thresholds: [abc]
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("time: invalid duration %q", "abc"),
|
|
errorLine: 6,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "invalid durations for cluster ping interval",
|
|
config: `
|
|
cluster {
|
|
port: -1
|
|
ping_interval: -1
|
|
ping_max: 6
|
|
}
|
|
`,
|
|
err: fmt.Errorf(`invalid use of field "ping_interval": ping_interval should be converted to a duration`),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "invalid durations for cluster ping interval",
|
|
config: `
|
|
cluster {
|
|
port: -1
|
|
ping_interval: '2m'
|
|
ping_max: 6
|
|
}
|
|
`,
|
|
warningErr: fmt.Errorf(`Cluster 'ping_interval' will reset to 30s which is the max for routes`),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "wrong type for leafnodes compression",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
compression: 123
|
|
}
|
|
`,
|
|
err: fmt.Errorf("field %q should be a boolean or a structure, got int64", "compression"),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "wrong type for leafnodes compression mode",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
compression: {
|
|
mode: 123
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("interface conversion: interface {} is int64, not string"),
|
|
errorLine: 5,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "wrong type for leafnodes compression rtt thresholds",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
compression: {
|
|
mode: "s2_auto"
|
|
rtt_thresholds: 123
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("interface conversion: interface {} is int64, not []interface {}"),
|
|
errorLine: 6,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "invalid durations for leafnodes compression rtt thresholds",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
compression: {
|
|
mode: "s2_auto"
|
|
rtt_thresholds: [abc]
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("time: invalid duration %q", "abc"),
|
|
errorLine: 6,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "wrong type for remote leafnodes compression",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
remotes [
|
|
{
|
|
url: "nats://127.0.0.1:123"
|
|
compression: 123
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf("field %q should be a boolean or a structure, got int64", "compression"),
|
|
errorLine: 7,
|
|
errorPos: 8,
|
|
},
|
|
{
|
|
name: "wrong type for remote leafnodes compression mode",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
remotes [
|
|
{
|
|
url: "nats://127.0.0.1:123"
|
|
compression: {
|
|
mode: 123
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf("interface conversion: interface {} is int64, not string"),
|
|
errorLine: 8,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "wrong type for remote leafnodes compression rtt thresholds",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
remotes [
|
|
{
|
|
url: "nats://127.0.0.1:123"
|
|
compression: {
|
|
mode: "s2_auto"
|
|
rtt_thresholds: 123
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf("interface conversion: interface {} is int64, not []interface {}"),
|
|
errorLine: 9,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "invalid durations for remote leafnodes compression rtt thresholds",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
remotes [
|
|
{
|
|
url: "nats://127.0.0.1:123"
|
|
compression: {
|
|
mode: "s2_auto"
|
|
rtt_thresholds: [abc]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf("time: invalid duration %q", "abc"),
|
|
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: "duplicate remote leafnode",
|
|
config: `
|
|
leafnodes {
|
|
port: -1
|
|
remotes [
|
|
{ url: "nats://user1:[email protected]:1234" }
|
|
{ url: "nats://user2:[email protected]:1235" }
|
|
{ url: "nats://user1:[email protected]:1234" }
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf("duplicate remote"),
|
|
errorLine: 7,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "show warnings on empty configs without values",
|
|
config: ``,
|
|
warningErr: errors.New(`config has no values or is empty`),
|
|
errorLine: 0,
|
|
errorPos: 0,
|
|
reason: "",
|
|
},
|
|
{
|
|
name: "show warnings on empty configs without values and only comments",
|
|
config: `# Valid file but has no usable values.
|
|
`,
|
|
warningErr: errors.New(`config has no values or is empty`),
|
|
errorLine: 0,
|
|
errorPos: 0,
|
|
reason: "",
|
|
},
|
|
{
|
|
name: "TLS handshake first, wrong type",
|
|
config: `
|
|
port: -1
|
|
tls {
|
|
first: 123
|
|
}
|
|
`,
|
|
err: fmt.Errorf("field %q should be a boolean or a string, got int64", "first"),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "TLS handshake first, wrong value",
|
|
config: `
|
|
port: -1
|
|
tls {
|
|
first: "123"
|
|
}
|
|
`,
|
|
err: fmt.Errorf("field %q's value %q is invalid", "first", "123"),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "TLS multiple certs",
|
|
config: `
|
|
port: -1
|
|
tls {
|
|
certs: [
|
|
{ cert_file: "configs/certs/server.pem", key_file: "configs/certs/key.pem"},
|
|
{ cert_file: "configs/certs/cert.new.pem", key_file: "configs/certs/key.new.pem"},
|
|
]
|
|
}
|
|
`,
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "TLS multiple certs, bad type",
|
|
config: `
|
|
port: -1
|
|
tls {
|
|
certs: [
|
|
{ cert_file: "configs/certs/server.pem", key_file: 123 },
|
|
{ cert_file: "configs/certs/cert.new.pem", key_file: "configs/certs/key.new.pem"},
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf("error parsing certificates config: unsupported type int64"),
|
|
errorLine: 5,
|
|
errorPos: 49,
|
|
},
|
|
{
|
|
name: "TLS multiple certs, missing key_file",
|
|
config: `
|
|
port: -1
|
|
tls {
|
|
certs: [
|
|
{ cert_file: "configs/certs/server.pem" }
|
|
{ cert_file: "configs/certs/cert.new.pem", key_file: "configs/certs/key.new.pem"}
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf("error parsing certificates config: both 'cert_file' and 'cert_key' options are required"),
|
|
errorLine: 5,
|
|
errorPos: 10,
|
|
},
|
|
{
|
|
name: "TLS multiple certs and single cert options at the same time",
|
|
config: `
|
|
port: -1
|
|
tls {
|
|
cert_file: "configs/certs/server.pem"
|
|
key_file: "configs/certs/key.pem"
|
|
certs: [
|
|
{ cert_file: "configs/certs/server.pem", key_file: "configs/certs/key.pem"},
|
|
{ cert_file: "configs/certs/cert.new.pem", key_file: "configs/certs/key.new.pem"},
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf("error parsing tls config, cannot combine 'cert_file' option with 'certs' option"),
|
|
errorLine: 3,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "TLS multiple certs used but not configured, but cert_file configured",
|
|
config: `
|
|
port: -1
|
|
tls {
|
|
cert_file: "configs/certs/server.pem"
|
|
key_file: "configs/certs/key.pem"
|
|
certs: []
|
|
}
|
|
`,
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "TLS multiple certs, missing bad path",
|
|
config: `
|
|
port: -1
|
|
tls {
|
|
certs: [
|
|
{ cert_file: "configs/certs/cert.new.pem", key_file: "configs/certs/key.new.pem"}
|
|
{ cert_file: "configs/certs/server.pem", key_file: "configs/certs/key.new.pom" }
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf("error parsing X509 certificate/key pair 2/2: open configs/certs/key.new.pom: no such file or directory"),
|
|
errorLine: 3,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "Proxies wrong type",
|
|
config: `
|
|
port: -1
|
|
proxies: 123
|
|
`,
|
|
err: errors.New("expected proxies to be a map/struct, got int64"),
|
|
errorLine: 3,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "Proxies unknown field",
|
|
config: `
|
|
port: -1
|
|
proxies: {
|
|
field1: 123
|
|
}
|
|
`,
|
|
err: errors.New("unknown field"),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "Proxies trusted wrong type",
|
|
config: `
|
|
port: -1
|
|
proxies: {
|
|
trusted: 123
|
|
}
|
|
`,
|
|
err: errors.New("expected proxies' trusted field to be an array, got int64"),
|
|
errorLine: 4,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "Proxies trusted key wrong type",
|
|
config: `
|
|
port: -1
|
|
proxies: {
|
|
trusted: [
|
|
"abc",
|
|
"def"
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New("expected proxies' trusted entry to be a map/struct, got string"),
|
|
errorLine: 5,
|
|
errorPos: 8,
|
|
},
|
|
{
|
|
name: "Proxies trusted unknown field",
|
|
config: `
|
|
port: -1
|
|
proxies: {
|
|
trusted: [
|
|
{
|
|
key: "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
|
|
field1: 123
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New("unknown field"),
|
|
errorLine: 7,
|
|
errorPos: 8,
|
|
},
|
|
{
|
|
name: "Proxies trusted key invalid",
|
|
config: `
|
|
port: -1
|
|
proxies: {
|
|
trusted: [
|
|
{key: "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"}
|
|
{key: "bad1"}
|
|
{key: "UD6AYQSOIN2IN5OGC6VQZCR4H3UFMIOXSW6NNS6N53CLJA4PB56CEJJI"}
|
|
{key: "bad2"}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New("invalid proxy key"),
|
|
errorLine: 6,
|
|
errorPos: 8,
|
|
},
|
|
{
|
|
name: "Auth user require proxied wrong type",
|
|
config: `
|
|
port: -1
|
|
authorization {
|
|
user: user
|
|
password: pwd
|
|
proxy_required: 123
|
|
}
|
|
`,
|
|
err: errors.New("interface conversion"),
|
|
errorLine: 6,
|
|
errorPos: 6,
|
|
},
|
|
{
|
|
name: "Auth users require proxied wrong type",
|
|
config: `
|
|
port: -1
|
|
authorization {
|
|
users: [
|
|
{
|
|
user: user
|
|
password: pwd
|
|
proxy_required: 123
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New("interface conversion"),
|
|
errorLine: 8,
|
|
errorPos: 8,
|
|
},
|
|
{
|
|
name: "Auth nkey users require proxied wrong type",
|
|
config: `
|
|
port: -1
|
|
authorization {
|
|
users: [
|
|
{
|
|
nkey: "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
|
|
proxy_required: 123
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New("interface conversion"),
|
|
errorLine: 7,
|
|
errorPos: 8,
|
|
},
|
|
{
|
|
name: "Account users require proxied wrong type",
|
|
config: `
|
|
port: -1
|
|
accounts {
|
|
A: {
|
|
users: [
|
|
{
|
|
user: user
|
|
password: pwd
|
|
proxy_required: 123
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New("interface conversion"),
|
|
errorLine: 9,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "Account nkey users require proxied wrong type",
|
|
config: `
|
|
port: -1
|
|
accounts {
|
|
A: {
|
|
users: [
|
|
{
|
|
nkey: "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
|
|
proxy_required: 123
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New("interface conversion"),
|
|
errorLine: 8,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "Leafnode user require proxied wrong type",
|
|
config: `
|
|
port: -1
|
|
leafnodes {
|
|
port: -1
|
|
authorization {
|
|
user: user
|
|
password: pwd
|
|
proxy_required: 123
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New("interface conversion"),
|
|
errorLine: 8,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "Leafnode neky user require proxied wrong type",
|
|
config: `
|
|
port: -1
|
|
leafnodes {
|
|
port: -1
|
|
authorization {
|
|
nkey: "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
|
|
proxy_required: 123
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New("interface conversion"),
|
|
errorLine: 7,
|
|
errorPos: 7,
|
|
},
|
|
{
|
|
name: "Leafnode users require proxied wrong type",
|
|
config: `
|
|
port: -1
|
|
leafnodes {
|
|
port: -1
|
|
authorization {
|
|
users: [
|
|
{
|
|
user: user
|
|
password: pwd
|
|
proxy_required: 123
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New("interface conversion"),
|
|
errorLine: 10,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "Leafnode nkey users require proxied wrong type",
|
|
config: `
|
|
port: -1
|
|
leafnodes {
|
|
port: -1
|
|
authorization {
|
|
users: [
|
|
{
|
|
nkey: "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
|
|
proxy_required: 123
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New("interface conversion"),
|
|
errorLine: 9,
|
|
errorPos: 9,
|
|
},
|
|
{
|
|
name: "leafnode proxy with unsupported scheme",
|
|
config: `
|
|
leafnodes {
|
|
remotes = [
|
|
{
|
|
url: "ws://127.0.0.1:7422"
|
|
proxy {
|
|
url: "ftp://proxy.example.com:8080"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New("proxy URL scheme must be http or https, got: ftp"),
|
|
errorLine: 6,
|
|
errorPos: 8,
|
|
},
|
|
{
|
|
name: "leafnode proxy with missing host",
|
|
config: `
|
|
leafnodes {
|
|
remotes = [
|
|
{
|
|
url: "ws://127.0.0.1:7422"
|
|
proxy {
|
|
url: "http://"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New("proxy URL must specify a host"),
|
|
errorLine: 6,
|
|
errorPos: 8,
|
|
},
|
|
{
|
|
name: "leafnode proxy with username but no password",
|
|
config: `
|
|
leafnodes {
|
|
remotes = [
|
|
{
|
|
url: "ws://127.0.0.1:7422"
|
|
proxy {
|
|
url: "http://proxy.example.com:8080"
|
|
username: "testuser"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New("proxy username and password must both be specified or both be empty"),
|
|
errorLine: 6,
|
|
errorPos: 8,
|
|
},
|
|
{
|
|
name: "leafnode proxy with password but no username",
|
|
config: `
|
|
leafnodes {
|
|
remotes = [
|
|
{
|
|
url: "ws://127.0.0.1:7422"
|
|
proxy {
|
|
url: "http://proxy.example.com:8080"
|
|
password: "testpass"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New("proxy username and password must both be specified or both be empty"),
|
|
errorLine: 6,
|
|
errorPos: 8,
|
|
},
|
|
{
|
|
name: "leafnode proxy with WSS URL but no TLS config",
|
|
config: `
|
|
leafnodes {
|
|
remotes = [
|
|
{
|
|
url: "wss://127.0.0.1:7422"
|
|
proxy {
|
|
url: "http://proxy.example.com:8080"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New("proxy is configured but remote URL wss://127.0.0.1:7422 requires TLS and no TLS configuration is provided"),
|
|
errorLine: 6,
|
|
errorPos: 8,
|
|
},
|
|
}
|
|
|
|
checkConfig := func(config string) error {
|
|
opts := &Options{
|
|
CheckConfig: true,
|
|
}
|
|
return opts.ProcessConfigFile(config)
|
|
}
|
|
|
|
checkErr := func(t *testing.T, err, expectedErr error) {
|
|
t.Helper()
|
|
switch {
|
|
case err == nil && expectedErr == nil:
|
|
// OK
|
|
case err != nil && expectedErr == nil:
|
|
t.Errorf("Unexpected error after processing config: %s", err)
|
|
case err == nil && expectedErr != nil:
|
|
t.Errorf("Expected %q error after processing invalid config but got nothing", expectedErr)
|
|
}
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
conf := createConfFile(t, []byte(test.config))
|
|
err := checkConfig(conf)
|
|
var expectedErr error
|
|
|
|
// Check for either warnings or errors.
|
|
if test.err != nil {
|
|
expectedErr = test.err
|
|
} else if test.warningErr != nil {
|
|
expectedErr = test.warningErr
|
|
}
|
|
|
|
if err != nil && expectedErr != nil {
|
|
var msg string
|
|
|
|
if test.errorPos > 0 {
|
|
msg = fmt.Sprintf("%s:%d:%d: %s", conf, test.errorLine, test.errorPos, expectedErr.Error())
|
|
if test.reason != "" {
|
|
msg += ": " + test.reason
|
|
}
|
|
} else if test.warningErr != nil {
|
|
msg = expectedErr.Error()
|
|
} else {
|
|
msg = test.reason
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), msg) {
|
|
t.Errorf("Expected:\n%q\ngot:\n%q", msg, err.Error())
|
|
}
|
|
}
|
|
|
|
checkErr(t, err, expectedErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConfigCheckIncludes(t *testing.T) {
|
|
// Check happy path first.
|
|
opts := &Options{
|
|
CheckConfig: true,
|
|
}
|
|
err := opts.ProcessConfigFile("./configs/include_conf_check_a.conf")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error processing include files with configuration check enabled: %v", err)
|
|
}
|
|
|
|
opts = &Options{
|
|
CheckConfig: true,
|
|
}
|
|
err = opts.ProcessConfigFile("./configs/include_bad_conf_check_a.conf")
|
|
if err == nil {
|
|
t.Errorf("Expected error processing include files with configuration check enabled: %v", err)
|
|
}
|
|
expectedErr := `include_bad_conf_check_b.conf:10:19: unknown field "monitoring_port"` + "\n"
|
|
if err != nil && !strings.HasSuffix(err.Error(), expectedErr) {
|
|
t.Errorf("Expected: \n%q, got\n: %q", expectedErr, err.Error())
|
|
}
|
|
}
|
|
|
|
func TestConfigCheckMultipleErrors(t *testing.T) {
|
|
opts := &Options{
|
|
CheckConfig: true,
|
|
}
|
|
err := opts.ProcessConfigFile("./configs/multiple_errors.conf")
|
|
if err == nil {
|
|
t.Errorf("Expected error processing config files with multiple errors check enabled: %v", err)
|
|
}
|
|
cerr, ok := err.(*processConfigErr)
|
|
if !ok {
|
|
t.Fatalf("Expected a configuration process error")
|
|
}
|
|
got := len(cerr.Warnings())
|
|
expected := 1
|
|
if got != expected {
|
|
t.Errorf("Expected a %d warning, got: %d", expected, got)
|
|
}
|
|
got = len(cerr.Errors())
|
|
// Could be 7 or 8 errors depending on internal ordering of the parsing.
|
|
if got != 7 && got != 8 {
|
|
t.Errorf("Expected 7 or 8 errors, got: %d", got)
|
|
}
|
|
|
|
errMsg := err.Error()
|
|
|
|
errs := []string{
|
|
`./configs/multiple_errors.conf:12:1: invalid use of field "write_deadline": write_deadline should be converted to a duration`,
|
|
`./configs/multiple_errors.conf:2:1: Cannot have a user/pass and token`,
|
|
`./configs/multiple_errors.conf:10:1: unknown field "monitoring"`,
|
|
`./configs/multiple_errors.conf:67:3: Cluster authorization does not allow multiple users`,
|
|
`./configs/multiple_errors.conf:21:5: Not a valid public nkey for an account: "OC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL"`,
|
|
`./configs/multiple_errors.conf:26:9: Not a valid public nkey for a user`,
|
|
`./configs/multiple_errors.conf:36:5: Not a valid public nkey for an account: "ODRZ42QBM7SXQDXXTSVWT2WLLFYOQGAFC4TO6WOAXHEKQHIXR4HFYJDS"`,
|
|
`./configs/multiple_errors.conf:41:9: Not a valid public nkey for a user`,
|
|
}
|
|
for _, msg := range errs {
|
|
found := strings.Contains(errMsg, msg)
|
|
if !found {
|
|
t.Fatalf("Expected to find error %q", msg)
|
|
}
|
|
}
|
|
if got == 8 {
|
|
extra := "./configs/multiple_errors.conf:54:5: Can not have a single user/pass and accounts"
|
|
if !strings.Contains(errMsg, extra) {
|
|
t.Fatalf("Expected to find error %q (%s)", extra, errMsg)
|
|
}
|
|
}
|
|
}
|