-
Notifications
You must be signed in to change notification settings - Fork 797
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
iptables-allow: new plugin which adds a host interface to the filter/…
…FORWARD chain Distros often have additional rules in the filter table that do things like: -A FORWARD -j REJECT --reject-with icmp-host-prohibited docker, for example, gets around this by adding explicit rules to the filter table's FORWARD chain to allow traffic from the docker0 interface. Do that for a given host interface too, as a chained plugin. eg: { "cniVersion": "0.3.1", "name": "bridge-thing", "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": "iptables-allow" } ] }
- Loading branch information
Showing
5 changed files
with
845 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,4 @@ plugins/main/ptp | |
plugins/main/vlan | ||
plugins/meta/portmap | ||
plugins/meta/tuning | ||
plugins/meta/iptables-allow |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# iptables-allow plugin | ||
|
||
## Overview | ||
|
||
This plugin creates iptables rules to allow traffic from the host network interface given by "ifName". | ||
It does not create any network interfaces and therefore does not set up connectivity by itself. | ||
It is only useful when used in addition to other plugins. | ||
|
||
## Operation | ||
The following network configuration file | ||
``` | ||
{ | ||
"name": "mynet", | ||
"type": "iptables-allow", | ||
"ifName": "cni0" | ||
} | ||
``` | ||
will create two new iptables chains in the `filter` table and add rules that allow the given interface to send/receive traffic. | ||
|
||
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 verbatim. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,297 @@ | ||
// 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" | ||
|
||
"github.com/coreos/go-iptables/iptables" | ||
|
||
"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/vishvananda/netlink" | ||
// . "github.com/onsi/ginkgo" | ||
) | ||
|
||
// IptAllowConf represents the iptables allow configuration. | ||
type IptAllowConf struct { | ||
types.NetConf | ||
|
||
// IfName is the interface name to apply rules for; if not given the | ||
// interface name will be pulled from the first host interface of | ||
// the PrevResult (eg, the first one without a sandbox key). | ||
IfName string `json:"ifName,omitempty"` | ||
// AdminChainName is an optional name to use instead of the default | ||
// admin rules override chain name that includes the interface name. | ||
AdminChainName string `json:"adminChainName,omitempty"` | ||
|
||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` | ||
PrevResult *current.Result `json:"-"` | ||
} | ||
|
||
func parseConf(data []byte) (*IptAllowConf, error) { | ||
conf := IptAllowConf{} | ||
if err := json.Unmarshal(data, &conf); err != nil { | ||
return nil, fmt.Errorf("failed to load netconf: %v", err) | ||
} | ||
|
||
// Parse previous result. | ||
if conf.RawPrevResult != nil { | ||
resultBytes, err := json.Marshal(conf.RawPrevResult) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not serialize prevResult: %v", err) | ||
} | ||
res, err := version.NewResult(conf.CNIVersion, resultBytes) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not parse prevResult: %v", err) | ||
} | ||
conf.RawPrevResult = nil | ||
conf.PrevResult, err = current.NewResultFromResult(res) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not convert result to current version: %v", err) | ||
} | ||
} | ||
|
||
return &conf, nil | ||
} | ||
|
||
func getPrivChainRules(intf string) [][]string { | ||
var rules [][]string | ||
rules = append(rules, []string{"-i", intf, "-j", "ACCEPT"}) | ||
rules = append(rules, []string{"-o", intf, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-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 generateFilterRules(intf, privChainName, adminChainName string) [][]string { | ||
var filterRules [][]string | ||
filterRules = append(filterRules, []string{"-m", "comment", "--comment", fmt.Sprintf("CNI interface %s private rules", intf), "-j", privChainName}) | ||
filterRules = append(filterRules, []string{"-i", intf, "!", "-o", intf, "-m", "comment", "--comment", fmt.Sprintf("CNI interface %s administrator overrides", intf), "-j", adminChainName}) | ||
return filterRules | ||
} | ||
|
||
func generateChainNames(intf, adminChainName string) (string, string) { | ||
if adminChainName == "" { | ||
adminChainName = fmt.Sprintf("CNI-ADMIN-%s", intf) | ||
} | ||
return fmt.Sprintf("CNI-FORWARD-%s", intf), adminChainName | ||
} | ||
|
||
func cleanupRules(ipt *iptables.IPTables, filterRules [][]string, privChainName, adminChainName string) { | ||
for _, rule := range filterRules { | ||
ipt.Delete("filter", "FORWARD", rule...) | ||
} | ||
|
||
ipt.ClearChain("filter", privChainName) | ||
ipt.DeleteChain("filter", privChainName) | ||
ipt.ClearChain("filter", adminChainName) | ||
ipt.DeleteChain("filter", adminChainName) | ||
} | ||
|
||
func addRules(intf, adminChainName string, proto iptables.Protocol) error { | ||
var ( | ||
err error | ||
ipt *iptables.IPTables | ||
exists bool | ||
privChainName string | ||
) | ||
|
||
ipt, err = iptables.NewWithProtocol(proto) | ||
if err != nil { | ||
return fmt.Errorf("failed to initialize iptables helper: %v", err) | ||
} | ||
|
||
privChainName, adminChainName = generateChainNames(intf, adminChainName) | ||
filterRules := generateFilterRules(intf, privChainName, adminChainName) | ||
defer func() { | ||
// Clean up on error | ||
if err != nil { | ||
cleanupRules(ipt, filterRules, privChainName, adminChainName) | ||
} | ||
}() | ||
|
||
if err := ensureChain(ipt, "filter", privChainName); err != nil { | ||
return err | ||
} | ||
if err := ensureChain(ipt, "filter", adminChainName); err != nil { | ||
return err | ||
} | ||
|
||
for _, rule := range filterRules { | ||
exists, err = ipt.Exists("filter", "FORWARD", rule...) | ||
if !exists && err == nil { | ||
err = ipt.Insert("filter", "FORWARD", 1, rule...) | ||
} | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
for _, rule := range getPrivChainRules(intf) { | ||
if err := ipt.AppendUnique("filter", privChainName, rule...); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func delRules(intf, adminChainName string, proto iptables.Protocol) { | ||
var privChainName string | ||
if ipt, err := iptables.NewWithProtocol(proto); err == nil { | ||
privChainName, adminChainName = generateChainNames(intf, adminChainName) | ||
filterRules := generateFilterRules(intf, privChainName, adminChainName) | ||
cleanupRules(ipt, filterRules, privChainName, adminChainName) | ||
} | ||
} | ||
|
||
func hasSlavePorts(ifName, contIfName string, containerNS ns.NetNS) (bool, error) { | ||
link, err := netlink.LinkByName(ifName) | ||
if err != nil { | ||
return false, fmt.Errorf("could not lookup %q: %v", ifName, err) | ||
} | ||
|
||
// DEL calls plugins in reverse order, thus the container interface and | ||
// any peer (if it's a veth, for example) may not have been removed from | ||
// the target interface (eg, a bridge) yet. So we need to ignore any | ||
// peer when looking for slave links of the target interface. | ||
var peerIndex int | ||
if containerNS != nil { | ||
containerNS.Do(func(_ ns.NetNS) error { | ||
containerLink, err := netlink.LinkByName(contIfName) | ||
if err != nil { | ||
return err | ||
} | ||
peerIndex = containerLink.Attrs().ParentIndex | ||
return nil | ||
}) | ||
} | ||
|
||
allLinks, err := netlink.LinkList() | ||
if err != nil { | ||
return false, fmt.Errorf("could not list links %q: %v", ifName, err) | ||
} | ||
for _, l := range allLinks { | ||
if l.Attrs().Index != peerIndex && l.Attrs().MasterIndex == link.Attrs().Index { | ||
return true, nil | ||
} | ||
} | ||
|
||
return false, nil | ||
} | ||
|
||
func getIfname(conf *IptAllowConf) (string, error) { | ||
if conf.IfName == "" { | ||
return "", fmt.Errorf("failed to find host interface name from config or PrevResult") | ||
} | ||
|
||
return conf.IfName, nil | ||
} | ||
|
||
func findProtos(conf *IptAllowConf) []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 | ||
} | ||
|
||
func cmdAdd(args *skel.CmdArgs) error { | ||
iptConf, err := parseConf(args.StdinData) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
ifName, err := getIfname(iptConf) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, proto := range findProtos(iptConf) { | ||
if err := addRules(ifName, iptConf.AdminChainName, proto); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
result := iptConf.PrevResult | ||
if result == nil { | ||
result = ¤t.Result{} | ||
} | ||
return types.PrintResult(result, iptConf.CNIVersion) | ||
} | ||
|
||
func cmdDel(args *skel.CmdArgs) error { | ||
iptConf, err := parseConf(args.StdinData) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
ifName, err := getIfname(iptConf) | ||
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 | ||
// Cleanup happens if the device is not a master-type device, or if | ||
// it is a master-type device but has no slaves/ports | ||
hasPorts, err := hasSlavePorts(ifName, args.IfName, containerNS) | ||
if !hasPorts && err == nil { | ||
for _, proto := range findProtos(iptConf) { | ||
delRules(ifName, iptConf.AdminChainName, proto) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func main() { | ||
skel.PluginMain(cmdAdd, cmdDel, version.All) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 TestIptablesAllow(t *testing.T) { | ||
RegisterFailHandler(Fail) | ||
RunSpecs(t, "iptables_allow Suite") | ||
} |
Oops, something went wrong.