| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- package cli
- import (
- "path/filepath"
- "reflect"
- "strings"
- "testing"
- "debtik/internal/aerotun"
- "debtik/internal/auth"
- "debtik/internal/dynamicrouting"
- "debtik/internal/sdwan"
- "debtik/internal/vpn"
- )
- func TestQuotedCommandArguments(t *testing.T) {
- got, err := splitCommand(`/interface aerotun set test comment="office uplink" psk='literal $\\key' name=""`)
- want := []string{"/interface", "aerotun", "set", "test", "comment=office uplink", `psk=literal $\\key`, "name="}
- if err != nil || !reflect.DeepEqual(got, want) {
- t.Fatalf("%q %v", got, err)
- }
- if _, err := splitCommand(`comment="unfinished`); err == nil {
- t.Fatal("accepted unterminated quote")
- }
- }
- func TestBareLogAndHandlerRecovery(t *testing.T) {
- e := &Executor{}
- user := &auth.PublicUser{Role: auth.RoleFull}
- output, _ := e.Execute("/log", user)
- if commandExitStatus(output) != 0 {
- t.Fatal(output)
- }
- output, _ = e.Execute("/interface print", user) // absent manager simulates a handler failure
- if commandExitStatus(output) == 0 || !strings.Contains(output, "still running") {
- t.Fatal(output)
- }
- output, _ = e.Execute("nonsense", user)
- if commandExitStatus(output) == 0 {
- t.Fatal("unknown command returned success")
- }
- output, _ = e.Execute("/help", user)
- if commandExitStatus(output) != 0 {
- t.Fatal("help returned failure")
- }
- }
- func TestAeroTUNCLIConfigurationAndPermissions(t *testing.T) {
- m := aerotun.NewManagerWithPath(filepath.Join(t.TempDir(), "aerotun.json"))
- defer m.StopAll()
- e := &Executor{vpnMgr: &vpn.Manager{AeroTUN: m}}
- admin := &auth.PublicUser{Role: auth.RoleFull}
- psk := strings.Repeat("12", 32)
- 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"`
- output, _ := e.Execute(command, admin)
- if commandExitStatus(output) != 0 {
- t.Fatal(output)
- }
- inst := m.List()[0]
- if !inst.Disabled || inst.PublicKey == "" || inst.Comment != "office uplink" {
- t.Fatal("draft configuration not retained")
- }
- for _, role := range []auth.Role{auth.RoleRead, auth.RoleWrite} {
- output, _ = e.Execute("/interface aerotun remove atest", &auth.PublicUser{Role: role})
- if commandExitStatus(output) == 0 {
- t.Fatal("non-admin removed tunnel")
- }
- }
- output, _ = e.Execute("/interface aerotun print detail=yes", admin)
- if strings.Contains(output, psk) || strings.Contains(output, inst.PrivateKey) || !strings.Contains(output, inst.PublicKey) {
- t.Fatal("secret disclosure or missing public identity")
- }
- output, _ = e.Execute(`/interface aerotun set atest comment="updated uplink"`, admin)
- if commandExitStatus(output) != 0 {
- t.Fatal(output)
- }
- updated, _ := m.Get(inst.ID)
- if updated.PSK != psk || updated.PublicKey != inst.PublicKey {
- t.Fatal("edit changed identity")
- }
- output, _ = e.Execute("/interface aerotun remove atest", admin)
- if commandExitStatus(output) != 0 || len(m.List()) != 0 {
- t.Fatal(output)
- }
- }
- func TestVXLANCLIConfigurationAndPermissions(t *testing.T) {
- vxlanMgr := vpn.NewVXLANManager(filepath.Join(t.TempDir(), "vxlan.json"))
- e := &Executor{vpnMgr: &vpn.Manager{VXLAN: vxlanMgr}}
- admin := &auth.PublicUser{Role: auth.RoleFull}
- viewer := &auth.PublicUser{Role: auth.RoleRead}
- // 1. Add VXLAN
- output, _ := e.Execute(`/interface vxlan add name=vxlan99 vni=99 port=4789 mtu=1500 comment="test overlay"`, admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("unexpected error adding vxlan: %s", output)
- }
- // 2. Print VXLAN
- output, _ = e.Execute("/interface vxlan print", viewer)
- if !strings.Contains(output, "vxlan99") || !strings.Contains(output, "99") {
- t.Fatalf("vxlan99 not found in print output: %s", output)
- }
- // 3. Add VTEP
- output, _ = e.Execute("/interface vxlan vteps add interface=vxlan99 remote-ip=192.168.1.50 comment=peera", admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("unexpected error adding vtep: %s", output)
- }
- // 4. Print VTEP
- output, _ = e.Execute("/interface vxlan vteps print", viewer)
- if !strings.Contains(output, "192.168.1.50") {
- t.Fatalf("vtep not found in print output: %s", output)
- }
- // 5. Viewer cannot modify
- output, _ = e.Execute("/interface vxlan remove vxlan99", viewer)
- if !strings.Contains(output, "Permission denied") {
- t.Fatalf("expected permission denied for viewer, got: %s", output)
- }
- // 6. Set comment
- output, _ = e.Execute(`/interface vxlan set vxlan99 comment="updated vxlan"`, admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("unexpected error setting vxlan: %s", output)
- }
- // 7. Remove VTEP
- output, _ = e.Execute("/interface vxlan vteps remove interface=vxlan99 remote-ip=192.168.1.50", admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("unexpected error removing vtep: %s", output)
- }
- // 8. Remove VXLAN
- output, _ = e.Execute("/interface vxlan remove vxlan99", admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("unexpected error removing vxlan: %s", output)
- }
- }
- func TestSDWANCLIConfigurationAndPermissions(t *testing.T) {
- sdwanMgr := sdwan.NewManager(filepath.Join(t.TempDir(), "sdwan.json"))
- defer sdwanMgr.Stop()
- e := &Executor{sdwanMgr: sdwanMgr}
- admin := &auth.PublicUser{Role: auth.RoleFull}
- viewer := &auth.PublicUser{Role: auth.RoleRead}
- // 1. Add SD-WAN group
- output, _ := e.Execute(`/ip sdwan add name=sdwan0 mode=weighted auto-nat=yes probe-interval=5 comment="Multi-WAN LB"`, admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("unexpected error adding sdwan group: %s", output)
- }
- // 2. Print SD-WAN groups
- output, _ = e.Execute("/ip sdwan print", viewer)
- if !strings.Contains(output, "sdwan0") || !strings.Contains(output, "weighted") {
- t.Fatalf("sdwan0 not found in print output: %s", output)
- }
- // 3. Status
- output, _ = e.Execute("/ip sdwan status", viewer)
- if !strings.Contains(output, "SD-WAN Telemetry Status") {
- t.Fatalf("status output unexpected: %s", output)
- }
- // 4. Add Members
- 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)
- if strings.Contains(output, "Error") {
- t.Fatalf("error adding member eth0: %s", output)
- }
- 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)
- if strings.Contains(output, "Error") {
- t.Fatalf("error adding member eth1: %s", output)
- }
- // 5. Print Members
- output, _ = e.Execute("/ip sdwan member print", viewer)
- if !strings.Contains(output, "eth0") || !strings.Contains(output, "eth1") {
- t.Fatalf("members not found in member print output: %s", output)
- }
- // 6. Permission check for viewer
- output, _ = e.Execute("/ip sdwan remove sdwan0", viewer)
- if !strings.Contains(output, "Permission denied") {
- t.Fatalf("expected permission denied for viewer, got: %s", output)
- }
- // 7. Member set & disable
- output, _ = e.Execute("/ip sdwan member set group=sdwan0 interface=eth0 weight=80", admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("error setting member weight: %s", output)
- }
- output, _ = e.Execute("/ip sdwan member disable group=sdwan0 interface=eth1", admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("error disabling member eth1: %s", output)
- }
- // 8. Group disable & enable
- output, _ = e.Execute("/ip sdwan disable sdwan0", admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("error disabling sdwan0: %s", output)
- }
- output, _ = e.Execute("/ip sdwan enable sdwan0", admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("error enabling sdwan0: %s", output)
- }
- // 9. Remove member
- output, _ = e.Execute("/ip sdwan member remove group=sdwan0 interface=eth1", admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("error removing member eth1: %s", output)
- }
- // 10. Remove group
- output, _ = e.Execute("/ip sdwan remove sdwan0", admin)
- if strings.Contains(output, "Error") {
- t.Fatalf("error removing group sdwan0: %s", output)
- }
- }
- func TestRoutingOSPFCLI(t *testing.T) {
- dynMgr := dynamicrouting.NewManager(filepath.Join(t.TempDir(), "dynamic.json"))
- e := &Executor{dynRoutingMgr: dynMgr}
- admin := &auth.PublicUser{Role: auth.RoleFull}
- viewer := &auth.PublicUser{Role: auth.RoleRead}
- // 1. Instance commands
- out, _ := e.Execute("/routing ospf instance print", admin)
- if !strings.Contains(out, "NAME") {
- t.Fatalf("expected NAME table header in instance print: %s", out)
- }
- out, _ = e.Execute("/routing ospf instance add name=ospf-test router-id=10.255.0.1 redistribute-connected=yes redistribute-static=no default-metric=30", admin)
- if !strings.Contains(out, "saved") {
- t.Fatalf("failed to add ospf instance: %s", out)
- }
- out, _ = e.Execute("/routing ospf instance set ospf-test default-metric=45", admin)
- if !strings.Contains(out, "updated") {
- t.Fatalf("failed to set ospf instance: %s", out)
- }
- out, _ = e.Execute("/routing ospf instance disable ospf-test", admin)
- if !strings.Contains(out, "disabled") {
- t.Fatalf("failed to disable ospf instance: %s", out)
- }
- out, _ = e.Execute("/routing ospf instance enable ospf-test", admin)
- if !strings.Contains(out, "enabled") {
- t.Fatalf("failed to enable ospf instance: %s", out)
- }
- // Viewer permission check
- out, _ = e.Execute("/routing ospf instance remove ospf-test", viewer)
- if !strings.Contains(out, "Permission denied") {
- t.Fatalf("expected permission denied for viewer: %s", out)
- }
- // 2. Area commands
- out, _ = e.Execute("/routing ospf area print", admin)
- if !strings.Contains(out, "AREA-ID") {
- t.Fatalf("expected AREA-ID table header in area print: %s", out)
- }
- out, _ = e.Execute("/routing ospf area add name=area1 area-id=0.0.0.1 type=stub default-cost=20 no-summaries=yes", admin)
- if !strings.Contains(out, "saved") {
- t.Fatalf("failed to add ospf area: %s", out)
- }
- out, _ = e.Execute("/routing ospf area set area1 default-cost=35", admin)
- if !strings.Contains(out, "updated") {
- t.Fatalf("failed to set ospf area: %s", out)
- }
- // 3. Area range commands
- out, _ = e.Execute("/routing ospf area range print", admin)
- if !strings.Contains(out, "PREFIX") {
- t.Fatalf("expected PREFIX in area range print: %s", out)
- }
- out, _ = e.Execute("/routing ospf area range add prefix=10.10.0.0/16 cost=10 advertise=no", admin)
- if !strings.Contains(out, "added") {
- t.Fatalf("failed to add area range: %s", out)
- }
- out, _ = e.Execute("/routing ospf area range set 10.10.0.0/16 cost=20 advertise=yes", admin)
- if !strings.Contains(out, "updated") {
- t.Fatalf("failed to set area range: %s", out)
- }
- out, _ = e.Execute("/routing ospf area range remove 10.10.0.0/16", admin)
- if !strings.Contains(out, "removed") {
- t.Fatalf("failed to remove area range: %s", out)
- }
- // 4. Interface template commands
- out, _ = e.Execute("/routing ospf interface print", admin)
- if !strings.Contains(out, "INTERFACE") {
- t.Fatalf("expected INTERFACE in interface print: %s", out)
- }
- out, _ = e.Execute("/routing ospf interface add interface=eth0,eth1 area=Default cost=15 network-type=nonbroadcast auth=md5 auth-key=secret123", admin)
- if !strings.Contains(out, "saved") {
- t.Fatalf("failed to add ospf interface template: %s", out)
- }
- out, _ = e.Execute("/routing ospf interface set eth0,eth1 cost=25", admin)
- if !strings.Contains(out, "updated") {
- t.Fatalf("failed to set ospf interface template: %s", out)
- }
- out, _ = e.Execute("/routing ospf interface remove eth0,eth1", admin)
- if !strings.Contains(out, "removed") {
- t.Fatalf("failed to remove ospf interface template: %s", out)
- }
- // 5. Static neighbor commands
- out, _ = e.Execute("/routing ospf static-neighbor print", admin)
- if !strings.Contains(out, "ADDRESS") {
- t.Fatalf("expected ADDRESS in static-neighbor print: %s", out)
- }
- out, _ = e.Execute("/routing ospf static-neighbor add address=192.168.100.2 poll-interval=30 cost=5", admin)
- if !strings.Contains(out, "saved") {
- t.Fatalf("failed to add static neighbor: %s", out)
- }
- out, _ = e.Execute("/routing ospf static-neighbor set 192.168.100.2 poll-interval=60", admin)
- if !strings.Contains(out, "updated") {
- t.Fatalf("failed to update static neighbor: %s", out)
- }
- out, _ = e.Execute("/routing ospf static-neighbor disable 192.168.100.2", admin)
- if !strings.Contains(out, "disabled") {
- t.Fatalf("failed to disable static neighbor: %s", out)
- }
- out, _ = e.Execute("/routing ospf static-neighbor enable 192.168.100.2", admin)
- if !strings.Contains(out, "enabled") {
- t.Fatalf("failed to enable static neighbor: %s", out)
- }
- out, _ = e.Execute("/routing ospf static-neighbor remove 192.168.100.2", admin)
- if !strings.Contains(out, "removed") {
- t.Fatalf("failed to remove static neighbor: %s", out)
- }
- // 6. Live status commands
- out, _ = e.Execute("/routing ospf neighbor print", admin)
- if !strings.Contains(out, "ROUTER-ID") {
- t.Fatalf("expected ROUTER-ID in neighbor print: %s", out)
- }
- out, _ = e.Execute("/routing ospf lsa print", admin)
- if !strings.Contains(out, "TYPE") {
- t.Fatalf("expected TYPE in lsa print: %s", out)
- }
- out, _ = e.Execute("/routing ospf route print", admin)
- if !strings.Contains(out, "PREFIX") {
- t.Fatalf("expected PREFIX in route print: %s", out)
- }
- // Cleanup
- out, _ = e.Execute("/routing ospf area remove area1", admin)
- if !strings.Contains(out, "removed") {
- t.Fatalf("failed to remove area1: %s", out)
- }
- out, _ = e.Execute("/routing ospf instance remove ospf-test", admin)
- if !strings.Contains(out, "removed") {
- t.Fatalf("failed to remove ospf-test: %s", out)
- }
- }
|