Skip to content

Commit

Permalink
firewall: add nftables backend
Browse files Browse the repository at this point in the history
Resolves: containernetworking#461

Signed-off-by: Paul Greenberg <greenpau@outlook.com>
  • Loading branch information
greenpau committed Jul 31, 2020
1 parent d713ec6 commit 61d9999
Show file tree
Hide file tree
Showing 4 changed files with 607 additions and 0 deletions.
96 changes: 96 additions & 0 deletions plugins/meta/firewall/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,99 @@ of the container as shown:
- `-s 10.88.0.2 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT`
- `-d 10.88.0.2 -j ACCEPT`

## nftables backend rule structure

The prerequisite for the backend is the existence of `filter` table and
the existence of `FORWARD` chain in the table.

A sample standalone config list (with the file extension `.conflist`) using
`nftables` backend might look like:

```json
{
"cniVersion": "0.4.0",
"name": "podman",
"plugins": [
{
"type": "bridge",
"bridge": "cni-podman0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"ranges": [
[
{
"subnet": "192.168.124.0/24",
"gateway": "192.168.124.1"
}
]
]
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
},
{
"type": "firewall",
"backend": "nftables"
}
]
}
```

Prior to the invocation of CNI `firewall` plugin, the `FORWARD` chain in `filter` table is:

```
$ nft list chain ip filter FORWARD -a
table ip filter {
chain FORWARD { # handle 2
type filter hook forward priority 0; policy drop;
oifname "virbr0" ip daddr 192.168.122.0/24 ct state established,related counter packets 0 bytes 0 accept # handle 51
iifname "virbr0" ip saddr 192.168.122.0/24 counter packets 0 bytes 0 accept # handle 52
iifname "virbr0" oifname "virbr0" counter packets 0 bytes 0 accept # handle 53
log prefix "IPv4 FORWARD drop: " flags all # handle 54
counter packets 10 bytes 630 drop # handle 55
}
}
```

After starting a container, the plugin executes the following commands based
on the configuration above. Please note that `position 51` refers to the handle
at the top of the chain.

```
nft insert rule filter FORWARD position 51 oifname "cni-podman0" ip daddr 192.168.124.0/24 ct state established,related counter packets 0 bytes 0 accept
nft insert rule filter FORWARD position 51 iifname "cni-podman0" ip saddr 192.168.124.0/24 counter packets 0 bytes 0 accept
nft insert rule filter FORWARD position 51 iifname "cni-podman0" oifname "cni-podman0" counter packets 0 bytes 0 accept
```

After the plugin's execution, the chain looks like this:

```
$ nft list chain ip filter FORWARD -a
table ip filter {
chain FORWARD { # handle 2
type filter hook forward priority 0; policy drop;
oifname "cni-podman0" ip daddr 192.168.124.0/24 ct state established,related counter packets 100 bytes 113413 accept # handle 71
iifname "cni-podman0" ip saddr 192.168.124.0/24 counter packets 124 bytes 12996 accept # handle 72
iifname "cni-podman0" oifname "cni-podman0" counter packets 0 bytes 0 accept # handle 73
oifname "virbr0" ip daddr 192.168.122.0/24 ct state established,related counter packets 0 bytes 0 accept # handle 51
iifname "virbr0" ip saddr 192.168.122.0/24 counter packets 0 bytes 0 accept # handle 52
iifname "virbr0" oifname "virbr0" counter packets 0 bytes 0 accept # handle 53
log prefix "IPv4 FORWARD drop: " flags all # handle 54
counter packets 10 bytes 630 drop # handle 55
}
}
```

Subsequent executions of the plugin do not create additional rules in the chain, unless
the CNI network configuration changes.
2 changes: 2 additions & 0 deletions plugins/meta/firewall/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ func getBackend(conf *FirewallNetConf) (FirewallBackend, error) {
switch conf.Backend {
case "iptables":
return newIptablesBackend(conf)
case "nftables":
return newNftablesBackend(conf)
case "firewalld":
return newFirewalldBackend(conf)
}
Expand Down
162 changes: 162 additions & 0 deletions plugins/meta/firewall/firewall_nftables_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// 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/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"

"github.com/vishvananda/netlink"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func validateNftRules(bytes []byte) {
prevResult := getPrevResult(bytes)

for _, ip := range prevResult.IPs {
Expect(ip).To(Equal(true))
/*
ipt, err := iptables.NewWithProtocol(protoForIP(ip.Address))
Expect(err).NotTo(HaveOccurred())
// Ensure chains
chains, err := ipt.ListChains("filter")
Expect(err).NotTo(HaveOccurred())
foundAdmin, foundPriv := findChains(chains)
Expect(foundAdmin).To(Equal(true))
Expect(foundPriv).To(Equal(true))
// Look for the FORWARD chain jump rules to our custom chains
rules, err := ipt.List("filter", "FORWARD")
Expect(err).NotTo(HaveOccurred())
Expect(len(rules)).Should(BeNumerically(">", 1))
_, foundPriv = findForwardJumpRules(rules)
Expect(foundPriv).To(Equal(true))
// Look for the allow rules in our custom FORWARD chain
rules, err = ipt.List("filter", "CNI-FORWARD")
Expect(err).NotTo(HaveOccurred())
Expect(len(rules)).Should(BeNumerically(">", 1))
foundAdmin, _ = findForwardJumpRules(rules)
Expect(foundAdmin).To(Equal(true))
// Look for the IP allow rules
foundOne, foundTwo := findForwardAllowRules(rules, ipString(ip.Address))
Expect(foundOne).To(Equal(true))
Expect(foundTwo).To(Equal(true))
*/
}
}

var _ = Describe("firewall plugin nftables backend v0.4.x", func() {
var originalNS, targetNS ns.NetNS
const IFNAME string = "dummy0"

fullConf := []byte(`{
"name": "test",
"type": "firewall",
"backend": "nftables",
"ifName": "dummy0",
"cniVersion": "0.4.0",
"prevResult": {
"interfaces": [
{"name": "dummy0"}
],
"ips": [
{
"version": "4",
"address": "192.168.200.10/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 nftables 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())
validateNftRules(fullConf)

/*
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
validateCleanedUp(fullConf)
*/
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})
Loading

0 comments on commit 61d9999

Please sign in to comment.