From 77fe2a91dd48d7d4427b8bfd8f2a2b76e73084ca Mon Sep 17 00:00:00 2001 From: Philippe Caseiro Date: Wed, 7 Nov 2018 16:36:45 +0100 Subject: [PATCH] Adding uci firewall support * Traffic Rules -> uci_firewall_rules.go * Redirections -> uci_firewall_redirect.go * Custom rules -> uci_firewall_custom_rules.go --- uci.go | 27 ++++++-- uci_dhcp_conf_test.go | 6 +- uci_firewall_custom_rules.go | 109 ++++++++++++++++++++++++++++++ uci_firewall_custom_rules_test.go | 71 +++++++++++++++++++ uci_firewall_redirect.go | 4 +- uci_firewall_redirect_test.go | 8 +-- uci_firewall_rules.go | 5 +- uci_firewall_rules_test.go | 6 +- uci_network_interface_test.go | 6 +- uci_test.go | 14 ++-- uci_wireless_conf_test.go | 4 +- uci_wireless_interface_test.go | 8 +-- 12 files changed, 230 insertions(+), 38 deletions(-) create mode 100644 uci_firewall_custom_rules.go create mode 100644 uci_firewall_custom_rules_test.go diff --git a/uci.go b/uci.go index 7f9b889..1f51a52 100644 --- a/uci.go +++ b/uci.go @@ -13,22 +13,25 @@ type Action struct { // UCI "Object" type UCI struct { - exec Executor - Wireless *UCIWirelessConf + exec Executor + CustomFirewallFile string + Wireless *UCIWirelessConf } // NewUCI return an UCI instance to interact with UCI func NewUCI() *UCI { exec := &localExecutor{} + customFWFile := "/etc/" wireless := &UCIWirelessConf{} - return &UCI{exec, wireless} + return &UCI{exec, customFWFile, wireless} } // NewUCIWithExecutor returns a UCI Instance an gives you the ability to provide // a different command executor than the default one. -func NewUCIWithExecutor(exec Executor) *UCI { +func NewUCIWithExecutor(exec Executor, customFWFile string) *UCI { + wireless := &UCIWirelessConf{} - return &UCI{exec, wireless} + return &UCI{exec, customFWFile, wireless} } // uciRun, private method to run the UCI command @@ -157,3 +160,17 @@ func (u *UCI) GetWifiDeviceByName(name string) map[string]string { } return nil } + +// Service make restart services via the UCI client possible +func (u *UCI) Service(name string, action string) error { + sys := NewSystemWithExecutor(u.exec) + res := sys.Service(name, action) + if res.ReturnCode != 0 { + return fmt.Errorf("%d - %s - %s - %s", + res.ReturnCode, + res.Command, + res.Stdout, + res.Stderr) + } + return nil +} diff --git a/uci_dhcp_conf_test.go b/uci_dhcp_conf_test.go index 9e15bbf..3436cd1 100644 --- a/uci_dhcp_conf_test.go +++ b/uci_dhcp_conf_test.go @@ -20,7 +20,7 @@ const ( func TestNetCreateWithDHCP(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") iface := NewUCINetworkInterface(uci) iface.Name = ifNameDHCP @@ -44,7 +44,7 @@ func TestNetCreateWithDHCP(t *testing.T) { func TestNetUpdateWithDHCP(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") iface := NewUCINetworkInterface(uci) iface.Name = ifNameDHCP @@ -80,7 +80,7 @@ func TestNetUpdateWithDHCP(t *testing.T) { func TestNetDeleteWithDHCP(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") iface := NewUCINetworkInterface(uci) iface.Name = ifNameDHCP diff --git a/uci_firewall_custom_rules.go b/uci_firewall_custom_rules.go new file mode 100644 index 0000000..4bc4b93 --- /dev/null +++ b/uci_firewall_custom_rules.go @@ -0,0 +1,109 @@ +package owrt + +import ( + "fmt" + "io/ioutil" + "os" + "strings" +) + +// UCIFirewallCustomRule is the description of an Wireless interface (cf Openwrt doc) on top of an Wireless Device +type UCIFirewallCustomRule struct { + Name string + Rule string + UCI *UCI +} + +// NewUCIFirewallCustomRule builds a new UCIFirewallCustomRule instance +func NewUCIFirewallCustomRule(client *UCI) *UCIFirewallCustomRule { + return &UCIFirewallCustomRule{ + UCI: client, + } +} + +// Create add a new firewall rule in UCI Configuration +func (cr *UCIFirewallCustomRule) Create() error { + + var file *os.File + customFWFile := cr.UCI.CustomFirewallFile + + fmt.Printf("DEBUG %s\n", customFWFile) + _, stErr := os.Stat(customFWFile) + if os.IsNotExist(stErr) { + var err error + file, err = os.Create(customFWFile) + if err != nil { + return err + } + } else { + var oErr error + file, oErr = os.OpenFile(customFWFile, os.O_RDWR, 0644) + if oErr != nil { + return oErr + + } + } + defer file.Close() + + line := fmt.Sprintf("%s # %s", cr.Rule, cr.Name) + _, err := file.WriteString(line) + return err +} + +// Save commit and relaod configuration (writes it to files !) +func (cr *UCIFirewallCustomRule) Save() error { + reload := cr.UCI.Reload() + if reload.ReturnCode != 0 { + return fmt.Errorf("%d - %s - %s - %s", + reload.ReturnCode, + reload.Command, + reload.Stdout, + reload.Stderr) + } + + sErr := cr.UCI.Service("firewall", "restart") + return sErr +} + +// Delete remove wifi interface from UCI Configuration +func (cr *UCIFirewallCustomRule) Delete() error { + input, err := ioutil.ReadFile(cr.UCI.CustomFirewallFile) + if err != nil { + return err + } + + lines := strings.Split(string(input), "\n") + var out []string + for _, line := range lines { + if !strings.Contains(line, cr.Name) { + out = append(out, line) + } + } + + output := strings.Join(out, "\n") + err = ioutil.WriteFile(cr.UCI.CustomFirewallFile, []byte(output), 0644) + return err +} + +// Update add a new entry for wifi interface in UCI Configuration +func (cr *UCIFirewallCustomRule) Update() error { + input, err := ioutil.ReadFile(cr.UCI.CustomFirewallFile) + if err != nil { + return err + } + + lines := strings.Split(string(input), "\n") + var out []string + for _, line := range lines { + if strings.Contains(line, cr.Name) { + nContent := fmt.Sprintf("%s # %s", cr.Rule, cr.Name) + out = append(out, nContent) + } else { + out = append(out, line) + } + } + + output := strings.Join(out, "\n") + err = ioutil.WriteFile(cr.UCI.CustomFirewallFile, []byte(output), 0644) + return err +} diff --git a/uci_firewall_custom_rules_test.go b/uci_firewall_custom_rules_test.go new file mode 100644 index 0000000..164694e --- /dev/null +++ b/uci_firewall_custom_rules_test.go @@ -0,0 +1,71 @@ +package owrt + +import ( + "io/ioutil" + "os" + "strings" + "testing" +) + +const ( + ruleA = "iptables -A PREROUTING -i br-dds -p tcp -m tcp --dport 443 -j DNAT --to-destination 10.100.10.1:8443" + ruleB = "iptables -A PREROUTING -i br-dds -p tcp -m tcp --dport 80 -j DNAT --to-destination 10.100.10.1:8080" +) + +func TestFWCustomRuleCreate(t *testing.T) { + exec := createMockExecutor("", "", 0) + uci := NewUCIWithExecutor(exec, "/tmp/myCustomRuleFile") + + _, sErr := os.Stat(uci.CustomFirewallFile) + if os.IsExist(sErr) { + rErr := os.Remove(uci.CustomFirewallFile) + if rErr != nil { + t.Fatal("Error cleaning temporary file") + } + } + custom := NewUCIFirewallCustomRule(uci) + custom.Name = "TestRule" + custom.Rule = ruleA + + create := custom.Create() + if create != nil { + t.Fatalf("UCIFirewallCustomRule.Create() failed !\n%s", create.Error()) + } + + sv := custom.Save() + if sv != nil { + t.Fatalf("%s", sv.Error()) + } +} + +func TestFWCustomRuleUpdate(t *testing.T) { + exec := createMockExecutor("", "", 0) + uci := NewUCIWithExecutor(exec, "/tmp/myCustomRuleFile") + + custom := NewUCIFirewallCustomRule(uci) + custom.Name = "SecondRule" + custom.Rule = ruleB + + if cr := custom.Create(); cr != nil { + t.Fatalf("UCIFirewallCustomRule.Create() failed !\n%s", cr.Error()) + } + + b, err := ioutil.ReadFile(uci.CustomFirewallFile) + if err != nil { + t.Fatalf("%s", err.Error()) + } + + if !strings.Contains(string(b), ruleB) { + t.Fatalf("Rule is not present in %s file", uci.CustomFirewallFile) + } + + custom.Rule = ruleA + if uErr := custom.Update(); uErr != nil { + t.Fatalf("UCIFirewallCustomRule.Update() faild ! %s", uErr.Error()) + } + + sv := custom.Save() + if sv != nil { + t.Fatalf("%s", sv.Error()) + } +} diff --git a/uci_firewall_redirect.go b/uci_firewall_redirect.go index 9f478c7..f8bb768 100644 --- a/uci_firewall_redirect.go +++ b/uci_firewall_redirect.go @@ -25,9 +25,7 @@ func NewUCIFirewallRedirect() *UCIFirewallRedirect { // Create add a new firewall rule in UCI Configuration func (rd *UCIFirewallRedirect) Create(uci *UCI) *Action { - var confPrefix string - - confPrefix = fmt.Sprintf("firewall.@redirect[%d]", rd.Index) + confPrefix := fmt.Sprintf("firewall.@redirect[%d]", rd.Index) conf := make(map[string][]string) diff --git a/uci_firewall_redirect_test.go b/uci_firewall_redirect_test.go index 1fe9dac..48f269e 100644 --- a/uci_firewall_redirect_test.go +++ b/uci_firewall_redirect_test.go @@ -18,7 +18,7 @@ const ( func TestFWRedirectCreate(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") redirect := NewUCIFirewallRedirect() redirect.Name = redirectName @@ -38,7 +38,7 @@ func TestFWRedirectCreate(t *testing.T) { func TestFWRedirectUpdate(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") redirect := NewUCIFirewallRedirect() redirect.Name = redirectName @@ -55,7 +55,7 @@ func TestFWRedirectUpdate(t *testing.T) { t.Fatalf("UCIFirewallRedirect.Create() failed !") } - redirect.Name = "Tutu" + redirect.Name = "NewRedirect" if redirect.Update(uci).ReturnCode != 0 { t.Fatalf("UCIFirewallRedirect.Update() failed !") @@ -64,7 +64,7 @@ func TestFWRedirectUpdate(t *testing.T) { func TestFWRedirectDelete(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") redirect := NewUCIFirewallRedirect() redirect.Name = redirectName diff --git a/uci_firewall_rules.go b/uci_firewall_rules.go index 3874d5e..643a0df 100644 --- a/uci_firewall_rules.go +++ b/uci_firewall_rules.go @@ -22,12 +22,9 @@ func NewUCIFirewallRule() *UCIFirewallRule { // Create add a new firewall rule in UCI Configuration func (fw *UCIFirewallRule) Create(uci *UCI) *Action { - var confPrefix string - - confPrefix = fmt.Sprintf("firewall.@rule[%d]", fw.Index) + confPrefix := fmt.Sprintf("firewall.@rule[%d]", fw.Index) conf := make(map[string][]string) - conf["name"] = append(conf["network"], fmt.Sprintf("%s.name", confPrefix), fw.Name) conf["src"] = append(conf["src"], fmt.Sprintf("%s.src", confPrefix), fw.Src) conf["target"] = append(conf["target"], fmt.Sprintf("%s.target", confPrefix), fw.Target) diff --git a/uci_firewall_rules_test.go b/uci_firewall_rules_test.go index 2ff8781..b52a84d 100644 --- a/uci_firewall_rules_test.go +++ b/uci_firewall_rules_test.go @@ -16,7 +16,7 @@ const ( func TestFWRuleCreate(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") rule := NewUCIFirewallRule() rule.Name = ruleName @@ -34,7 +34,7 @@ func TestFWRuleCreate(t *testing.T) { func TestFWRuleUpdate(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") rule := NewUCIFirewallRule() rule.Name = ruleName @@ -58,7 +58,7 @@ func TestFWRuleUpdate(t *testing.T) { func TestFWRuleDelete(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") rule := NewUCIFirewallRule() rule.Name = ruleName diff --git a/uci_network_interface_test.go b/uci_network_interface_test.go index 8a18533..3b89359 100644 --- a/uci_network_interface_test.go +++ b/uci_network_interface_test.go @@ -17,7 +17,7 @@ const ( func TestNetCreate(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") iface := NewUCINetworkInterface(uci) iface.Name = ifName @@ -36,7 +36,7 @@ func TestNetCreate(t *testing.T) { func TestNetUpdate(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") iface := NewUCINetworkInterface(uci) iface.Name = ifName @@ -61,7 +61,7 @@ func TestNetUpdate(t *testing.T) { func TestNetDelete(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") iface := NewUCINetworkInterface(uci) iface.Name = ifName diff --git a/uci_test.go b/uci_test.go index 1ed89eb..970b3b6 100644 --- a/uci_test.go +++ b/uci_test.go @@ -8,7 +8,7 @@ import ( func TestUCIAdd(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") res := uci.Add("wireless", "test") if res.ReturnCode != 0 { t.Error("Bad Return Code !") @@ -25,7 +25,7 @@ func TestUCIAdd(t *testing.T) { func TestUCIAddFailed(t *testing.T) { exec := createMockExecutor("", "BigError", 3) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") res := uci.Add("wireless", "test") if res.ReturnCode != 3 { t.Error("Bad Return Code !") @@ -34,7 +34,7 @@ func TestUCIAddFailed(t *testing.T) { func TestUCIDelete(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") res := uci.Delete("wireless.@wifi-iface[1]") if res.ReturnCode != 0 { t.Error("Bad Return Code !") @@ -51,7 +51,7 @@ func TestUCIDelete(t *testing.T) { func TestUCISet(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") res := uci.Set("wireless.@wifi-iface[1].network", "OrionNetwork") if res.ReturnCode != 0 { t.Error("Bad Return Code !") @@ -68,7 +68,7 @@ func TestUCISet(t *testing.T) { func TestUCICommit(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") res := uci.Commit() if res.ReturnCode != 0 { t.Error("Bad Return Code !") @@ -85,7 +85,7 @@ func TestUCICommit(t *testing.T) { func TestUCIReload(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") res := uci.Reload() if res.ReturnCode != 0 { t.Error("Bad Return Code !") @@ -106,7 +106,7 @@ func TestGetWifiIfaceBySSID(t *testing.T) { t.Fatal(err) } exec := createMockExecutor(string(config), "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") uci.LoadWirelessConf() wifi := uci.GetWifiIfaceBySSID("Pyxis2") fmt.Printf("%s\n", wifi.Ssid) diff --git a/uci_wireless_conf_test.go b/uci_wireless_conf_test.go index 75a6e22..693571d 100644 --- a/uci_wireless_conf_test.go +++ b/uci_wireless_conf_test.go @@ -11,7 +11,7 @@ func TestUCIGetWirelessConf(t *testing.T) { t.Fatal(err) } exec := createMockExecutor(string(config), "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") uci.LoadWirelessConf() if g, e := uci.Wireless.DefaultInterface["Name"], "wifi-iface"; g != e { t.Fatalf("DefaultDevice.Name is expected to be [%s] and we have [%s]", e, g) @@ -22,7 +22,7 @@ func TestUCIGetWirelessConf(t *testing.T) { t.Fatal(err) } exec = createMockExecutor(string(config), "", 0) - uci = NewUCIWithExecutor(exec) + uci = NewUCIWithExecutor(exec, "") uci.LoadWirelessConf() if g, e := uci.Wireless.Interfaces[1].Name, "wifi-iface"; g != e { t.Fatalf("DefaultDevice.Name is expected to be [%s] and we have [%s]", e, g) diff --git a/uci_wireless_interface_test.go b/uci_wireless_interface_test.go index d4c739f..529b1c8 100644 --- a/uci_wireless_interface_test.go +++ b/uci_wireless_interface_test.go @@ -56,7 +56,7 @@ func TestGetSysDevName(t *testing.T) { func TestCreate(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") iface := NewUCIWirelessInterface() iface.Name = ifaceName @@ -75,7 +75,7 @@ func TestCreate(t *testing.T) { func TestUpdate(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") iface := NewUCIWirelessInterface() iface.Name = ifaceName @@ -102,7 +102,7 @@ func TestUpdate(t *testing.T) { func TestDelete(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") iface := NewUCIWirelessInterface() iface.Name = ifaceName @@ -124,7 +124,7 @@ func TestDelete(t *testing.T) { func TestConnect(t *testing.T) { exec := createMockExecutor("", "", 0) - uci := NewUCIWithExecutor(exec) + uci := NewUCIWithExecutor(exec, "") iface := NewUCIWirelessInterface() iface.Name = ifaceName