Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: firewall: add nftables backend #462

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 3 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c
github.com/golang/protobuf v1.3.1 // indirect
github.com/google/nftables v0.0.0-20200802175506-c25e4f69b425
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
github.com/mattn/go-shellwords v1.0.3
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b
Expand All @@ -24,10 +25,8 @@ require (
github.com/sirupsen/logrus v1.0.6 // indirect
github.com/stretchr/testify v1.3.0 // indirect
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 // indirect
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc
golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
)
25 changes: 25 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,22 @@ github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c h1:RBUpb2b14UnmRHNd2uH
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/nftables v0.0.0-20200316075819-7127d9d22474 h1:D6bN82zzK92ywYsE+Zjca7EHZCRZbcNTU3At7WdxQ+c=
github.com/google/nftables v0.0.0-20200316075819-7127d9d22474/go.mod h1:cfspEyr/Ap+JDIITA+N9a0ernqG0qZ4W1aqMRgDZa1g=
github.com/google/nftables v0.0.0-20200802175506-c25e4f69b425 h1:Ob7HrdEgedxSwCofNfvAYCNiuXbcuELBXP+Y2loxpXM=
github.com/google/nftables v0.0.0-20200802175506-c25e4f69b425/go.mod h1:cfspEyr/Ap+JDIITA+N9a0ernqG0qZ4W1aqMRgDZa1g=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56 h1:742eGXur0715JMq73aD95/FU0XpVKXqNuTnEfXsLOYQ=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d h1:MFX8DxRnKMY/2M3H61iSsVbo/n3h0MWGmWNN1UViOU0=
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d/go.mod h1:QHb4k4cr1fQikUahfcRVPcEXiUgFsdIstGqlurL0XL4=
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v0.0.0-20191009155606-de872b0d824b h1:W3er9pI7mt2gOqOWzwvx20iJ8Akiqz1mUMTxU6wdvl8=
github.com/mdlayher/netlink v0.0.0-20191009155606-de872b0d824b/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b h1:Ey6yH0acn50T/v6CB75bGP4EMJqnv9WvnjN7oZaj+xE=
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a h1:KfNOeFvoAssuZLT7IntKZElKwi/5LRuxY71k+t6rfaM=
Expand All @@ -49,10 +61,23 @@ github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrB
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 h1:qBTHLajHecfu+xzRI9PqVDcqx7SdHj9d4B+EzSn3tAc=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss=
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c h1:S/FtSvpNLtFBgjTqcKsRpsa6aVsI6iztaz1bQd9BJwE=
golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
Expand Down
206 changes: 205 additions & 1 deletion plugins/meta/firewall/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,218 @@ 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.100.0/24",
"gateway": "192.168.100.1"
}
]
]
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
},
{
"type": "firewall",
"backend": "nftables"
}
]
}
```

Prior to the invocation of CNI `firewall` plugin, the `FORWARD` chain in `filter`
table might be configured be as follows:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not guaranteed to exist. It will likely exist on any machine that uses the iptables-nft variant of iptables as it implicitly create this table/chain. I would not assume the table/chain exists.

CNI could use its own table and set of chains and thus avoid many interaction issues with iptables-nft. However, a separate table and chains has its own issue related to netfilter hooks. The tl;dr is nftables allows multiple chains to use the same netfilter hook. A consequence of this is that packets that are accepted are still subject to the rules of other chains hooked into the hook type.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not guaranteed to exist. It will likely exist on any machine that uses the iptables-nft variant of iptables as it implicitly create this table/chain. I would not assume the table/chain exists.

@erig0, I can create a chain. that is I do it when testing changes.

@dcbw , @mars1024 , agreed?


```
table ip filter {
chain FORWARD { # handle 1
type filter hook forward priority filter; policy drop;
log prefix "IPv4 FORWARD drop: " flags all # handle 28
counter packets 0 bytes 0 drop # handle 29
}
}
```

Subsequently, the plugin creates "non-base chain", e.g. `cnins-3-4026543850-dummy0`
and link it to `FORWARD` chain
via [`jump` instruction](https://wiki.nftables.org/wiki-nftables/index.php/Jumping_to_chain).

```
table ip filter {
chain FORWARD { # handle 1
type filter hook forward priority filter; policy drop;
jump cnins-3-4026543850-dummy0 # handle 10
log prefix "IPv4 FORWARD drop: " flags all # handle 28
counter packets 0 bytes 0 drop # handle 29
}

chain cnins-3-4026543850-dummy0 { # handle 2
oifname "dummy0" ip daddr 192.168.100.100 ct state established,related counter packets 0 bytes 0 accept # handle 3
iifname "dummy0" ip saddr 192.168.100.100 counter packets 0 bytes 0 accept # handle 4
iifname "dummy0" oifname "dummy0" counter packets 0 bytes 0 accept # handle 5
}
}
```

The name of the chain is is prefixed with `CNINS-` and followed by `Dev` and `Ino`
of `Stat_t` struct. See [here](https://github.com/vishvananda/netns/blob/master/netns.go#L60)
for more information.

Generally, the testing of nftables backend of this plugin begins with defining
the data structure the plugin would receive when processing a request.
In this example, the plugin received single interface `dummy0`, with IPv4 and
IPv6 addresses.

```json
{
"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
}
]
}
}
```

Prior to running tests, the test harness does the following:

1. creates `originalNS` namespace
2. adds `dummy0` interface to `originalNS` via Netlink
3. checks that the `dummy0` interface is available in the `originalNS`
4. creates `targetNS` namespace

Upon the completion of the testing, the test harness does the following:

1. closes `originalNS` namespace
2. closes `targetNS` namespace

The tests in the harness start with `It()`.

Generally, a test contains a number of input arguments. In the case of
"installs nftables rules, checks the rules exist, then cleans up on delete using v4.0.x",
the test has the following arguments:

* container id: `dummy`
* the path to container namespace, i.e. `targetNS`
* the name of the interface
* the JSON payload containing a dummy request

The test uses the same arguments and runs the following operations in
`originalNS` namespace:

* `cmdAdd`
* `cmdCheck`
* `cmdDel`

The operations correspond to the following functions:

| **Operation** | **Function** |
| --- | --- |
| `cmdAdd` | `func (nb *nftBackend) Add(conf *FirewallNetConf, result *current.Result)` |
| `cmdCheck` | `func (nb *nftBackend) Del(conf *FirewallNetConf, result *current.Result)` |
| `cmdDel` | `func (nb *nftBackend) Check(conf *FirewallNetConf, result *current.Result)` |

The following command triggers the testing of `firewall` plugin:

```bash
sudo go test -v ./plugins/meta/firewall
```

At the outset, the test outputs dummy host and container namespaces:

```
Host Namespace: /var/run/netns/cnitest-ba94096d-68e1-90c0-5e0a-4acf3a8339cd
Container Namespace: /var/run/netns/cnitest-762bc306-9af5-5882-af95-e011590ce8d3
```

The knowing the last part of the namespace path helps inspecting namespaces
with `sudo ip netns exec` command. For example, the following command
show `nftables` tables, chains, and rules.

```bash

$ sudo ip netns exec cnitest-ba94096d-68e1-90c0-5e0a-4acf3a8339cd nft list ruleset
table ip filter {
chain FORWARD {
type filter hook forward priority filter; policy drop;
jump cnins-3-4026550857-dummy0
}

chain cnins-3-4026550857-dummy0 {
oifname "dummy0" ip daddr 192.168.100.100 ct state established,related counter packets 0 bytes 0 accept
iifname "dummy0" ip saddr 192.168.100.100 counter packets 0 bytes 0 accept
iifname "dummy0" oifname "dummy0" counter packets 0 bytes 0 accept
}
}
table ip6 filter {
chain FORWARD {
type filter hook forward priority filter; policy drop;
jump cnins-3-4026550857-dummy0
}

chain cnins-3-4026550857-dummy0 {
oifname "dummy0" ip6 daddr 2001:db8:100:100::1 ct state established,related counter packets 0 bytes 0 accept
iifname "dummy0" ip6 saddr 2001:db8:100:100::1 counter packets 0 bytes 0 accept
iifname "dummy0" oifname "dummy0" counter packets 0 bytes 0 accept
}
Comment on lines +321 to +337
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally these would be combined into one chain in a table of family inet. I don't think there is value in splitting them up. It can probably avoid some duplicate rules.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally these would be combined into one chain in a table of family inet. I don't think there is value in splitting them up. It can probably avoid some duplicate rules.

@erig0, please see @mars1024 comment that he wanted a jump.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is value in splitting them up. It can probably avoid some duplicate rules.

@erig0, it has a value in a sense that it is easier to delete the rule when you have a jump like this. It would be very hard to parse each rule.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the jump to a dedicated chain. That 100% makes sense. I was saying I don't think you need to split the "ip" and "ip6" families. You could use "inet" which covers both IPv4 and IPv6.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the jump to a dedicated chain. That 100% makes sense. I was saying I don't think you need to split the "ip" and "ip6" families. You could use "inet" which covers both IPv4 and IPv6.

@erig0, that I could do. 👍 It makes sense because IPv4/6 are all a part of the same construct.

}
=======
The `CNI-FORWARD` chain first sends all traffic to `CNI-ADMIN` chain, which is intended as an user-controlled chain for custom rules that run prior to rules managed by the `firewall` plugin. The `firewall` plugin does not add, delete or modify rules in the `CNI-ADMIN` chain.

`CNI-FORWARD` chain:
- `-j CNI-ADMIN`

The chain name `CNI-ADMIN` can be overridden by specifying `iptablesAdminChainName` in the plugin configuration

```
```json
{
"type": "firewall",
"backend": "iptables",
Expand Down
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
Loading