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