|
| 1 | +--- |
| 2 | +title: Firewall Container |
| 3 | +author: troglobit |
| 4 | +date: 2024-03-12 08:08:41 +0100 |
| 5 | +categories: [showcase] |
| 6 | +tags: [container, containers, networking, firewall, docker, podman] |
| 7 | +--- |
| 8 | + |
| 9 | +{: width="200" .right} |
| 10 | + |
| 11 | +This is the third post in a series about containers in Infix. This time |
| 12 | +we dive into using containers as "apps" to extend the system for custom |
| 13 | +firewall setups. Infix does not yet have native support for setting up |
| 14 | +firewalls or ACLs, so the approach is useful even for more basic setups. |
| 15 | + |
| 16 | +See the [first post][1] for a background and networking basics. |
| 17 | + |
| 18 | +> This post assumes knowledge and familiarity with the [Infix Network |
| 19 | +> Operating System](https://kernelkit.github.io/). Ensure you have |
| 20 | +> either a network connection or console access to your Infix system and |
| 21 | +> can log in to it using SSH. Recommended reading includes the |
| 22 | +> [networking documentation][0]. |
| 23 | +{: .prompt-info } |
| 24 | + |
| 25 | +---- |
| 26 | + |
| 27 | + |
| 28 | +## Introduction |
| 29 | + |
| 30 | +A clever feature of containers in Linux is that any namespace[^1] used |
| 31 | +to "contain" a process can be either unique to the process, a set of |
| 32 | +processes sharing the same container, or shared between containers. |
| 33 | +Infix limits this flexibility for containers to: |
| 34 | + |
| 35 | +{: width="200" .right} |
| 36 | + |
| 37 | + 1. Fully contained, e.g., unique network namespace, fully isolated |
| 38 | + 2. Shared network namespace with the host |
| 39 | + |
| 40 | +The second one is what we'll focus on in this blog post because it |
| 41 | +allows us to add functionality to Infix that extends its networking |
| 42 | +capabilities. |
| 43 | + |
| 44 | +The application in question is an [nftables][5] container, again from |
| 45 | +the [curiOS][2] project, that we will use to create basic router setup |
| 46 | +with NAT[^2] firewall. |
| 47 | + |
| 48 | +> [nftables][5] is the Linux firewall, [see below](#example-etcnftablesconf) |
| 49 | +> for an `nftables.conf` example. |
| 50 | +{: .prompt-tip } |
| 51 | + |
| 52 | + |
| 53 | +_**Figure 1:** Basic router, single WAN port, LAN ports bridged._ |
| 54 | + |
| 55 | +## Configuration |
| 56 | + |
| 57 | +The Infix configuration consists of two parts: networking setup and the |
| 58 | +container. We start with the networking, we want a single port as our |
| 59 | +WAN port, connected to the Internet, all the other ports are bridged |
| 60 | +in a LAN configuration. |
| 61 | + |
| 62 | +```console |
| 63 | +admin@infix:/> configure |
| 64 | +admin@infix:/config/> set dhcp-client client-if eth0 |
| 65 | +admin@infix:/config/> edit interface br0 |
| 66 | +admin@infix:/config/interface/br0/> set ipv4 address 192.168.0.1 prefix-length 24 |
| 67 | +admin@infix:/config/interface/br0/> end |
| 68 | +admin@infix:/config/> set interface eth1 bridge-port bridge br0 |
| 69 | +admin@infix:/config/> set interface eth2 bridge-port bridge br0 |
| 70 | +admin@infix:/config/> set interface eth3 bridge-port bridge br0 |
| 71 | +admin@infix:/config/> leave |
| 72 | +``` |
| 73 | + |
| 74 | +Time for the container configuration. This time we don't need to mark |
| 75 | +any interfaces as `container-network`. The key configuration settings |
| 76 | +are `set network host`, which tells Infix to share its network namespace |
| 77 | +with the container, and *privileged* mode. |
| 78 | + |
| 79 | +```console |
| 80 | +admin@infix:/> configure |
| 81 | +admin@infix:/config> edit container nftables |
| 82 | +admin@infix:/config/container/nftables/> set image docker://ghcr.io/kernelkit/curios-nftables:24.02.0 |
| 83 | +admin@infix:/config/container/nftables/> set network host |
| 84 | +admin@infix:/config/container/nftables/> set privileged true |
| 85 | +admin@infix:/config/container/nftables/> edit mount nftables.conf |
| 86 | +admin@infix:/config/container/nftables/mount/nftables.conf/> set target /etc/nftables.conf |
| 87 | +admin@infix:/config/container/nftables/mount/nftables.conf/> text-editor content |
| 88 | +... interactive editor starts up where you can paste your rules ... |
| 89 | +admin@infix:/config/container/nftables/mount/nftables.conf/> leave |
| 90 | +``` |
| 91 | + |
| 92 | +Notice the command `text-editor content`, it opens up an interactive |
| 93 | +[text editor][4] where you can paste in the contents of the file |
| 94 | +`/etc/nftables.conf` that will be mounted in the container. |
| 95 | + |
| 96 | +The `text-editor` command base64 encodes the content and stores it |
| 97 | +in the Infix `startup-config`, thus ensuring that the configuration |
| 98 | +of the firewall is retained even when reconfigure the container, or |
| 99 | +upgrade the container image. |
| 100 | + |
| 101 | +## The Result |
| 102 | + |
| 103 | +We should now have a running container. |
| 104 | + |
| 105 | +```console |
| 106 | +admin@infix:/> show container |
| 107 | +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
| 108 | +aae7178c00f8 ghcr.io/kernelkit/curios-nftables:24.02.0 /usr/sbin/nft-hel... 56 seconds ago Up 56 seconds nftables |
| 109 | +``` |
| 110 | + |
| 111 | +This particular container cannot be connected to, like in the previous |
| 112 | +post, because it is only a plain application container with no embedded |
| 113 | +shell included. But we can exit to our host's shell and inspect the |
| 114 | +kernel netfilter rules: |
| 115 | + |
| 116 | +```console |
| 117 | +admin@infix:/> exit |
| 118 | +admin@infix:~$ sudo nft list ruleset |
| 119 | +table ip filter { |
| 120 | + chain wan { |
| 121 | + icmp type echo-request limit rate 5/second accept |
| 122 | + } |
| 123 | + |
| 124 | + chain lan { |
| 125 | + icmp type echo-request accept |
| 126 | + ip protocol . th dport vmap { tcp . 22 : accept, tcp . 53 : accept, udp . 53 : accept, udp . 67 : accept } |
| 127 | + } |
| 128 | + |
| 129 | + chain input { |
| 130 | + type filter hook input priority filter; policy drop; |
| 131 | + ct state vmap { invalid : drop, established : accept, related : accept } |
| 132 | + iifname vmap { "eth0" : jump wan, "lo" : accept, "br0" : jump lan } |
| 133 | + } |
| 134 | + |
| 135 | + chain forward { |
| 136 | + type filter hook forward priority filter; policy drop; |
| 137 | + ct state vmap { invalid : drop, established : accept, related : accept } |
| 138 | + iifname "br0" accept |
| 139 | + } |
| 140 | +} |
| 141 | +table ip nat { |
| 142 | + chain prerouting { |
| 143 | + type nat hook prerouting priority filter; policy accept; |
| 144 | + } |
| 145 | + |
| 146 | + chain postrouting { |
| 147 | + type nat hook postrouting priority srcnat; policy accept; |
| 148 | + ip saddr 192.168.0.0/24 oif "eth0" masquerade |
| 149 | + } |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +> Here we run the `nft` program shipped with Infix. It is of course |
| 154 | +> possible to run the `nft` binary from the container, albeit not as |
| 155 | +> easily: |
| 156 | +> |
| 157 | +> ```shell |
| 158 | +> admin@infix:~$ sudo podman run --network=host --privileged=true --entrypoint=/usr/sbin/nft ghcr.io/kernelkit/curios-nftables:24.02.0 list ruleset |
| 159 | +> ``` |
| 160 | +{: .prompt-tip } |
| 161 | +
|
| 162 | +## Example /etc/nftables.conf |
| 163 | +
|
| 164 | +This is the example firewall configuration referred to above. |
| 165 | +If your interface names or IP address plan differs, make sure |
| 166 | +to modify the file to match. |
| 167 | +
|
| 168 | +``` |
| 169 | +define WAN = eth0 |
| 170 | +define LAN = br0 |
| 171 | +define NET = 192.168.0.0/24 |
| 172 | +
|
| 173 | +# Drop all rules to allow reloading this file after edit |
| 174 | +flush ruleset |
| 175 | +
|
| 176 | +table ip filter { |
| 177 | + chain wan { |
| 178 | + # Accept ping for diagnostics, with rate limit |
| 179 | + icmp type echo-request limit rate 5/second accept |
| 180 | +
|
| 181 | + # allow SSH connections from some well-known internet host |
| 182 | + #ip saddr 81.209.165.42 tcp dport ssh accept |
| 183 | + } |
| 184 | +
|
| 185 | + chain lan { |
| 186 | + icmp type echo-request accept |
| 187 | +
|
| 188 | + # allow DHCP, DNS and SSH from the private network |
| 189 | + ip protocol . th dport vmap { |
| 190 | + tcp . 22 : accept, |
| 191 | + udp . 53 : accept, |
| 192 | + tcp . 53 : accept, |
| 193 | + udp . 67 : accept |
| 194 | + } |
| 195 | + } |
| 196 | +
|
| 197 | + chain input { |
| 198 | + type filter hook input priority 0; policy drop; |
| 199 | +
|
| 200 | + # Allow traffic from established and related packets, drop invalid |
| 201 | + ct state vmap { |
| 202 | + established : accept, |
| 203 | + related : accept, |
| 204 | + invalid : drop |
| 205 | + } |
| 206 | +
|
| 207 | + # allow loopback traffic, anything else jump to chain for further evaluation |
| 208 | + iifname vmap { |
| 209 | + lo : accept, |
| 210 | + $WAN : jump wan, |
| 211 | + $LAN : jump lan |
| 212 | + } |
| 213 | +
|
| 214 | + # the rest is dropped by the above policy |
| 215 | + } |
| 216 | +
|
| 217 | + chain forward { |
| 218 | + type filter hook forward priority 0; policy drop; |
| 219 | +
|
| 220 | + # Allow traffic from established and related packets, drop invalid |
| 221 | + ct state vmap { |
| 222 | + established : accept, |
| 223 | + related : accept, |
| 224 | + invalid : drop |
| 225 | + } |
| 226 | +
|
| 227 | + # connections from LAN to the internet or to other intranets are allowed |
| 228 | + iifname $LAN accept |
| 229 | +
|
| 230 | + # the rest is dropped by the above policy |
| 231 | + } |
| 232 | +} |
| 233 | +
|
| 234 | +table ip nat { |
| 235 | + chain prerouting { |
| 236 | + type nat hook prerouting priority filter; policy accept; |
| 237 | + } |
| 238 | +
|
| 239 | + chain postrouting { |
| 240 | + type nat hook postrouting priority srcnat; policy accept; |
| 241 | + ip saddr $NET oif $WAN masquerade |
| 242 | + } |
| 243 | +} |
| 244 | +``` |
| 245 | +
|
| 246 | + |
| 247 | +## Fin |
| 248 | + |
| 249 | +That concludes the third post about containers in Infix. As usual, |
| 250 | +remember to |
| 251 | + |
| 252 | +```console |
| 253 | +admin@infix:/> copy running-config startup-config |
| 254 | +``` |
| 255 | + |
| 256 | +Take care! <3 |
| 257 | + |
| 258 | +---- |
| 259 | + |
| 260 | +[^1]: Linux namespaces partition system resources such that one set of |
| 261 | + processes sees one set of resources, while another set of processes |
| 262 | + sees a different set of resources. There are many types: mount, |
| 263 | + user, PID, network, ... |
| 264 | + |
| 265 | +[^2]: NAT firewall, in this context an RFC 2636 NAPT, or IP masquerading |
| 266 | + firewall with filtering of incoming traffic. For more information, see |
| 267 | + the excellent [Wikipedia article][3] on the topic. |
| 268 | + |
| 269 | +[0]: https://github.com/kernelkit/infix/blob/main/doc/networking.md |
| 270 | +[1]: /posts/containers/ |
| 271 | +[2]: https://github.com/kernelkit/curiOS/ |
| 272 | +[3]: https://en.wikipedia.org/wiki/Network_address_translation |
| 273 | +[4]: https://github.com/kernelkit/infix/blob/main/doc/cli/text-editor.md |
| 274 | +[5]: https://wiki.nftables.org/wiki-nftables/index.php/Main_Page |
0 commit comments