[management,signal,proxy,relay,misc] Limit shared config loader to combined server

This commit is contained in:
jnfrati
2026-09-25 16:46:31 +02:00
parent 6cb41b9909
commit e0ba007e0f
24 changed files with 316 additions and 4331 deletions
-84
View File
@@ -1,84 +0,0 @@
package cmd
import (
"fmt"
"os"
"strconv"
"github.com/spf13/cobra"
"golang.org/x/crypto/acme"
"github.com/netbirdio/netbird/proxy"
"github.com/netbirdio/netbird/trustedproxy"
configloader "github.com/netbirdio/netbird/util/config"
)
type commandConfig struct {
proxy.Config `yaml:",inline"`
LogLevel string `yaml:"logLevel" env:"NB_PROXY_LOG_LEVEL" flag:"log-level"`
PreallocatedBuffers *uint32 `yaml:"preallocatedBuffers" env:"-"`
MaxBatchSize *uint32 `yaml:"maxBatchSize" env:"-"`
}
func defaultConfig() *commandConfig {
return &commandConfig{
Config: proxy.Config{
ListenAddr: ":443",
ManagementAddress: DefaultManagementURL,
CertificateDirectory: "./certs",
CertificateFile: "tls.crt",
CertificateKeyFile: "tls.key",
ACMEChallengeAddress: ":80",
ACMEDirectory: acme.LetsEncryptURL,
ACMEChallengeType: "tls-alpn-01",
CertLockMethod: "auto",
DebugEndpointAddress: "localhost:8444",
HealthAddr: "localhost:8080",
TrustedProxies: trustedproxy.FromPrefixes(nil),
ForwardedProto: "auto",
SupportsCustomPorts: true,
GeoDataDir: "/var/lib/netbird/geolocation",
},
LogLevel: "info",
}
}
func loadConfig(cmd *cobra.Command, configPath string) (*commandConfig, error) {
cfg, err := configloader.Load(configPath, defaultConfig(), configloader.Options{
TagName: "yaml",
AllowMissing: configPath == "",
FlagSet: cmd.Flags(),
Strict: true,
InvalidEnvironment: configloader.InvalidEnvironmentIgnore,
})
if err != nil {
return nil, err
}
if err := applyPerformanceEnvironment(cfg); err != nil {
return nil, err
}
return cfg, nil
}
func applyPerformanceEnvironment(cfg *commandConfig) error {
for _, setting := range []struct {
name string
target **uint32
}{
{name: envPreallocatedBuffers, target: &cfg.PreallocatedBuffers},
{name: envMaxBatchSize, target: &cfg.MaxBatchSize},
} {
raw := os.Getenv(setting.name)
if raw == "" {
continue
}
parsed, err := strconv.ParseUint(raw, 10, 32)
if err != nil {
return fmt.Errorf("invalid %s %q: %w", setting.name, raw, err)
}
value := uint32(parsed)
*setting.target = &value
}
return nil
}
-923
View File
@@ -1,923 +0,0 @@
package cmd
import (
"io"
"os"
"path/filepath"
"testing"
"time"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/trustedproxy"
)
func TestExampleConfig(t *testing.T) {
clearProxyConfigEnvironment(t)
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, filepath.Join("..", "..", "..", "config.example.yaml"))
require.NoError(t, err)
assert.Equal(t, "proxy.example.com", cfg.ProxyURL, "Example config should load")
}
func TestLoadConfigPreservesLegacyDefaults(t *testing.T) {
clearProxyConfigEnvironment(t)
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
require.NoError(t, err)
assert.Equal(t, legacyProxyDefaults(t), cfg, "Proxy defaults should remain unchanged")
}
func TestLoadConfigPreservesLegacyEnvironmentBindings(t *testing.T) {
clearProxyConfigEnvironment(t)
t.Setenv("NB_PROXY_LOG_LEVEL", "debug")
t.Setenv("NB_PROXY_ADDRESS", ":8443")
t.Setenv("NB_PROXY_MANAGEMENT_ADDRESS", "https://management.example.com:443")
t.Setenv("NB_PROXY_DOMAIN", "proxy.example.com")
t.Setenv("NB_PROXY_TOKEN", "proxy-token")
t.Setenv("NB_PROXY_CERTIFICATE_DIRECTORY", "/var/lib/proxy/certs")
t.Setenv("NB_PROXY_CERTIFICATE_FILE", "proxy.crt")
t.Setenv("NB_PROXY_CERTIFICATE_KEY_FILE", "proxy.key")
t.Setenv("NB_PROXY_ACME_CERTIFICATES", "true")
t.Setenv("NB_PROXY_ACME_ADDRESS", ":8080")
t.Setenv("NB_PROXY_ACME_DIRECTORY", "https://acme.example.com/directory")
t.Setenv("NB_PROXY_ACME_EAB_KID", "eab-kid")
t.Setenv("NB_PROXY_ACME_EAB_HMAC_KEY", "eab-hmac")
t.Setenv("NB_PROXY_ACME_CHALLENGE_TYPE", "http-01")
t.Setenv("NB_PROXY_CERT_LOCK_METHOD", "flock")
t.Setenv("NB_PROXY_WILDCARD_CERT_DIR", "/var/lib/proxy/wildcards")
t.Setenv("NB_PROXY_DEBUG_ENDPOINT", "true")
t.Setenv("NB_PROXY_DEBUG_ENDPOINT_ADDRESS", "localhost:9444")
t.Setenv("NB_PROXY_HEALTH_ADDRESS", "localhost:9080")
t.Setenv("NB_PROXY_FORWARDED_PROTO", "https")
t.Setenv("NB_PROXY_TRUSTED_PROXIES", "192.0.2.0/24")
t.Setenv("NB_PROXY_WG_PORT", "51820")
t.Setenv("NB_PROXY_PROXY_PROTOCOL", "true")
t.Setenv("NB_PROXY_PRESHARED_KEY", "pre-shared-key")
t.Setenv("NB_PROXY_SUPPORTS_CUSTOM_PORTS", "false")
t.Setenv("NB_PROXY_REQUIRE_SUBDOMAIN", "true")
t.Setenv("NB_PROXY_PRIVATE", "true")
t.Setenv("NB_PROXY_MAX_DIAL_TIMEOUT", "5s")
t.Setenv("NB_PROXY_MAX_SESSION_IDLE_TIMEOUT", "10m")
t.Setenv("NB_PROXY_MAPPING_BATCH_WATCHDOG", "30s")
t.Setenv("NB_PROXY_GEO_DATA_DIR", "/var/lib/proxy/geo")
t.Setenv("NB_PROXY_CROWDSEC_API_URL", "https://crowdsec.example.com")
t.Setenv("NB_PROXY_CROWDSEC_API_KEY", "crowdsec-key")
t.Setenv("NB_PROXY_PREALLOCATED_BUFFERS", "1024")
t.Setenv("NB_PROXY_MAX_BATCH_SIZE", "64")
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
require.NoError(t, err)
assert.Equal(t, "debug", cfg.LogLevel, "Legacy log-level environment binding should remain supported")
assert.Equal(t, ":8443", cfg.ListenAddr, "Legacy address environment binding should remain supported")
assert.Equal(t, "https://management.example.com:443", cfg.ManagementAddress,
"Legacy management environment binding should remain supported")
assert.Equal(t, "proxy.example.com", cfg.ProxyURL, "Legacy domain environment binding should remain supported")
assert.Equal(t, "proxy-token", cfg.ProxyToken, "Legacy token environment binding should remain supported")
assert.Equal(t, "/var/lib/proxy/certs", cfg.CertificateDirectory, "Legacy certificate directory should remain supported")
assert.Equal(t, "proxy.crt", cfg.CertificateFile, "Legacy certificate file should remain supported")
assert.Equal(t, "proxy.key", cfg.CertificateKeyFile, "Legacy certificate key should remain supported")
assert.True(t, cfg.GenerateACMECertificates, "Legacy ACME toggle should remain supported")
assert.Equal(t, ":8080", cfg.ACMEChallengeAddress, "Legacy ACME address should remain supported")
assert.Equal(t, "https://acme.example.com/directory", cfg.ACMEDirectory, "Legacy ACME directory should remain supported")
assert.Equal(t, "eab-kid", cfg.ACMEEABKID, "Legacy EAB KID should remain supported")
assert.Equal(t, "eab-hmac", cfg.ACMEEABHMACKey, "Legacy EAB HMAC key should remain supported")
assert.Equal(t, "http-01", cfg.ACMEChallengeType, "Legacy ACME challenge type should remain supported")
assert.Equal(t, "flock", string(cfg.CertLockMethod), "Legacy certificate lock method should remain supported")
assert.Equal(t, "/var/lib/proxy/wildcards", cfg.WildcardCertDir, "Legacy wildcard certificate directory should remain supported")
assert.True(t, cfg.DebugEndpointEnabled, "Legacy debug endpoint toggle should remain supported")
assert.Equal(t, "localhost:9444", cfg.DebugEndpointAddress, "Legacy debug endpoint address should remain supported")
assert.Equal(t, "localhost:9080", cfg.HealthAddr, "Legacy health address should remain supported")
assert.Equal(t, "https", cfg.ForwardedProto, "Legacy forwarded-proto environment binding should remain supported")
require.NotNil(t, cfg.TrustedProxies, "Legacy trusted proxy environment binding should remain supported")
assert.False(t, cfg.TrustedProxies.Empty(), "Legacy trusted proxy environment binding should remain populated")
assert.Equal(t, uint16(51820), cfg.WireguardPort, "Legacy tunnel port should remain supported")
assert.True(t, cfg.ProxyProtocol, "Legacy PROXY protocol toggle should remain supported")
assert.Equal(t, "pre-shared-key", cfg.PreSharedKey, "Legacy pre-shared key should remain supported")
assert.False(t, cfg.SupportsCustomPorts, "Legacy custom-port toggle should remain supported")
assert.True(t, cfg.RequireSubdomain, "Legacy subdomain toggle should remain supported")
assert.True(t, cfg.Private, "Legacy private toggle should remain supported")
assert.Equal(t, 5*time.Second, cfg.MaxDialTimeout, "Legacy dial timeout should remain supported")
assert.Equal(t, 10*time.Minute, cfg.MaxSessionIdleTimeout, "Legacy idle timeout should remain supported")
assert.Equal(t, 30*time.Second, cfg.MappingBatchWatchdog, "Legacy mapping watchdog should remain supported")
assert.Equal(t, "/var/lib/proxy/geo", cfg.GeoDataDir, "Legacy geodata directory should remain supported")
assert.Equal(t, "https://crowdsec.example.com", cfg.CrowdSecAPIURL, "Legacy CrowdSec URL should remain supported")
assert.Equal(t, "crowdsec-key", cfg.CrowdSecAPIKey, "Legacy CrowdSec key should remain supported")
require.NotNil(t, cfg.PreallocatedBuffers, "Legacy preallocated buffer environment binding should remain supported")
assert.Equal(t, uint32(1024), *cfg.PreallocatedBuffers, "Legacy preallocated buffer value should remain supported")
require.NotNil(t, cfg.MaxBatchSize, "Legacy maximum batch environment binding should remain supported")
assert.Equal(t, uint32(64), *cfg.MaxBatchSize, "Legacy maximum batch value should remain supported")
}
func TestLoadConfigPreservesLegacyInvalidEnvironmentBehavior(t *testing.T) {
tests := []struct {
name string
envName string
value string
wantErr bool
validate func(*testing.T, *commandConfig)
}{
{
name: "invalid boolean uses default",
envName: "NB_PROXY_SUPPORTS_CUSTOM_PORTS",
value: "invalid",
validate: func(t *testing.T, cfg *commandConfig) {
assert.True(t, cfg.SupportsCustomPorts, "Invalid legacy booleans should retain their default")
},
},
{
name: "invalid uint16 uses default",
envName: "NB_PROXY_WG_PORT",
value: "invalid",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Zero(t, cfg.WireguardPort, "Invalid legacy uint16 values should retain their default")
},
},
{
name: "invalid duration uses default",
envName: "NB_PROXY_MAX_DIAL_TIMEOUT",
value: "invalid",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Zero(t, cfg.MaxDialTimeout, "Invalid legacy durations should retain their default")
},
},
{
name: "invalid watchdog uses default",
envName: "NB_PROXY_MAPPING_BATCH_WATCHDOG",
value: "invalid",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Zero(t, cfg.MappingBatchWatchdog, "Invalid legacy watchdog values should retain their default")
},
},
{
name: "empty performance value remains absent",
envName: "NB_PROXY_PREALLOCATED_BUFFERS",
value: "",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Nil(t, cfg.PreallocatedBuffers, "Empty legacy performance values should remain absent")
},
},
{
name: "invalid performance value remains fatal",
envName: "NB_PROXY_PREALLOCATED_BUFFERS",
value: "invalid",
wantErr: true,
},
{
name: "empty maximum batch remains absent",
envName: "NB_PROXY_MAX_BATCH_SIZE",
value: "",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Nil(t, cfg.MaxBatchSize, "Empty legacy maximum batch values should remain absent")
},
},
{
name: "invalid maximum batch remains fatal",
envName: "NB_PROXY_MAX_BATCH_SIZE",
value: "invalid",
wantErr: true,
},
{
name: "empty string clears default",
envName: "NB_PROXY_MANAGEMENT_ADDRESS",
value: "",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Empty(t, cfg.ManagementAddress, "Empty legacy strings should continue to clear their default")
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clearProxyConfigEnvironment(t)
t.Setenv(test.envName, test.value)
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
if test.wantErr {
assert.Error(t, err, "Legacy-fatal environment input should remain fatal")
return
}
if !assert.NoError(t, err, "Legacy fallback parsing should not abort Proxy startup") {
return
}
test.validate(t, cfg)
})
}
}
func TestLegacyProxyFlagsRemainRegistered(t *testing.T) {
for _, name := range []string{
"mgmt",
"addr",
"domain",
"cert-dir",
"acme-certs",
"acme-addr",
"acme-dir",
"acme-eab-kid",
"acme-eab-hmac-key",
"acme-challenge-type",
"debug-endpoint",
"debug-endpoint-addr",
"health-addr",
"forwarded-proto",
"trusted-proxies",
"cert-file",
"cert-key-file",
"cert-lock-method",
"wildcard-cert-dir",
"wg-port",
"proxy-protocol",
"preshared-key",
"supports-custom-ports",
"require-subdomain",
"private",
"max-dial-timeout",
"max-session-idle-timeout",
"geo-data-dir",
"crowdsec-api-url",
"crowdsec-api-key",
} {
assert.NotNil(t, rootCmd.Flags().Lookup(name), "Legacy flag %s should remain registered", name)
}
for _, name := range []string{"log-level", "debug"} {
assert.NotNil(t, rootCmd.PersistentFlags().Lookup(name), "Legacy persistent flag %s should remain registered", name)
}
}
func TestLoadConfigIgnoresEmptyBooleanEnvironment(t *testing.T) {
clearProxyConfigEnvironment(t)
t.Setenv("NB_PROXY_SUPPORTS_CUSTOM_PORTS", "")
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
require.NoError(t, err)
assert.True(t, cfg.SupportsCustomPorts,
"An empty boolean environment variable should retain the previous default")
}
func TestDeprecatedDebugEnvironmentOverridesLogLevelFlag(t *testing.T) {
clearProxyConfigEnvironment(t)
t.Setenv("NB_PROXY_TOKEN", "test-token")
t.Setenv("NB_PROXY_DOMAIN", "invalid domain")
t.Setenv("NB_PROXY_DEBUG_LOGS", "true")
cmd := newLegacyProxyCommand(t)
logLevelFlag := cmd.Flags().Lookup("log-level")
require.NotNil(t, logLevelFlag, "Log level flag should be registered")
oldDebugLogs := debugLogs
oldConfigPath := configPath
t.Cleanup(func() {
debugLogs = oldDebugLogs
configPath = oldConfigPath
})
require.NoError(t, logLevelFlag.Value.Set("trace"))
logLevelFlag.Changed = true
debugLogs = envBoolOrDefault("NB_PROXY_DEBUG_LOGS", false)
configPath = ""
require.True(t, debugLogs, "Deprecated debug environment variable should be enabled")
var runErr error
output := captureStderr(t, func() {
runErr = runServer(cmd, nil)
})
require.Error(t, runErr)
assert.Contains(t, output, "configured log level: debug",
"The deprecated debug environment variable should retain its previous precedence")
}
func TestLoadConfigPrecedence(t *testing.T) {
clearProxyConfigEnvironment(t)
configPath := filepath.Join(t.TempDir(), "proxy.yaml")
require.NoError(t, os.WriteFile(configPath, []byte(`
domain: file.example.com
trustedProxies: 192.0.2.0/24
maxDialTimeout: 5s
`), 0o600))
t.Setenv("NB_PROXY_TOKEN", "environment-token")
cmd := newLegacyProxyCommand(t)
domainFlag := cmd.Flags().Lookup("domain")
require.NoError(t, domainFlag.Value.Set("flag.example.com"))
domainFlag.Changed = true
cfg, err := loadConfig(cmd, configPath)
require.NoError(t, err)
assert.Equal(t, "flag.example.com", cfg.ProxyURL, "Flags should override the configuration file")
assert.Equal(t, "environment-token", cfg.ProxyToken, "Environment should populate secrets")
assert.Equal(t, 5*time.Second, cfg.MaxDialTimeout, "Durations should be decoded from the file")
require.NotNil(t, cfg.TrustedProxies, "Trusted proxies should be decoded")
assert.False(t, cfg.TrustedProxies.Empty(), "Configured trusted proxies should not be empty")
}
// proxy-01: legacy read only the explicit NB_PROXY_* names; NB_<YAMLKEY> style
// variables were never consulted.
func TestLoadConfigIgnoresAutomaticEnvironmentAliases(t *testing.T) {
tests := []struct {
name string
alias string
value string
legacy map[string]string
validate func(*testing.T, *commandConfig)
}{
{
name: "listen address alias is ignored",
alias: "NB_LISTENADDRESS",
value: ":9999",
legacy: map[string]string{"NB_PROXY_ADDRESS": ":8443"},
validate: func(t *testing.T, cfg *commandConfig) {
assert.Equal(t, ":8443", cfg.ListenAddr,
"Legacy only read NB_PROXY_ADDRESS; NB_LISTENADDRESS must not override it")
},
},
{
name: "log level alias is ignored",
alias: "NB_LOGLEVEL",
value: "trace",
legacy: map[string]string{"NB_PROXY_LOG_LEVEL": "warn"},
validate: func(t *testing.T, cfg *commandConfig) {
assert.Equal(t, "warn", cfg.LogLevel,
"Legacy only read NB_PROXY_LOG_LEVEL; NB_LOGLEVEL must not override it")
},
},
{
name: "token alias is ignored",
alias: "NB_PROXYTOKEN",
value: "alias-token",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Empty(t, cfg.ProxyToken,
"Legacy only read NB_PROXY_TOKEN; NB_PROXYTOKEN must not satisfy the token requirement")
},
},
{
name: "private alias is ignored",
alias: "NB_PRIVATE",
value: "true",
validate: func(t *testing.T, cfg *commandConfig) {
assert.False(t, cfg.Private,
"Legacy only read NB_PROXY_PRIVATE; NB_PRIVATE must not enable private mode")
},
},
{
name: "id alias is ignored",
alias: "NB_ID",
value: "alias-id",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Empty(t, cfg.ID, "Legacy never read NB_ID; the proxy ID must stay empty")
},
},
{
name: "management address alias is ignored",
alias: "NB_MANAGEMENTADDRESS",
value: "https://alias.example.com:443",
legacy: map[string]string{"NB_PROXY_MANAGEMENT_ADDRESS": "https://management.example.com:443"},
validate: func(t *testing.T, cfg *commandConfig) {
assert.Equal(t, "https://management.example.com:443", cfg.ManagementAddress,
"Legacy only read NB_PROXY_MANAGEMENT_ADDRESS; NB_MANAGEMENTADDRESS must not override it")
},
},
{
name: "wireguard port alias is ignored",
alias: "NB_WIREGUARDPORT",
value: "51820",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Zero(t, cfg.WireguardPort,
"Legacy only read NB_PROXY_WG_PORT; NB_WIREGUARDPORT must not set the tunnel port")
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clearProxyConfigEnvironment(t)
for name, value := range test.legacy {
t.Setenv(name, value)
}
t.Setenv(test.alias, test.value)
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
require.NoError(t, err)
test.validate(t, cfg)
})
}
}
// proxy-02: NB_DOMAIN is a common self-hosted variable; legacy ignored it and
// only honoured NB_PROXY_DOMAIN / --domain.
func TestLoadConfigIgnoresSharedDomainEnvironment(t *testing.T) {
tests := []struct {
name string
domain string
}{
{name: "valid shared domain is ignored", domain: "netbird.example.org"},
{name: "invalid shared domain is ignored", domain: "bad domain"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clearProxyConfigEnvironment(t)
t.Setenv("NB_PROXY_DOMAIN", "proxy.example.com")
t.Setenv("NB_DOMAIN", test.domain)
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
require.NoError(t, err)
assert.Equal(t, "proxy.example.com", cfg.ProxyURL,
"Legacy only read NB_PROXY_DOMAIN; the shared NB_DOMAIN variable must not override it")
})
}
}
// proxy-03: legacy had no NB_PROXY_ID binding; the ID was always generated at
// Server.Start time.
func TestLoadConfigIgnoresProxyIDEnvironment(t *testing.T) {
clearProxyConfigEnvironment(t)
t.Setenv("NB_PROXY_ID", "my-proxy")
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
require.NoError(t, err)
assert.Empty(t, cfg.ID,
"Legacy never read NB_PROXY_ID; the proxy ID must stay empty so Server.Start generates it")
}
// proxy-04: legacy strconv.ParseBool rejected yes/no/on/off/y/n, logging a
// warning and keeping the flag default.
func TestLoadConfigPreservesLegacyBooleanVocabulary(t *testing.T) {
tests := []struct {
name string
envName string
value string
validate func(*testing.T, *commandConfig)
}{
{
name: "no keeps custom ports enabled",
envName: "NB_PROXY_SUPPORTS_CUSTOM_PORTS",
value: "no",
validate: func(t *testing.T, cfg *commandConfig) {
assert.True(t, cfg.SupportsCustomPorts, "Legacy ParseBool rejected \"no\" and kept the default true")
},
},
{
name: "off keeps custom ports enabled",
envName: "NB_PROXY_SUPPORTS_CUSTOM_PORTS",
value: "off",
validate: func(t *testing.T, cfg *commandConfig) {
assert.True(t, cfg.SupportsCustomPorts, "Legacy ParseBool rejected \"off\" and kept the default true")
},
},
{
name: "n keeps custom ports enabled",
envName: "NB_PROXY_SUPPORTS_CUSTOM_PORTS",
value: "n",
validate: func(t *testing.T, cfg *commandConfig) {
assert.True(t, cfg.SupportsCustomPorts, "Legacy ParseBool rejected \"n\" and kept the default true")
},
},
{
name: "uppercase OFF keeps custom ports enabled",
envName: "NB_PROXY_SUPPORTS_CUSTOM_PORTS",
value: "OFF",
validate: func(t *testing.T, cfg *commandConfig) {
assert.True(t, cfg.SupportsCustomPorts, "Legacy ParseBool rejected \"OFF\" and kept the default true")
},
},
{
name: "yes keeps ACME disabled",
envName: "NB_PROXY_ACME_CERTIFICATES",
value: "yes",
validate: func(t *testing.T, cfg *commandConfig) {
assert.False(t, cfg.GenerateACMECertificates, "Legacy ParseBool rejected \"yes\" and kept the default false")
},
},
{
name: "uppercase ON keeps ACME disabled",
envName: "NB_PROXY_ACME_CERTIFICATES",
value: "ON",
validate: func(t *testing.T, cfg *commandConfig) {
assert.False(t, cfg.GenerateACMECertificates, "Legacy ParseBool rejected \"ON\" and kept the default false")
},
},
{
name: "y keeps ACME disabled",
envName: "NB_PROXY_ACME_CERTIFICATES",
value: "y",
validate: func(t *testing.T, cfg *commandConfig) {
assert.False(t, cfg.GenerateACMECertificates, "Legacy ParseBool rejected \"y\" and kept the default false")
},
},
{
name: "on keeps PROXY protocol disabled",
envName: "NB_PROXY_PROXY_PROTOCOL",
value: "on",
validate: func(t *testing.T, cfg *commandConfig) {
assert.False(t, cfg.ProxyProtocol, "Legacy ParseBool rejected \"on\" and kept the default false")
},
},
{
name: "Y keeps subdomain requirement disabled",
envName: "NB_PROXY_REQUIRE_SUBDOMAIN",
value: "Y",
validate: func(t *testing.T, cfg *commandConfig) {
assert.False(t, cfg.RequireSubdomain, "Legacy ParseBool rejected \"Y\" and kept the default false")
},
},
{
name: "on keeps private mode disabled",
envName: "NB_PROXY_PRIVATE",
value: "on",
validate: func(t *testing.T, cfg *commandConfig) {
assert.False(t, cfg.Private, "Legacy ParseBool rejected \"on\" and kept the default false")
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clearProxyConfigEnvironment(t)
t.Setenv(test.envName, test.value)
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
if !assert.NoError(t, err, "Legacy boolean fallback parsing should not abort Proxy startup") {
return
}
test.validate(t, cfg)
})
}
}
// proxy-05: legacy parsed NB_PROXY_WG_PORT with strconv.ParseUint(v, 10, 16).
func TestLoadConfigPreservesLegacyWireguardPortParsing(t *testing.T) {
tests := []struct {
name string
value string
want uint16
}{
{name: "leading zero is decimal", value: "010", want: 10},
{name: "hex prefix falls back to default", value: "0x1F", want: 0},
{name: "octal prefix falls back to default", value: "0o17", want: 0},
{name: "underscore separator falls back to default", value: "1_000", want: 0},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clearProxyConfigEnvironment(t)
t.Setenv("NB_PROXY_WG_PORT", test.value)
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
if !assert.NoError(t, err, "Legacy uint16 fallback parsing should not abort Proxy startup") {
return
}
assert.Equal(t, test.want, cfg.WireguardPort,
"Legacy parsed NB_PROXY_WG_PORT=%q in base 10 (parse errors fell back to 0)", test.value)
})
}
}
// proxy-07: legacy time.ParseDuration("") failed, logging a warning and
// keeping the default of 0; startup continued.
func TestLoadConfigPreservesLegacyEmptyDurationEnvironment(t *testing.T) {
tests := []struct {
name string
envName string
validate func(*testing.T, *commandConfig)
}{
{
name: "empty dial timeout uses default",
envName: "NB_PROXY_MAX_DIAL_TIMEOUT",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Zero(t, cfg.MaxDialTimeout, "Legacy treated an empty dial timeout as the default 0")
},
},
{
name: "empty idle timeout uses default",
envName: "NB_PROXY_MAX_SESSION_IDLE_TIMEOUT",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Zero(t, cfg.MaxSessionIdleTimeout, "Legacy treated an empty idle timeout as the default 0")
},
},
{
name: "empty watchdog uses default",
envName: "NB_PROXY_MAPPING_BATCH_WATCHDOG",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Zero(t, cfg.MappingBatchWatchdog, "Legacy treated an empty watchdog as the default 0")
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clearProxyConfigEnvironment(t)
t.Setenv(test.envName, "")
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
if !assert.NoError(t, err, "Legacy treated an empty duration environment value as a warning, not a fatal error") {
return
}
test.validate(t, cfg)
})
}
}
// proxy-08: legacy time.ParseDuration errors (missing unit / invalid) logged a
// warning and kept the default of 0; startup continued.
func TestLoadConfigPreservesLegacyDurationFallbacks(t *testing.T) {
tests := []struct {
name string
envName string
value string
validate func(*testing.T, *commandConfig)
}{
{
name: "unit-less dial timeout uses default",
envName: "NB_PROXY_MAX_DIAL_TIMEOUT",
value: "5",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Zero(t, cfg.MaxDialTimeout, "Legacy treated a unit-less dial timeout as the default 0")
},
},
{
name: "unit-less idle timeout uses default",
envName: "NB_PROXY_MAX_SESSION_IDLE_TIMEOUT",
value: "5",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Zero(t, cfg.MaxSessionIdleTimeout, "Legacy treated a unit-less idle timeout as the default 0")
},
},
{
name: "invalid idle timeout uses default",
envName: "NB_PROXY_MAX_SESSION_IDLE_TIMEOUT",
value: "invalid",
validate: func(t *testing.T, cfg *commandConfig) {
assert.Zero(t, cfg.MaxSessionIdleTimeout, "Legacy treated an invalid idle timeout as the default 0")
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clearProxyConfigEnvironment(t)
t.Setenv(test.envName, test.value)
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
if !assert.NoError(t, err, "Legacy treated an unparseable duration environment value as a warning, not a fatal error") {
return
}
test.validate(t, cfg)
})
}
}
// proxy-09: legacy parsed the performance variables with
// strconv.ParseUint(raw, 10, 32) and aborted startup on any parse error.
func TestLoadConfigPreservesLegacyPerformanceParsing(t *testing.T) {
tests := []struct {
name string
envName string
value string
want uint32
wantErr bool
}{
{name: "preallocated buffers leading zero is decimal", envName: "NB_PROXY_PREALLOCATED_BUFFERS", value: "010", want: 10},
{name: "preallocated buffers two leading zeros is decimal", envName: "NB_PROXY_PREALLOCATED_BUFFERS", value: "0100", want: 100},
{name: "preallocated buffers hex remains fatal", envName: "NB_PROXY_PREALLOCATED_BUFFERS", value: "0x10", wantErr: true},
{name: "preallocated buffers long hex remains fatal", envName: "NB_PROXY_PREALLOCATED_BUFFERS", value: "0x100", wantErr: true},
{name: "preallocated buffers underscore remains fatal", envName: "NB_PROXY_PREALLOCATED_BUFFERS", value: "1_000", wantErr: true},
{name: "maximum batch leading zero is decimal", envName: "NB_PROXY_MAX_BATCH_SIZE", value: "010", want: 10},
{name: "maximum batch two leading zeros is decimal", envName: "NB_PROXY_MAX_BATCH_SIZE", value: "0100", want: 100},
{name: "maximum batch hex remains fatal", envName: "NB_PROXY_MAX_BATCH_SIZE", value: "0x10", wantErr: true},
{name: "maximum batch long hex remains fatal", envName: "NB_PROXY_MAX_BATCH_SIZE", value: "0x100", wantErr: true},
{name: "maximum batch underscore remains fatal", envName: "NB_PROXY_MAX_BATCH_SIZE", value: "1_000", wantErr: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clearProxyConfigEnvironment(t)
t.Setenv(test.envName, test.value)
cmd := newLegacyProxyCommand(t)
cfg, err := loadConfig(cmd, "")
if test.wantErr {
assert.Error(t, err,
"Legacy strconv.ParseUint(%q, 10, 32) failed and aborted Proxy startup", test.value)
return
}
if !assert.NoError(t, err, "Legacy base-10 performance values should not abort Proxy startup") {
return
}
got := cfg.PreallocatedBuffers
if test.envName == "NB_PROXY_MAX_BATCH_SIZE" {
got = cfg.MaxBatchSize
}
require.NotNil(t, got, "Legacy performance values should be present when set")
assert.Equal(t, test.want, *got,
"Legacy parsed %s=%q in base 10", test.envName, test.value)
})
}
}
// proxy-10 / proxy-12: legacy read the token only from NB_PROXY_TOKEN, checked
// it before anything else, and reported a fixed message when it was missing.
func TestRunServerPreservesLegacyTokenRequirement(t *testing.T) {
const legacyTokenError = "proxy token is required: set NB_PROXY_TOKEN environment variable"
tests := []struct {
name string
env map[string]string
fileConfig string
}{
{
name: "missing token without other sources",
},
{
name: "file token does not satisfy the requirement",
fileConfig: "proxyToken: file-token\ndomain: \"invalid domain\"\n",
},
{
name: "token alias does not satisfy the requirement",
env: map[string]string{"NB_PROXYTOKEN": "alias-token", "NB_PROXY_DOMAIN": "invalid domain"},
},
{
name: "empty token with file token remains fatal",
env: map[string]string{"NB_PROXY_TOKEN": ""},
fileConfig: "proxyToken: file-token\ndomain: \"invalid domain\"\n",
},
{
name: "missing token is reported before performance parsing",
env: map[string]string{"NB_PROXY_PREALLOCATED_BUFFERS": "invalid"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clearProxyConfigEnvironment(t)
for name, value := range test.env {
t.Setenv(name, value)
}
cmd := newLegacyProxyCommand(t)
oldDebugLogs := debugLogs
oldConfigPath := configPath
t.Cleanup(func() {
debugLogs = oldDebugLogs
configPath = oldConfigPath
})
debugLogs = false
configPath = ""
if test.fileConfig != "" {
configPath = filepath.Join(t.TempDir(), "proxy.yaml")
require.NoError(t, os.WriteFile(configPath, []byte(test.fileConfig), 0o600))
}
var runErr error
captureStderr(t, func() {
runErr = runServer(cmd, nil)
})
require.Error(t, runErr)
assert.EqualError(t, runErr, legacyTokenError,
"Legacy accepted the token only from NB_PROXY_TOKEN and reported it before any other startup step")
})
}
}
func newLegacyProxyCommand(t *testing.T) *cobra.Command {
t.Helper()
defaults := legacyProxyDefaults(t)
cmd := &cobra.Command{Use: "proxy-test"}
flags := cmd.Flags()
flags.String("log-level", defaults.LogLevel, "")
flags.Bool("debug", false, "")
flags.String("mgmt", defaults.ManagementAddress, "")
flags.String("addr", defaults.ListenAddr, "")
flags.String("domain", defaults.ProxyURL, "")
flags.String("cert-dir", defaults.CertificateDirectory, "")
flags.Bool("acme-certs", defaults.GenerateACMECertificates, "")
flags.String("acme-addr", defaults.ACMEChallengeAddress, "")
flags.String("acme-dir", defaults.ACMEDirectory, "")
flags.String("acme-eab-kid", defaults.ACMEEABKID, "")
flags.String("acme-eab-hmac-key", defaults.ACMEEABHMACKey, "")
flags.String("acme-challenge-type", defaults.ACMEChallengeType, "")
flags.Bool("debug-endpoint", defaults.DebugEndpointEnabled, "")
flags.String("debug-endpoint-addr", defaults.DebugEndpointAddress, "")
flags.String("health-addr", defaults.HealthAddr, "")
flags.String("forwarded-proto", defaults.ForwardedProto, "")
flags.String("trusted-proxies", "", "")
flags.String("cert-file", defaults.CertificateFile, "")
flags.String("cert-key-file", defaults.CertificateKeyFile, "")
flags.String("cert-lock-method", string(defaults.CertLockMethod), "")
flags.String("wildcard-cert-dir", defaults.WildcardCertDir, "")
flags.Uint16("wg-port", defaults.WireguardPort, "")
flags.Bool("proxy-protocol", defaults.ProxyProtocol, "")
flags.String("preshared-key", defaults.PreSharedKey, "")
flags.Bool("supports-custom-ports", defaults.SupportsCustomPorts, "")
flags.Bool("require-subdomain", defaults.RequireSubdomain, "")
flags.Bool("private", defaults.Private, "")
flags.Duration("max-dial-timeout", defaults.MaxDialTimeout, "")
flags.Duration("max-session-idle-timeout", defaults.MaxSessionIdleTimeout, "")
flags.String("geo-data-dir", defaults.GeoDataDir, "")
flags.String("crowdsec-api-url", defaults.CrowdSecAPIURL, "")
flags.String("crowdsec-api-key", defaults.CrowdSecAPIKey, "")
return cmd
}
func legacyProxyDefaults(t *testing.T) *commandConfig {
t.Helper()
trustedProxies, err := trustedproxy.Parse("")
require.NoError(t, err)
cfg := &commandConfig{LogLevel: "info"}
cfg.ListenAddr = ":443"
cfg.ManagementAddress = DefaultManagementURL
cfg.CertificateDirectory = "./certs"
cfg.CertificateFile = "tls.crt"
cfg.CertificateKeyFile = "tls.key"
cfg.ACMEChallengeAddress = ":80"
cfg.ACMEDirectory = "https://acme-v02.api.letsencrypt.org/directory"
cfg.ACMEChallengeType = "tls-alpn-01"
cfg.CertLockMethod = "auto"
cfg.DebugEndpointAddress = "localhost:8444"
cfg.HealthAddr = "localhost:8080"
cfg.ForwardedProto = "auto"
cfg.TrustedProxies = trustedProxies
cfg.SupportsCustomPorts = true
cfg.GeoDataDir = "/var/lib/netbird/geolocation"
return cfg
}
func clearProxyConfigEnvironment(t *testing.T) {
t.Helper()
for _, name := range []string{
"NB_PROXY_LOG_LEVEL",
"NB_PROXY_DEBUG_LOGS",
"NB_PROXY_MANAGEMENT_ADDRESS",
"NB_PROXY_ADDRESS",
"NB_PROXY_DOMAIN",
"NB_PROXY_TOKEN",
"NB_PROXY_CERTIFICATE_DIRECTORY",
"NB_PROXY_CERTIFICATE_FILE",
"NB_PROXY_CERTIFICATE_KEY_FILE",
"NB_PROXY_ACME_CERTIFICATES",
"NB_PROXY_ACME_ADDRESS",
"NB_PROXY_ACME_DIRECTORY",
"NB_PROXY_ACME_EAB_KID",
"NB_PROXY_ACME_EAB_HMAC_KEY",
"NB_PROXY_ACME_CHALLENGE_TYPE",
"NB_PROXY_CERT_LOCK_METHOD",
"NB_PROXY_WILDCARD_CERT_DIR",
"NB_PROXY_DEBUG_ENDPOINT",
"NB_PROXY_DEBUG_ENDPOINT_ADDRESS",
"NB_PROXY_HEALTH_ADDRESS",
"NB_PROXY_FORWARDED_PROTO",
"NB_PROXY_TRUSTED_PROXIES",
"NB_PROXY_WG_PORT",
"NB_PROXY_PROXY_PROTOCOL",
"NB_PROXY_PRESHARED_KEY",
"NB_PROXY_SUPPORTS_CUSTOM_PORTS",
"NB_PROXY_REQUIRE_SUBDOMAIN",
"NB_PROXY_PRIVATE",
"NB_PROXY_MAX_DIAL_TIMEOUT",
"NB_PROXY_MAX_SESSION_IDLE_TIMEOUT",
"NB_PROXY_MAPPING_BATCH_WATCHDOG",
"NB_PROXY_GEO_DATA_DIR",
"NB_PROXY_CROWDSEC_API_URL",
"NB_PROXY_CROWDSEC_API_KEY",
"NB_PROXY_PREALLOCATED_BUFFERS",
"NB_PROXY_MAX_BATCH_SIZE",
} {
t.Setenv(name, "")
require.NoError(t, os.Unsetenv(name))
}
}
func captureStderr(t *testing.T, fn func()) string {
t.Helper()
reader, writer, err := os.Pipe()
require.NoError(t, err)
oldStderr := os.Stderr
os.Stderr = writer
defer func() {
os.Stderr = oldStderr
}()
fn()
os.Stderr = oldStderr
require.NoError(t, writer.Close())
output, err := io.ReadAll(reader)
require.NoError(t, err)
require.NoError(t, reader.Close())
return string(output)
}
+99 -48
View File
@@ -15,7 +15,10 @@ import (
"github.com/netbirdio/netbird/shared/management/domain"
"github.com/netbirdio/netbird/client/embed"
"github.com/netbirdio/netbird/proxy"
nbacme "github.com/netbirdio/netbird/proxy/internal/acme"
"github.com/netbirdio/netbird/trustedproxy"
"github.com/netbirdio/netbird/util"
)
@@ -23,7 +26,10 @@ const (
// envPreallocatedBuffers caps the per-tunnel buffer pool. Zero (unset)
// keeps the upstream uncapped default.
envPreallocatedBuffers = "NB_PROXY_PREALLOCATED_BUFFERS"
envMaxBatchSize = "NB_PROXY_MAX_BATCH_SIZE"
// envMaxBatchSize overrides the per-tunnel batch size, which controls
// how many buffers each receive/TUN worker eagerly allocates. Zero
// (unset) keeps the platform default.
envMaxBatchSize = "NB_PROXY_MAX_BATCH_SIZE"
)
const DefaultManagementURL = "https://api.netbird.io:443"
@@ -73,7 +79,6 @@ var (
geoDataDir string
crowdsecAPIURL string
crowdsecAPIKey string
configPath string
)
var rootCmd = &cobra.Command{
@@ -86,7 +91,6 @@ var rootCmd = &cobra.Command{
}
func init() {
rootCmd.Flags().StringVar(&configPath, "config", "", "path to configuration file")
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", envStringOrDefault("NB_PROXY_LOG_LEVEL", "info"), "Log level: panic, fatal, error, warn, info, debug, trace")
rootCmd.PersistentFlags().BoolVar(&debugLogs, "debug", envBoolOrDefault("NB_PROXY_DEBUG_LOGS", false), "Enable debug logs")
_ = rootCmd.PersistentFlags().MarkDeprecated("debug", "use --log-level instead")
@@ -146,66 +150,113 @@ func runServer(cmd *cobra.Command, args []string) error {
return fmt.Errorf("proxy token is required: set %s environment variable", envProxyToken)
}
cfg, err := loadConfig(cmd, configPath)
if err != nil {
return fmt.Errorf("load config: %w", err)
}
cfg.ProxyToken = proxyToken
level := cfg.LogLevel
level := logLevel
if debugLogs {
level = "debug"
}
logger := log.New()
_ = util.InitLogger(logger, level, util.LogConsole)
logger.Infof("configured log level: %s", level)
proxyConfig := cfg.Config
proxyConfig.Logger = logger
proxyConfig.Version = Version
applyPerformanceConfig(&proxyConfig, cfg, logger)
switch proxyConfig.ForwardedProto {
case "auto", "http", "https":
default:
return fmt.Errorf("invalid --forwarded-proto value %q: must be auto, http, or https", proxyConfig.ForwardedProto)
var wgPool, wgBatch uint64
var perf embed.Performance
if raw := os.Getenv(envPreallocatedBuffers); raw != "" {
n, err := strconv.ParseUint(raw, 10, 32)
if err != nil {
return fmt.Errorf("invalid %s %q: %w", envPreallocatedBuffers, raw, err)
}
wgPool = n
v := uint32(n)
perf.PreallocatedBuffersPerPool = &v
logger.Infof("tunnel preallocated buffers per pool: %d", n)
}
if raw := os.Getenv(envMaxBatchSize); raw != "" {
n, err := strconv.ParseUint(raw, 10, 32)
if err != nil {
return fmt.Errorf("invalid %s %q: %w", envMaxBatchSize, raw, err)
}
wgBatch = n
v := uint32(n)
perf.MaxBatchSize = &v
logger.Infof("tunnel max batch size override: %d", n)
}
if wgPool > 0 {
// Each bind recv goroutine (IPv4 + IPv6 + ICE relay) plus
// RoutineReadFromTUN eagerly reserves `batch` message buffers for
// the lifetime of the Device. A pool cap below that floor blocks
// the receive pipeline at startup.
batch := wgBatch
if batch == 0 {
batch = 128
}
const recvGoroutines = 4
floor := batch * recvGoroutines
if wgPool < floor {
logger.Warnf("%s=%d is below the eager-allocation floor (~%d for batch=%d); startup may deadlock",
envPreallocatedBuffers, wgPool, floor, batch)
}
}
if _, err := domain.ValidateDomains([]string{proxyConfig.ProxyURL}); err != nil {
return fmt.Errorf("invalid domain value %q: %w", proxyConfig.ProxyURL, err)
switch forwardedProto {
case "auto", "http", "https":
default:
return fmt.Errorf("invalid --forwarded-proto value %q: must be auto, http, or https", forwardedProto)
}
_, err := domain.ValidateDomains([]string{proxyDomain})
if err != nil {
return fmt.Errorf("invalid domain value %q: %w", proxyDomain, err)
}
parsedTrustedProxies, err := trustedproxy.Parse(trustedProxies)
if err != nil {
return fmt.Errorf("invalid --trusted-proxies: %w", err)
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
defer stop()
srv := proxy.New(ctx, proxyConfig)
return srv.ListenAndServe(ctx, proxyConfig.ListenAddr)
}
srv := proxy.New(ctx, proxy.Config{
ListenAddr: addr,
Logger: logger,
Version: Version,
ManagementAddress: mgmtAddr,
ProxyURL: proxyDomain,
ProxyToken: proxyToken,
CertificateDirectory: certDir,
CertificateFile: certFile,
CertificateKeyFile: certKeyFile,
GenerateACMECertificates: acmeCerts,
ACMEChallengeAddress: acmeAddr,
ACMEDirectory: acmeDir,
ACMEEABKID: acmeEABKID,
ACMEEABHMACKey: acmeEABHMACKey,
ACMEChallengeType: acmeChallengeType,
DebugEndpointEnabled: debugEndpoint,
DebugEndpointAddress: debugEndpointAddr,
HealthAddr: healthAddr,
ForwardedProto: forwardedProto,
TrustedProxies: parsedTrustedProxies,
CertLockMethod: nbacme.CertLockMethod(certLockMethod),
WildcardCertDir: wildcardCertDir,
WireguardPort: wgPort,
Performance: perf,
ProxyProtocol: proxyProtocol,
PreSharedKey: preSharedKey,
SupportsCustomPorts: supportsCustomPorts,
RequireSubdomain: requireSubdomain,
Private: private,
MaxDialTimeout: maxDialTimeout,
MaxSessionIdleTimeout: maxSessionIdleTimeout,
MappingBatchWatchdog: envDurationOrDefault("NB_PROXY_MAPPING_BATCH_WATCHDOG", 0),
GeoDataDir: geoDataDir,
CrowdSecAPIURL: crowdsecAPIURL,
CrowdSecAPIKey: crowdsecAPIKey,
})
func applyPerformanceConfig(proxyConfig *proxy.Config, cfg *commandConfig, logger *log.Logger) {
if cfg.PreallocatedBuffers != nil {
proxyConfig.Performance.PreallocatedBuffersPerPool = cfg.PreallocatedBuffers
logger.Infof("tunnel preallocated buffers per pool: %d", *cfg.PreallocatedBuffers)
}
if cfg.MaxBatchSize != nil {
proxyConfig.Performance.MaxBatchSize = cfg.MaxBatchSize
logger.Infof("tunnel max batch size override: %d", *cfg.MaxBatchSize)
}
if cfg.PreallocatedBuffers == nil || *cfg.PreallocatedBuffers == 0 {
return
}
batch := uint64(128)
if cfg.MaxBatchSize != nil && *cfg.MaxBatchSize > 0 {
batch = uint64(*cfg.MaxBatchSize)
}
const recvGoroutines = 4
floor := batch * recvGoroutines
pool := uint64(*cfg.PreallocatedBuffers)
if pool < floor {
logger.Warnf("%s=%d is below the eager-allocation floor (~%d for batch=%d); startup may deadlock",
envPreallocatedBuffers, pool, floor, batch)
}
return srv.ListenAndServe(ctx, addr)
}
func envBoolOrDefault(key string, def bool) bool {
-41
View File
@@ -1,41 +0,0 @@
# Command-line flags override environment variables, which override this file.
listenAddress: ":443"
id: "proxy-1"
domain: "proxy.example.com"
managementAddress: "https://api.netbird.io:443"
proxyToken: "replace-with-a-proxy-token"
certificateDirectory: "/var/lib/netbird/certs"
certificateFile: "tls.crt"
certificateKeyFile: "tls.key"
generateACMECertificates: true
acmeChallengeAddress: ":80"
acmeDirectory: "https://acme-v02.api.letsencrypt.org/directory"
acmeEABKID: ""
acmeEABHMACKey: ""
acmeChallengeType: "tls-alpn-01"
certLockMethod: "auto"
wildcardCertDir: ""
debugEndpointEnabled: false
debugEndpointAddress: "localhost:8444"
healthAddress: "localhost:8080"
forwardedProto: "auto"
trustedProxies: ""
wireguardPort: 0
proxyProtocol: false
preSharedKey: ""
supportsCustomPorts: true
requireSubdomain: false
private: false
maxDialTimeout: "0s"
maxSessionIdleTimeout: "0s"
mappingBatchWatchdog: "0s"
geoDataDir: "/var/lib/netbird/geolocation"
crowdSecAPIURL: ""
crowdSecAPIKey: ""
logLevel: "info"
# Optional tunnel memory and throughput tuning.
# preallocatedBuffers: 4096
# maxBatchSize: 128
+36 -36
View File
@@ -20,112 +20,112 @@ import (
// adding fields here must not change the zero-value behaviour of Server.
type Config struct {
// ListenAddr is the TCP address the main listener binds. Required.
ListenAddr string `yaml:"listenAddress" env:"NB_PROXY_ADDRESS" flag:"addr"`
ListenAddr string
// ID identifies this proxy instance to management. Empty values are
// replaced with a timestamped default at Server.Start time (see
// initDefaults), not in New.
ID string `yaml:"id" env:"-"`
ID string
// Logger is the logrus logger used everywhere. Empty values fall
// back to log.StandardLogger() at Server.Start time (see
// initDefaults), not in New.
Logger *log.Logger `yaml:"-" env:"-" flag:"-"`
Logger *log.Logger
// Version is the build version string reported to management. Empty
// values are replaced with "dev" at Server.Start time (see
// initDefaults), not in New.
Version string `yaml:"-" env:"-" flag:"-"`
Version string
// ProxyURL is the public address operators use to reach this proxy.
ProxyURL string `yaml:"domain" env:"NB_PROXY_DOMAIN" flag:"domain"`
ProxyURL string
// ManagementAddress is the gRPC URL of the management server.
ManagementAddress string `yaml:"managementAddress" env:"NB_PROXY_MANAGEMENT_ADDRESS" flag:"mgmt"`
ManagementAddress string
// ProxyToken authenticates this proxy with the management server.
ProxyToken string `yaml:"proxyToken" env:"NB_PROXY_TOKEN"`
ProxyToken string
// CertificateDirectory is the directory holding TLS certificate
// material (static or ACME-provisioned).
CertificateDirectory string `yaml:"certificateDirectory" env:"NB_PROXY_CERTIFICATE_DIRECTORY" flag:"cert-dir"`
CertificateDirectory string
// CertificateFile is the certificate filename within
// CertificateDirectory.
CertificateFile string `yaml:"certificateFile" env:"NB_PROXY_CERTIFICATE_FILE" flag:"cert-file"`
CertificateFile string
// CertificateKeyFile is the private key filename within
// CertificateDirectory.
CertificateKeyFile string `yaml:"certificateKeyFile" env:"NB_PROXY_CERTIFICATE_KEY_FILE" flag:"cert-key-file"`
CertificateKeyFile string
// GenerateACMECertificates toggles ACME certificate provisioning.
GenerateACMECertificates bool `yaml:"generateACMECertificates" env:"NB_PROXY_ACME_CERTIFICATES" flag:"acme-certs"`
GenerateACMECertificates bool
// ACMEChallengeAddress is the listen address for HTTP-01 challenges.
ACMEChallengeAddress string `yaml:"acmeChallengeAddress" env:"NB_PROXY_ACME_ADDRESS" flag:"acme-addr"`
ACMEChallengeAddress string
// ACMEDirectory is the ACME directory URL (Let's Encrypt by default).
ACMEDirectory string `yaml:"acmeDirectory" env:"NB_PROXY_ACME_DIRECTORY" flag:"acme-dir"`
ACMEDirectory string
// ACMEEABKID is the External Account Binding Key ID for CAs that
// require EAB (e.g. ZeroSSL).
ACMEEABKID string `yaml:"acmeEABKID" env:"NB_PROXY_ACME_EAB_KID" flag:"acme-eab-kid"`
ACMEEABKID string
// ACMEEABHMACKey is the External Account Binding HMAC key for CAs
// that require EAB.
ACMEEABHMACKey string `yaml:"acmeEABHMACKey" env:"NB_PROXY_ACME_EAB_HMAC_KEY" flag:"acme-eab-hmac-key"`
ACMEEABHMACKey string
// ACMEChallengeType is the ACME challenge type ("tls-alpn-01" or
// "http-01"). Empty defaults to "tls-alpn-01".
ACMEChallengeType string `yaml:"acmeChallengeType" env:"NB_PROXY_ACME_CHALLENGE_TYPE" flag:"acme-challenge-type"`
ACMEChallengeType string
// CertLockMethod controls how ACME certificate locks are coordinated
// across replicas.
CertLockMethod acme.CertLockMethod `yaml:"certLockMethod" env:"NB_PROXY_CERT_LOCK_METHOD" flag:"cert-lock-method"`
CertLockMethod acme.CertLockMethod
// WildcardCertDir is an optional directory containing static wildcard
// certificates that override ACME for matching domains.
WildcardCertDir string `yaml:"wildcardCertDir" env:"NB_PROXY_WILDCARD_CERT_DIR" flag:"wildcard-cert-dir"`
WildcardCertDir string
// DebugEndpointEnabled toggles the debug HTTP endpoint.
DebugEndpointEnabled bool `yaml:"debugEndpointEnabled" env:"NB_PROXY_DEBUG_ENDPOINT" flag:"debug-endpoint"`
DebugEndpointEnabled bool
// DebugEndpointAddress is the bind address for the debug endpoint.
DebugEndpointAddress string `yaml:"debugEndpointAddress" env:"NB_PROXY_DEBUG_ENDPOINT_ADDRESS" flag:"debug-endpoint-addr"`
DebugEndpointAddress string
// HealthAddr is the bind address for the health probe and metrics
// surface. Empty disables the health probe entirely (library callers
// can attach their own).
HealthAddr string `yaml:"healthAddress" env:"NB_PROXY_HEALTH_ADDRESS" flag:"health-addr"`
HealthAddr string
// ForwardedProto overrides the X-Forwarded-Proto value sent to
// backends. Valid values: "auto", "http", "https".
ForwardedProto string `yaml:"forwardedProto" env:"NB_PROXY_FORWARDED_PROTO" flag:"forwarded-proto"`
ForwardedProto string
// TrustedProxies is the set of trusted upstream proxies that may set
// forwarding headers.
TrustedProxies *trustedproxy.List `yaml:"trustedProxies" env:"NB_PROXY_TRUSTED_PROXIES" flag:"trusted-proxies"`
TrustedProxies *trustedproxy.List
// WireguardPort is the UDP port for the embedded NetBird tunnel.
// Zero asks the OS for a random port.
WireguardPort uint16 `yaml:"wireguardPort" env:"NB_PROXY_WG_PORT" flag:"wg-port"`
WireguardPort uint16
// ProxyProtocol enables PROXY protocol (v1/v2) on TCP listeners.
ProxyProtocol bool `yaml:"proxyProtocol" env:"NB_PROXY_PROXY_PROTOCOL" flag:"proxy-protocol"`
ProxyProtocol bool
// PreSharedKey is the WireGuard pre-shared key used between the
// proxy's embedded clients and peers.
PreSharedKey string `yaml:"preSharedKey" env:"NB_PROXY_PRESHARED_KEY" flag:"preshared-key"`
PreSharedKey string
// Performance configures the tunnel pool/batch sizes for every
// embedded client this proxy creates. Zero values fall back to
// upstream defaults.
Performance embed.Performance `yaml:"performance" env:"-" flag:"-"`
Performance embed.Performance
// SupportsCustomPorts indicates whether the proxy can bind arbitrary
// ports for TCP/UDP/TLS services.
SupportsCustomPorts bool `yaml:"supportsCustomPorts" env:"NB_PROXY_SUPPORTS_CUSTOM_PORTS" flag:"supports-custom-ports"`
SupportsCustomPorts bool
// RequireSubdomain forces accounts to use a subdomain in front of
// the proxy's cluster domain.
RequireSubdomain bool `yaml:"requireSubdomain" env:"NB_PROXY_REQUIRE_SUBDOMAIN" flag:"require-subdomain"`
RequireSubdomain bool
// Private flags this proxy as embedded in a netbird client and
// serving exclusively over the WireGuard tunnel. Also enables
// per-account inbound listeners on each embedded client's netstack.
Private bool `yaml:"private" env:"NB_PROXY_PRIVATE" flag:"private"`
Private bool
// MaxDialTimeout caps the per-service backend dial timeout.
MaxDialTimeout time.Duration `yaml:"maxDialTimeout" env:"NB_PROXY_MAX_DIAL_TIMEOUT" flag:"max-dial-timeout"`
MaxDialTimeout time.Duration
// MaxSessionIdleTimeout caps the per-service session idle timeout.
MaxSessionIdleTimeout time.Duration `yaml:"maxSessionIdleTimeout" env:"NB_PROXY_MAX_SESSION_IDLE_TIMEOUT" flag:"max-session-idle-timeout"`
MaxSessionIdleTimeout time.Duration
// MappingBatchWatchdog bounds how long a single mapping batch may spend
// being applied before the receive loop reconnects to resync. Zero falls
// back to the internal default.
MappingBatchWatchdog time.Duration `yaml:"mappingBatchWatchdog" env:"NB_PROXY_MAPPING_BATCH_WATCHDOG"`
MappingBatchWatchdog time.Duration
// GeoDataDir is the directory containing GeoLite2 MMDB files.
GeoDataDir string `yaml:"geoDataDir" env:"NB_PROXY_GEO_DATA_DIR" flag:"geo-data-dir"`
GeoDataDir string
// CrowdSecAPIURL is the CrowdSec LAPI URL. Empty disables CrowdSec.
CrowdSecAPIURL string `yaml:"crowdSecAPIURL" env:"NB_PROXY_CROWDSEC_API_URL" flag:"crowdsec-api-url"`
CrowdSecAPIURL string
// CrowdSecAPIKey is the CrowdSec bouncer API key. Empty disables
// CrowdSec.
CrowdSecAPIKey string `yaml:"crowdSecAPIKey" env:"NB_PROXY_CROWDSEC_API_KEY" flag:"crowdsec-api-key"`
CrowdSecAPIKey string
}
// New builds a Server from cfg without performing any I/O. No goroutines