command_test.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. package cli
  2. import (
  3. "path/filepath"
  4. "reflect"
  5. "strings"
  6. "testing"
  7. "debtik/internal/aerotun"
  8. "debtik/internal/auth"
  9. "debtik/internal/sdwan"
  10. "debtik/internal/vpn"
  11. )
  12. func TestQuotedCommandArguments(t *testing.T) {
  13. got, err := splitCommand(`/interface aerotun set test comment="office uplink" psk='literal $\\key' name=""`)
  14. want := []string{"/interface", "aerotun", "set", "test", "comment=office uplink", `psk=literal $\\key`, "name="}
  15. if err != nil || !reflect.DeepEqual(got, want) {
  16. t.Fatalf("%q %v", got, err)
  17. }
  18. if _, err := splitCommand(`comment="unfinished`); err == nil {
  19. t.Fatal("accepted unterminated quote")
  20. }
  21. }
  22. func TestBareLogAndHandlerRecovery(t *testing.T) {
  23. e := &Executor{}
  24. user := &auth.PublicUser{Role: auth.RoleFull}
  25. output, _ := e.Execute("/log", user)
  26. if commandExitStatus(output) != 0 {
  27. t.Fatal(output)
  28. }
  29. output, _ = e.Execute("/interface print", user) // absent manager simulates a handler failure
  30. if commandExitStatus(output) == 0 || !strings.Contains(output, "still running") {
  31. t.Fatal(output)
  32. }
  33. output, _ = e.Execute("nonsense", user)
  34. if commandExitStatus(output) == 0 {
  35. t.Fatal("unknown command returned success")
  36. }
  37. output, _ = e.Execute("/help", user)
  38. if commandExitStatus(output) != 0 {
  39. t.Fatal("help returned failure")
  40. }
  41. }
  42. func TestAeroTUNCLIConfigurationAndPermissions(t *testing.T) {
  43. m := aerotun.NewManagerWithPath(filepath.Join(t.TempDir(), "aerotun.json"))
  44. defer m.StopAll()
  45. e := &Executor{vpnMgr: &vpn.Manager{AeroTUN: m}}
  46. admin := &auth.PublicUser{Role: auth.RoleFull}
  47. psk := strings.Repeat("12", 32)
  48. command := `/interface aerotun add name=atest mode=server local-address=10.90.0.1/30 peer-address=10.90.0.2/32 allowed-ips=10.90.0.2/32 psk=` + psk + ` comment="office uplink"`
  49. output, _ := e.Execute(command, admin)
  50. if commandExitStatus(output) != 0 {
  51. t.Fatal(output)
  52. }
  53. inst := m.List()[0]
  54. if !inst.Disabled || inst.PublicKey == "" || inst.Comment != "office uplink" {
  55. t.Fatal("draft configuration not retained")
  56. }
  57. for _, role := range []auth.Role{auth.RoleRead, auth.RoleWrite} {
  58. output, _ = e.Execute("/interface aerotun remove atest", &auth.PublicUser{Role: role})
  59. if commandExitStatus(output) == 0 {
  60. t.Fatal("non-admin removed tunnel")
  61. }
  62. }
  63. output, _ = e.Execute("/interface aerotun print detail=yes", admin)
  64. if strings.Contains(output, psk) || strings.Contains(output, inst.PrivateKey) || !strings.Contains(output, inst.PublicKey) {
  65. t.Fatal("secret disclosure or missing public identity")
  66. }
  67. output, _ = e.Execute(`/interface aerotun set atest comment="updated uplink"`, admin)
  68. if commandExitStatus(output) != 0 {
  69. t.Fatal(output)
  70. }
  71. updated, _ := m.Get(inst.ID)
  72. if updated.PSK != psk || updated.PublicKey != inst.PublicKey {
  73. t.Fatal("edit changed identity")
  74. }
  75. output, _ = e.Execute("/interface aerotun remove atest", admin)
  76. if commandExitStatus(output) != 0 || len(m.List()) != 0 {
  77. t.Fatal(output)
  78. }
  79. }
  80. func TestVXLANCLIConfigurationAndPermissions(t *testing.T) {
  81. vxlanMgr := vpn.NewVXLANManager(filepath.Join(t.TempDir(), "vxlan.json"))
  82. e := &Executor{vpnMgr: &vpn.Manager{VXLAN: vxlanMgr}}
  83. admin := &auth.PublicUser{Role: auth.RoleFull}
  84. viewer := &auth.PublicUser{Role: auth.RoleRead}
  85. // 1. Add VXLAN
  86. output, _ := e.Execute(`/interface vxlan add name=vxlan99 vni=99 port=4789 mtu=1500 comment="test overlay"`, admin)
  87. if strings.Contains(output, "Error") {
  88. t.Fatalf("unexpected error adding vxlan: %s", output)
  89. }
  90. // 2. Print VXLAN
  91. output, _ = e.Execute("/interface vxlan print", viewer)
  92. if !strings.Contains(output, "vxlan99") || !strings.Contains(output, "99") {
  93. t.Fatalf("vxlan99 not found in print output: %s", output)
  94. }
  95. // 3. Add VTEP
  96. output, _ = e.Execute("/interface vxlan vteps add interface=vxlan99 remote-ip=192.168.1.50 comment=peera", admin)
  97. if strings.Contains(output, "Error") {
  98. t.Fatalf("unexpected error adding vtep: %s", output)
  99. }
  100. // 4. Print VTEP
  101. output, _ = e.Execute("/interface vxlan vteps print", viewer)
  102. if !strings.Contains(output, "192.168.1.50") {
  103. t.Fatalf("vtep not found in print output: %s", output)
  104. }
  105. // 5. Viewer cannot modify
  106. output, _ = e.Execute("/interface vxlan remove vxlan99", viewer)
  107. if !strings.Contains(output, "Permission denied") {
  108. t.Fatalf("expected permission denied for viewer, got: %s", output)
  109. }
  110. // 6. Set comment
  111. output, _ = e.Execute(`/interface vxlan set vxlan99 comment="updated vxlan"`, admin)
  112. if strings.Contains(output, "Error") {
  113. t.Fatalf("unexpected error setting vxlan: %s", output)
  114. }
  115. // 7. Remove VTEP
  116. output, _ = e.Execute("/interface vxlan vteps remove interface=vxlan99 remote-ip=192.168.1.50", admin)
  117. if strings.Contains(output, "Error") {
  118. t.Fatalf("unexpected error removing vtep: %s", output)
  119. }
  120. // 8. Remove VXLAN
  121. output, _ = e.Execute("/interface vxlan remove vxlan99", admin)
  122. if strings.Contains(output, "Error") {
  123. t.Fatalf("unexpected error removing vxlan: %s", output)
  124. }
  125. }
  126. func TestSDWANCLIConfigurationAndPermissions(t *testing.T) {
  127. sdwanMgr := sdwan.NewManager(filepath.Join(t.TempDir(), "sdwan.json"))
  128. defer sdwanMgr.Stop()
  129. e := &Executor{sdwanMgr: sdwanMgr}
  130. admin := &auth.PublicUser{Role: auth.RoleFull}
  131. viewer := &auth.PublicUser{Role: auth.RoleRead}
  132. // 1. Add SD-WAN group
  133. output, _ := e.Execute(`/ip sdwan add name=sdwan0 mode=weighted auto-nat=yes probe-interval=5 comment="Multi-WAN LB"`, admin)
  134. if strings.Contains(output, "Error") {
  135. t.Fatalf("unexpected error adding sdwan group: %s", output)
  136. }
  137. // 2. Print SD-WAN groups
  138. output, _ = e.Execute("/ip sdwan print", viewer)
  139. if !strings.Contains(output, "sdwan0") || !strings.Contains(output, "weighted") {
  140. t.Fatalf("sdwan0 not found in print output: %s", output)
  141. }
  142. // 3. Status
  143. output, _ = e.Execute("/ip sdwan status", viewer)
  144. if !strings.Contains(output, "SD-WAN Telemetry Status") {
  145. t.Fatalf("status output unexpected: %s", output)
  146. }
  147. // 4. Add Members
  148. output, _ = e.Execute("/ip sdwan member add group=sdwan0 interface=eth0 gateway=192.168.1.1 weight=70 target-probe=1.1.1.1", admin)
  149. if strings.Contains(output, "Error") {
  150. t.Fatalf("error adding member eth0: %s", output)
  151. }
  152. output, _ = e.Execute("/ip sdwan member add group=sdwan0 interface=eth1 gateway=192.168.2.1 weight=30 target-probe=8.8.8.8", admin)
  153. if strings.Contains(output, "Error") {
  154. t.Fatalf("error adding member eth1: %s", output)
  155. }
  156. // 5. Print Members
  157. output, _ = e.Execute("/ip sdwan member print", viewer)
  158. if !strings.Contains(output, "eth0") || !strings.Contains(output, "eth1") {
  159. t.Fatalf("members not found in member print output: %s", output)
  160. }
  161. // 6. Permission check for viewer
  162. output, _ = e.Execute("/ip sdwan remove sdwan0", viewer)
  163. if !strings.Contains(output, "Permission denied") {
  164. t.Fatalf("expected permission denied for viewer, got: %s", output)
  165. }
  166. // 7. Member set & disable
  167. output, _ = e.Execute("/ip sdwan member set group=sdwan0 interface=eth0 weight=80", admin)
  168. if strings.Contains(output, "Error") {
  169. t.Fatalf("error setting member weight: %s", output)
  170. }
  171. output, _ = e.Execute("/ip sdwan member disable group=sdwan0 interface=eth1", admin)
  172. if strings.Contains(output, "Error") {
  173. t.Fatalf("error disabling member eth1: %s", output)
  174. }
  175. // 8. Group disable & enable
  176. output, _ = e.Execute("/ip sdwan disable sdwan0", admin)
  177. if strings.Contains(output, "Error") {
  178. t.Fatalf("error disabling sdwan0: %s", output)
  179. }
  180. output, _ = e.Execute("/ip sdwan enable sdwan0", admin)
  181. if strings.Contains(output, "Error") {
  182. t.Fatalf("error enabling sdwan0: %s", output)
  183. }
  184. // 9. Remove member
  185. output, _ = e.Execute("/ip sdwan member remove group=sdwan0 interface=eth1", admin)
  186. if strings.Contains(output, "Error") {
  187. t.Fatalf("error removing member eth1: %s", output)
  188. }
  189. // 10. Remove group
  190. output, _ = e.Execute("/ip sdwan remove sdwan0", admin)
  191. if strings.Contains(output, "Error") {
  192. t.Fatalf("error removing group sdwan0: %s", output)
  193. }
  194. }