Files
pangolin/server/internal/originauth/originauth_test.go
T
wangjia 7d89ec9d91 feat(infra/domains): 域名池 + CDN 前置 + 签名端点分发 (tsk_NU9JuUweHWMt)
- domains.md: 四组域名隔离登记 + 冷备池 ≥5 + 启用流程(不含身份信息)
- cdn/terraform: Cloudflare 配置即代码(WAF/bot/速率限制/代理DNS/回源鉴权注入)+ 30min 重放 Runbook
- server/internal/originauth: 回源鉴权中间件,非 CDN 网段或鉴权头不符一律 403,支持双值轮换
- tools/endpoint-signer: 离线 Ed25519 签名 CLI(端点 + 公告文档,单调版本防回滚,key_id 双公钥轮换)
- tools/publish-mirrors: ≥3 镜像发布 + hash 一致性校验 + 故障转移取回
- CLIENT-CONTRACT.md: schema/验签/防回滚/合并/兜底链/channel 客户端契约
- 出站独立出口要求写入部署文档;私钥/token/身份信息一律不入库

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 14:21:55 +08:00

136 lines
3.8 KiB
Go

package originauth
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/go-chi/chi/v5"
)
func testMW(t *testing.T) *Middleware {
t.Helper()
mw, err := New(Config{
Current: "secret-current",
Previous: "secret-previous",
AllowedCIDRs: []string{"203.0.113.0/24", "2001:db8::/32"},
})
if err != nil {
t.Fatalf("New: %v", err)
}
return mw
}
func do(t *testing.T, mw *Middleware, remoteAddr, headerVal string) int {
t.Helper()
h := mw.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}))
req := httptest.NewRequest(http.MethodGet, "/v1/nodes", nil)
req.RemoteAddr = remoteAddr
if headerVal != "" {
req.Header.Set(DefaultHeader, headerVal)
}
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
return rec.Code
}
func TestAllowedIPCurrentValue(t *testing.T) {
mw := testMW(t)
if code := do(t, mw, "203.0.113.5:443", "secret-current"); code != http.StatusOK {
t.Fatalf("want 200, got %d", code)
}
}
func TestAllowedIPPreviousValueDuringRotation(t *testing.T) {
mw := testMW(t)
if code := do(t, mw, "203.0.113.5:443", "secret-previous"); code != http.StatusOK {
t.Fatalf("rotation window: want 200, got %d", code)
}
}
func TestAllowedIPWrongValue(t *testing.T) {
mw := testMW(t)
if code := do(t, mw, "203.0.113.5:443", "nope"); code != http.StatusForbidden {
t.Fatalf("want 403, got %d", code)
}
}
func TestAllowedIPMissingValue(t *testing.T) {
mw := testMW(t)
if code := do(t, mw, "203.0.113.5:443", ""); code != http.StatusForbidden {
t.Fatalf("want 403, got %d", code)
}
}
func TestDirectConnectBypassRejected(t *testing.T) {
mw := testMW(t)
// Correct header but a source IP outside the CDN range = someone hitting the
// origin directly. Must be 403.
if code := do(t, mw, "198.51.100.7:55000", "secret-current"); code != http.StatusForbidden {
t.Fatalf("direct connect must be 403, got %d", code)
}
}
func TestIPv6Allowed(t *testing.T) {
mw := testMW(t)
if code := do(t, mw, "[2001:db8::1]:443", "secret-current"); code != http.StatusOK {
t.Fatalf("want 200 for allowed v6, got %d", code)
}
}
func TestForgedXFFDoesNotBypass(t *testing.T) {
mw := testMW(t)
h := mw.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/v1/nodes", nil)
req.RemoteAddr = "198.51.100.7:55000" // real peer: not a CDN range
req.Header.Set("X-Forwarded-For", "203.0.113.5")
req.Header.Set(DefaultHeader, "secret-current")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("forged XFF must not bypass, got %d", rec.Code)
}
}
func TestNewValidation(t *testing.T) {
if _, err := New(Config{AllowedCIDRs: []string{"203.0.113.0/24"}}); err == nil {
t.Fatal("want error: missing Current")
}
if _, err := New(Config{Current: "x"}); err == nil {
t.Fatal("want error: missing CIDRs")
}
if _, err := New(Config{Current: "x", AllowedCIDRs: []string{"not-a-cidr"}}); err == nil {
t.Fatal("want error: bad CIDR")
}
}
func TestIntegrationWithChiRouter(t *testing.T) {
mw := testMW(t)
r := chi.NewRouter()
r.Use(mw.Handler)
r.Get("/v1/nodes", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("nodes"))
})
srv := httptest.NewServer(r)
defer srv.Close()
// Through-the-stack request with a non-CDN client IP (httptest loopback)
// must be rejected even with the correct header.
req, _ := http.NewRequest(http.MethodGet, srv.URL+"/v1/nodes", nil)
req.Header.Set(DefaultHeader, "secret-current")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusForbidden {
t.Fatalf("loopback (non-CDN) should be 403, got %d", resp.StatusCode)
}
}