diff options
-rw-r--r-- | libpod/runtime.go | 39 | ||||
-rw-r--r-- | pkg/firewall/common.go | 51 | ||||
-rw-r--r-- | pkg/firewall/firewall_linux.go | 47 | ||||
-rw-r--r-- | pkg/firewall/firewall_none.go | 41 | ||||
-rw-r--r-- | pkg/firewall/firewall_unsupported.go | 27 | ||||
-rw-r--r-- | pkg/firewall/firewalld.go | 119 | ||||
-rw-r--r-- | pkg/firewall/iptables.go | 212 | ||||
-rw-r--r-- | vendor/github.com/coreos/go-iptables/LICENSE | 191 | ||||
-rw-r--r-- | vendor/github.com/coreos/go-iptables/NOTICE | 5 | ||||
-rw-r--r-- | vendor/github.com/coreos/go-iptables/README.md | 10 | ||||
-rw-r--r-- | vendor/github.com/coreos/go-iptables/iptables/iptables.go | 475 | ||||
-rw-r--r-- | vendor/github.com/coreos/go-iptables/iptables/lock.go | 84 |
12 files changed, 1288 insertions, 13 deletions
diff --git a/libpod/runtime.go b/libpod/runtime.go index c405eb773..8dc561cd8 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -13,6 +13,7 @@ import ( is "github.com/containers/image/storage" "github.com/containers/image/types" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/firewall" "github.com/containers/libpod/pkg/hooks" sysreg "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/rootless" @@ -70,19 +71,20 @@ type RuntimeOption func(*Runtime) error // Runtime is the core libpod runtime type Runtime struct { - config *RuntimeConfig - state State - store storage.Store - storageService *storageService - imageContext *types.SystemContext - ociRuntime *OCIRuntime - lockDir string - netPlugin ocicni.CNIPlugin - ociRuntimePath string - conmonPath string - valid bool - lock sync.RWMutex - imageRuntime *image.Runtime + config *RuntimeConfig + state State + store storage.Store + storageService *storageService + imageContext *types.SystemContext + ociRuntime *OCIRuntime + lockDir string + netPlugin ocicni.CNIPlugin + ociRuntimePath string + conmonPath string + valid bool + lock sync.RWMutex + imageRuntime *image.Runtime + firewallBackend firewall.FirewallBackend } // RuntimeConfig contains configuration options used to set up the runtime @@ -507,6 +509,17 @@ func makeRuntime(runtime *Runtime) (err error) { } runtime.netPlugin = netPlugin + // Set up a firewall backend + backendType := "" + if os.Geteuid() != 0 { + backendType = "none" + } + fwBackend, err := firewall.GetBackend(backendType) + if err != nil { + return err + } + runtime.firewallBackend = fwBackend + // Set up the state switch runtime.config.StateType { case InMemoryStateStore: 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 +} diff --git a/vendor/github.com/coreos/go-iptables/LICENSE b/vendor/github.com/coreos/go-iptables/LICENSE new file mode 100644 index 000000000..37ec93a14 --- /dev/null +++ b/vendor/github.com/coreos/go-iptables/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/coreos/go-iptables/NOTICE b/vendor/github.com/coreos/go-iptables/NOTICE new file mode 100644 index 000000000..23a0ada2f --- /dev/null +++ b/vendor/github.com/coreos/go-iptables/NOTICE @@ -0,0 +1,5 @@ +CoreOS Project +Copyright 2018 CoreOS, Inc + +This product includes software developed at CoreOS, Inc. +(http://www.coreos.com/). diff --git a/vendor/github.com/coreos/go-iptables/README.md b/vendor/github.com/coreos/go-iptables/README.md new file mode 100644 index 000000000..974a983e0 --- /dev/null +++ b/vendor/github.com/coreos/go-iptables/README.md @@ -0,0 +1,10 @@ +# go-iptables + +[![GoDoc](https://godoc.org/github.com/coreos/go-iptables/iptables?status.svg)](https://godoc.org/github.com/coreos/go-iptables/iptables) +[![Build Status](https://travis-ci.org/coreos/go-iptables.png?branch=master)](https://travis-ci.org/coreos/go-iptables) + +Go bindings for iptables utility. + +In-kernel netfilter does not have a good userspace API. The tables are manipulated via setsockopt that sets/replaces the entire table. Changes to existing table need to be resolved by userspace code which is difficult and error-prone. Netfilter developers heavily advocate using iptables utlity for programmatic manipulation. + +go-iptables wraps invocation of iptables utility with functions to append and delete rules; create, clear and delete chains. diff --git a/vendor/github.com/coreos/go-iptables/iptables/iptables.go b/vendor/github.com/coreos/go-iptables/iptables/iptables.go new file mode 100644 index 000000000..3b62fe205 --- /dev/null +++ b/vendor/github.com/coreos/go-iptables/iptables/iptables.go @@ -0,0 +1,475 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 iptables + +import ( + "bytes" + "fmt" + "io" + "net" + "os/exec" + "regexp" + "strconv" + "strings" + "syscall" +) + +// Adds the output of stderr to exec.ExitError +type Error struct { + exec.ExitError + cmd exec.Cmd + msg string +} + +func (e *Error) ExitStatus() int { + return e.Sys().(syscall.WaitStatus).ExitStatus() +} + +func (e *Error) Error() string { + return fmt.Sprintf("running %v: exit status %v: %v", e.cmd.Args, e.ExitStatus(), e.msg) +} + +// IsNotExist returns true if the error is due to the chain or rule not existing +func (e *Error) IsNotExist() bool { + return e.ExitStatus() == 1 && + (e.msg == "iptables: Bad rule (does a matching rule exist in that chain?).\n" || + e.msg == "iptables: No chain/target/match by that name.\n") +} + +// Protocol to differentiate between IPv4 and IPv6 +type Protocol byte + +const ( + ProtocolIPv4 Protocol = iota + ProtocolIPv6 +) + +type IPTables struct { + path string + proto Protocol + hasCheck bool + hasWait bool + hasRandomFully bool + v1 int + v2 int + v3 int +} + +// New creates a new IPTables. +// For backwards compatibility, this always uses IPv4, i.e. "iptables". +func New() (*IPTables, error) { + return NewWithProtocol(ProtocolIPv4) +} + +// New creates a new IPTables for the given proto. +// The proto will determine which command is used, either "iptables" or "ip6tables". +func NewWithProtocol(proto Protocol) (*IPTables, error) { + path, err := exec.LookPath(getIptablesCommand(proto)) + if err != nil { + return nil, err + } + vstring, err := getIptablesVersionString(path) + v1, v2, v3, err := extractIptablesVersion(vstring) + + checkPresent, waitPresent, randomFullyPresent, err := getIptablesCommandSupport(v1, v2, v3) + if err != nil { + return nil, fmt.Errorf("error checking iptables version: %v", err) + } + ipt := IPTables{ + path: path, + proto: proto, + hasCheck: checkPresent, + hasWait: waitPresent, + hasRandomFully: randomFullyPresent, + v1: v1, + v2: v2, + v3: v3, + } + return &ipt, nil +} + +// Proto returns the protocol used by this IPTables. +func (ipt *IPTables) Proto() Protocol { + return ipt.proto +} + +// Exists checks if given rulespec in specified table/chain exists +func (ipt *IPTables) Exists(table, chain string, rulespec ...string) (bool, error) { + if !ipt.hasCheck { + return ipt.existsForOldIptables(table, chain, rulespec) + + } + cmd := append([]string{"-t", table, "-C", chain}, rulespec...) + err := ipt.run(cmd...) + eerr, eok := err.(*Error) + switch { + case err == nil: + return true, nil + case eok && eerr.ExitStatus() == 1: + return false, nil + default: + return false, err + } +} + +// Insert inserts rulespec to specified table/chain (in specified pos) +func (ipt *IPTables) Insert(table, chain string, pos int, rulespec ...string) error { + cmd := append([]string{"-t", table, "-I", chain, strconv.Itoa(pos)}, rulespec...) + return ipt.run(cmd...) +} + +// Append appends rulespec to specified table/chain +func (ipt *IPTables) Append(table, chain string, rulespec ...string) error { + cmd := append([]string{"-t", table, "-A", chain}, rulespec...) + return ipt.run(cmd...) +} + +// AppendUnique acts like Append except that it won't add a duplicate +func (ipt *IPTables) AppendUnique(table, chain string, rulespec ...string) error { + exists, err := ipt.Exists(table, chain, rulespec...) + if err != nil { + return err + } + + if !exists { + return ipt.Append(table, chain, rulespec...) + } + + return nil +} + +// Delete removes rulespec in specified table/chain +func (ipt *IPTables) Delete(table, chain string, rulespec ...string) error { + cmd := append([]string{"-t", table, "-D", chain}, rulespec...) + return ipt.run(cmd...) +} + +// List rules in specified table/chain +func (ipt *IPTables) List(table, chain string) ([]string, error) { + args := []string{"-t", table, "-S", chain} + return ipt.executeList(args) +} + +// List rules (with counters) in specified table/chain +func (ipt *IPTables) ListWithCounters(table, chain string) ([]string, error) { + args := []string{"-t", table, "-v", "-S", chain} + return ipt.executeList(args) +} + +// ListChains returns a slice containing the name of each chain in the specified table. +func (ipt *IPTables) ListChains(table string) ([]string, error) { + args := []string{"-t", table, "-S"} + + result, err := ipt.executeList(args) + if err != nil { + return nil, err + } + + // Iterate over rules to find all default (-P) and user-specified (-N) chains. + // Chains definition always come before rules. + // Format is the following: + // -P OUTPUT ACCEPT + // -N Custom + var chains []string + for _, val := range result { + if strings.HasPrefix(val, "-P") || strings.HasPrefix(val, "-N") { + chains = append(chains, strings.Fields(val)[1]) + } else { + break + } + } + return chains, nil +} + +// Stats lists rules including the byte and packet counts +func (ipt *IPTables) Stats(table, chain string) ([][]string, error) { + args := []string{"-t", table, "-L", chain, "-n", "-v", "-x"} + lines, err := ipt.executeList(args) + if err != nil { + return nil, err + } + + appendSubnet := func(addr string) string { + if strings.IndexByte(addr, byte('/')) < 0 { + if strings.IndexByte(addr, '.') < 0 { + return addr + "/128" + } + return addr + "/32" + } + return addr + } + + ipv6 := ipt.proto == ProtocolIPv6 + + rows := [][]string{} + for i, line := range lines { + // Skip over chain name and field header + if i < 2 { + continue + } + + // Fields: + // 0=pkts 1=bytes 2=target 3=prot 4=opt 5=in 6=out 7=source 8=destination 9=options + line = strings.TrimSpace(line) + fields := strings.Fields(line) + + // The ip6tables verbose output cannot be naively split due to the default "opt" + // field containing 2 single spaces. + if ipv6 { + // Check if field 6 is "opt" or "source" address + dest := fields[6] + ip, _, _ := net.ParseCIDR(dest) + if ip == nil { + ip = net.ParseIP(dest) + } + + // If we detected a CIDR or IP, the "opt" field is empty.. insert it. + if ip != nil { + f := []string{} + f = append(f, fields[:4]...) + f = append(f, " ") // Empty "opt" field for ip6tables + f = append(f, fields[4:]...) + fields = f + } + } + + // Adjust "source" and "destination" to include netmask, to match regular + // List output + fields[7] = appendSubnet(fields[7]) + fields[8] = appendSubnet(fields[8]) + + // Combine "options" fields 9... into a single space-delimited field. + options := fields[9:] + fields = fields[:9] + fields = append(fields, strings.Join(options, " ")) + rows = append(rows, fields) + } + return rows, nil +} + +func (ipt *IPTables) executeList(args []string) ([]string, error) { + var stdout bytes.Buffer + if err := ipt.runWithOutput(args, &stdout); err != nil { + return nil, err + } + + rules := strings.Split(stdout.String(), "\n") + if len(rules) > 0 && rules[len(rules)-1] == "" { + rules = rules[:len(rules)-1] + } + + return rules, nil +} + +// NewChain creates a new chain in the specified table. +// If the chain already exists, it will result in an error. +func (ipt *IPTables) NewChain(table, chain string) error { + return ipt.run("-t", table, "-N", chain) +} + +// ClearChain flushed (deletes all rules) in the specified table/chain. +// If the chain does not exist, a new one will be created +func (ipt *IPTables) ClearChain(table, chain string) error { + err := ipt.NewChain(table, chain) + + eerr, eok := err.(*Error) + switch { + case err == nil: + return nil + case eok && eerr.ExitStatus() == 1: + // chain already exists. Flush (clear) it. + return ipt.run("-t", table, "-F", chain) + default: + return err + } +} + +// RenameChain renames the old chain to the new one. +func (ipt *IPTables) RenameChain(table, oldChain, newChain string) error { + return ipt.run("-t", table, "-E", oldChain, newChain) +} + +// DeleteChain deletes the chain in the specified table. +// The chain must be empty +func (ipt *IPTables) DeleteChain(table, chain string) error { + return ipt.run("-t", table, "-X", chain) +} + +// ChangePolicy changes policy on chain to target +func (ipt *IPTables) ChangePolicy(table, chain, target string) error { + return ipt.run("-t", table, "-P", chain, target) +} + +// Check if the underlying iptables command supports the --random-fully flag +func (ipt *IPTables) HasRandomFully() bool { + return ipt.hasRandomFully +} + +// Return version components of the underlying iptables command +func (ipt *IPTables) GetIptablesVersion() (int, int, int) { + return ipt.v1, ipt.v2, ipt.v3 +} + +// run runs an iptables command with the given arguments, ignoring +// any stdout output +func (ipt *IPTables) run(args ...string) error { + return ipt.runWithOutput(args, nil) +} + +// runWithOutput runs an iptables command with the given arguments, +// writing any stdout output to the given writer +func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error { + args = append([]string{ipt.path}, args...) + if ipt.hasWait { + args = append(args, "--wait") + } else { + fmu, err := newXtablesFileLock() + if err != nil { + return err + } + ul, err := fmu.tryLock() + if err != nil { + return err + } + defer ul.Unlock() + } + + var stderr bytes.Buffer + cmd := exec.Cmd{ + Path: ipt.path, + Args: args, + Stdout: stdout, + Stderr: &stderr, + } + + if err := cmd.Run(); err != nil { + switch e := err.(type) { + case *exec.ExitError: + return &Error{*e, cmd, stderr.String()} + default: + return err + } + } + + return nil +} + +// getIptablesCommand returns the correct command for the given protocol, either "iptables" or "ip6tables". +func getIptablesCommand(proto Protocol) string { + if proto == ProtocolIPv6 { + return "ip6tables" + } else { + return "iptables" + } +} + +// Checks if iptables has the "-C" and "--wait" flag +func getIptablesCommandSupport(v1 int, v2 int, v3 int) (bool, bool, bool, error) { + + return iptablesHasCheckCommand(v1, v2, v3), iptablesHasWaitCommand(v1, v2, v3), iptablesHasRandomFully(v1, v2, v3), nil +} + +// getIptablesVersion returns the first three components of the iptables version. +// e.g. "iptables v1.3.66" would return (1, 3, 66, nil) +func extractIptablesVersion(str string) (int, int, int, error) { + versionMatcher := regexp.MustCompile("v([0-9]+)\\.([0-9]+)\\.([0-9]+)") + result := versionMatcher.FindStringSubmatch(str) + if result == nil { + return 0, 0, 0, fmt.Errorf("no iptables version found in string: %s", str) + } + + v1, err := strconv.Atoi(result[1]) + if err != nil { + return 0, 0, 0, err + } + + v2, err := strconv.Atoi(result[2]) + if err != nil { + return 0, 0, 0, err + } + + v3, err := strconv.Atoi(result[3]) + if err != nil { + return 0, 0, 0, err + } + + return v1, v2, v3, nil +} + +// Runs "iptables --version" to get the version string +func getIptablesVersionString(path string) (string, error) { + cmd := exec.Command(path, "--version") + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return "", err + } + return out.String(), nil +} + +// Checks if an iptables version is after 1.4.11, when --check was added +func iptablesHasCheckCommand(v1 int, v2 int, v3 int) bool { + if v1 > 1 { + return true + } + if v1 == 1 && v2 > 4 { + return true + } + if v1 == 1 && v2 == 4 && v3 >= 11 { + return true + } + return false +} + +// Checks if an iptables version is after 1.4.20, when --wait was added +func iptablesHasWaitCommand(v1 int, v2 int, v3 int) bool { + if v1 > 1 { + return true + } + if v1 == 1 && v2 > 4 { + return true + } + if v1 == 1 && v2 == 4 && v3 >= 20 { + return true + } + return false +} + +// Checks if an iptables version is after 1.6.2, when --random-fully was added +func iptablesHasRandomFully(v1 int, v2 int, v3 int) bool { + if v1 > 1 { + return true + } + if v1 == 1 && v2 > 6 { + return true + } + if v1 == 1 && v2 == 6 && v3 >= 2 { + return true + } + return false +} + +// Checks if a rule specification exists for a table +func (ipt *IPTables) existsForOldIptables(table, chain string, rulespec []string) (bool, error) { + rs := strings.Join(append([]string{"-A", chain}, rulespec...), " ") + args := []string{"-t", table, "-S"} + var stdout bytes.Buffer + err := ipt.runWithOutput(args, &stdout) + if err != nil { + return false, err + } + return strings.Contains(stdout.String(), rs), nil +} diff --git a/vendor/github.com/coreos/go-iptables/iptables/lock.go b/vendor/github.com/coreos/go-iptables/iptables/lock.go new file mode 100644 index 000000000..a88e92b4e --- /dev/null +++ b/vendor/github.com/coreos/go-iptables/iptables/lock.go @@ -0,0 +1,84 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 iptables + +import ( + "os" + "sync" + "syscall" +) + +const ( + // In earlier versions of iptables, the xtables lock was implemented + // via a Unix socket, but now flock is used via this lockfile: + // http://git.netfilter.org/iptables/commit/?id=aa562a660d1555b13cffbac1e744033e91f82707 + // Note the LSB-conforming "/run" directory does not exist on old + // distributions, so assume "/var" is symlinked + xtablesLockFilePath = "/var/run/xtables.lock" + + defaultFilePerm = 0600 +) + +type Unlocker interface { + Unlock() error +} + +type nopUnlocker struct{} + +func (_ nopUnlocker) Unlock() error { return nil } + +type fileLock struct { + // mu is used to protect against concurrent invocations from within this process + mu sync.Mutex + fd int +} + +// tryLock takes an exclusive lock on the xtables lock file without blocking. +// This is best-effort only: if the exclusive lock would block (i.e. because +// another process already holds it), no error is returned. Otherwise, any +// error encountered during the locking operation is returned. +// The returned Unlocker should be used to release the lock when the caller is +// done invoking iptables commands. +func (l *fileLock) tryLock() (Unlocker, error) { + l.mu.Lock() + err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB) + switch err { + case syscall.EWOULDBLOCK: + l.mu.Unlock() + return nopUnlocker{}, nil + case nil: + return l, nil + default: + l.mu.Unlock() + return nil, err + } +} + +// Unlock closes the underlying file, which implicitly unlocks it as well. It +// also unlocks the associated mutex. +func (l *fileLock) Unlock() error { + defer l.mu.Unlock() + return syscall.Close(l.fd) +} + +// newXtablesFileLock opens a new lock on the xtables lockfile without +// acquiring the lock +func newXtablesFileLock() (*fileLock, error) { + fd, err := syscall.Open(xtablesLockFilePath, os.O_CREATE, defaultFilePerm) + if err != nil { + return nil, err + } + return &fileLock{fd: fd}, nil +} |