check / check (push) Canceled after 0s
The backend's own TRUSTED_PROXIES now follows the ruling for every app: unset, it trusts 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16, and no longer loopback; a list replaces that; set and empty, it trusts no proxy. viper reads an empty environment variable as unset, so an empty value got the default. config.New now reads this one setting with os.LookupEnv first. viper's AllowEmptyEnv would apply to every setting, so an empty PORT or DEBUG would stop the start instead of counting as unset, as README.md says it does. bin/entrypoint.sh still starts the backend with TRUSTED_PROXIES=127.0.0.1/32. Model: opus-5-5
206 lines
5.7 KiB
Go
206 lines
5.7 KiB
Go
package config_test
|
|
|
|
import (
|
|
"os"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/netwatch/internal/config"
|
|
"sneak.berlin/go/netwatch/internal/globals"
|
|
"sneak.berlin/go/netwatch/internal/logger"
|
|
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
// requireConfigError builds the config as main does and fails the
|
|
// test unless that fails with an error naming setting. It uses
|
|
// fx.New, because fxtest.New fails the test itself on an error.
|
|
func requireConfigError(t *testing.T, setting string) {
|
|
t.Helper()
|
|
|
|
app := fx.New(
|
|
fx.NopLogger,
|
|
fx.Provide(globals.New, logger.New, config.New),
|
|
fx.Invoke(func(*config.Config) {}),
|
|
)
|
|
|
|
err := app.Err()
|
|
if err == nil || !strings.Contains(err.Error(), setting) {
|
|
t.Fatalf("config error = %v, want one naming %s", err, setting)
|
|
}
|
|
}
|
|
|
|
// requireTrustedProxies builds the config as main does and fails the
|
|
// test unless its TrustedProxies are want.
|
|
func requireTrustedProxies(t *testing.T, want []string) {
|
|
t.Helper()
|
|
|
|
var cfg *config.Config
|
|
|
|
app := fx.New(
|
|
fx.NopLogger,
|
|
fx.Provide(globals.New, logger.New, config.New),
|
|
fx.Populate(&cfg),
|
|
)
|
|
|
|
err := app.Err()
|
|
if err != nil {
|
|
t.Fatalf("config error = %v", err)
|
|
}
|
|
|
|
if !slices.Equal(cfg.TrustedProxies, want) {
|
|
t.Fatalf("TrustedProxies = %q, want %q", cfg.TrustedProxies, want)
|
|
}
|
|
}
|
|
|
|
// TestTrustedProxiesUnsetTrustsRFC1918: unset, the server trusts the
|
|
// RFC 1918 ranges and nothing else, loopback included.
|
|
func TestTrustedProxiesUnsetTrustsRFC1918(t *testing.T) {
|
|
// t.Setenv first, so the variable is put back as it was after the
|
|
// test.
|
|
t.Setenv("TRUSTED_PROXIES", "")
|
|
|
|
err := os.Unsetenv("TRUSTED_PROXIES")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
requireTrustedProxies(t,
|
|
[]string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"})
|
|
}
|
|
|
|
// TestTrustedProxiesEmptyTrustsNothing: set and empty, the server
|
|
// trusts no proxy. viper alone would read the empty variable as unset
|
|
// and give the default.
|
|
func TestTrustedProxiesEmptyTrustsNothing(t *testing.T) {
|
|
t.Setenv("TRUSTED_PROXIES", "")
|
|
|
|
requireTrustedProxies(t, []string{})
|
|
}
|
|
|
|
// TestTrustedProxiesListReplacesDefault: a list is used as given, with
|
|
// none of the default ranges added.
|
|
func TestTrustedProxiesListReplacesDefault(t *testing.T) {
|
|
t.Setenv("TRUSTED_PROXIES", "172.17.0.1/32, 2001:db8::/32")
|
|
|
|
requireTrustedProxies(t, []string{"172.17.0.1/32", "2001:db8::/32"})
|
|
}
|
|
|
|
// TestSettingsLoadAsGiven: valid values pass the checks and are used
|
|
// as given. bin/entrypoint.sh starts the server with these
|
|
// BIND_ADDRESS and PORT values.
|
|
func TestSettingsLoadAsGiven(t *testing.T) {
|
|
t.Setenv("BIND_ADDRESS", "127.0.0.1")
|
|
t.Setenv("PORT", "8081")
|
|
t.Setenv("DEBUG", "true")
|
|
|
|
var cfg *config.Config
|
|
|
|
app := fx.New(
|
|
fx.NopLogger,
|
|
fx.Provide(globals.New, logger.New, config.New),
|
|
fx.Populate(&cfg),
|
|
)
|
|
|
|
err := app.Err()
|
|
if err != nil {
|
|
t.Fatalf("config error = %v", err)
|
|
}
|
|
|
|
if cfg.BindAddress != "127.0.0.1" || cfg.Port != 8081 || !cfg.Debug {
|
|
t.Fatalf("BindAddress, Port, Debug = %q, %d, %t; "+
|
|
"want \"127.0.0.1\", 8081, true",
|
|
cfg.BindAddress, cfg.Port, cfg.Debug)
|
|
}
|
|
}
|
|
|
|
// TestPortMustBeAPortNumber: viper reads a value that is not a number
|
|
// as 0, on which the server would listen on a random port.
|
|
func TestPortMustBeAPortNumber(t *testing.T) {
|
|
for _, value := range []string{"abc", "0", "65536", "8080.5"} {
|
|
t.Run(value, func(t *testing.T) {
|
|
t.Setenv("PORT", value)
|
|
|
|
requireConfigError(t, "PORT")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDebugMustBeTrueOrFalse: viper reads any other value, such as
|
|
// "yes", as false.
|
|
func TestDebugMustBeTrueOrFalse(t *testing.T) {
|
|
t.Setenv("DEBUG", "yes")
|
|
|
|
requireConfigError(t, "DEBUG")
|
|
}
|
|
|
|
// TestBindAddressMustBeAnIPAddress: a host name would be looked up
|
|
// only once the server starts listening, and a mistyped one would stop
|
|
// it then with an error that does not name the setting.
|
|
func TestBindAddressMustBeAnIPAddress(t *testing.T) {
|
|
t.Setenv("BIND_ADDRESS", "localhost")
|
|
|
|
requireConfigError(t, "BIND_ADDRESS")
|
|
}
|
|
|
|
// TestReportsPerMinuteMustBePositive: unchecked, zero would panic
|
|
// when the routes are built, and a negative rate would lift the
|
|
// limit.
|
|
func TestReportsPerMinuteMustBePositive(t *testing.T) {
|
|
t.Setenv("REPORTS_PER_MINUTE", "0")
|
|
|
|
requireConfigError(t, "REPORTS_PER_MINUTE")
|
|
}
|
|
|
|
// TestDataDirMaxBytesMustBeANumber: viper reads a value that is not
|
|
// a number, such as "1GB", as 0, which would refuse every report.
|
|
func TestDataDirMaxBytesMustBeANumber(t *testing.T) {
|
|
t.Setenv("DATA_DIR_MAX_BYTES", "1GB")
|
|
|
|
requireConfigError(t, "DATA_DIR_MAX_BYTES")
|
|
}
|
|
|
|
// TestMetricsCredentialsGoTogether: with only one of the two set, the
|
|
// server would quietly serve no metrics, so the start fails, naming
|
|
// both.
|
|
func TestMetricsCredentialsGoTogether(t *testing.T) {
|
|
for _, set := range []string{"METRICS_USERNAME", "METRICS_PASSWORD"} {
|
|
t.Run(set, func(t *testing.T) {
|
|
t.Setenv("METRICS_USERNAME", "")
|
|
t.Setenv("METRICS_PASSWORD", "")
|
|
t.Setenv(set, "prometheus")
|
|
|
|
requireConfigError(t, "METRICS_USERNAME")
|
|
requireConfigError(t, "METRICS_PASSWORD")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestMetricsUsernameMustNotContainColon: basic auth splits the
|
|
// credentials at the first ":", so such a user name would get 401 on
|
|
// every request to /metrics.
|
|
func TestMetricsUsernameMustNotContainColon(t *testing.T) {
|
|
t.Setenv("METRICS_USERNAME", "prom:etheus")
|
|
t.Setenv("METRICS_PASSWORD", "secret")
|
|
|
|
requireConfigError(t, "METRICS_USERNAME")
|
|
}
|
|
|
|
// TestCORSAllowedOriginsMustBeOrigins: "*" would let every origin in,
|
|
// and an entry that is not a plain origin would match no page.
|
|
func TestCORSAllowedOriginsMustBeOrigins(t *testing.T) {
|
|
for _, entry := range []string{
|
|
"*",
|
|
"https://*.netwatch.example",
|
|
"netwatch.example",
|
|
"https://netwatch.example/",
|
|
} {
|
|
t.Run(entry, func(t *testing.T) {
|
|
t.Setenv("CORS_ALLOWED_ORIGINS", entry)
|
|
|
|
requireConfigError(t, "CORS_ALLOWED_ORIGINS")
|
|
})
|
|
}
|
|
}
|