mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-17 20:29:07 +02:00
656 lines
22 KiB
Go
656 lines
22 KiB
Go
package cmd
|
|
|
|
import (
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/spf13/pflag"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestExampleConfig(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
require.NoError(t, rootCmd.ParseFlags(nil))
|
|
oldConfigPath := configPath
|
|
configPath = filepath.Join("..", "config.example.yaml")
|
|
t.Cleanup(func() {
|
|
configPath = oldConfigPath
|
|
})
|
|
|
|
cfg, err := loadConfig(rootCmd)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "rels://relay.example.com:443", cfg.ExposedAddress, "Example config should load")
|
|
assert.True(t, cfg.EnableSTUN, "Example config should enable STUN")
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyEffectiveDefaults(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
cfg.LetsencryptDomains = nil
|
|
assert.Equal(t, defaultConfig(), cfg, "Relay effective defaults should remain unchanged")
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyEnvironmentBindings(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_LISTEN_ADDRESS", ":7443")
|
|
t.Setenv("NB_EXPOSED_ADDRESS", "rels://relay.example.com:443")
|
|
t.Setenv("NB_METRICS_PORT", "9191")
|
|
t.Setenv("NB_LETSENCRYPT_EMAIL", "admin@example.com")
|
|
t.Setenv("NB_LETSENCRYPT_DATA_DIR", "/var/lib/relay/certs")
|
|
t.Setenv("NB_LETSENCRYPT_DOMAINS", "relay.example.com,relay-alt.example.com")
|
|
t.Setenv("NB_LETSENCRYPT_AWS_ROUTE53", "true")
|
|
t.Setenv("NB_TLS_CERT_FILE", "/etc/relay/tls.crt")
|
|
t.Setenv("NB_TLS_KEY_FILE", "/etc/relay/tls.key")
|
|
t.Setenv("NB_AUTH_SECRET", "relay-secret")
|
|
t.Setenv("NB_LOG_LEVEL", "debug")
|
|
t.Setenv("NB_LOG_FILE", "/var/log/relay.log")
|
|
t.Setenv("NB_HEALTH_LISTEN_ADDRESS", ":9001")
|
|
t.Setenv("NB_TRUSTED_PROXIES", "192.0.2.0/24")
|
|
t.Setenv("NB_ENABLE_STUN", "true")
|
|
t.Setenv("NB_STUN_PORTS", "3479,3480")
|
|
t.Setenv("NB_STUN_LOG_LEVEL", "trace")
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, &Config{
|
|
ListenAddress: ":7443",
|
|
ExposedAddress: "rels://relay.example.com:443",
|
|
MetricsPort: 9191,
|
|
LetsencryptEmail: "admin@example.com",
|
|
LetsencryptDataDir: "/var/lib/relay/certs",
|
|
LetsencryptDomains: []string{"relay.example.com", "relay-alt.example.com"},
|
|
LetsencryptAWSRoute53: true,
|
|
TlsCertFile: "/etc/relay/tls.crt",
|
|
TlsKeyFile: "/etc/relay/tls.key",
|
|
AuthSecret: "relay-secret",
|
|
LogLevel: "debug",
|
|
LogFile: "/var/log/relay.log",
|
|
HealthcheckListenAddress: ":9001",
|
|
TrustedProxies: "192.0.2.0/24",
|
|
EnableSTUN: true,
|
|
STUNPorts: []int{3479, 3480},
|
|
STUNLogLevel: "trace",
|
|
}, cfg, "Every legacy Relay environment binding should remain supported")
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyEnvironmentParsing(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
envName string
|
|
value string
|
|
validate func(*testing.T, *Config)
|
|
}{
|
|
{
|
|
name: "invalid integer becomes zero",
|
|
envName: "NB_METRICS_PORT",
|
|
value: "invalid",
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Zero(t, cfg.MetricsPort, "Invalid legacy integer input should retain its parsed zero value")
|
|
},
|
|
},
|
|
{
|
|
name: "legacy boolean spelling remains false",
|
|
envName: "NB_ENABLE_STUN",
|
|
value: "yes",
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.False(t, cfg.EnableSTUN, "Unsupported legacy boolean spelling should remain disabled")
|
|
},
|
|
},
|
|
{
|
|
name: "invalid boolean remains false",
|
|
envName: "NB_ENABLE_STUN",
|
|
value: "invalid",
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.False(t, cfg.EnableSTUN, "Invalid legacy boolean input should retain its default")
|
|
},
|
|
},
|
|
{
|
|
name: "empty integer list retains default",
|
|
envName: "NB_STUN_PORTS",
|
|
value: "",
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, []int{3478}, cfg.STUNPorts, "Empty legacy integer lists should retain their default")
|
|
},
|
|
},
|
|
{
|
|
name: "trailing comma integer list retains default",
|
|
envName: "NB_STUN_PORTS",
|
|
value: "3479,",
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, []int{3478}, cfg.STUNPorts, "Invalid legacy integer lists should retain their default")
|
|
},
|
|
},
|
|
{
|
|
name: "quoted comma in string list",
|
|
envName: "NB_LETSENCRYPT_DOMAINS",
|
|
value: `"relay,one.example.com",relay-two.example.com`,
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, []string{"relay,one.example.com", "relay-two.example.com"}, cfg.LetsencryptDomains,
|
|
"Legacy string lists should retain CSV quoting")
|
|
},
|
|
},
|
|
{
|
|
name: "malformed quoted string list is ignored",
|
|
envName: "NB_LETSENCRYPT_DOMAINS",
|
|
value: `"relay.example.com`,
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Nil(t, cfg.LetsencryptDomains, "Malformed legacy string lists should retain their default")
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv(test.envName, test.value)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
if !assert.NoError(t, err, "Legacy environment parse errors should not abort Relay startup") {
|
|
return
|
|
}
|
|
test.validate(t, cfg)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLegacyRelayFlagsRemainRegistered(t *testing.T) {
|
|
expected := map[string]string{
|
|
"listen-address": "l",
|
|
"exposed-address": "e",
|
|
"metrics-port": "",
|
|
"letsencrypt-data-dir": "d",
|
|
"letsencrypt-domains": "a",
|
|
"letsencrypt-email": "",
|
|
"letsencrypt-aws-route53": "",
|
|
"tls-cert-file": "c",
|
|
"tls-key-file": "k",
|
|
"auth-secret": "s",
|
|
"log-level": "",
|
|
"log-file": "",
|
|
"health-listen-address": "H",
|
|
"trusted-proxies": "",
|
|
"enable-stun": "",
|
|
"stun-ports": "",
|
|
"stun-log-level": "",
|
|
}
|
|
|
|
for name, shorthand := range expected {
|
|
flag := rootCmd.Flags().Lookup(name)
|
|
require.NotNil(t, flag, "Legacy flag %s should remain registered", name)
|
|
assert.Equal(t, shorthand, flag.Shorthand, "Legacy shorthand for %s should remain unchanged", name)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPrecedence(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
path := filepath.Join(t.TempDir(), "relay.yaml")
|
|
require.NoError(t, os.WriteFile(path, []byte(`
|
|
listenAddress: ":8443"
|
|
exposedAddress: "relay.example.com:443"
|
|
authSecret: file-secret
|
|
metricsPort: 9091
|
|
enableSTUN: true
|
|
stunPorts: [3478, 3479]
|
|
`), 0o600))
|
|
t.Setenv("NB_METRICS_PORT", "9191")
|
|
require.NoError(t, rootCmd.ParseFlags(nil))
|
|
|
|
listenFlag := rootCmd.Flags().Lookup("listen-address")
|
|
oldListen := listenFlag.Value.String()
|
|
oldChanged := listenFlag.Changed
|
|
t.Cleanup(func() {
|
|
require.NoError(t, listenFlag.Value.Set(oldListen))
|
|
listenFlag.Changed = oldChanged
|
|
})
|
|
require.NoError(t, listenFlag.Value.Set(":7443"))
|
|
listenFlag.Changed = true
|
|
|
|
oldConfigPath := configPath
|
|
configPath = path
|
|
t.Cleanup(func() {
|
|
configPath = oldConfigPath
|
|
})
|
|
|
|
cfg, err := loadConfig(rootCmd)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ":7443", cfg.ListenAddress, "Flags should override the configuration file")
|
|
assert.Equal(t, 9191, cfg.MetricsPort, "Environment should override the configuration file")
|
|
assert.Equal(t, "relay.example.com:443", cfg.ExposedAddress, "File values should override defaults")
|
|
assert.True(t, cfg.EnableSTUN, "STUN should be configurable from the file")
|
|
assert.Equal(t, []int{3478, 3479}, cfg.STUNPorts, "STUN ports should load from the file")
|
|
}
|
|
|
|
func loadRelayConfigWithoutFile(t *testing.T) (*Config, error) {
|
|
t.Helper()
|
|
|
|
require.NoError(t, rootCmd.ParseFlags(nil))
|
|
oldConfigPath := configPath
|
|
configPath = ""
|
|
t.Cleanup(func() {
|
|
configPath = oldConfigPath
|
|
})
|
|
return loadConfig(rootCmd)
|
|
}
|
|
|
|
func clearRelayConfigEnvironment(t *testing.T) {
|
|
t.Helper()
|
|
|
|
for _, name := range []string{
|
|
"NB_LISTEN_ADDRESS",
|
|
"NB_EXPOSED_ADDRESS",
|
|
"NB_METRICS_PORT",
|
|
"NB_LETSENCRYPT_EMAIL",
|
|
"NB_LETSENCRYPT_DATA_DIR",
|
|
"NB_LETSENCRYPT_DOMAINS",
|
|
"NB_LETSENCRYPT_AWS_ROUTE53",
|
|
"NB_TLS_CERT_FILE",
|
|
"NB_TLS_KEY_FILE",
|
|
"NB_AUTH_SECRET",
|
|
"NB_LOG_LEVEL",
|
|
"NB_LOG_FILE",
|
|
"NB_HEALTH_LISTEN_ADDRESS",
|
|
"NB_TRUSTED_PROXIES",
|
|
"NB_ENABLE_STUN",
|
|
"NB_STUN_PORTS",
|
|
"NB_STUN_LOG_LEVEL",
|
|
} {
|
|
t.Setenv(name, "")
|
|
require.NoError(t, os.Unsetenv(name))
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyIntegerOverflowParsing(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_METRICS_PORT", "99999999999999999999")
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
if !assert.NoError(t, err, "Legacy pflag stored the clamped integer and only logged the range error, so loading continued") {
|
|
return
|
|
}
|
|
assert.Equal(t, math.MaxInt64, cfg.MetricsPort,
|
|
"Legacy strconv.ParseInt range errors left math.MaxInt64 in the metrics port instead of aborting startup")
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyRoute53BooleanSpelling(t *testing.T) {
|
|
for _, value := range []string{"yes", "y", "on", "YES", "On"} {
|
|
t.Run(value, func(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_LETSENCRYPT_AWS_ROUTE53", value)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
if !assert.NoError(t, err, "Legacy boolean parse errors should not abort Relay startup") {
|
|
return
|
|
}
|
|
assert.False(t, cfg.LetsencryptAWSRoute53,
|
|
"Legacy strconv.ParseBool rejected %q, leaving Route 53 disabled so the Route 53 TLS branch was never taken", value)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyRoute53InvalidBoolean(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
}{
|
|
{name: "invalid word", value: "invalid"},
|
|
{name: "numeric two", value: "2"},
|
|
{name: "mixed case true", value: "tRuE"},
|
|
{name: "trailing space", value: "true "},
|
|
{name: "enabled", value: "enabled"},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_LETSENCRYPT_AWS_ROUTE53", test.value)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
if !assert.NoError(t, err, "Legacy invalid boolean input was logged at Info and ignored, not fatal") {
|
|
return
|
|
}
|
|
assert.False(t, cfg.LetsencryptAWSRoute53,
|
|
"Legacy strconv.ParseBool rejected %q and pflag stored false, so Relay started with Route 53 disabled", test.value)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacySTUNPortListRejection(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
}{
|
|
{name: "non-numeric trailing element", value: "3479,abc"},
|
|
{name: "non-numeric single element", value: "abc"},
|
|
{name: "space after comma", value: "3479, 3480"},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_STUN_PORTS", test.value)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
if !assert.NoError(t, err, "Legacy integer list parse errors were logged at Info and ignored, not fatal") {
|
|
return
|
|
}
|
|
assert.Equal(t, []int{3478}, cfg.STUNPorts,
|
|
"Legacy pflag intSlice ran strconv.Atoi on every element and rejected the whole value %q, retaining the default", test.value)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacySTUNPortDecimalParsing(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
expected []int
|
|
}{
|
|
{name: "leading zero", value: "010", expected: []int{10}},
|
|
{name: "double leading zero", value: "0010", expected: []int{10}},
|
|
{name: "leading zero element", value: "3478,010", expected: []int{3478, 10}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_STUN_PORTS", test.value)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, test.expected, cfg.STUNPorts,
|
|
"Legacy pflag intSlice used strconv.Atoi (base 10 only), so leading zeros were decimal and never octal")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacySTUNPortStrictDecimalSyntax(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
}{
|
|
{name: "hex prefix", value: "0x10"},
|
|
{name: "underscore separator", value: "3_479"},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_STUN_PORTS", test.value)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
if !assert.NoError(t, err, "Legacy integer list parse errors were logged at Info and ignored, not fatal") {
|
|
return
|
|
}
|
|
assert.Equal(t, []int{3478}, cfg.STUNPorts,
|
|
"Legacy strconv.Atoi rejected %q (no hex prefix or underscores), so the default STUN port list was retained", test.value)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyNewlineInDomainList(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_LETSENCRYPT_DOMAINS", "a.example.com\nb.example.com")
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"a.example.com"}, cfg.LetsencryptDomains,
|
|
"Legacy pflag readAsCSV performed a single csv.Reader.Read, so only the first line of a newline separated value was used")
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyEnvAndFlagDomainAppend(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
flagValues []string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "single flag value",
|
|
flagValues: []string{"b.example.com"},
|
|
expected: []string{"a.example.com", "b.example.com"},
|
|
},
|
|
{
|
|
name: "repeated flag values",
|
|
flagValues: []string{"b.example.com", "c.example.com"},
|
|
expected: []string{"a.example.com", "b.example.com", "c.example.com"},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_LETSENCRYPT_DOMAINS", "a.example.com")
|
|
setRelaySliceFlag(t, "letsencrypt-domains", test.flagValues)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, test.expected, cfg.LetsencryptDomains,
|
|
"Legacy applied NB_LETSENCRYPT_DOMAINS via flags.Set before argv parsing, so command line domains were appended to the environment list")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyEnvAndFlagSTUNPortAppend(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_STUN_PORTS", "3479")
|
|
setRelaySliceFlag(t, "stun-ports", []string{"3480"})
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []int{3479, 3480}, cfg.STUNPorts,
|
|
"Legacy applied NB_STUN_PORTS via flags.Set before argv parsing, so --stun-ports appended to the environment list and both UDP ports were bound")
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyDuplicateSTUNPortFromEnvAndFlag(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_EXPOSED_ADDRESS", "rels://relay.example.com:443")
|
|
t.Setenv("NB_AUTH_SECRET", "relay-secret")
|
|
t.Setenv("NB_ENABLE_STUN", "true")
|
|
t.Setenv("NB_STUN_PORTS", "3478")
|
|
setRelaySliceFlag(t, "stun-ports", []string{"3478"})
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []int{3478, 3478}, cfg.STUNPorts,
|
|
"Legacy appended the --stun-ports value to the NB_STUN_PORTS value, producing a duplicated port list")
|
|
|
|
err = cfg.Validate()
|
|
if assert.Error(t, err, "Legacy Validate rejected the duplicated STUN port list and Relay exited") {
|
|
assert.Contains(t, err.Error(), "duplicate STUN port 3478", "Legacy reported the duplicate STUN port")
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyEnvironmentNameMatching(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
env map[string]string
|
|
validate func(*testing.T, *Config)
|
|
}{
|
|
{
|
|
name: "listen address alias does not outrank legacy name",
|
|
env: map[string]string{"NB_LISTENADDRESS": ":1", "NB_LISTEN_ADDRESS": ":2"},
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, ":2", cfg.ListenAddress,
|
|
"Legacy only read NB_LISTEN_ADDRESS; the un-underscored NB_LISTENADDRESS was ignored")
|
|
},
|
|
},
|
|
{
|
|
name: "healthcheck alias is ignored",
|
|
env: map[string]string{"NB_HEALTHCHECKLISTENADDRESS": ":1"},
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, ":9000", cfg.HealthcheckListenAddress,
|
|
"Legacy only read NB_HEALTH_LISTEN_ADDRESS; NB_HEALTHCHECKLISTENADDRESS was ignored and the default retained")
|
|
},
|
|
},
|
|
{
|
|
name: "stun ports alias is ignored",
|
|
env: map[string]string{"NB_STUNPORTS": "1111"},
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, []int{3478}, cfg.STUNPorts,
|
|
"Legacy only read NB_STUN_PORTS; NB_STUNPORTS was ignored and the default retained")
|
|
},
|
|
},
|
|
{
|
|
name: "auth secret alias is ignored",
|
|
env: map[string]string{"NB_AUTHSECRET": "alias-secret"},
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Empty(t, cfg.AuthSecret,
|
|
"Legacy only read NB_AUTH_SECRET; NB_AUTHSECRET was ignored")
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
for name, value := range test.env {
|
|
t.Setenv(name, value)
|
|
}
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
test.validate(t, cfg)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyValuesWithEmptyEnvironmentAliases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
env map[string]string
|
|
validate func(*testing.T, *Config)
|
|
}{
|
|
{
|
|
name: "empty log level alias keeps default",
|
|
env: map[string]string{"NB_LOGLEVEL": ""},
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, "info", cfg.LogLevel,
|
|
"Legacy never read NB_LOGLEVEL, so an empty alias could not blank the log level and break InitLog")
|
|
},
|
|
},
|
|
{
|
|
name: "empty listen address alias keeps legacy name",
|
|
env: map[string]string{"NB_LISTENADDRESS": "", "NB_LISTEN_ADDRESS": ":2"},
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, ":2", cfg.ListenAddress,
|
|
"Legacy never read NB_LISTENADDRESS, so NB_LISTEN_ADDRESS remained effective")
|
|
},
|
|
},
|
|
{
|
|
name: "empty healthcheck alias keeps legacy name",
|
|
env: map[string]string{"NB_HEALTHCHECKLISTENADDRESS": "", "NB_HEALTH_LISTEN_ADDRESS": ":1"},
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, ":1", cfg.HealthcheckListenAddress,
|
|
"Legacy never read NB_HEALTHCHECKLISTENADDRESS, so NB_HEALTH_LISTEN_ADDRESS remained effective")
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
for name, value := range test.env {
|
|
t.Setenv(name, value)
|
|
}
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
test.validate(t, cfg)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyNBConfigIgnored(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
path := filepath.Join(t.TempDir(), "relay.yaml")
|
|
require.NoError(t, os.WriteFile(path, []byte("authSecret: file-secret\n"), 0o600))
|
|
t.Setenv("NB_CONFIG", path)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, cfg.AuthSecret, "Legacy Relay never read NB_CONFIG, so no configuration file was loaded from it")
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyNilDomainsDefault(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
assert.Nil(t, cfg.LetsencryptDomains,
|
|
"Legacy left LetsencryptDomains nil when neither NB_LETSENCRYPT_DOMAINS nor --letsencrypt-domains was given, and Route53TLS.Domains received nil")
|
|
}
|
|
|
|
func setRelaySliceFlag(t *testing.T, name string, values []string) {
|
|
t.Helper()
|
|
|
|
require.NoError(t, rootCmd.ParseFlags(nil))
|
|
flag := rootCmd.Flags().Lookup(name)
|
|
require.NotNil(t, flag, "flag %s should be registered", name)
|
|
sliceValue, ok := flag.Value.(pflag.SliceValue)
|
|
require.True(t, ok, "flag %s should be a slice flag", name)
|
|
|
|
oldValues := sliceValue.GetSlice()
|
|
oldChanged := flag.Changed
|
|
t.Cleanup(func() {
|
|
require.NoError(t, sliceValue.Replace(oldValues))
|
|
flag.Changed = oldChanged
|
|
})
|
|
require.NoError(t, sliceValue.Replace(values))
|
|
flag.Changed = true
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyQuotedDomainListElements(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
expected []string
|
|
}{
|
|
{name: "single quoted element", value: `"relay.example.com"`, expected: []string{"relay.example.com"}},
|
|
{name: "quoted trailing element", value: `a.example.com,"b.example.com"`, expected: []string{"a.example.com", "b.example.com"}},
|
|
{name: "csv escaped double quote", value: `"a""b"`, expected: []string{`a"b`}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_LETSENCRYPT_DATA_DIR", "/var/lib/relay/certs")
|
|
t.Setenv("NB_LETSENCRYPT_DOMAINS", test.value)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, test.expected, cfg.LetsencryptDomains,
|
|
"Legacy pflag readAsCSV used encoding/csv, which stripped surrounding quotes and unescaped doubled quotes in %q, so the Let's Encrypt host whitelist held the clean hostname", test.value)
|
|
assert.True(t, cfg.HasLetsEncrypt(), "Legacy still entered the Let's Encrypt path for a quoted domain list")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigPreservesLegacyTrailingNewlineInDomainList(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
expected []string
|
|
}{
|
|
{name: "single domain", value: "relay.example.com\n", expected: []string{"relay.example.com"}},
|
|
{name: "multiple domains", value: "a.example.com,b.example.com\n", expected: []string{"a.example.com", "b.example.com"}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
clearRelayConfigEnvironment(t)
|
|
t.Setenv("NB_LETSENCRYPT_DATA_DIR", "/var/lib/relay/certs")
|
|
t.Setenv("NB_LETSENCRYPT_DOMAINS", test.value)
|
|
|
|
cfg, err := loadRelayConfigWithoutFile(t)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, test.expected, cfg.LetsencryptDomains,
|
|
"Legacy pflag readAsCSV let csv.Reader.Read consume the trailing newline in %q as the record terminator, so the Let's Encrypt host whitelist held the clean hostname without a newline", test.value)
|
|
assert.True(t, cfg.HasLetsEncrypt(), "Legacy still entered the Let's Encrypt path for a newline terminated domain list")
|
|
})
|
|
}
|
|
}
|