package nginxconfig import ( "regexp" "strings" "testing" "github.com/gin-gonic/gin" ) func TestRenderAllowlistAndHTTP01(t *testing.T) { routes := gin.RoutesInfo{ {Method: "GET", Path: "/api/health"}, {Method: "GET", Path: "/api/tunnels/:id/status"}, {Method: "POST", Path: "/api/tunnels/:id/start"}, {Method: "PUT", Path: "/api/tunnels/:id/start"}, } httpSite, httpsSite, err := Render("test-link.appx365.com", routes) if err != nil { t.Fatal(err) } for _, fragment := range []string{"server_name test-link.appx365.com;", "location ^~ /.well-known/acme-challenge/", "try_files $uri =404;", "location / { return 444; }"} { if !strings.Contains(httpSite, fragment) { t.Errorf("missing HTTP rule %q", fragment) } } for _, fragment := range []string{ "ssl_reject_handshake on;", "location = /api/health", "if ($host != test-link.appx365.com) { return 444; }", "location ~ ^/api/tunnels/[^/]+/status$", "location ~ ^/api/tunnels/[^/]+/start$", "location = /login", "location = /settings", "location = /favicon.svg", "location / { return 444; }", } { if !strings.Contains(httpsSite, fragment) { t.Errorf("missing HTTPS rule %q", fragment) } } if strings.Count(httpsSite, "location ~ ^/api/tunnels/[^/]+/start$") != 1 { t.Fatal("duplicate API paths must have one location") } pattern := regexp.MustCompile(routePattern("/api/tunnels/:id/status")) for _, input := range []string{"/api/tunnels/4/status", "/api/tunnels/abc/status"} { if !pattern.MatchString(input) { t.Errorf("valid route rejected: %s", input) } } for _, input := range []string{"/api/tunnels/4/status/evil", "/api/tunnels/4/config", "/.well-known/openid-configuration"} { if pattern.MatchString(input) { t.Errorf("unknown route accepted: %s", input) } } } func TestRenderRejectsInjectedDomainAndRoute(t *testing.T) { if _, _, err := Render("example.com; return 200", nil); err == nil { t.Fatal("injected domain accepted") } _, httpsSite, err := Render("example.com", gin.RoutesInfo{{Path: "/api/health\nreturn 200;"}}) if err != nil { t.Fatal(err) } if strings.Contains(httpsSite, "return 200;") { t.Fatal("injected route accepted") } } func TestRenderIdentityAndSingleAuthPort(t *testing.T) { _, site, err := RenderWithAuth("vpn.example.com", gin.RoutesInfo{{Method: "GET", Path: "/api/health"}}, 19090) if err != nil { t.Fatal(err) } for _, route := range []string{"location = /id/.well-known/openid-configuration", "location = /id/feishu/callback", "location = /id/token", "location ^~ /oauth2/", "proxy_pass http://127.0.0.1:19090;", "location / { return 444; }"} { if !strings.Contains(site, route) { t.Errorf("identity route missing: %s", route) } } if strings.Contains(site, "location ^~ /id/") || strings.Contains(site, "location ^~ /.well-known/") { t.Fatal("identity routes must stay allowlisted") } if _, _, err := RenderWithAuth("vpn.example.com", nil, 80); err == nil { t.Fatal("invalid auth port accepted") } _, withoutAuth, err := Render("vpn.example.com", nil) if err != nil || strings.Contains(withoutAuth, "location ^~ /oauth2/") { t.Fatal("auth component exposed without a configured port") } }