Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73ee4e74c7 |
+4
-1
@@ -137,6 +137,7 @@ func runServe(ctx context.Context, cfg config.Config) error {
|
||||
sess.SetPresence(presApp)
|
||||
uplink.presence = presApp
|
||||
|
||||
trustedNets := httpx.ParseCIDRs(cfg.TrustedProxies)
|
||||
idApp := identity.New(identity.Config{
|
||||
DB: db,
|
||||
Hash: hashPool,
|
||||
@@ -145,6 +146,9 @@ func runServe(ctx context.Context, cfg config.Config) error {
|
||||
MaxScheduleSeconds: int64(cfg.Limits.MaxScheduleSeconds),
|
||||
Logger: slog.Default(),
|
||||
ConnControl: brk,
|
||||
ClientIP: func(r *http.Request) string {
|
||||
return httpx.ClientIP(r, trustedNets)
|
||||
},
|
||||
})
|
||||
uplink.identity = idApp
|
||||
|
||||
@@ -161,7 +165,6 @@ func runServe(ctx context.Context, cfg config.Config) error {
|
||||
return fmt.Errorf("message recover: %w", recoverErr)
|
||||
}
|
||||
|
||||
trustedNets := httpx.ParseCIDRs(cfg.TrustedProxies)
|
||||
adminHandler := admin.New(admin.Deps{
|
||||
DB: db,
|
||||
Hash: hashPool,
|
||||
|
||||
@@ -1092,3 +1092,12 @@
|
||||
- 原因:同一连接上 `group.create`/`group.add` 同步向本连接注入下行时,与 mochi InlineClient 互相等待,`resp` 回不去(`TestUplinkDMOfflineGroupRecall` 在清掉测试客户端 dial deadline 后稳定复现)。
|
||||
- 备选方案:broker 层对 Inline 发布做无锁队列。
|
||||
- 影响:`group_event` 可能略晚于 `resp` 到达;业务结果仍以 `resp` 为准。
|
||||
|
||||
### 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"
|
||||
|
||||
"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/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) {
|
||||
env := openTestEnv(t)
|
||||
env.setRegistration(t, true, "taken-code")
|
||||
|
||||
Reference in New Issue
Block a user