304 lines
12 KiB
Go
304 lines
12 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestLoadFileUsesYAMLAndEnvironmentOverrides(t *testing.T) {
|
|
path := writeSettings(t, `schema_version: 1
|
|
server:
|
|
listen_address: 127.0.0.1:9080
|
|
trusted_proxy_cidrs: [127.0.0.0/8, 10.0.0.0/8]
|
|
registration:
|
|
attempts: 5
|
|
window_seconds: 900
|
|
provider:
|
|
http_timeout_seconds: 120
|
|
max_response_bytes: 1048576
|
|
allowed_ports: [80, 443, 8080, 8080]
|
|
worker:
|
|
lease_seconds: 150
|
|
poll_milliseconds: 500
|
|
`)
|
|
cfg, err := LoadFileFromLookup(path, lookup(map[string]string{"CHORUS_DSN": "test-dsn"}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.ProviderHTTPTimeout != 120*time.Second || cfg.ProviderMaxResponseBytes != 1048576 || cfg.WorkerLeaseDuration != 150*time.Second || cfg.WorkerPollInterval != 500*time.Millisecond {
|
|
t.Fatalf("YAML settings were not loaded: %#v", cfg)
|
|
}
|
|
if cfg.ListenAddress != "127.0.0.1:9080" {
|
|
t.Fatalf("listen address = %q", cfg.ListenAddress)
|
|
}
|
|
if cfg.RegistrationAttempts != 5 || cfg.RegistrationWindow != 15*time.Minute || len(cfg.TrustedProxyCIDRs) != 2 {
|
|
t.Fatalf("registration settings were not loaded: %#v", cfg)
|
|
}
|
|
if got := cfg.ProviderAllowedPorts; len(got) != 3 || got[2] != 8080 {
|
|
t.Fatalf("provider ports = %v", got)
|
|
}
|
|
|
|
overrides := map[string]string{
|
|
"CHORUS_DSN": "test-dsn",
|
|
"CHORUS_LISTEN_ADDRESS": "127.0.0.1:9081",
|
|
"CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS": "60",
|
|
"CHORUS_PROVIDER_MAX_RESPONSE_BYTES": "2097152",
|
|
"CHORUS_PROVIDER_ALLOWED_PORTS": "80,443,8443",
|
|
"CHORUS_WORKER_LEASE_SECONDS": "90",
|
|
"CHORUS_WORKER_POLL_MILLISECONDS": "100",
|
|
"CHORUS_TRUSTED_PROXY_CIDRS": "192.0.2.0/24",
|
|
"CHORUS_REGISTRATION_ATTEMPTS": "7",
|
|
"CHORUS_REGISTRATION_WINDOW_SECONDS": "600",
|
|
}
|
|
cfg, err = LoadFileFromLookup(path, lookup(overrides))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.ProviderHTTPTimeout != 60*time.Second || cfg.ProviderMaxResponseBytes != 2097152 || cfg.WorkerLeaseDuration != 90*time.Second || cfg.WorkerPollInterval != 100*time.Millisecond || cfg.ProviderAllowedPorts[2] != 8443 {
|
|
t.Fatalf("environment overrides were not applied: %#v", cfg)
|
|
}
|
|
if cfg.ListenAddress != "127.0.0.1:9081" {
|
|
t.Fatalf("environment listen address override = %q", cfg.ListenAddress)
|
|
}
|
|
if cfg.RegistrationAttempts != 7 || cfg.RegistrationWindow != 10*time.Minute || len(cfg.TrustedProxyCIDRs) != 1 {
|
|
t.Fatalf("registration overrides were not applied: %#v", cfg)
|
|
}
|
|
}
|
|
|
|
func TestLoadFileRejectsInvalidSettings(t *testing.T) {
|
|
tests := map[string]string{
|
|
"unknown field": `schema_version: 1
|
|
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: [80]}
|
|
worker: {lease_seconds: 60, poll_milliseconds: 250}
|
|
unknown: true
|
|
`,
|
|
"schema": `schema_version: 2
|
|
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: [80]}
|
|
worker: {lease_seconds: 60, poll_milliseconds: 250}
|
|
`,
|
|
"empty ports": `schema_version: 1
|
|
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: []}
|
|
worker: {lease_seconds: 60, poll_milliseconds: 250}
|
|
`,
|
|
"unsafe lease": `schema_version: 1
|
|
provider: {http_timeout_seconds: 60, max_response_bytes: 1024, allowed_ports: [80]}
|
|
worker: {lease_seconds: 65, poll_milliseconds: 250}
|
|
`,
|
|
"empty listen address": `schema_version: 1
|
|
server: {listen_address: ""}
|
|
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: [80]}
|
|
worker: {lease_seconds: 60, poll_milliseconds: 250}
|
|
`,
|
|
"invalid listen address": `schema_version: 1
|
|
server: {listen_address: "http://127.0.0.1:8080"}
|
|
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: [80]}
|
|
worker: {lease_seconds: 60, poll_milliseconds: 250}
|
|
`,
|
|
"partial registration": `schema_version: 1
|
|
registration: {attempts: 5}
|
|
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: [80]}
|
|
worker: {lease_seconds: 60, poll_milliseconds: 250}
|
|
`,
|
|
}
|
|
for name, body := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
if _, err := LoadFileFromLookup(writeSettings(t, body), lookup(map[string]string{"CHORUS_DSN": "test-dsn"})); err == nil {
|
|
t.Fatal("invalid settings were accepted")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsInvalidListenAddressEnvironmentOverride(t *testing.T) {
|
|
for _, value := range []string{"127.0.0.1", ":8080", "127.0.0.1:0", "127.0.0.1:65536"} {
|
|
_, err := LoadFromLookup(lookup(map[string]string{
|
|
"CHORUS_DSN": "test-dsn", "CHORUS_LISTEN_ADDRESS": value,
|
|
}))
|
|
if err == nil || !strings.Contains(err.Error(), "listen address") {
|
|
t.Fatalf("invalid listen address %q was accepted: %v", value, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func writeSettings(t *testing.T, body string) string {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "settings.yml")
|
|
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func lookup(values map[string]string) func(string) (string, bool) {
|
|
return func(key string) (string, bool) {
|
|
value, ok := values[key]
|
|
return value, ok
|
|
}
|
|
}
|
|
|
|
func TestLoadDevelopmentRequiresDSNWithoutLeakingIt(t *testing.T) {
|
|
secret := "user:secret@tcp(localhost:3308)/chorus"
|
|
cfg, err := LoadFromLookup(lookup(map[string]string{
|
|
"CHORUS_ENV": "development",
|
|
"CHORUS_DSN": secret,
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("LoadFromLookup() error = %v", err)
|
|
}
|
|
if cfg.DBDSN != secret || cfg.ListenAddress != "127.0.0.1:8080" {
|
|
t.Fatalf("unexpected config: %#v", cfg)
|
|
}
|
|
if cfg.WorkerLeaseDuration != 60*time.Second || cfg.WorkerPollInterval != 250*time.Millisecond || cfg.ProviderHTTPTimeout != 45*time.Second || cfg.ProviderMaxResponseBytes != 32<<20 || cfg.UserRateLimitCapacity != 60 || cfg.APIKeyRateLimitCapacity != 120 || cfg.ProviderRateLimitCapacity != 60 {
|
|
t.Fatalf("unexpected worker defaults: %#v", cfg)
|
|
}
|
|
if len(cfg.ProviderAllowedPorts) != 2 || cfg.ProviderAllowedPorts[0] != 80 || cfg.ProviderAllowedPorts[1] != 443 {
|
|
t.Fatalf("unexpected provider port defaults: %v", cfg.ProviderAllowedPorts)
|
|
}
|
|
}
|
|
|
|
func TestLoadProviderAllowedPorts(t *testing.T) {
|
|
cfg, err := LoadFromLookup(lookup(map[string]string{
|
|
"CHORUS_DSN": "test-dsn", "CHORUS_PROVIDER_ALLOWED_PORTS": "80, 443,8080,80",
|
|
}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []uint16{80, 443, 8080}
|
|
if len(cfg.ProviderAllowedPorts) != len(want) {
|
|
t.Fatalf("provider ports = %v, want %v", cfg.ProviderAllowedPorts, want)
|
|
}
|
|
for index := range want {
|
|
if cfg.ProviderAllowedPorts[index] != want[index] {
|
|
t.Fatalf("provider ports = %v, want %v", cfg.ProviderAllowedPorts, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsInvalidProviderAllowedPorts(t *testing.T) {
|
|
for _, value := range []string{"0", "65536", "80,,443", "https", "-1"} {
|
|
_, err := LoadFromLookup(lookup(map[string]string{
|
|
"CHORUS_DSN": "test-dsn", "CHORUS_PROVIDER_ALLOWED_PORTS": value,
|
|
}))
|
|
if err == nil || !strings.Contains(err.Error(), "CHORUS_PROVIDER_ALLOWED_PORTS") {
|
|
t.Fatalf("invalid provider ports %q were accepted: %v", value, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadProductionFailsClosed(t *testing.T) {
|
|
_, err := LoadFromLookup(lookup(map[string]string{
|
|
"CHORUS_ENV": "production",
|
|
"CHORUS_DSN": "sensitive-dsn",
|
|
}))
|
|
if err == nil {
|
|
t.Fatal("LoadFromLookup() expected an error")
|
|
}
|
|
for _, name := range []string{"CHORUS_SESSION_KEY", "CHORUS_STORAGE_ROOT"} {
|
|
if !strings.Contains(err.Error(), name) {
|
|
t.Errorf("error %q does not identify %s", err, name)
|
|
}
|
|
}
|
|
if strings.Contains(err.Error(), "sensitive-dsn") {
|
|
t.Fatalf("error leaked DSN: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsUnknownEnvironment(t *testing.T) {
|
|
_, err := LoadFromLookup(lookup(map[string]string{"CHORUS_ENV": "prod"}))
|
|
if err == nil {
|
|
t.Fatal("LoadFromLookup() expected an error")
|
|
}
|
|
}
|
|
|
|
func TestLoadProductionPortalLimits(t *testing.T) {
|
|
values := completeProductionValues()
|
|
cfg, err := LoadFromLookup(lookup(values))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := cfg.ValidateProduction(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
values["CHORUS_MAX_UPLOAD_BYTES"] = "100"
|
|
if _, err := LoadFromLookup(lookup(values)); err == nil {
|
|
t.Fatal("inconsistent upload limits accepted")
|
|
}
|
|
}
|
|
|
|
func completeProductionValues() map[string]string {
|
|
return map[string]string{
|
|
"CHORUS_ENV": "production", "CHORUS_DSN": "sensitive-dsn",
|
|
"CHORUS_SESSION_KEY": strings.Repeat("s", 32), "CHORUS_STORAGE_ROOT": "/var/lib/chorus",
|
|
"CHORUS_SESSION_TTL_MINUTES": "480", "CHORUS_LOGIN_ATTEMPTS": "5", "CHORUS_LOGIN_WINDOW_SECONDS": "60",
|
|
"CHORUS_MAX_PROMPT_BYTES": "8000", "CHORUS_MAX_IMAGES": "8", "CHORUS_MAX_IMAGE_BYTES": "10485760",
|
|
"CHORUS_MAX_UPLOAD_BYTES": "33554432", "CHORUS_MAX_IMAGE_PIXELS": "40000000", "CHORUS_HISTORY_LIMIT": "50",
|
|
"CHORUS_WORKER_LEASE_SECONDS": "60", "CHORUS_WORKER_POLL_MILLISECONDS": "250",
|
|
"CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS": "45", "CHORUS_PROVIDER_MAX_RESPONSE_BYTES": "33554432",
|
|
"CHORUS_USER_RATE_LIMIT_CAPACITY": "60", "CHORUS_USER_RATE_LIMIT_WINDOW_SECONDS": "60",
|
|
"CHORUS_API_KEY_RATE_LIMIT_CAPACITY": "120", "CHORUS_API_KEY_RATE_LIMIT_WINDOW_SECONDS": "60",
|
|
"CHORUS_PROVIDER_RATE_LIMIT_CAPACITY": "60", "CHORUS_PROVIDER_RATE_LIMIT_WINDOW_SECONDS": "60",
|
|
"CHORUS_REGISTRATION_ATTEMPTS": "5", "CHORUS_REGISTRATION_WINDOW_SECONDS": "900",
|
|
}
|
|
}
|
|
|
|
func TestProductionRequiresEveryRateLimitValue(t *testing.T) {
|
|
for _, name := range []string{
|
|
"CHORUS_USER_RATE_LIMIT_CAPACITY", "CHORUS_USER_RATE_LIMIT_WINDOW_SECONDS",
|
|
"CHORUS_API_KEY_RATE_LIMIT_CAPACITY", "CHORUS_API_KEY_RATE_LIMIT_WINDOW_SECONDS",
|
|
"CHORUS_PROVIDER_RATE_LIMIT_CAPACITY", "CHORUS_PROVIDER_RATE_LIMIT_WINDOW_SECONDS",
|
|
"CHORUS_REGISTRATION_ATTEMPTS", "CHORUS_REGISTRATION_WINDOW_SECONDS",
|
|
} {
|
|
values := completeProductionValues()
|
|
delete(values, name)
|
|
if _, err := LoadFromLookup(lookup(values)); err == nil || !strings.Contains(err.Error(), name) {
|
|
t.Fatalf("missing %s was accepted: %v", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsInvalidRateLimits(t *testing.T) {
|
|
for _, name := range []string{
|
|
"CHORUS_USER_RATE_LIMIT_CAPACITY", "CHORUS_USER_RATE_LIMIT_WINDOW_SECONDS",
|
|
"CHORUS_API_KEY_RATE_LIMIT_CAPACITY", "CHORUS_API_KEY_RATE_LIMIT_WINDOW_SECONDS",
|
|
"CHORUS_PROVIDER_RATE_LIMIT_CAPACITY", "CHORUS_PROVIDER_RATE_LIMIT_WINDOW_SECONDS",
|
|
"CHORUS_REGISTRATION_ATTEMPTS", "CHORUS_REGISTRATION_WINDOW_SECONDS",
|
|
} {
|
|
values := map[string]string{"CHORUS_DSN": "test-dsn", name: "0"}
|
|
if _, err := LoadFromLookup(lookup(values)); err == nil || !strings.Contains(err.Error(), name) {
|
|
t.Fatalf("invalid %s was accepted: %v", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadTrustedProxyCIDRs(t *testing.T) {
|
|
cfg, err := LoadFromLookup(lookup(map[string]string{"CHORUS_DSN": "test-dsn", "CHORUS_TRUSTED_PROXY_CIDRS": "127.0.0.0/8, 10.0.0.0/8,127.0.0.0/8"}))
|
|
if err != nil || len(cfg.TrustedProxyCIDRs) != 2 {
|
|
t.Fatalf("trusted proxy CIDRs = %v, %v", cfg.TrustedProxyCIDRs, err)
|
|
}
|
|
if _, err := LoadFromLookup(lookup(map[string]string{"CHORUS_DSN": "test-dsn", "CHORUS_TRUSTED_PROXY_CIDRS": "not-a-cidr"})); err == nil {
|
|
t.Fatal("invalid trusted proxy CIDR accepted")
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsUnsafeWorkerLease(t *testing.T) {
|
|
_, err := LoadFromLookup(lookup(map[string]string{
|
|
"CHORUS_DSN": "test-dsn", "CHORUS_WORKER_LEASE_SECONDS": "50", "CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS": "45",
|
|
}))
|
|
if err == nil || !strings.Contains(err.Error(), "worker lease") {
|
|
t.Fatalf("expected worker lease error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWorkerCanOnlyBeDisabledInTest(t *testing.T) {
|
|
if _, err := LoadFromLookup(lookup(map[string]string{"CHORUS_DSN": "test-dsn", "CHORUS_TEST_DISABLE_WORKER": "true"})); err == nil {
|
|
t.Fatal("development accepted disabled worker")
|
|
}
|
|
cfg, err := LoadFromLookup(lookup(map[string]string{"CHORUS_ENV": "test", "CHORUS_DSN": "test-dsn", "CHORUS_TEST_DISABLE_WORKER": "true"}))
|
|
if err != nil || !cfg.TestDisableWorker {
|
|
t.Fatalf("test worker fixture was rejected: %#v %v", cfg, err)
|
|
}
|
|
}
|