实测发现:入口端口上若没有匹配 server_name 的请求,nginx 会交给该端口排序 第一的 server 块,于是一个打错的域名会访问到别的服务(若第一条恰好是开放 路由,等于把该服务暴露在错误域名下)。现在为 HTTP/HTTPS 各生成一个 default_server 返回 404,并在单测中断言其存在。
137 lines
4.3 KiB
Go
137 lines
4.3 KiB
Go
package main
|
||
|
||
import "testing"
|
||
|
||
func TestRenderGatewayConfAuthPerRoute(t *testing.T) {
|
||
cfg := &GatewayConfig{
|
||
ListenPort: 8081,
|
||
AuthURL: "http://127.0.0.1:4001/_auth",
|
||
LoginURL: "https://login.example.com/login",
|
||
Routes: []Route{
|
||
{Host: "protected.example.com", Upstream: "http://10.0.0.1:3000", RequireLogin: true, Websocket: true, MaxBodyMB: 64},
|
||
{Host: "open.example.com", Upstream: "http://10.0.0.2:8000"},
|
||
},
|
||
}
|
||
out, err := RenderGatewayConf(cfg)
|
||
if err != nil {
|
||
t.Fatalf("渲染失败: %v", err)
|
||
}
|
||
// 受保护的域名要有 auth_request 与登录跳转
|
||
if !contains(out, "server_name protected.example.com;") {
|
||
t.Fatalf("缺少受保护域名的 server 块:\n%s", out)
|
||
}
|
||
if !contains(out, "auth_request /_auth;") {
|
||
t.Fatalf("受保护域名缺少 auth_request:\n%s", out)
|
||
}
|
||
if !contains(out, "location @login") {
|
||
t.Fatalf("缺少登录跳转")
|
||
}
|
||
if !contains(out, "proxy_set_header Upgrade $http_upgrade;") {
|
||
t.Fatalf("缺少 websocket 透传")
|
||
}
|
||
if !contains(out, "client_max_body_size 64m;") {
|
||
t.Fatalf("缺少上传上限")
|
||
}
|
||
// 不受保护的域名不得出现 auth_request("哪些服务受保护"是按路由决定的)
|
||
open := section(out, "server_name open.example.com;")
|
||
if open == "" {
|
||
t.Fatalf("缺少开放域名的 server 块")
|
||
}
|
||
if contains(open, "auth_request") {
|
||
t.Fatalf("未受保护的域名不应有 auth_request:\n%s", open)
|
||
}
|
||
// 兜底 server:未知域名必须被拒绝,而不是落进某条路由
|
||
if !contains(out, "default_server") || !contains(out, "return 404") {
|
||
t.Fatalf("缺少未知域名兜底 server:\n%s", out)
|
||
}
|
||
if contains(open, "client_max_body_size") {
|
||
t.Fatalf("未设置上限时不应生成 client_max_body_size:\n%s", open)
|
||
}
|
||
}
|
||
|
||
func TestRenderGatewayConfRejectsBadInput(t *testing.T) {
|
||
base := func(r Route) *GatewayConfig {
|
||
return &GatewayConfig{ListenPort: 8081, AuthURL: "http://x/_auth", Routes: []Route{r}}
|
||
}
|
||
cases := []struct {
|
||
name string
|
||
cfg *GatewayConfig
|
||
}{
|
||
{"空域名", base(Route{Host: "", Upstream: "http://a"})},
|
||
{"域名带协议", base(Route{Host: "http://a.com", Upstream: "http://a"})},
|
||
{"后端非 http", base(Route{Host: "a.com", Upstream: "10.0.0.1:80"})},
|
||
{"域名重复", &GatewayConfig{ListenPort: 8081, AuthURL: "http://x/_auth", Routes: []Route{
|
||
{Host: "a.com", Upstream: "http://a"}, {Host: "a.com", Upstream: "http://b"},
|
||
}}},
|
||
{"需认证但无认证地址", &GatewayConfig{ListenPort: 8081, Routes: []Route{{Host: "a.com", Upstream: "http://a", RequireLogin: true}}}},
|
||
{"启用HTTPS但无证书", &GatewayConfig{ListenPort: 8081, ListenHTTPS: true, Routes: []Route{{Host: "a.com", Upstream: "http://a"}}}},
|
||
}
|
||
for _, c := range cases {
|
||
if _, err := RenderGatewayConf(c.cfg); err == nil {
|
||
t.Errorf("%s: 应当报错但没有", c.name)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestGatewayRoundTrip(t *testing.T) {
|
||
dir := t.TempDir()
|
||
oldFile, oldOut := gatewayFile, gatewayOutDir
|
||
gatewayFile = dir + "/gateway.yml"
|
||
gatewayOutDir = dir + "/gateway"
|
||
defer func() { gatewayFile, gatewayOutDir = oldFile, oldOut }()
|
||
|
||
// 文件不存在时应给出默认配置而不是报错
|
||
cfg, err := loadGateway()
|
||
if err != nil {
|
||
t.Fatalf("缺文件应回落默认配置: %v", err)
|
||
}
|
||
if cfg.ListenPort != 8081 || len(cfg.Routes) != 0 {
|
||
t.Fatalf("默认配置不对: %+v", cfg)
|
||
}
|
||
cfg.Routes = []Route{{Host: "a.com", Upstream: "http://a", RequireLogin: true}}
|
||
if err := saveGateway(cfg); err != nil {
|
||
t.Fatalf("保存失败: %v", err)
|
||
}
|
||
got, err := loadGateway()
|
||
if err != nil || len(got.Routes) != 1 || !got.Routes[0].RequireLogin {
|
||
t.Fatalf("回读不一致: %+v err=%v", got, err)
|
||
}
|
||
}
|
||
|
||
func contains(hay, needle string) bool {
|
||
return len(hay) >= len(needle) && (func() bool {
|
||
for i := 0; i+len(needle) <= len(hay); i++ {
|
||
if hay[i:i+len(needle)] == needle {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
})()
|
||
}
|
||
|
||
// section 取出 server_name 所属的那个 server 块(到下一个 server{ 之前)
|
||
func section(all, marker string) string {
|
||
i := indexOf(all, marker)
|
||
if i < 0 {
|
||
return ""
|
||
}
|
||
start := i
|
||
for start > 0 && all[start-1] != '\n' {
|
||
start--
|
||
}
|
||
rest := all[i:]
|
||
if j := indexOf(rest, "\nserver {"); j >= 0 {
|
||
return rest[:j]
|
||
}
|
||
return rest
|
||
}
|
||
|
||
func indexOf(hay, needle string) int {
|
||
for i := 0; i+len(needle) <= len(hay); i++ {
|
||
if hay[i:i+len(needle)] == needle {
|
||
return i
|
||
}
|
||
}
|
||
return -1
|
||
}
|