2ea2111420
build / build (push) Successful in 1m1s
Nginx 使用原始 Host 和端口转发,不修改后端跨站防护。补充无 Fetch Metadata 的 LAN/Tailscale 同源和异源、异端口、转发头伪造回归。 验证:配置回归先因丢失端口失败后通过;HTTP竞态、真实隔离Nginx正反例及完整make verify均通过。部署前真实入口无Metadata为403,同源Metadata对照可过;源库及人工复核无写入。
63 lines
2.6 KiB
Go
63 lines
2.6 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"net/http/httptest"
|
|
"os"
|
|
"regexp"
|
|
"testing"
|
|
)
|
|
|
|
// HTTP LAN/tailnet origins may omit Fetch Metadata. nginx must preserve the
|
|
// external authority including its port so Origin can be checked without it.
|
|
func TestQAProxyPreservesBrowserAuthority(t *testing.T) {
|
|
config, err := os.ReadFile("../../deploy/nginx.conf")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hosts := regexp.MustCompile(`(?m)^\s*proxy_set_header\s+Host\s+([^;]+);`).FindAllStringSubmatch(string(config), -1)
|
|
if len(hosts) < 2 {
|
|
t.Fatal("API and health proxy Host directives must both be explicit")
|
|
}
|
|
for _, host := range hosts {
|
|
if host[1] != "$http_host" {
|
|
t.Fatalf("proxy drops browser authority/port: got %q, want $http_host", host[1])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQAIntentWithoutFetchMetadataChecksExactAuthority(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name, host, origin, forwarded, site, intent string
|
|
allowed bool
|
|
}{
|
|
{"LAN same authority", "192.168.50.134:8088", "http://192.168.50.134:8088", "", "", "qa-quality", true},
|
|
{"tailnet same authority", "100.92.49.78:8088", "http://100.92.49.78:8088", "", "", "qa-quality", true},
|
|
{"proxy stripped port", "192.168.50.134", "http://192.168.50.134:8088", "", "", "qa-quality", false},
|
|
{"foreign origin", "192.168.50.134:8088", "http://foreign.invalid:8088", "", "", "qa-quality", false},
|
|
{"different port", "192.168.50.134:8088", "http://192.168.50.134:8089", "", "", "qa-quality", false},
|
|
{"forwarded header cannot grant", "192.168.50.134:8088", "http://foreign.invalid:8088", "foreign.invalid:8088", "", "qa-quality", false},
|
|
{"opaque origin", "192.168.50.134:8088", "null", "", "", "qa-quality", false},
|
|
{"explicit cross site", "192.168.50.134:8088", "http://192.168.50.134:8088", "", "cross-site", "qa-quality", false},
|
|
{"missing intent", "192.168.50.134:8088", "http://192.168.50.134:8088", "", "", "", false},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := httptest.NewRequest("POST", "http://"+tc.host+"/api/v1/qa-ops/quality/query", nil)
|
|
req.Header.Set("Origin", tc.origin)
|
|
req.Header.Set("X-Governance-Action", tc.intent)
|
|
if tc.forwarded != "" {
|
|
req.Header.Set("X-Forwarded-Host", tc.forwarded)
|
|
}
|
|
if tc.site != "" {
|
|
req.Header.Set("Sec-Fetch-Site", tc.site)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
if got := authorizeQAIntent(rec, req, "qa-quality"); got != tc.allowed {
|
|
t.Fatalf("authorized=%v want %v, HTTP=%d", got, tc.allowed, rec.Code)
|
|
}
|
|
if !tc.allowed && rec.Code != 403 {
|
|
t.Fatalf("rejected authority should be 403, got %d", rec.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|