diff --git a/config/example_load_test.go b/config/example_load_test.go index c5c06be..d7576da 100644 --- a/config/example_load_test.go +++ b/config/example_load_test.go @@ -1,6 +1,7 @@ package config import ( + "fmt" "os" "path/filepath" "strings" @@ -128,6 +129,42 @@ upstream = "https://backend.example.com" } } +// TestExampleSowerdConfigTOMLMatchesFile pins the embedded example to the +// checked-in file byte for byte. They are the same artifact in two places: +// the constant is what the installer writes to /etc/sower/sowerd.toml, and +// the file is what `-c config/sowerd.toml` loads during development. Let +// them drift and operators get guidance that contradicts shipped behavior. +func TestExampleSowerdConfigTOMLMatchesFile(t *testing.T) { + t.Parallel() + + file := mustReadFileForTest(t, "sowerd.toml") + if ExampleSowerdConfigTOML != file { + t.Fatalf("ExampleSowerdConfigTOML and config/sowerd.toml have drifted:\n%s", + exampleConfigDiff(ExampleSowerdConfigTOML, file)) + } +} + +func exampleConfigDiff(embedded, file string) string { + embeddedLines := strings.Split(strings.TrimRight(embedded, "\n"), "\n") + fileLines := strings.Split(strings.TrimRight(file, "\n"), "\n") + + var b strings.Builder + fmt.Fprintf(&b, " const has %d lines, file has %d lines\n", len(embeddedLines), len(fileLines)) + for i := 0; i < len(embeddedLines) || i < len(fileLines); i++ { + var e, f string + if i < len(embeddedLines) { + e = embeddedLines[i] + } + if i < len(fileLines) { + f = fileLines[i] + } + if e != f { + fmt.Fprintf(&b, " line %d\n const: %q\n file: %q\n", i+1, e, f) + } + } + return b.String() +} + func loadConfigFileForTest(path string, dst any) error { return aconfig.LoaderFor(dst, aconfig.Config{ SkipEnv: true, diff --git a/config/sowerd.go b/config/sowerd.go index b20b436..902799e 100644 --- a/config/sowerd.go +++ b/config/sowerd.go @@ -41,7 +41,9 @@ fake_site = "/var/www" # Fallback fake site directory or address (def # SSL/TLS certificate configuration [cert] email = "your-email@example.com" # Email for Let's Encrypt certificate -# domains = ["proxy.example.com"] # Domains autocert may issue for, in addition to site_routes domains (required for direct connections like the sower client remote addr) +# domains = ["proxy.example.com"] # Issuance whitelist beyond site_routes (autocert mode). +# The FIRST entry answers TLS handshakes for unknown/empty SNI: keep it a concrete +# domain, not a wildcard. See docs/security.md. cert = "" # Path to custom certificate file (optional) key = "" # Path to custom private key file (optional) ` diff --git a/config/sowerd.toml b/config/sowerd.toml index ba6f72d..daa832d 100644 --- a/config/sowerd.toml +++ b/config/sowerd.toml @@ -9,15 +9,18 @@ serve_ip = "0.0.0.0" # IP address to listen on for ports 80 and 443 password = "change_me" # Password for client authentication fake_site = "/var/www" # Fallback fake site directory or address (default: /var/www) -# Site routing configuration -# When a TLS connection does not match sower transport, sowerd uses -# the TLS SNI to select an upstream URL. Unmatched names fall back to fake_site. +# Site routing: map domains to upstream URLs for fallback traffic. +# When a TLS connection does not match sower transport, +# the SNI is looked up here. Unmatched connections fall through to fake_site. # Custom certificate mode requires the certificate SANs to cover all routed domains. # # [[site_routes]] # domains = ["a.example.com", "b.example.com"] # upstream = "http://127.0.0.1:8080" # +# [site_routes.routes] +# "/ws" = "http://127.0.0.1:8082" +# # [[site_routes]] # domains = ["c.example.com"] # upstream = "https://backend.example.com" @@ -25,6 +28,8 @@ fake_site = "/var/www" # Fallback fake site directory or address (def # SSL/TLS certificate configuration [cert] email = "your-email@example.com" # Email for Let's Encrypt certificate -domains = ["proxy.example.com"] # Issuance whitelist beyond site_routes. The FIRST entry answers TLS handshakes for unknown/empty SNI (keep it a concrete domain, not a wildcard); see docs/security.md +# domains = ["proxy.example.com"] # Issuance whitelist beyond site_routes (autocert mode). +# The FIRST entry answers TLS handshakes for unknown/empty SNI: keep it a concrete +# domain, not a wildcard. See docs/security.md. cert = "" # Path to custom certificate file (optional) key = "" # Path to custom private key file (optional)