94 lines
3.4 KiB
Go
94 lines
3.4 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"},
|
|
{Method: "PUT", Path: "/api/system/tls/domain"},
|
|
{Method: "PUT", Path: "/api/system/identity"},
|
|
{Method: "POST", Path: "/api/system/identity/gateway"},
|
|
}, 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 = /api/system/tls/domain", "location = /api/system/identity", "location = /api/system/identity/gateway", "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")
|
|
}
|
|
}
|