inbound_node_sync_test.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. package controller
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "net/url"
  6. "path/filepath"
  7. "strconv"
  8. "strings"
  9. "testing"
  10. "github.com/gin-gonic/gin"
  11. "github.com/mhsanaei/3x-ui/v3/internal/database"
  12. "github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
  13. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  14. "github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
  15. "github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
  16. )
  17. // A sub-node stores whatever the master pushes. A master row whose certificate
  18. // predates the TLS guard must still land, or the node silently falls out of sync.
  19. func TestNodeSyncPushSkipsOperatorTLSGuard(t *testing.T) {
  20. gin.SetMode(gin.TestMode)
  21. dbDir := t.TempDir()
  22. t.Setenv("XUI_DB_FOLDER", dbDir)
  23. dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
  24. prev := runtime.GetManager()
  25. runtime.SetManager(runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }, SetNeedRestart: func() {}}))
  26. t.Cleanup(func() { runtime.SetManager(prev) })
  27. for name, scope := range map[string]string{
  28. "node-sync": model.ApiScopeNodeSync,
  29. "node-admin": model.ApiScopeNodeAdmin,
  30. "admin": model.ApiScopeAdmin,
  31. } {
  32. row := &model.ApiToken{Name: name, Token: crypto.HashTokenSHA256(name + "-token"), Enabled: true, Scope: scope}
  33. if err := database.GetDB().Create(row).Error; err != nil {
  34. t.Fatalf("seed %s token: %v", name, err)
  35. }
  36. }
  37. engine := gin.New()
  38. a := &APIController{}
  39. api := engine.Group("/panel/api")
  40. api.Use(a.checkAPIAuth, a.enforceTokenScope)
  41. NewInboundController(api.Group("/inbounds"))
  42. const legacyStream = `{"network":"tcp","security":"tls","tlsSettings":{"certificates":[{"certificateFile":"","keyFile":"","certificate":[],"key":[]}]}}`
  43. add := func(t *testing.T, token string, port int) string {
  44. t.Helper()
  45. form := url.Values{
  46. "protocol": {"vless"},
  47. "port": {strconv.Itoa(port)},
  48. "tag": {"tls-legacy-" + strconv.Itoa(port)},
  49. "enable": {"true"},
  50. "settings": {`{"clients":[]}`},
  51. "streamSettings": {legacyStream},
  52. }
  53. req := httptest.NewRequest(http.MethodPost, "/panel/api/inbounds/add", strings.NewReader(form.Encode()))
  54. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  55. req.Header.Set("Authorization", "Bearer "+token)
  56. w := httptest.NewRecorder()
  57. engine.ServeHTTP(w, req)
  58. return w.Body.String()
  59. }
  60. rows := func(t *testing.T, tag string) int64 {
  61. t.Helper()
  62. var n int64
  63. if err := database.GetDB().Model(&model.Inbound{}).Where("tag = ?", tag).Count(&n).Error; err != nil {
  64. t.Fatalf("count %s: %v", tag, err)
  65. }
  66. return n
  67. }
  68. t.Run("a master push lands on the node", func(t *testing.T) {
  69. body := add(t, "node-sync-token", 45001)
  70. if !strings.Contains(body, `"success":true`) {
  71. t.Fatalf("node-sync add rejected: %s", body)
  72. }
  73. if got := rows(t, "tls-legacy-45001"); got != 1 {
  74. t.Fatalf("stored rows = %d, want 1", got)
  75. }
  76. })
  77. t.Run("a node admin push keeps node sync semantics", func(t *testing.T) {
  78. body := add(t, "node-admin-token", 45003)
  79. if !strings.Contains(body, `"success":true`) {
  80. t.Fatalf("node-admin add rejected: %s", body)
  81. }
  82. if got := rows(t, "tls-legacy-45003"); got != 1 {
  83. t.Fatalf("stored rows = %d, want 1", got)
  84. }
  85. })
  86. t.Run("an operator token is still held to the guard", func(t *testing.T) {
  87. body := add(t, "admin-token", 45002)
  88. if !strings.Contains(body, `"success":false`) || !strings.Contains(body, "TLS") {
  89. t.Fatalf("admin add should fail on TLS, got: %s", body)
  90. }
  91. if got := rows(t, "tls-legacy-45002"); got != 0 {
  92. t.Fatalf("stored rows = %d, want 0", got)
  93. }
  94. })
  95. }