mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-06 14:12:09 +03:00
fix(api-token): let a node-sync master reach every node endpoint it calls
Invariant: every request runtime.Remote sends to a node is accepted under the node-sync scope, except /server/updatePanel, which #6201 withholds on purpose. The allowlist was written on 2026-08-15 and three Remote calls arrived after it without being added: /clients/activeInbounds (#6164, same day), /inbounds/:id/subSortIndex (#6179) and /clients/bulkResetTraffic (4210a50c). A master enrolled with a node-sync token or mTLS got 403 on each: the subscription sort order and multi-client traffic resets never reached the node, and online attribution silently fell back to email-only because FetchTrafficSnapshot logs that failure at debug level. TestMasterNodeContract drives every exported Remote method through the production router, once per enrollment scope, fails on any 401/403 the node returns even when Remote swallows it, and fails when a Remote method has no cell. It replaces TestNodeSyncScopeAllowlistMatchesRemoteInventory, a hand-copied duplicate of the allowlist that never read Remote and so missed all three; its updatePanel rule stays in TestNodeSyncScopeUsesFullPathPatterns. It also supersedes TestMasterPushWithAdminTokenAppliesClients (its UpdateInbound cell); that file's removal landed indae91276by a staging slip.
This commit is contained in:
@@ -98,6 +98,7 @@ var nodeSyncScopeAllow = map[string]map[string]struct{}{
|
||||
"/inbounds/add": {http.MethodPost: {}},
|
||||
"/inbounds/del/:id": {http.MethodPost: {}},
|
||||
"/inbounds/update/:id": {http.MethodPost: {}},
|
||||
"/inbounds/:id/subSortIndex": {http.MethodPost: {}},
|
||||
"/clients/add": {http.MethodPost: {}},
|
||||
"/clients/del/:email": {http.MethodPost: {}},
|
||||
"/clients/:email/detach": {http.MethodPost: {}},
|
||||
@@ -106,11 +107,13 @@ var nodeSyncScopeAllow = map[string]map[string]struct{}{
|
||||
"/server/getWebCertFiles": {http.MethodGet: {}},
|
||||
"/server/descendants": {http.MethodGet: {}},
|
||||
"/clients/resetTraffic/:email": {http.MethodPost: {}},
|
||||
"/clients/bulkResetTraffic": {http.MethodPost: {}},
|
||||
"/inbounds/resetAllTraffics": {http.MethodPost: {}},
|
||||
"/inbounds/:id/resetTraffic": {http.MethodPost: {}},
|
||||
"/clients/onlinesByGuid": {http.MethodPost: {}},
|
||||
"/clients/onlines": {http.MethodPost: {}},
|
||||
"/clients/lastOnline": {http.MethodPost: {}},
|
||||
"/clients/activeInbounds": {http.MethodPost: {}},
|
||||
"/inbounds/pushClientTraffics": {http.MethodPost: {}},
|
||||
"/server/clientIps": {http.MethodGet: {}, http.MethodPost: {}},
|
||||
"/clients/clientIpsByGuid": {http.MethodPost: {}},
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"net/http/cookiejar"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
@@ -138,39 +137,6 @@ func TestCheckAPIAuth_AcceptsVerifiedClientCert(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeSyncScopeAllowlistMatchesRemoteInventory(t *testing.T) {
|
||||
expected := map[string]map[string]struct{}{
|
||||
"/server/status": {http.MethodGet: {}},
|
||||
"/inbounds/list": {http.MethodGet: {}},
|
||||
"/inbounds/add": {http.MethodPost: {}},
|
||||
"/inbounds/del/:id": {http.MethodPost: {}},
|
||||
"/inbounds/update/:id": {http.MethodPost: {}},
|
||||
"/clients/add": {http.MethodPost: {}},
|
||||
"/clients/del/:email": {http.MethodPost: {}},
|
||||
"/clients/:email/detach": {http.MethodPost: {}},
|
||||
"/clients/update/:email": {http.MethodPost: {}},
|
||||
"/server/restartXrayService": {http.MethodPost: {}},
|
||||
"/server/getWebCertFiles": {http.MethodGet: {}},
|
||||
"/server/descendants": {http.MethodGet: {}},
|
||||
"/clients/resetTraffic/:email": {http.MethodPost: {}},
|
||||
"/inbounds/resetAllTraffics": {http.MethodPost: {}},
|
||||
"/inbounds/:id/resetTraffic": {http.MethodPost: {}},
|
||||
"/clients/onlinesByGuid": {http.MethodPost: {}},
|
||||
"/clients/onlines": {http.MethodPost: {}},
|
||||
"/clients/lastOnline": {http.MethodPost: {}},
|
||||
"/inbounds/pushClientTraffics": {http.MethodPost: {}},
|
||||
"/server/clientIps": {http.MethodGet: {}, http.MethodPost: {}},
|
||||
"/clients/clientIpsByGuid": {http.MethodPost: {}},
|
||||
"/hosts/list": {http.MethodGet: {}},
|
||||
}
|
||||
if !reflect.DeepEqual(nodeSyncScopeAllow, expected) {
|
||||
t.Fatalf("node-sync allowlist drift:\n got: %#v\nwant: %#v", nodeSyncScopeAllow, expected)
|
||||
}
|
||||
if _, ok := nodeSyncScopeAllow["/server/updatePanel"]; ok {
|
||||
t.Fatal("node-sync must not include /server/updatePanel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeSyncScopeUsesFullPathPatterns(t *testing.T) {
|
||||
engine, _ := newAPIAuthTestEngine(t)
|
||||
cases := []struct {
|
||||
|
||||
Reference in New Issue
Block a user