Layer 4 TLS SNI proxy with global firewall (IP/CIDR/GeoIP blocking), per-listener route tables, bidirectional TCP relay with half-close propagation, and a gRPC admin API (routes, firewall, status) with TLS/mTLS support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
187 lines
3.9 KiB
Go
187 lines
3.9 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestLoadValid(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "test.toml")
|
|
|
|
data := `
|
|
[[listeners]]
|
|
addr = ":443"
|
|
|
|
[[listeners.routes]]
|
|
hostname = "example.com"
|
|
backend = "127.0.0.1:8443"
|
|
|
|
[proxy]
|
|
connect_timeout = "5s"
|
|
idle_timeout = "300s"
|
|
shutdown_timeout = "30s"
|
|
|
|
[log]
|
|
level = "info"
|
|
`
|
|
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if len(cfg.Listeners) != 1 {
|
|
t.Fatalf("got %d listeners, want 1", len(cfg.Listeners))
|
|
}
|
|
if cfg.Listeners[0].Addr != ":443" {
|
|
t.Fatalf("got listener addr %q, want %q", cfg.Listeners[0].Addr, ":443")
|
|
}
|
|
if len(cfg.Listeners[0].Routes) != 1 {
|
|
t.Fatalf("got %d routes, want 1", len(cfg.Listeners[0].Routes))
|
|
}
|
|
if cfg.Listeners[0].Routes[0].Hostname != "example.com" {
|
|
t.Fatalf("got hostname %q, want %q", cfg.Listeners[0].Routes[0].Hostname, "example.com")
|
|
}
|
|
}
|
|
|
|
func TestLoadNoListeners(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "test.toml")
|
|
|
|
data := `
|
|
[log]
|
|
level = "info"
|
|
`
|
|
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing listeners")
|
|
}
|
|
}
|
|
|
|
func TestLoadNoRoutes(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "test.toml")
|
|
|
|
data := `
|
|
[[listeners]]
|
|
addr = ":443"
|
|
`
|
|
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing routes")
|
|
}
|
|
}
|
|
|
|
func TestLoadDuplicateHostnames(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "test.toml")
|
|
|
|
data := `
|
|
[[listeners]]
|
|
addr = ":443"
|
|
|
|
[[listeners.routes]]
|
|
hostname = "example.com"
|
|
backend = "127.0.0.1:8443"
|
|
|
|
[[listeners.routes]]
|
|
hostname = "example.com"
|
|
backend = "127.0.0.1:9443"
|
|
`
|
|
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil {
|
|
t.Fatal("expected error for duplicate hostnames")
|
|
}
|
|
}
|
|
|
|
func TestLoadGeoIPRequiredWithCountries(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "test.toml")
|
|
|
|
data := `
|
|
[[listeners]]
|
|
addr = ":443"
|
|
|
|
[[listeners.routes]]
|
|
hostname = "example.com"
|
|
backend = "127.0.0.1:8443"
|
|
|
|
[firewall]
|
|
blocked_countries = ["CN"]
|
|
`
|
|
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil {
|
|
t.Fatal("expected error for blocked_countries without geoip_db")
|
|
}
|
|
}
|
|
|
|
func TestLoadMultipleListeners(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "test.toml")
|
|
|
|
data := `
|
|
[[listeners]]
|
|
addr = ":443"
|
|
|
|
[[listeners.routes]]
|
|
hostname = "public.example.com"
|
|
backend = "127.0.0.1:8443"
|
|
|
|
[[listeners]]
|
|
addr = ":8443"
|
|
|
|
[[listeners.routes]]
|
|
hostname = "internal.example.com"
|
|
backend = "127.0.0.1:9443"
|
|
`
|
|
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if len(cfg.Listeners) != 2 {
|
|
t.Fatalf("got %d listeners, want 2", len(cfg.Listeners))
|
|
}
|
|
if cfg.Listeners[0].Routes[0].Hostname != "public.example.com" {
|
|
t.Fatalf("listener 0 hostname = %q, want %q", cfg.Listeners[0].Routes[0].Hostname, "public.example.com")
|
|
}
|
|
if cfg.Listeners[1].Routes[0].Hostname != "internal.example.com" {
|
|
t.Fatalf("listener 1 hostname = %q, want %q", cfg.Listeners[1].Routes[0].Hostname, "internal.example.com")
|
|
}
|
|
}
|
|
|
|
func TestDuration(t *testing.T) {
|
|
var d Duration
|
|
if err := d.UnmarshalText([]byte("5s")); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if d.Duration.Seconds() != 5 {
|
|
t.Fatalf("got %v, want 5s", d.Duration)
|
|
}
|
|
}
|