summaryrefslogtreecommitdiff
path: root/pkg/firewall
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/firewall')
-rw-r--r--pkg/firewall/common.go51
-rw-r--r--pkg/firewall/firewall_linux.go47
-rw-r--r--pkg/firewall/firewall_none.go41
-rw-r--r--pkg/firewall/firewall_unsupported.go27
-rw-r--r--pkg/firewall/firewalld.go119
-rw-r--r--pkg/firewall/iptables.go212
6 files changed, 497 insertions, 0 deletions
diff --git a/pkg/firewall/common.go b/pkg/firewall/common.go
new file mode 100644
index 000000000..993c691cd
--- /dev/null
+++ b/pkg/firewall/common.go
@@ -0,0 +1,51 @@
+package firewall
+
+// 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.
+
+import (
+ "net"
+
+ "github.com/containernetworking/cni/pkg/types/current"
+)
+
+// FirewallNetConf represents the firewall configuration.
+type FirewallNetConf struct {
+ //types.NetConf
+
+ // IptablesAdminChainName is an optional name to use instead of the default
+ // admin rules override chain name that includes the interface name.
+ IptablesAdminChainName string
+
+ // 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
+
+ PrevResult *current.Result
+}
+
+// FirewallBackend is an interface to the system firewall, allowing addition and
+// removal of firewall rules.
+type FirewallBackend interface {
+ Add(*FirewallNetConf) error
+ Del(*FirewallNetConf) error
+}
+
+func ipString(ip net.IPNet) string {
+ if ip.IP.To4() == nil {
+ return ip.IP.String() + "/128"
+ }
+ return ip.IP.String() + "/32"
+}
diff --git a/pkg/firewall/firewall_linux.go b/pkg/firewall/firewall_linux.go
new file mode 100644
index 000000000..4ac45427b
--- /dev/null
+++ b/pkg/firewall/firewall_linux.go
@@ -0,0 +1,47 @@
+// +build linux
+
+// 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.
+
+package firewall
+
+import (
+ "fmt"
+)
+
+// GetBackend retrieves a firewall backend for adding or removing firewall rules
+// on the system.
+// Valid backend names are firewalld, iptables, and none.
+// If the empty string is given, a firewalld backend will be returned if
+// firewalld is running, and an iptables backend will be returned otherwise.
+func GetBackend(backend string) (FirewallBackend, error) {
+ switch backend {
+ case "firewalld":
+ return newFirewalldBackend()
+ case "iptables":
+ return newIptablesBackend()
+ case "none":
+ return newNoneBackend()
+ case "":
+ // Default to firewalld if it's running
+ if isFirewalldRunning() {
+ return newFirewalldBackend()
+ }
+
+ // Otherwise iptables
+ return newIptablesBackend()
+ default:
+ return nil, fmt.Errorf("unrecognized firewall backend %q", backend)
+ }
+}
diff --git a/pkg/firewall/firewall_none.go b/pkg/firewall/firewall_none.go
new file mode 100644
index 000000000..9f9594b4a
--- /dev/null
+++ b/pkg/firewall/firewall_none.go
@@ -0,0 +1,41 @@
+// 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.
+
+package firewall
+
+import (
+ "fmt"
+)
+
+// FirewallNone is a firewall backend for environments where manipulating the
+// system firewall is unsupported (for example, when running without root)
+type FirewallNone struct {}
+
+func newNoneBackend() (FirewallBackend, error) {
+ return &FirewallNone{}, nil
+}
+
+// Add adds a rule to the system firewall.
+// No action is taken and an error is unconditionally returned as this backend
+// does not support manipulating the firewall.
+func (f *FirewallNone) Add(conf *FirewallNetConf) error {
+ return fmt.Errorf("cannot modify system firewall rules")
+}
+
+// Del deletes a rule from the system firewall.
+// No action is taken and an error is unconditionally returned as this backend
+// does not support manipulating the firewall.
+func (f *FirewallNone) Del(conf *FirewallNetConf) error {
+ return fmt.Errorf("cannot modify system firewall rules")
+}
diff --git a/pkg/firewall/firewall_unsupported.go b/pkg/firewall/firewall_unsupported.go
new file mode 100644
index 000000000..24c07a8a9
--- /dev/null
+++ b/pkg/firewall/firewall_unsupported.go
@@ -0,0 +1,27 @@
+// +build !linux
+
+// 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.
+
+package firewall
+
+import (
+ "fmt"
+)
+
+// GetBackend retrieves a firewall backend for adding or removing firewall rules
+// on the system.
+func GetBackend(backend string) (FirewallBackend, error) {
+ return nil, fmt.Errorf("firewall backends are not presently supported on this OS")
+}
diff --git a/pkg/firewall/firewalld.go b/pkg/firewall/firewalld.go
new file mode 100644
index 000000000..32c2337a0
--- /dev/null
+++ b/pkg/firewall/firewalld.go
@@ -0,0 +1,119 @@
+// +build linux
+
+// 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 firewall
+
+import (
+ "fmt"
+ "strings"
+
+ "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"
+
+ 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() (FirewallBackend, error) {
+ conn, err := getConn()
+ if err != nil {
+ return nil, err
+ }
+
+ backend := &fwdBackend{
+ conn: conn,
+ }
+ return backend, nil
+}
+
+func getFirewalldZone(conf *FirewallNetConf) string {
+ if conf.FirewalldZone != "" {
+ return conf.FirewalldZone
+ }
+
+ return "trusted"
+}
+
+func (fb *fwdBackend) Add(conf *FirewallNetConf) error {
+ zone := getFirewalldZone(conf)
+
+ for _, ip := range conf.PrevResult.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, zone, 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, zone, err)
+ }
+ }
+ }
+ return nil
+}
+
+func (fb *fwdBackend) Del(conf *FirewallNetConf) error {
+ for _, ip := range conf.PrevResult.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, getFirewalldZone(conf), ipStr).Store(&res)
+ }
+ return nil
+}
diff --git a/pkg/firewall/iptables.go b/pkg/firewall/iptables.go
new file mode 100644
index 000000000..9f065dbcf
--- /dev/null
+++ b/pkg/firewall/iptables.go
@@ -0,0 +1,212 @@
+// +build linux
+
+// 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 firewall
+
+import (
+ "fmt"
+ "net"
+
+ "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, ipt *iptables.IPTables, proto iptables.Protocol) error {
+ rules := make([][]string, 0)
+ for _, ip := range conf.PrevResult.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, ipt *iptables.IPTables, proto iptables.Protocol) error {
+ rules := make([][]string, 0)
+ for _, ip := range conf.PrevResult.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 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 {
+ 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() (FirewallBackend, error) {
+ 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) error {
+ for proto, ipt := range ib.protos {
+ if err := ib.addRules(conf, ipt, proto); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (ib *iptablesBackend) Del(conf *FirewallNetConf) error {
+ for proto, ipt := range ib.protos {
+ ib.delRules(conf, ipt, proto)
+ }
+ return nil
+}