fix(node): keep master requests on the node they were sent to

The master's node clients followed redirects to any host, presenting the
same panel-wide credentials (the mTLS client certificate) and replaying
the request there, so one node could have the master push to another.
Node clients and the certificate-pin probe now stop at a redirect off
the original host:port and report the 3xx as the node's answer. The
probe change is wiring; TestNodeClientsDoNotFollowARedirectToAnotherHost
covers the guard.
This commit is contained in:
MHSanaei
2026-10-31 09:14:57 +00:00
parent f1e1b0fb0c
commit 08482154c2
3 changed files with 44 additions and 1 deletions
+31
View File
@@ -662,3 +662,34 @@ func TestDecodeCertPin(t *testing.T) {
})
}
}
// The master presents the same credentials to every node; a node's redirect must not
// carry a request (a push, the mTLS certificate) to another host.
func TestNodeClientsDoNotFollowARedirectToAnotherHost(t *testing.T) {
var otherHit atomic.Int32
other := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { otherHit.Add(1) }))
defer other.Close()
hostile := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, other.URL+"/panel/api/inbounds/update/1", http.StatusTemporaryRedirect)
}))
defer hostile.Close()
u, _ := url.Parse(hostile.URL)
port, _ := strconv.Atoi(u.Port())
for _, node := range []*model.Node{
{Id: 41, Address: u.Hostname(), Port: port, Scheme: "https", TlsVerifyMode: "skip", AllowPrivateAddress: true},
{Id: 42, Address: u.Hostname(), Port: port, Scheme: "http", AllowPrivateAddress: true},
} {
client, err := HTTPClientForNode(node, "")
if err != nil {
t.Fatalf("HTTPClientForNode: %v", err)
}
req, _ := http.NewRequestWithContext(netsafe.ContextWithAllowPrivate(context.Background(), true),
http.MethodPost, hostile.URL+"/panel/api/inbounds/list", strings.NewReader("{}"))
if resp, err := client.Do(req); err == nil {
_ = resp.Body.Close()
}
}
if otherHit.Load() != 0 {
t.Fatalf("a node's redirect reached another host %d time(s)", otherHit.Load())
}
}