diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 08fa15d5..e0386f02 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -144,6 +144,11 @@ "ImportPath": "github.com/d2g/dhcp4server/leasepool/memorypool", "Rev": "477b11cea4dcc56af002849238d4f9c1e093c744" }, + { + "ImportPath": "github.com/godbus/dbus", + "Comment": "v4.1.0-6-g885f9cc", + "Rev": "885f9cc04c9c1a6a61a2008e211d36c5737be3f5" + }, { "ImportPath": "github.com/j-keck/arping", "Rev": "2cf9dc699c5640a7e2c81403a44127bf28033600" 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/linux_only.txt b/plugins/linux_only.txt new file mode 100644 index 00000000..b789fb4c --- /dev/null +++ b/plugins/linux_only.txt @@ -0,0 +1,12 @@ +plugins/ipam/dhcp +plugins/main/bridge +plugins/main/host-device +plugins/main/ipvlan +plugins/main/loopback +plugins/main/macvlan +plugins/main/ptp +plugins/main/vlan +plugins/meta/portmap +plugins/meta/tuning +plugins/meta/bandwidth +plugins/meta/firewall diff --git a/plugins/meta/firewall/README.md b/plugins/meta/firewall/README.md new file mode 100644 index 00000000..f73a4121 --- /dev/null +++ b/plugins/meta/firewall/README.md @@ -0,0 +1,135 @@ +# firewall plugin + +## Overview + +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 intended to be used as a chained plugins. + +## Operation +The following network configuration file + +```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", + } + ] +} +``` + +will allow any IP addresses configured by earlier plugins to send/receive traffic via the host. + +A successful result would simply be an empty result, unless a previous plugin passed a previous result, in which case this plugin will return that previous result. + +## Backends + +This plugin supports multiple firewall backends that implement the desired functionality. +Available backends include `iptables` and `firewalld` and may be selected with the `backend` key. +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. + +### 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 new file mode 100644 index 00000000..ccb0545b --- /dev/null +++ b/plugins/meta/firewall/firewall.go @@ -0,0 +1,182 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This is a "meta-plugin". It reads in its own netconf, it does not create +// any network interface but just changes the network sysctl. + +package main + +import ( + "encoding/json" + "fmt" + "net" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ns" +) + +// FirewallNetConf represents the firewall configuration. +type FirewallNetConf struct { + types.NetConf + + // Backend is the firewall type to add rules to. Allowed values are + // 'iptables' and 'firewalld'. + Backend string `json:"backend"` + + // IptablesAdminChainName is an optional name to use instead of the default + // admin rules override chain name that includes the interface name. + IptablesAdminChainName string `json:"iptablesAdminChainName,omitempty"` + + // FirewalldZone is an optional firewalld zone to place the interface into. If + // the firewalld backend is used but the zone is not given, it defaults + // to 'trusted' + FirewalldZone string `json:"firewalldZone,omitempty"` +} + +type FirewallBackend interface { + Add(*FirewallNetConf, *current.Result) error + Del(*FirewallNetConf, *current.Result) error + Check(*FirewallNetConf, *current.Result) error +} + +func ipString(ip net.IPNet) string { + if ip.IP.To4() == nil { + return ip.IP.String() + "/128" + } + return ip.IP.String() + "/32" +} + +func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) { + conf := FirewallNetConf{} + if err := json.Unmarshal(data, &conf); err != nil { + 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 + if conf.FirewalldZone == "" { + conf.FirewalldZone = "trusted" + } + + return &conf, result, nil +} + +func getBackend(conf *FirewallNetConf) (FirewallBackend, error) { + switch conf.Backend { + case "iptables": + return newIptablesBackend(conf) + case "firewalld": + return newFirewalldBackend(conf) + } + + // Default to firewalld if it's running + if isFirewalldRunning() { + return newFirewalldBackend(conf) + } + + // Otherwise iptables + return newIptablesBackend(conf) +} + +func cmdAdd(args *skel.CmdArgs) error { + conf, result, err := parseConf(args.StdinData) + if err != nil { + return err + } + + backend, err := getBackend(conf) + if err != nil { + return err + } + + if err := backend.Add(conf, result); err != nil { + return err + } + + if result == nil { + result = ¤t.Result{} + } + return types.PrintResult(result, conf.CNIVersion) +} + +func cmdDel(args *skel.CmdArgs) error { + conf, result, err := parseConf(args.StdinData) + if err != nil { + return err + } + + backend, err := getBackend(conf) + if err != nil { + return err + } + + // Tolerate errors if the container namespace has been torn down already + containerNS, err := ns.GetNS(args.Netns) + if err == nil { + defer containerNS.Close() + } + + // Runtime errors are ignored + if err := backend.Del(conf, result); err != nil { + return err + } + + return nil +} + +func main() { + 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 new file mode 100644 index 00000000..68a00793 --- /dev/null +++ b/plugins/meta/firewall/firewall_firewalld_test.go @@ -0,0 +1,343 @@ +// Copyright 2018 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bufio" + "fmt" + "os/exec" + "strings" + "sync" + "syscall" + + "github.com/containernetworking/cni/pkg/invoke" + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/testutils" + + "github.com/godbus/dbus" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const ( + confTmpl = `{ + "cniVersion": "0.3.1", + "name": "firewalld-test", + "type": "firewall", + "backend": "firewalld", + "zone": "trusted", + "prevResult": { + "cniVersion": "0.3.0", + "interfaces": [ + {"name": "%s", "sandbox": "%s"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}` + ifname = "eth0" +) + +type fakeFirewalld struct { + zone string + source string +} + +func (f *fakeFirewalld) clear() { + f.zone = "" + f.source = "" +} + +func (f *fakeFirewalld) AddSource(zone, source string) (string, *dbus.Error) { + f.zone = zone + f.source = source + return "", nil +} + +func (f *fakeFirewalld) RemoveSource(zone, source string) (string, *dbus.Error) { + f.zone = zone + f.source = source + 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{ + "/bin", "/sbin", "/usr/bin", "/usr/sbin", + }) + Expect(err).NotTo(HaveOccurred()) + cmd := exec.Command(path, "--session", "--print-address", "--nofork", "--nopidfile") + stdout, err := cmd.StdoutPipe() + Expect(err).NotTo(HaveOccurred()) + err = cmd.Start() + Expect(err).NotTo(HaveOccurred()) + + // Wait for dbus-daemon to print the bus address + bytes, err := bufio.NewReader(stdout).ReadString('\n') + Expect(err).NotTo(HaveOccurred()) + busAddr := strings.TrimSpace(string(bytes)) + Expect(strings.HasPrefix(busAddr, "unix:abstract")).To(BeTrue()) + + var startWg sync.WaitGroup + wg.Add(1) + startWg.Add(1) + go func() { + defer GinkgoRecover() + + startWg.Done() + err = cmd.Wait() + Expect(err).NotTo(HaveOccurred()) + wg.Done() + }() + + startWg.Wait() + return busAddr, cmd +} + +var _ = Describe("firewalld test", func() { + var ( + targetNs ns.NetNS + cmd *exec.Cmd + conn *dbus.Conn + wg sync.WaitGroup + fwd *fakeFirewalld + busAddr string + ) + + BeforeEach(func() { + var err error + targetNs, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + // Start a private D-Bus session bus + busAddr, cmd = spawnSessionDbus(&wg) + conn, err = dbus.Dial(busAddr) + Expect(err).NotTo(HaveOccurred()) + err = conn.Auth(nil) + Expect(err).NotTo(HaveOccurred()) + err = conn.Hello() + Expect(err).NotTo(HaveOccurred()) + + // Start our fake firewalld + reply, err := conn.RequestName(firewalldName, dbus.NameFlagDoNotQueue) + Expect(err).NotTo(HaveOccurred()) + Expect(reply).To(Equal(dbus.RequestNameReplyPrimaryOwner)) + + fwd = &fakeFirewalld{} + // Because firewalld D-Bus methods start with lower-case, and + // because in Go lower-case methods are private, we need to remap + // Go public methods to the D-Bus name + methods := map[string]string{ + "AddSource": firewalldAddSourceMethod, + "QuerySource": firewalldQuerySourceMethod, + "RemoveSource": firewalldRemoveSourceMethod, + } + conn.ExportWithMap(fwd, methods, firewalldPath, firewalldZoneInterface) + + // Make sure the plugin uses our private session bus + testConn = conn + }) + + AfterEach(func() { + _, err := conn.ReleaseName(firewalldName) + Expect(err).NotTo(HaveOccurred()) + + err = cmd.Process.Signal(syscall.SIGTERM) + Expect(err).NotTo(HaveOccurred()) + + wg.Wait() + }) + + It("works with a 0.3.1 config", func() { + Expect(isFirewalldRunning()).To(BeTrue()) + + conf := fmt.Sprintf(confTmpl, ifname, targetNs.Path()) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: ifname, + StdinData: []byte(conf), + } + _, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), 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")) + fwd.clear() + + err = testutils.CmdDel(targetNs.Path(), args.ContainerID, ifname, 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")) + }) + + It("defaults to the firewalld backend", func() { + conf := `{ + "cniVersion": "0.3.1", + "name": "firewalld-test", + "type": "firewall", + "zone": "trusted", + "prevResult": { + "cniVersion": "0.3.0", + "interfaces": [ + {"name": "eth0", "sandbox": "/foobar"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } + }` + + Expect(isFirewalldRunning()).To(BeTrue()) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: ifname, + StdinData: []byte(conf), + } + _, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), 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")) + }) + + It("passes through the prevResult", func() { + conf := `{ + "cniVersion": "0.3.1", + "name": "firewalld-test", + "type": "firewall", + "zone": "trusted", + "prevResult": { + "cniVersion": "0.3.0", + "interfaces": [ + {"name": "eth0", "sandbox": "/foobar"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } + }` + + Expect(isFirewalldRunning()).To(BeTrue()) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: ifname, + StdinData: []byte(conf), + } + r, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal("eth0")) + 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 new file mode 100644 index 00000000..6c7358f4 --- /dev/null +++ b/plugins/meta/firewall/firewall_iptables_test.go @@ -0,0 +1,512 @@ +// Copyright 2017 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/testutils" + + "github.com/vishvananda/netlink" + + "github.com/coreos/go-iptables/iptables" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func findChains(chains []string) (bool, bool) { + var foundAdmin, foundPriv bool + for _, ch := range chains { + if ch == "CNI-ADMIN" { + foundAdmin = true + } else if ch == "CNI-FORWARD" { + foundPriv = true + } + } + return foundAdmin, foundPriv +} + +func findForwardJumpRules(rules []string) (bool, bool) { + var foundAdmin, foundPriv bool + for _, rule := range rules { + if strings.Contains(rule, "-j CNI-ADMIN") { + foundAdmin = true + } else if strings.Contains(rule, "-j CNI-FORWARD") { + foundPriv = true + } + } + return foundAdmin, foundPriv +} + +func findForwardAllowRules(rules []string, ip string) (bool, bool) { + var foundOne, foundTwo bool + for _, rule := range rules { + if !strings.HasSuffix(rule, "-j ACCEPT") { + continue + } + if strings.Contains(rule, fmt.Sprintf(" -s %s ", ip)) { + foundOne = true + } else if strings.Contains(rule, fmt.Sprintf(" -d %s ", ip)) && strings.Contains(rule, "RELATED,ESTABLISHED") { + foundTwo = true + } + } + return foundOne, foundTwo +} + +func getPrevResult(bytes []byte) *current.Result { + type TmpConf struct { + types.NetConf + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult *current.Result `json:"-"` + } + + conf := &TmpConf{} + err := json.Unmarshal(bytes, conf) + Expect(err).NotTo(HaveOccurred()) + if conf.RawPrevResult == nil { + return nil + } + + resultBytes, err := json.Marshal(conf.RawPrevResult) + Expect(err).NotTo(HaveOccurred()) + res, err := version.NewResult(conf.CNIVersion, resultBytes) + Expect(err).NotTo(HaveOccurred()) + prevResult, err := current.NewResultFromResult(res) + Expect(err).NotTo(HaveOccurred()) + + return prevResult +} + +func validateFullRuleset(bytes []byte) { + prevResult := getPrevResult(bytes) + + for _, ip := range prevResult.IPs { + ipt, err := iptables.NewWithProtocol(protoForIP(ip.Address)) + Expect(err).NotTo(HaveOccurred()) + + // Ensure chains + chains, err := ipt.ListChains("filter") + Expect(err).NotTo(HaveOccurred()) + foundAdmin, foundPriv := findChains(chains) + Expect(foundAdmin).To(Equal(true)) + Expect(foundPriv).To(Equal(true)) + + // Look for the FORWARD chain jump rules to our custom chains + rules, err := ipt.List("filter", "FORWARD") + Expect(err).NotTo(HaveOccurred()) + Expect(len(rules)).Should(BeNumerically(">", 1)) + _, foundPriv = findForwardJumpRules(rules) + Expect(foundPriv).To(Equal(true)) + + // Look for the allow rules in our custom FORWARD chain + rules, err = ipt.List("filter", "CNI-FORWARD") + Expect(err).NotTo(HaveOccurred()) + Expect(len(rules)).Should(BeNumerically(">", 1)) + foundAdmin, _ = findForwardJumpRules(rules) + Expect(foundAdmin).To(Equal(true)) + + // Look for the IP allow rules + foundOne, foundTwo := findForwardAllowRules(rules, ipString(ip.Address)) + Expect(foundOne).To(Equal(true)) + Expect(foundTwo).To(Equal(true)) + } +} + +func validateCleanedUp(bytes []byte) { + prevResult := getPrevResult(bytes) + + for _, ip := range prevResult.IPs { + ipt, err := iptables.NewWithProtocol(protoForIP(ip.Address)) + Expect(err).NotTo(HaveOccurred()) + + // Our private and admin chains don't get cleaned up + chains, err := ipt.ListChains("filter") + Expect(err).NotTo(HaveOccurred()) + foundAdmin, foundPriv := findChains(chains) + Expect(foundAdmin).To(Equal(true)) + Expect(foundPriv).To(Equal(true)) + + // Look for the FORWARD chain jump rules to our custom chains + rules, err := ipt.List("filter", "FORWARD") + Expect(err).NotTo(HaveOccurred()) + _, foundPriv = findForwardJumpRules(rules) + Expect(foundPriv).To(Equal(true)) + + // Look for the allow rules in our custom FORWARD chain + rules, err = ipt.List("filter", "CNI-FORWARD") + Expect(err).NotTo(HaveOccurred()) + foundAdmin, _ = findForwardJumpRules(rules) + Expect(foundAdmin).To(Equal(true)) + + // Expect no IP address rules for this IP + foundOne, foundTwo := findForwardAllowRules(rules, ipString(ip.Address)) + Expect(foundOne).To(Equal(false)) + Expect(foundTwo).To(Equal(false)) + } +} + +var _ = Describe("firewall plugin iptables backend", func() { + var originalNS, targetNS ns.NetNS + const IFNAME string = "dummy0" + + fullConf := []byte(`{ + "name": "test", + "type": "firewall", + "backend": "iptables", + "ifName": "dummy0", + "cniVersion": "0.3.1", + "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("passes prevResult through unchanged", 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.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(2)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + Expect(result.IPs[1].Address.String()).To(Equal("2001:db8:1:2::1/64")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("installs the right iptables rules on the host", 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("correctly handles a custom IptablesAdminChainName", func() { + conf := []byte(`{ + "name": "test", + "type": "firewall", + "backend": "iptables", + "ifName": "dummy0", + "cniVersion": "0.3.1", + "iptablesAdminChainName": "CNI-foobar", + "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 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, conf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + var ipt *iptables.IPTables + for _, proto := range []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} { + ipt, err = iptables.NewWithProtocol(proto) + Expect(err).NotTo(HaveOccurred()) + + // Ensure custom admin chain name + chains, err := ipt.ListChains("filter") + Expect(err).NotTo(HaveOccurred()) + var foundAdmin bool + for _, ch := range chains { + if ch == "CNI-foobar" { + foundAdmin = true + } + } + Expect(foundAdmin).To(Equal(true)) + } + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("cleans up on delete", 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()) + }) + + 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()) + validateCleanedUp(fullConf) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) diff --git a/plugins/meta/firewall/firewall_suite_test.go b/plugins/meta/firewall/firewall_suite_test.go new file mode 100644 index 00000000..d3b10e21 --- /dev/null +++ b/plugins/meta/firewall/firewall_suite_test.go @@ -0,0 +1,27 @@ +// Copyright 2017 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestFirewall(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "firewall Suite") +} diff --git a/plugins/meta/firewall/firewalld.go b/plugins/meta/firewall/firewalld.go new file mode 100644 index 00000000..ac9328c3 --- /dev/null +++ b/plugins/meta/firewall/firewalld.go @@ -0,0 +1,122 @@ +// Copyright 2018 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "strings" + + "github.com/containernetworking/cni/pkg/types/current" + "github.com/godbus/dbus" +) + +const ( + dbusName = "org.freedesktop.DBus" + dbusPath = "/org/freedesktop/DBus" + dbusGetNameOwnerMethod = "GetNameOwner" + + firewalldName = "org.fedoraproject.FirewallD1" + firewalldPath = "/org/fedoraproject/FirewallD1" + firewalldZoneInterface = "org.fedoraproject.FirewallD1.zone" + firewalldAddSourceMethod = "addSource" + firewalldRemoveSourceMethod = "removeSource" + firewalldQuerySourceMethod = "querySource" + + errZoneAlreadySet = "ZONE_ALREADY_SET" +) + +// Only used for testcases to override the D-Bus connection +var testConn *dbus.Conn + +type fwdBackend struct { + conn *dbus.Conn +} + +// fwdBackend implements the FirewallBackend interface +var _ FirewallBackend = &fwdBackend{} + +func getConn() (*dbus.Conn, error) { + if testConn != nil { + return testConn, nil + } + return dbus.SystemBus() +} + +// isFirewalldRunning checks whether firewalld is running. +func isFirewalldRunning() bool { + conn, err := getConn() + if err != nil { + return false + } + + dbusObj := conn.Object(dbusName, dbusPath) + var res string + if err := dbusObj.Call(dbusName+"."+dbusGetNameOwnerMethod, 0, firewalldName).Store(&res); err != nil { + return false + } + + return true +} + +func newFirewalldBackend(conf *FirewallNetConf) (FirewallBackend, error) { + conn, err := getConn() + if err != nil { + return nil, err + } + + backend := &fwdBackend{ + conn: conn, + } + return backend, nil +} + +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) + var res string + if err := firewalldObj.Call(firewalldZoneInterface+"."+firewalldAddSourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res); err != nil { + if !strings.Contains(err.Error(), errZoneAlreadySet) { + return fmt.Errorf("failed to add the address %v to %v zone: %v", ipStr, conf.FirewalldZone, err) + } + } + } + return nil +} + +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) + var res string + firewalldObj.Call(firewalldZoneInterface+"."+firewalldRemoveSourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res) + } + 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 new file mode 100644 index 00000000..faae35c6 --- /dev/null +++ b/plugins/meta/firewall/iptables.go @@ -0,0 +1,279 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This is a "meta-plugin". It reads in its own netconf, it does not create +// any network interface but just changes the network sysctl. + +package main + +import ( + "fmt" + "net" + + "github.com/containernetworking/cni/pkg/types/current" + "github.com/coreos/go-iptables/iptables" +) + +func getPrivChainRules(ip string) [][]string { + var rules [][]string + rules = append(rules, []string{"-d", ip, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}) + rules = append(rules, []string{"-s", ip, "-j", "ACCEPT"}) + return rules +} + +func ensureChain(ipt *iptables.IPTables, table, chain string) error { + chains, err := ipt.ListChains(table) + if err != nil { + return fmt.Errorf("failed to list iptables chains: %v", err) + } + for _, ch := range chains { + if ch == chain { + return nil + } + } + + return ipt.NewChain(table, chain) +} + +func generateFilterRule(privChainName string) []string { + return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName} +} + +func generateAdminRule(adminChainName string) []string { + return []string{"-m", "comment", "--comment", "CNI firewall plugin admin overrides", "-j", adminChainName} +} + +func cleanupRules(ipt *iptables.IPTables, privChainName string, rules [][]string) { + for _, rule := range rules { + ipt.Delete("filter", privChainName, rule...) + } +} + +func ensureFirstChainRule(ipt *iptables.IPTables, chain string, rule []string) error { + exists, err := ipt.Exists("filter", chain, rule...) + if !exists && err == nil { + err = ipt.Insert("filter", chain, 1, rule...) + } + return err +} + +func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error { + privRule := generateFilterRule(ib.privChainName) + adminRule := generateFilterRule(ib.adminChainName) + + // 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 + if err := ensureFirstChainRule(ipt, "FORWARD", privRule); err != nil { + return err + } + + // Ensure our admin override chain rule exists in our private chain + if err := ensureFirstChainRule(ipt, ib.privChainName, adminRule); err != nil { + return err + } + + return nil +} + +func protoForIP(ip net.IPNet) iptables.Protocol { + if ip.IP.To4() != nil { + return iptables.ProtocolIPv4 + } + return iptables.ProtocolIPv6 +} + +func (ib *iptablesBackend) addRules(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 { + if err := ib.setupChains(ipt); err != nil { + return err + } + + // Clean up on any errors + var err error + defer func() { + if err != nil { + cleanupRules(ipt, ib.privChainName, rules) + } + }() + + for _, rule := range rules { + err = ipt.AppendUnique("filter", ib.privChainName, rule...) + if err != nil { + return err + } + } + } + + return nil +} + +func (ib *iptablesBackend) delRules(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 { + cleanupRules(ipt, ib.privChainName, rules) + } + + 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{} + result, _ := current.NewResultFromResult(conf.PrevResult) + for _, addr := range result.IPs { + if addr.Address.IP.To4() != nil { + protos = append(protos, iptables.ProtocolIPv4) + } else { + protos = append(protos, iptables.ProtocolIPv6) + } + } + } + return protos +} + +type iptablesBackend struct { + protos map[iptables.Protocol]*iptables.IPTables + privChainName string + adminChainName string + ifName string +} + +// iptablesBackend implements the FirewallBackend interface +var _ FirewallBackend = &iptablesBackend{} + +func newIptablesBackend(conf *FirewallNetConf) (FirewallBackend, error) { + adminChainName := conf.IptablesAdminChainName + if adminChainName == "" { + adminChainName = "CNI-ADMIN" + } + + backend := &iptablesBackend{ + privChainName: "CNI-FORWARD", + adminChainName: adminChainName, + protos: make(map[iptables.Protocol]*iptables.IPTables), + } + + for _, proto := range []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} { + ipt, err := iptables.NewWithProtocol(proto) + if err != nil { + return nil, fmt.Errorf("could not initialize iptables protocol %v: %v", proto, err) + } + backend.protos[proto] = ipt + } + + return backend, nil +} + +func (ib *iptablesBackend) Add(conf *FirewallNetConf, result *current.Result) error { + for proto, ipt := range ib.protos { + if err := ib.addRules(conf, result, ipt, proto); err != nil { + return err + } + } + return nil +} + +func (ib *iptablesBackend) Del(conf *FirewallNetConf, result *current.Result) error { + for proto, ipt := range ib.protos { + 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 +} diff --git a/vendor/github.com/godbus/dbus/.travis.yml b/vendor/github.com/godbus/dbus/.travis.yml new file mode 100644 index 00000000..2e1bbb78 --- /dev/null +++ b/vendor/github.com/godbus/dbus/.travis.yml @@ -0,0 +1,40 @@ +dist: precise +language: go +go_import_path: github.com/godbus/dbus +sudo: true + +go: + - 1.6.3 + - 1.7.3 + - tip + +env: + global: + matrix: + - TARGET=amd64 + - TARGET=arm64 + - TARGET=arm + - TARGET=386 + - TARGET=ppc64le + +matrix: + fast_finish: true + allow_failures: + - go: tip + exclude: + - go: tip + env: TARGET=arm + - go: tip + env: TARGET=arm64 + - go: tip + env: TARGET=386 + - go: tip + env: TARGET=ppc64le + +addons: + apt: + packages: + - dbus + - dbus-x11 + +before_install: diff --git a/vendor/github.com/godbus/dbus/CONTRIBUTING.md b/vendor/github.com/godbus/dbus/CONTRIBUTING.md new file mode 100644 index 00000000..c88f9b2b --- /dev/null +++ b/vendor/github.com/godbus/dbus/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# How to Contribute + +## Getting Started + +- Fork the repository on GitHub +- Read the [README](README.markdown) for build and test instructions +- Play with the project, submit bugs, submit patches! + +## Contribution Flow + +This is a rough outline of what a contributor's workflow looks like: + +- Create a topic branch from where you want to base your work (usually master). +- Make commits of logical units. +- Make sure your commit messages are in the proper format (see below). +- Push your changes to a topic branch in your fork of the repository. +- Make sure the tests pass, and add any new tests as appropriate. +- Submit a pull request to the original repository. + +Thanks for your contributions! + +### Format of the Commit Message + +We follow a rough convention for commit messages that is designed to answer two +questions: what changed and why. The subject line should feature the what and +the body of the commit should describe the why. + +``` +scripts: add the test-cluster command + +this uses tmux to setup a test cluster that you can easily kill and +start for debugging. + +Fixes #38 +``` + +The format can be described more formally as follows: + +``` +: + + + +