diff --git a/openflare_server/common/constants.go b/openflare_server/common/constants.go index 4a74bf75..2228637c 100644 --- a/openflare_server/common/constants.go +++ b/openflare_server/common/constants.go @@ -103,7 +103,7 @@ events { http { include mime.types; default_type application/octet-stream; - log_format openflare_json escape=json '{"ts":"$time_iso8601","host":"$host","path":"$request_uri","remote_addr":"$remote_addr","status":$status,"request_time":$request_time,"bytes_sent":$body_bytes_sent,"request_length":$request_length}'; +{{OpenRestyConnectionUpgradeMap}}{{OpenRestyDefaultServerBlock}} log_format openflare_json escape=json '{"ts":"$time_iso8601","host":"$host","path":"$request_uri","remote_addr":"$remote_addr","status":$status,"request_time":$request_time,"bytes_sent":$body_bytes_sent,"request_length":$request_length}'; access_log {{OpenRestyAccessLogPath}} openflare_json; sendfile on; tcp_nopush on; diff --git a/openflare_server/service/config_version.go b/openflare_server/service/config_version.go index f9b945fb..61066d2b 100644 --- a/openflare_server/service/config_version.go +++ b/openflare_server/service/config_version.go @@ -136,6 +136,8 @@ var requiredMainConfigTemplatePlaceholders = []string{ "{{OpenRestyWorkerProcesses}}", "{{OpenRestyWorkerConnections}}", "{{OpenRestyWorkerRlimitNofile}}", + "{{OpenRestyConnectionUpgradeMap}}", + "{{OpenRestyDefaultServerBlock}}", "{{OpenRestyAccessLogPath}}", "{{OpenRestyEventsUseDirective}}", "{{OpenRestyEventsMultiAcceptDirective}}", @@ -664,6 +666,8 @@ func renderMainConfigTemplate(templateText string, cfg openRestyConfigSnapshot) "{{OpenRestyWorkerProcesses}}", cfg.WorkerProcesses, "{{OpenRestyWorkerConnections}}", fmt.Sprintf("%d", cfg.WorkerConnections), "{{OpenRestyWorkerRlimitNofile}}", fmt.Sprintf("%d", cfg.WorkerRlimitNofile), + "{{OpenRestyConnectionUpgradeMap}}", renderConnectionUpgradeMap(), + "{{OpenRestyDefaultServerBlock}}", renderDefaultServerBlock(), "{{OpenRestyAccessLogPath}}", nginxAccessLogPlaceholder, "{{OpenRestyEventsUseDirective}}", renderTemplateDirective(cfg.EventsUse != "", fmt.Sprintf("use %s;", cfg.EventsUse)), "{{OpenRestyEventsMultiAcceptDirective}}", renderTemplateDirective(cfg.EventsMultiAcceptEnabled, "multi_accept on;"), @@ -765,21 +769,25 @@ func nextVersionNumber(now time.Time) (string, error) { } func renderHTTPProxyServer(domain string, originURL string, originHost string, customHeaders []ProxyRouteCustomHeaderInput, cfg openRestyConfigSnapshot) string { - return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n%s\n location / {\n%s%s }\n}\n\n", domain, renderExactHostGuard(domain), renderProxyHeaderBlock(originURL, originHost, customHeaders), renderProxyPassBlock(originURL, cfg)) + return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n\n location / {\n%s%s }\n}\n\n", domain, renderProxyHeaderBlock(originURL, originHost, customHeaders), renderProxyPassBlock(originURL, cfg)) } func renderHTTPRedirectServer(domain string) string { - return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n%s\n return 301 https://$host$request_uri;\n}\n\n", domain, renderExactHostGuard(domain)) + return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n\n return 301 https://$host$request_uri;\n}\n\n", domain) } func renderHTTPSServer(domain string, originURL string, originHost string, certificateID uint, customHeaders []ProxyRouteCustomHeaderInput, cfg openRestyConfigSnapshot) string { certPath := fmt.Sprintf("%s/%s", nginxCertDirPlaceholder, certificateCertFileName(certificateID)) keyPath := fmt.Sprintf("%s/%s", nginxCertDirPlaceholder, certificateKeyFileName(certificateID)) - return fmt.Sprintf("server {\n listen 443 ssl http2;\n server_name %s;\n ssl_certificate %s;\n ssl_certificate_key %s;\n%s\n location / {\n%s%s }\n}\n\n", domain, certPath, keyPath, renderExactHostGuard(domain), renderProxyHeaderBlock(originURL, originHost, customHeaders), renderProxyPassBlock(originURL, cfg)) + return fmt.Sprintf("server {\n listen 443 ssl http2;\n server_name %s;\n ssl_certificate %s;\n ssl_certificate_key %s;\n\n location / {\n%s%s }\n}\n\n", domain, certPath, keyPath, renderProxyHeaderBlock(originURL, originHost, customHeaders), renderProxyPassBlock(originURL, cfg)) } -func renderExactHostGuard(domain string) string { - return fmt.Sprintf(" if ($host != %q) {\n return 404;\n }\n", domain) +func renderConnectionUpgradeMap() string { + return " map $http_upgrade $connection_upgrade {\n default upgrade;\n '' close;\n }\n\n" +} + +func renderDefaultServerBlock() string { + return " server {\n listen 80 default_server;\n server_name _;\n\n return 404;\n }\n\n" } func renderProxyHeaderBlock(originURL string, originHost string, customHeaders []ProxyRouteCustomHeaderInput) string { @@ -799,7 +807,7 @@ func renderProxyHeaderBlock(originURL string, originHost string, customHeaders [ if common.OpenRestyWebsocketEnabled { builder.WriteString(" proxy_http_version 1.1;\n") builder.WriteString(" proxy_set_header Upgrade $http_upgrade;\n") - builder.WriteString(" proxy_set_header Connection $http_connection;\n") + builder.WriteString(" proxy_set_header Connection $connection_upgrade;\n") } for _, header := range customHeaders { builder.WriteString(fmt.Sprintf(" proxy_set_header %s %s;\n", header.Key, quoteNginxHeaderValue(header.Value))) diff --git a/openflare_server/service/https_phase1_test.go b/openflare_server/service/https_phase1_test.go index aeac2ff4..8c49c0f1 100644 --- a/openflare_server/service/https_phase1_test.go +++ b/openflare_server/service/https_phase1_test.go @@ -72,8 +72,8 @@ func TestCreateTLSCertificateAndRenderHTTPSConfig(t *testing.T) { if !strings.Contains(result.Version.RenderedConfig, "listen 443 ssl http2;") { t.Fatal("expected rendered config to include https server block with http2 enabled") } - if !strings.Contains(result.Version.RenderedConfig, `if ($host != "app.example.com") {`) { - t.Fatal("expected rendered config to reject unmatched host headers with 404") + if strings.Contains(result.Version.RenderedConfig, `if ($host != "app.example.com") {`) { + t.Fatal("expected rendered config to avoid per-route host guard") } if !strings.Contains(result.Version.RenderedConfig, "return 301 https://$host$request_uri;") { t.Fatal("expected rendered config to include http redirect") @@ -138,8 +138,8 @@ func TestPublishConfigVersionRendersCustomHeaders(t *testing.T) { if !strings.Contains(result.Version.RenderedConfig, "proxy_set_header Upgrade $http_upgrade;") { t.Fatal("expected rendered config to forward websocket upgrade header") } - if !strings.Contains(result.Version.RenderedConfig, "proxy_set_header Connection $http_connection;") { - t.Fatal("expected rendered config to forward websocket connection header") + if !strings.Contains(result.Version.RenderedConfig, "proxy_set_header Connection $connection_upgrade;") { + t.Fatal("expected rendered config to use normalized websocket connection header") } if !strings.Contains(result.Version.RenderedConfig, "proxy_pass https://origin.internal;") { t.Fatal("expected rendered config to keep direct proxy_pass when no resolvers are configured") @@ -262,7 +262,7 @@ func TestPreviewConfigVersionCanDisableWebsocketHeaders(t *testing.T) { if strings.Contains(preview.RenderedConfig, "proxy_set_header Upgrade $http_upgrade;") { t.Fatal("expected preview config to omit websocket upgrade header when disabled") } - if strings.Contains(preview.RenderedConfig, "proxy_set_header Connection $http_connection;") { + if strings.Contains(preview.RenderedConfig, "proxy_set_header Connection $connection_upgrade;") { t.Fatal("expected preview config to omit websocket connection header when disabled") } } @@ -411,7 +411,7 @@ func TestPreviewAndDiffConfigVersion(t *testing.T) { } } -func TestRenderConfigRejectsUnknownSubdomainHosts(t *testing.T) { +func TestRenderConfigUsesDefaultServerFallback(t *testing.T) { setupServiceTestDB(t) _, err := CreateProxyRoute(ProxyRouteInput{ @@ -431,11 +431,17 @@ func TestRenderConfigRejectsUnknownSubdomainHosts(t *testing.T) { if !strings.Contains(preview.RenderedConfig, `server_name git.arctel.net;`) { t.Fatal("expected rendered config to include exact server_name") } - if !strings.Contains(preview.RenderedConfig, `if ($host != "git.arctel.net") {`) { - t.Fatal("expected rendered config to guard against unknown subdomain host matches") + if strings.Contains(preview.RenderedConfig, `if ($host != "git.arctel.net") {`) { + t.Fatal("expected rendered config to avoid per-route host guard") } - if !strings.Contains(preview.RenderedConfig, "return 404;") { - t.Fatal("expected rendered config to return 404 when host does not exactly match route domain") + if !strings.Contains(preview.MainConfig, "listen 80 default_server;") { + t.Fatal("expected preview main config to include default http server") + } + if !strings.Contains(preview.MainConfig, "server_name _;") { + t.Fatal("expected preview main config to include default server_name") + } + if !strings.Contains(preview.MainConfig, "return 404;") { + t.Fatal("expected preview main config to return 404 for unmatched hosts") } } @@ -483,6 +489,12 @@ func TestOpenRestyMainConfigTemplateRenderAndValidate(t *testing.T) { if !strings.Contains(preview.MainConfig, "access_log __OPENFLARE_ACCESS_LOG__ openflare_json;") { t.Fatal("expected preview main config to preserve managed access log placeholder") } + if !strings.Contains(preview.MainConfig, "map $http_upgrade $connection_upgrade {") { + t.Fatal("expected preview main config to preserve managed websocket upgrade map") + } + if !strings.Contains(preview.MainConfig, "listen 80 default_server;") { + t.Fatal("expected preview main config to preserve managed default server block") + } invalidTemplate := strings.ReplaceAll( common.OpenRestyMainConfigTemplate, @@ -501,6 +513,15 @@ func TestOpenRestyMainConfigTemplateRenderAndValidate(t *testing.T) { if err := ValidateOpenRestyMainConfigTemplate(invalidTemplate); err == nil { t.Fatal("expected template without managed access log placeholder to fail validation") } + + invalidTemplate = strings.ReplaceAll( + common.OpenRestyMainConfigTemplate, + "{{OpenRestyConnectionUpgradeMap}}", + "", + ) + if err := ValidateOpenRestyMainConfigTemplate(invalidTemplate); err == nil { + t.Fatal("expected template without managed websocket upgrade map placeholder to fail validation") + } } func TestOpenRestyCommonRequestOptionsRender(t *testing.T) {