diff --git a/README.md b/README.md index f156bbba..ca904a51 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions. * `portmap`: An iptables-based portmapping plugin. Maps ports from the host's address space to the container. * `bandwidth`: Allows bandwidth-limiting through use of traffic control tbf (ingress/egress). * `sbr`: A plugin that configures source based routing for an interface (from which it is chained). +* `firewall`: A firewall plugin which uses iptables or firewalld to add rules to allow traffic to/from the container. ### Sample The sample plugin provides an example for building your own plugin. diff --git a/plugins/meta/firewall/README.md b/plugins/meta/firewall/README.md index a437c8d0..f73a4121 100644 --- a/plugins/meta/firewall/README.md +++ b/plugins/meta/firewall/README.md @@ -4,7 +4,7 @@ This plugin creates firewall rules to allow traffic to/from container IP address via the host network . It does not create any network interfaces and therefore does not set up connectivity by itself. -It is only useful when used in addition to other plugins. +It is intended to be used as a chained plugins. ## Operation The following network configuration file @@ -45,7 +45,91 @@ Available backends include `iptables` and `firewalld` and may be selected with t If no `backend` key is given, the plugin will use firewalld if the service exists on the D-Bus system bus. If no firewalld service is found, it will fall back to iptables. +## firewalld backend rule structure +When the `firewalld` backend is used, this example will place the IPAM allocated address for the container (e.g. 10.88.0.2) into firewalld's `trusted` zone, allowing it to send/receive traffic. + + +A sample standalone config list (with the file extension .conflist) using firewalld backend might +look like: + +```json +{ + "cniVersion": "0.3.1", + "name": "bridge-firewalld", + "plugins": [ + { + "type": "bridge", + "bridge": "cni0", + "isGateway": true, + "ipMasq": true, + "ipam": { + "type": "host-local", + "subnet": "10.88.0.0/16", + "routes": [ + { "dst": "0.0.0.0/0" } + ] + } + }, + { + "type": "firewall", + "backend": "firewalld" + } + ] +} +``` + + +`FORWARD_IN_ZONES_SOURCE` chain: +- `-d 10.88.0.2 -j FWDI_trusted` + +`CNI_FORWARD_OUT_ZONES_SOURCE` chain: +- `-s 10.88.0.2 -j FWDO_trusted` + + +## iptables backend rule structure + +A sample standalone config list (with the file extension .conflist) using iptables backend might +look like: + +```json +{ + "cniVersion": "0.3.1", + "name": "bridge-firewalld", + "plugins": [ + { + "type": "bridge", + "bridge": "cni0", + "isGateway": true, + "ipMasq": true, + "ipam": { + "type": "host-local", + "subnet": "10.88.0.0/16", + "routes": [ + { "dst": "0.0.0.0/0" } + ] + } + }, + { + "type": "firewall", + "backend": "iptables" + } + ] +} +``` + When the `iptables` backend is used, the above example will create two new iptables chains in the `filter` table and add rules that allow the given interface to send/receive traffic. -When the `firewalld` backend is used, the above example will place the `cni0` interface into firewalld's `trusted` zone, allowing it to send/receive traffic. +### FORWARD +A new chain, CNI-FORWARD is added to the FORWARD chain. CNI-FORWARD is the chain where rules will be added +when containers are created and from where rules will be removed when containers terminate. + +`FORWARD` chain: +- `-j CNI-FORWARD` + +CNI-FORWARD will have a pair of rules added, one for each direction, using the IPAM assigned IP address +of the container as shown: + +`CNI_FORWARD` chain: +- `-s 10.88.0.2 -m conntrack --ctstate RELATED,ESTABLISHED -j CNI-FORWARD` +- `-d 10.88.0.2 -j CNI-FORWARD` diff --git a/plugins/meta/firewall/firewall.go b/plugins/meta/firewall/firewall.go index 44099bc5..ccb0545b 100644 --- a/plugins/meta/firewall/firewall.go +++ b/plugins/meta/firewall/firewall.go @@ -45,14 +45,12 @@ type FirewallNetConf struct { // the firewalld backend is used but the zone is not given, it defaults // to 'trusted' FirewalldZone string `json:"firewalldZone,omitempty"` - - RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` - PrevResult *current.Result `json:"-"` } type FirewallBackend interface { - Add(*FirewallNetConf) error - Del(*FirewallNetConf) error + Add(*FirewallNetConf, *current.Result) error + Del(*FirewallNetConf, *current.Result) error + Check(*FirewallNetConf, *current.Result) error } func ipString(ip net.IPNet) string { @@ -62,10 +60,27 @@ func ipString(ip net.IPNet) string { return ip.IP.String() + "/32" } -func parseConf(data []byte) (*FirewallNetConf, error) { +func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) { conf := FirewallNetConf{} if err := json.Unmarshal(data, &conf); err != nil { - return nil, fmt.Errorf("failed to load netconf: %v", err) + return nil, nil, fmt.Errorf("failed to load netconf: %v", err) + } + + // Parse previous result. + if conf.RawPrevResult == nil { + return nil, nil, fmt.Errorf("missing prevResult from earlier plugin") + } + + // Parse previous result. + var result *current.Result + var err error + if err = version.ParsePrevResult(&conf.NetConf); err != nil { + return nil, nil, fmt.Errorf("could not parse prevResult: %v", err) + } + + result, err = current.NewResultFromResult(conf.PrevResult) + if err != nil { + return nil, nil, fmt.Errorf("could not convert result to current version: %v", err) } // Default the firewalld zone to trusted @@ -73,26 +88,7 @@ func parseConf(data []byte) (*FirewallNetConf, error) { conf.FirewalldZone = "trusted" } - // Parse previous result. - if conf.RawPrevResult == nil { - return nil, fmt.Errorf("missing prevResult from earlier plugin") - } - - resultBytes, err := json.Marshal(conf.RawPrevResult) - if err != nil { - return nil, fmt.Errorf("could not serialize prevResult: %v", err) - } - res, err := version.NewResult(conf.CNIVersion, resultBytes) - if err != nil { - return nil, fmt.Errorf("could not parse prevResult: %v", err) - } - conf.RawPrevResult = nil - conf.PrevResult, err = current.NewResultFromResult(res) - if err != nil { - return nil, fmt.Errorf("could not convert result to current version: %v", err) - } - - return &conf, nil + return &conf, result, nil } func getBackend(conf *FirewallNetConf) (FirewallBackend, error) { @@ -113,7 +109,7 @@ func getBackend(conf *FirewallNetConf) (FirewallBackend, error) { } func cmdAdd(args *skel.CmdArgs) error { - conf, err := parseConf(args.StdinData) + conf, result, err := parseConf(args.StdinData) if err != nil { return err } @@ -123,11 +119,10 @@ func cmdAdd(args *skel.CmdArgs) error { return err } - if err := backend.Add(conf); err != nil { + if err := backend.Add(conf, result); err != nil { return err } - result := conf.PrevResult if result == nil { result = ¤t.Result{} } @@ -135,7 +130,7 @@ func cmdAdd(args *skel.CmdArgs) error { } func cmdDel(args *skel.CmdArgs) error { - conf, err := parseConf(args.StdinData) + conf, result, err := parseConf(args.StdinData) if err != nil { return err } @@ -152,7 +147,7 @@ func cmdDel(args *skel.CmdArgs) error { } // Runtime errors are ignored - if err := backend.Del(conf); err != nil { + if err := backend.Del(conf, result); err != nil { return err } @@ -160,5 +155,28 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.4.0"), "TODO") +} + +func cmdCheck(args *skel.CmdArgs) error { + conf, result, err := parseConf(args.StdinData) + if err != nil { + return err + } + + // Ensure we have previous result. + if result == nil { + return fmt.Errorf("Required prevResult missing") + } + + backend, err := getBackend(conf) + if err != nil { + return err + } + + if err := backend.Check(conf, result); err != nil { + return err + } + + return nil } diff --git a/plugins/meta/firewall/firewall_firewalld_test.go b/plugins/meta/firewall/firewall_firewalld_test.go index 9d174fac..68a00793 100644 --- a/plugins/meta/firewall/firewall_firewalld_test.go +++ b/plugins/meta/firewall/firewall_firewalld_test.go @@ -81,6 +81,16 @@ func (f *fakeFirewalld) RemoveSource(zone, source string) (string, *dbus.Error) return "", nil } +func (f *fakeFirewalld) QuerySource(zone, source string) (bool, *dbus.Error) { + if f.zone != zone { + return false, nil + } + if f.source != source { + return false, nil + } + return true, nil +} + func spawnSessionDbus(wg *sync.WaitGroup) (string, *exec.Cmd) { // Start a private D-Bus session bus path, err := invoke.FindInPath("dbus-daemon", []string{ @@ -150,6 +160,7 @@ var _ = Describe("firewalld test", func() { // Go public methods to the D-Bus name methods := map[string]string{ "AddSource": firewalldAddSourceMethod, + "QuerySource": firewalldQuerySourceMethod, "RemoveSource": firewalldRemoveSourceMethod, } conn.ExportWithMap(fwd, methods, firewalldPath, firewalldZoneInterface) @@ -178,7 +189,7 @@ var _ = Describe("firewalld test", func() { IfName: ifname, StdinData: []byte(conf), } - _, _, err := testutils.CmdAddWithResult(targetNs.Path(), ifname, []byte(conf), func() error { + _, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) @@ -186,7 +197,7 @@ var _ = Describe("firewalld test", func() { Expect(fwd.source).To(Equal("10.0.0.2/32")) fwd.clear() - err = testutils.CmdDelWithResult(targetNs.Path(), ifname, func() error { + err = testutils.CmdDel(targetNs.Path(), args.ContainerID, ifname, func() error { return cmdDel(args) }) Expect(err).NotTo(HaveOccurred()) @@ -224,7 +235,7 @@ var _ = Describe("firewalld test", func() { IfName: ifname, StdinData: []byte(conf), } - _, _, err := testutils.CmdAddWithResult(targetNs.Path(), ifname, []byte(conf), func() error { + _, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) @@ -262,7 +273,7 @@ var _ = Describe("firewalld test", func() { IfName: ifname, StdinData: []byte(conf), } - r, _, err := testutils.CmdAddWithResult(targetNs.Path(), ifname, []byte(conf), func() error { + r, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) @@ -275,4 +286,58 @@ var _ = Describe("firewalld test", func() { Expect(len(result.IPs)).To(Equal(1)) Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) }) + + It("works with a 0.4.0 config, including Check", func() { + Expect(isFirewalldRunning()).To(BeTrue()) + + conf := `{ + "cniVersion": "0.4.0", + "name": "firewalld-test", + "type": "firewall", + "backend": "firewalld", + "zone": "trusted", + "prevResult": { + "cniVersion": "0.4.0", + "interfaces": [ + {"name": "eth0", "sandbox": "/foobar"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } + }` + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: ifname, + StdinData: []byte(conf), + } + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(fwd.zone).To(Equal("trusted")) + Expect(fwd.source).To(Equal("10.0.0.2/32")) + + _, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdCheckWithArgs(args, func() error { + return cmdCheck(args) + }) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(fwd.zone).To(Equal("trusted")) + Expect(fwd.source).To(Equal("10.0.0.2/32")) + }) }) diff --git a/plugins/meta/firewall/firewall_iptables_test.go b/plugins/meta/firewall/firewall_iptables_test.go index 8cb1fb6f..6c7358f4 100644 --- a/plugins/meta/firewall/firewall_iptables_test.go +++ b/plugins/meta/firewall/firewall_iptables_test.go @@ -235,7 +235,7 @@ var _ = Describe("firewall plugin iptables backend", func() { err := originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() - r, _, err := testutils.CmdAddWithResult(targetNS.Path(), IFNAME, fullConf, func() error { + r, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) @@ -264,7 +264,7 @@ var _ = Describe("firewall plugin iptables backend", func() { err := originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() - _, _, err := testutils.CmdAddWithResult(targetNS.Path(), IFNAME, fullConf, func() error { + _, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) @@ -312,7 +312,7 @@ var _ = Describe("firewall plugin iptables backend", func() { err := originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() - _, _, err := testutils.CmdAddWithResult(targetNS.Path(), IFNAME, conf, func() error { + _, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, conf, func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) @@ -350,13 +350,157 @@ var _ = Describe("firewall plugin iptables backend", func() { err := originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() - _, _, err := testutils.CmdAddWithResult(targetNS.Path(), IFNAME, fullConf, func() error { + _, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) validateFullRuleset(fullConf) - err = testutils.CmdDelWithResult(targetNS.Path(), IFNAME, func() error { + err = testutils.CmdDel(targetNS.Path(), args.ContainerID, IFNAME, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateCleanedUp(fullConf) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("installs the right iptables rules on the host v4.0.x and check is successful", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + validateFullRuleset(fullConf) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("cleans up on delete v4.0.x", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateFullRuleset(fullConf) + + err = testutils.CmdDel(targetNS.Path(), args.ContainerID, IFNAME, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateCleanedUp(fullConf) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) + +var _ = Describe("firewall plugin iptables backend v0.4.x", func() { + var originalNS, targetNS ns.NetNS + const IFNAME string = "dummy0" + + fullConf := []byte(`{ + "name": "test", + "type": "firewall", + "backend": "iptables", + "ifName": "dummy0", + "cniVersion": "0.4.0", + "prevResult": { + "interfaces": [ + {"name": "dummy0"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "interface": 0 + }, + { + "version": "6", + "address": "2001:db8:1:2::1/64", + "interface": 0 + } + ] + } + }`) + + BeforeEach(func() { + // Create a new NetNS so we don't modify the host + var err error + originalNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err = netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: IFNAME, + }, + }) + Expect(err).NotTo(HaveOccurred()) + _, err = netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + targetNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(originalNS.Close()).To(Succeed()) + Expect(targetNS.Close()).To(Succeed()) + }) + + It("installs iptables rules, Check rules then cleans up on delete using v4.0.x", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + _, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdCheckWithArgs(args, func() error { + return cmdCheck(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateFullRuleset(fullConf) + + err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).NotTo(HaveOccurred()) diff --git a/plugins/meta/firewall/firewalld.go b/plugins/meta/firewall/firewalld.go index 61d40e41..ac9328c3 100644 --- a/plugins/meta/firewall/firewalld.go +++ b/plugins/meta/firewall/firewalld.go @@ -18,6 +18,7 @@ import ( "fmt" "strings" + "github.com/containernetworking/cni/pkg/types/current" "github.com/godbus/dbus" ) @@ -31,6 +32,7 @@ const ( firewalldZoneInterface = "org.fedoraproject.FirewallD1.zone" firewalldAddSourceMethod = "addSource" firewalldRemoveSourceMethod = "removeSource" + firewalldQuerySourceMethod = "querySource" errZoneAlreadySet = "ZONE_ALREADY_SET" ) @@ -80,8 +82,8 @@ func newFirewalldBackend(conf *FirewallNetConf) (FirewallBackend, error) { return backend, nil } -func (fb *fwdBackend) Add(conf *FirewallNetConf) error { - for _, ip := range conf.PrevResult.IPs { +func (fb *fwdBackend) Add(conf *FirewallNetConf, result *current.Result) error { + for _, ip := range result.IPs { ipStr := ipString(ip.Address) // Add a firewalld rule which assigns the given source IP to the given zone firewalldObj := fb.conn.Object(firewalldName, firewalldPath) @@ -95,8 +97,8 @@ func (fb *fwdBackend) Add(conf *FirewallNetConf) error { return nil } -func (fb *fwdBackend) Del(conf *FirewallNetConf) error { - for _, ip := range conf.PrevResult.IPs { +func (fb *fwdBackend) Del(conf *FirewallNetConf, result *current.Result) error { + for _, ip := range result.IPs { ipStr := ipString(ip.Address) // Remove firewalld rules which assigned the given source IP to the given zone firewalldObj := fb.conn.Object(firewalldName, firewalldPath) @@ -105,3 +107,16 @@ func (fb *fwdBackend) Del(conf *FirewallNetConf) error { } return nil } + +func (fb *fwdBackend) Check(conf *FirewallNetConf, result *current.Result) error { + for _, ip := range result.IPs { + ipStr := ipString(ip.Address) + // Check for a firewalld rule for the given source IP to the given zone + firewalldObj := fb.conn.Object(firewalldName, firewalldPath) + var res bool + if err := firewalldObj.Call(firewalldZoneInterface+"."+firewalldQuerySourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res); err != nil { + return fmt.Errorf("failed to find the address %v in %v zone", ipStr, conf.FirewalldZone) + } + } + return nil +} diff --git a/plugins/meta/firewall/iptables.go b/plugins/meta/firewall/iptables.go index 5f451d70..faae35c6 100644 --- a/plugins/meta/firewall/iptables.go +++ b/plugins/meta/firewall/iptables.go @@ -21,6 +21,7 @@ import ( "fmt" "net" + "github.com/containernetworking/cni/pkg/types/current" "github.com/coreos/go-iptables/iptables" ) @@ -99,9 +100,9 @@ func protoForIP(ip net.IPNet) iptables.Protocol { return iptables.ProtocolIPv6 } -func (ib *iptablesBackend) addRules(conf *FirewallNetConf, ipt *iptables.IPTables, proto iptables.Protocol) error { +func (ib *iptablesBackend) addRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error { rules := make([][]string, 0) - for _, ip := range conf.PrevResult.IPs { + for _, ip := range result.IPs { if protoForIP(ip.Address) == proto { rules = append(rules, getPrivChainRules(ipString(ip.Address))...) } @@ -131,9 +132,9 @@ func (ib *iptablesBackend) addRules(conf *FirewallNetConf, ipt *iptables.IPTable return nil } -func (ib *iptablesBackend) delRules(conf *FirewallNetConf, ipt *iptables.IPTables, proto iptables.Protocol) error { +func (ib *iptablesBackend) delRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error { rules := make([][]string, 0) - for _, ip := range conf.PrevResult.IPs { + for _, ip := range result.IPs { if protoForIP(ip.Address) == proto { rules = append(rules, getPrivChainRules(ipString(ip.Address))...) } @@ -146,13 +147,69 @@ func (ib *iptablesBackend) delRules(conf *FirewallNetConf, ipt *iptables.IPTable return nil } +func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error { + rules := make([][]string, 0) + for _, ip := range result.IPs { + if protoForIP(ip.Address) == proto { + rules = append(rules, getPrivChainRules(ipString(ip.Address))...) + } + } + + if len(rules) <= 0 { + return nil + } + + // Ensure our private chains exist + if err := ensureChain(ipt, "filter", ib.privChainName); err != nil { + return err + } + if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil { + return err + } + + // Ensure our filter rule exists in the forward chain + privRule := generateFilterRule(ib.privChainName) + privExists, err := ipt.Exists("filter", "FORWARD", privRule...) + if err != nil { + return err + } + if !privExists { + return fmt.Errorf("expected %v rule %v not found", "FORWARD", privRule) + } + + // Ensure our admin override chain rule exists in our private chain + adminRule := generateFilterRule(ib.adminChainName) + adminExists, err := ipt.Exists("filter", ib.privChainName, adminRule...) + if err != nil { + return err + } + if !adminExists { + return fmt.Errorf("expected %v rule %v not found", ib.privChainName, adminRule) + } + + // ensure rules for this IP address exist + for _, rule := range rules { + // Ensure our rule exists in our private chain + exists, err := ipt.Exists("filter", ib.privChainName, rule...) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("expected rule %v not found", rule) + } + } + + return nil +} + func findProtos(conf *FirewallNetConf) []iptables.Protocol { protos := []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} if conf.PrevResult != nil { // If PrevResult is given, scan all IP addresses to figure out // which IP versions to use protos = []iptables.Protocol{} - for _, addr := range conf.PrevResult.IPs { + result, _ := current.NewResultFromResult(conf.PrevResult) + for _, addr := range result.IPs { if addr.Address.IP.To4() != nil { protos = append(protos, iptables.ProtocolIPv4) } else { @@ -196,18 +253,27 @@ func newIptablesBackend(conf *FirewallNetConf) (FirewallBackend, error) { return backend, nil } -func (ib *iptablesBackend) Add(conf *FirewallNetConf) error { +func (ib *iptablesBackend) Add(conf *FirewallNetConf, result *current.Result) error { for proto, ipt := range ib.protos { - if err := ib.addRules(conf, ipt, proto); err != nil { + if err := ib.addRules(conf, result, ipt, proto); err != nil { return err } } return nil } -func (ib *iptablesBackend) Del(conf *FirewallNetConf) error { +func (ib *iptablesBackend) Del(conf *FirewallNetConf, result *current.Result) error { for proto, ipt := range ib.protos { - ib.delRules(conf, ipt, proto) + ib.delRules(conf, result, ipt, proto) + } + return nil +} + +func (ib *iptablesBackend) Check(conf *FirewallNetConf, result *current.Result) error { + for proto, ipt := range ib.protos { + if err := ib.checkRules(conf, result, ipt, proto); err != nil { + return err + } } return nil }