fix: 合并自助注册按 trusted_proxies 解析客户端 IP
This commit is contained in:
+4
-1
@@ -137,6 +137,7 @@ func runServe(ctx context.Context, cfg config.Config) error {
|
|||||||
sess.SetPresence(presApp)
|
sess.SetPresence(presApp)
|
||||||
uplink.presence = presApp
|
uplink.presence = presApp
|
||||||
|
|
||||||
|
trustedNets := httpx.ParseCIDRs(cfg.TrustedProxies)
|
||||||
idApp := identity.New(identity.Config{
|
idApp := identity.New(identity.Config{
|
||||||
DB: db,
|
DB: db,
|
||||||
Hash: hashPool,
|
Hash: hashPool,
|
||||||
@@ -145,6 +146,9 @@ func runServe(ctx context.Context, cfg config.Config) error {
|
|||||||
MaxScheduleSeconds: int64(cfg.Limits.MaxScheduleSeconds),
|
MaxScheduleSeconds: int64(cfg.Limits.MaxScheduleSeconds),
|
||||||
Logger: slog.Default(),
|
Logger: slog.Default(),
|
||||||
ConnControl: brk,
|
ConnControl: brk,
|
||||||
|
ClientIP: func(r *http.Request) string {
|
||||||
|
return httpx.ClientIP(r, trustedNets)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
uplink.identity = idApp
|
uplink.identity = idApp
|
||||||
|
|
||||||
@@ -161,7 +165,6 @@ func runServe(ctx context.Context, cfg config.Config) error {
|
|||||||
return fmt.Errorf("message recover: %w", recoverErr)
|
return fmt.Errorf("message recover: %w", recoverErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
trustedNets := httpx.ParseCIDRs(cfg.TrustedProxies)
|
|
||||||
adminHandler := admin.New(admin.Deps{
|
adminHandler := admin.New(admin.Deps{
|
||||||
DB: db,
|
DB: db,
|
||||||
Hash: hashPool,
|
Hash: hashPool,
|
||||||
|
|||||||
@@ -1110,3 +1110,12 @@
|
|||||||
- 原因:`completed` 不在回执枚举(accepted|recalled|expired|dropped|rejected)内,会误导 SDK/后台。
|
- 原因:`completed` 不在回执枚举(accepted|recalled|expired|dropped|rejected)内,会误导 SDK/后台。
|
||||||
- 备选方案:沿用 `completed`(违反协议)。
|
- 备选方案:沿用 `completed`(违反协议)。
|
||||||
- 影响:仅修正解散路径回执字段;不改 emit / PublishDown。
|
- 影响:仅修正解散路径回执字段;不改 emit / PublishDown。
|
||||||
|
|
||||||
|
### fix-issue-2
|
||||||
|
|
||||||
|
1. **自助注册接入 trusted_proxies 客户端 IP**
|
||||||
|
- 原条款:PRD F23 / D18 注册安全码按来源 IP 锁定;DEVELOPMENT 4.5 来自受信代理时用 `X-Forwarded-For`;I1.4 曾写「经代理部署时接线方必须注入真实 IP」。
|
||||||
|
- 实际做法:`cmd/nixmsg/serve.go` 在 `identity.New` 注入与管理接口相同的 `httpx.ClientIP(r, trustedNets)`;不改锁定阈值与注册开关/安全码语义,不在 identity 内复制解析。
|
||||||
|
- 原因:L-WIRE 已挂注册 Handler,管理与 WS 已接 `trusted_proxies`,唯独注册漏接,反向代理后会把安全码锁定计到代理 IP。
|
||||||
|
- 备选方案:在 listener 层统一改写 `RemoteAddr` 后再交给注册 Handler。
|
||||||
|
- 影响:经受信代理开放注册时,输错安全码按真实客户端 IP 锁定。
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.asio.asia/nixevol/NixMsg/internal/auth"
|
"git.asio.asia/nixevol/NixMsg/internal/auth"
|
||||||
|
"git.asio.asia/nixevol/NixMsg/internal/httpx"
|
||||||
"git.asio.asia/nixevol/NixMsg/internal/protocol"
|
"git.asio.asia/nixevol/NixMsg/internal/protocol"
|
||||||
"git.asio.asia/nixevol/NixMsg/internal/store"
|
"git.asio.asia/nixevol/NixMsg/internal/store"
|
||||||
)
|
)
|
||||||
@@ -309,6 +310,83 @@ func TestRegisterF23_WrongCodeLock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRegisterTrustedProxyClientIPLock 验证与管理接口相同的 httpx.ClientIP:
|
||||||
|
// 受信代理的 X-Forwarded-For 按真实客户端 IP 计锁;非信任来源不采信转发头。
|
||||||
|
func TestRegisterTrustedProxyClientIPLock(t *testing.T) {
|
||||||
|
db, err := store.Open(t.TempDir(), "FULL")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { _ = db.Close() })
|
||||||
|
|
||||||
|
locks := newRegisterIPLocker(time.Now)
|
||||||
|
trusted := httpx.ParseCIDRs([]string{"127.0.0.1/32"})
|
||||||
|
handler := NewRegisterHandler(RegisterConfig{
|
||||||
|
DB: db,
|
||||||
|
Hash: auth.NewStubHashPool(),
|
||||||
|
Locks: locks,
|
||||||
|
ClientIP: func(r *http.Request) string {
|
||||||
|
return httpx.ClientIP(r, trusted)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
env := &testEnv{db: db, hash: auth.NewStubHashPool(), locks: locks, handler: handler}
|
||||||
|
env.setRegistration(t, true, "proxy-lock-1")
|
||||||
|
|
||||||
|
post := func(remote, xff, body string) (int, registerResp) {
|
||||||
|
t.Helper()
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/client/register", strings.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.RemoteAddr = remote
|
||||||
|
if xff != "" {
|
||||||
|
req.Header.Set("X-Forwarded-For", xff)
|
||||||
|
}
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
var resp registerResp
|
||||||
|
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
||||||
|
t.Fatalf("decode: %v body=%s", err, rr.Body.String())
|
||||||
|
}
|
||||||
|
return rr.Code, resp
|
||||||
|
}
|
||||||
|
|
||||||
|
wrong := `{"registration_code":"wrong-code","id":"ep_px","login_password":"password1"}`
|
||||||
|
good := `{"registration_code":"proxy-lock-1","id":"ep_px","login_password":"password1"}`
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
code, resp := post("127.0.0.1:9000", "198.51.100.7", wrong)
|
||||||
|
if code != http.StatusForbidden || resp.Error == nil || resp.Error.Code != protocol.CodeRegistrationCodeInvalid {
|
||||||
|
t.Fatalf("trusted fail #%d: status=%d resp=%+v", i+1, code, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code, resp := post("127.0.0.1:9000", "198.51.100.7", good)
|
||||||
|
if code != http.StatusTooManyRequests || resp.Error == nil || resp.Error.Code != protocol.CodeRateLimited {
|
||||||
|
t.Fatalf("real client should be locked: status=%d resp=%+v", code, resp)
|
||||||
|
}
|
||||||
|
code, resp = post("127.0.0.1:9000", "198.51.100.8", good)
|
||||||
|
if code != http.StatusOK || !resp.OK || resp.Data.ID != "ep_px" {
|
||||||
|
t.Fatalf("other XFF client must not share lock: status=%d resp=%+v", code, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非信任对端:忽略 XFF,按 RemoteAddr 计锁。
|
||||||
|
locks.Clear(auth.LockKey{Kind: auth.LockRegisterIP, IP: "198.51.100.7"})
|
||||||
|
locks.Clear(auth.LockKey{Kind: auth.LockRegisterIP, IP: "203.0.113.50"})
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
code, resp := post("203.0.113.50:4433", "198.51.100.7", wrong)
|
||||||
|
if code != http.StatusForbidden || resp.Error == nil || resp.Error.Code != protocol.CodeRegistrationCodeInvalid {
|
||||||
|
t.Fatalf("untrusted fail #%d: status=%d resp=%+v", i+1, code, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code, resp = post("203.0.113.50:4433", "198.51.100.7", `{"registration_code":"proxy-lock-1","id":"ep_px2","login_password":"password1"}`)
|
||||||
|
if code != http.StatusTooManyRequests || resp.Error == nil || resp.Error.Code != protocol.CodeRateLimited {
|
||||||
|
t.Fatalf("untrusted RemoteAddr should be locked: status=%d resp=%+v", code, resp)
|
||||||
|
}
|
||||||
|
// 若误采信 XFF,198.51.100.7 会已锁;直连该 IP 应仍可注册。
|
||||||
|
code, resp = post("198.51.100.7:5555", "", `{"registration_code":"proxy-lock-1","id":"ep_px3","login_password":"password1"}`)
|
||||||
|
if code != http.StatusOK || !resp.OK || resp.Data.ID != "ep_px3" {
|
||||||
|
t.Fatalf("spoofed XFF must not lock real client: status=%d resp=%+v", code, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRegisterF23_IDTakenKeepsOriginal(t *testing.T) {
|
func TestRegisterF23_IDTakenKeepsOriginal(t *testing.T) {
|
||||||
env := openTestEnv(t)
|
env := openTestEnv(t)
|
||||||
env.setRegistration(t, true, "taken-code")
|
env.setRegistration(t, true, "taken-code")
|
||||||
|
|||||||
Reference in New Issue
Block a user