Files
opsx-vpn/backend/internal/nginxconfig/nginxconfig_test.go
T

89 lines
3.1 KiB
Go

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")
}
}