Kodi server part 7: Firewall Rules

This is the seventh and final part of a multi-part tutorial describing how to configure the "perfect" Kodi media centre running on top of ubuntu server.

Other parts of the tutorial may be found here:

This section deals with firewall configuration. Here you will find Uncomplicated Fire Wall (UFW) and iptables rules to prevent transmission from sending traffic using your normal wifi interface, as well as a few extra rules to secure the server against traffic from the VPN side.

UFW basic configuration

I chose to use UFW for most of the firewall configuration because:

  • It's installed by default on Ubuntu server
  • UFW abstracts the most confusing parts of iptables syntax and rules are generally easier to read
  • For iptables rules to be persistent across reboots, some kind of utility like iptables-restore or UFW is required to store and load rules
  • For the cases where additional features of iptables are required, we can add them in a config file to be loaded either before or after the UFW rules

First we need to decide on a default policy. For incoming and outgoing traffic, we can either allow by default or deny by default.

If this server was only connected to the internet via the router, I would be say the firewall on the router provides adequate protection from the public internet. However, the addition of the VPN connection adds complexity. Your VPN provider probably provides a connection to a VPN server that is firewalled on a small "LAN" on the provider's infrastructure. That machine may have a firewall too similar to the one on your router (default policy of denying incoming requests and allowing outgoing) but that's not guaranteed. Since we're configuring a firewall anyway to block torrent traffic on non-VPN interfaces, it's also worth considering how much you trust your VPN provider to provide an effective firewall, and how much you trust them not to abuse their position inside the LAN firewall.

You can see a list of listening services using the following netstat command:

sudo netstat -tulpn | grep LISTEN

I find it helps to make a list of connections, to help decide how to set up the firewall. Here are some incoming connections you probably want to deny on the VPN interface:

  • Traffic to rpcbind and your NFS shares
  • Traffic to the mysql server hosting your media database
  • Traffic to the SSH daemon
  • Traffic to transmission's RPC interface

And incoming connections you will want to allow on the VPN interface:

  • Incoming torrent traffic

I can't think of any outgoing connections you want to deny, apart from outgoing torrent traffic on any interface apart from the VPN interface. We'll come back to that later.

The easiest way to handle all of this is to deny incoming connections on the VPN interface, and allow incoming connections on the LAN interface for your wifi card or ethernet port. Then we can add exceptions to these default rules.

Don't enable ufw without making changes first, because the default rules will kill your SSH session!

To set the default policy (deny incoming, allow outgoing), run:

sudo ufw default deny incoming
sudo ufw default allow outgoing

Then we can allow incoming traffic on your LAN interface. You need to know the interface name for this rule, which you can find from ifconfig - my wifi interface is wlp2s0, so the rule is:

sudo ufw allow in on wlp2s0 comment 'allow incoming connections from LAN'

Now to allow torrent traffic, find out which port transmission listens on by searching your transmission config file:

sudo cat /etc/transmission-daemon/settings.json | grep peer-port

Your output should look something like this, but possibly with different ports:

    "peer-port": 51413, 
    "peer-port-random-high": 65535, 
    "peer-port-random-low": 49152, 
    "peer-port-random-on-start": false,

In my configuration, peer-port-random-on-start is turned off, so transmission will always listen to the same port. If peer-port-random-on-start was true, then transmission would choose a new port between peer-port-random-low and peer-port-random-high to listen to each time it starts up.

If peer-port-random-on-start is true, you can use a rule like this allowing incoming traffic on your current peer port:

sudo ufw allow in on tun0 to any port 51413 comment 'allow incoming transmission torrent connections on VPN'

If you are using peer-port-random-on-start, you will have to allow incoming traffic on the whole range. When using port ranges, it is necessary to create rules for tcp and udp separately:

sudo ufw allow in on tun0 to any port 49152:65535 proto tcp comment 'allow incoming tcp transmission torrent connections on VPN'
sudo ufw allow in on tun0 to any port 49152:65535 proto udp comment 'allow incoming udp transmission torrent connections on VPN'

Now enable ufw:

sudo ufw enable

And list the rules that are active:

sudo ufw status

If you need to delete any rules, you can add delete in front of the rule you used to add it, for example:

sudo ufw delete allow in on tun0 to any port 49152:65535 proto tcp

You should now have a setup that will give your kodi box equivalent firewall protection on the VPN interface to the protection you get on your LAN, without having to rely on your VPN provider configuring the firewall correctly.

Disable ipv6

Since the solution created earlier for binding transmission to the VPN interface only works for ipv6, I chose to disable ipv6 altogether (it's not supported on my network so I'm not losing anything by doing this, and may even be speeding up things like DNS lookups).

To do this, run the following commands:

sysctl net.ipv6.conf.all.disable_ipv6
sysctl net.ipv6.conf.default.disable_ipv6
sysctl net.ipv6.conf.lo.disable_ipv6

To make these changes persistent, edit the file /etc/sysctl.conf and add the following lines at the bottom of the file:

# disable ipv6
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1

sysctl has a -p option, which reloads settings from /etc/sysctl.conf (or another file if specified). :

sudo sysctl -p

Now you shouldn't see any IPv6 addresses in your ifconfig, for example compare the output of ifconfig from the wifi section before:

wlp2s0    Link encap:Ethernet  HWaddr 80:86:f2:bd:91:f6  
          inet addr:192.168.1.2  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::8286:f2ff:febd:91f6/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:9566008 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2290526 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:12721694676 (12.7 GB)  TX bytes:260800582 (260.8 MB)

To after:

wlp2s0    Link encap:Ethernet  HWaddr 80:86:f2:bd:91:f6  
          inet addr:192.168.2.2  Bcast:192.168.2.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:3127620 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1993181 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:3906592619 (3.9 GB)  TX bytes:1624021652 (1.6 GB)

If you don't see any ipv6 addresses then the changes have worked.

Firewall rules to prevent Transmission from sending traffic on other interfaces

One other thing we wanted to achieve with UFW is to ensure that transmission can't send traffic on any interface apart from the VPN interface.

There's a neat way to do this, but it requires using iptables rules directly because it supports some features that are not available in UFW (which is a nice simplified frontend for iptables). Normal iptables rules are not persistent across reboots, so some kind of tool is required to store and load the rules. Sometimes people use iptables-restore for this, but UFW can do this too.

There are two UFW configuration files where you can add firewall rules in iptables syntax:

/etc/ufw/before.rules
/etc/ufw/after.rules

The iptables rule you would use on the commandline is this one:

sudo iptables -A OUTPUT -m owner --gid-owner vpnroute \! -o tun0 -j REJECT

Breaking this down, this means:

  • -A OUTPUT: append a rule to the OUTPUT chain
  • -m owner: use an iptables extension module called owner to match packets
  • --gid-owner vpnroute: using the owner module, match traffic from processes running as the vpnroute group
  • \! -o tun0: if the output interface is not tun0 (exclamation mark means 'not', and is escaped because ! is a special character in the shell)
  • -j REJECT: jump to the REJECT target (i.e. drop the packet and send a reject response to the sender)

To make this work, we need to create the vpnroute group and add the user that transmission runs as (debian-transmission) to that group:

sudo groupadd vpnroute
sudo usermod -a -G vpnroute debian-transmission

Now we need to add the iptables rule to our UFW config.

The order of rule evaluation is important here - firewall rules are a bit like coin sorting machines in that they wait for the first rule that has a definite action (accept, reject etc.) and after the packet matches a rule it is routed and the processing stops. The default actions are catch-alls for when no other more specific rule has matched.

Because our rule rejects traffic that would otherwise be accepted by the default rules, we need it to be processed before any rules that would accept the torrent traffic on non-VPN interfaces. The easiest way to ensure this happens is to add it to the start of the chain instead of the end, using -I for insert instead of -A (append):

sudo iptables -I OUTPUT -m owner --gid-owner vpnroute \! -o tun0 -j REJECT

If -I is used without a number (like above) then by default the rule is inserted at the top of the chain - if we wanted to insert it in second place, we could have used -I 2 OUTPUT.

Now we need to choose which file to add this to (either /etc/ufw/before.rules or /etc/ufw/after.rules)... before.rules contains iptables rules that are added before the UFW rules are loaded, and after.rules contains iptables rules to be added after the UFW rules have been loaded.

I chose to add this rule to after.rules because I want this rule to be evaluated right at the top of the OUTPUT chain, and we can't be certain that one of the UFW rules won't insert itself ahead of our rule.

At first I attempted to add the rule to one of UFW's chains in the hope that I could log it along with the rest of the UFW output, but I didn't have any luck - if you know how to do this, please let me know!

Open the file (/etc/ufw/after.rules) and add the following line near the bottom of the file but before the COMMIT keyword. The line is basically the iptables command you would run in the shell, but without sudo and iptables, and missing the backslash before the exclamation mark because it won't be run in the shell so the special character doesn't need to be escaped:

-I OUTPUT -m owner --gid-owner vpnroute ! -o tun0 -j REJECT

Now restart UFW:

sudo service ufw force-reload

If you list the iptables rules in the OUTPUT chain, you should now see it at the top:

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
REJECT     all  --  anywhere             anywhere             owner GID match vpnroute reject-with icmp-port-unreachable
ufw-before-logging-output  all  --  anywhere             anywhere            
ufw-before-output  all  --  anywhere             anywhere            
ufw-after-output  all  --  anywhere             anywhere            
ufw-after-logging-output  all  --  anywhere             anywhere            
ufw-reject-output  all  --  anywhere             anywhere            
ufw-track-output  all  --  anywhere             anywhere

Now we need a way to test it. The following command returns your current IP address by querying a third party server:

curl icanhazip.com

We can run this command as the vpnroute group like this:

sudo -u vpnroute sh -c 'curl icanhazip.com'

You should get an IP address returned. Now if you stop the VPN:

sudo service openvpn stop

And then run the same command again:

sudo -u vpnroute sh -c 'curl icanhazip.com'

You should get an error like:

curl: (6) Could not resolve host: icanhazip.com

Which indicates that the iptables rule is working, and has blocked the DNS lookup for icanhazip.com. The HTTP request would also have been blocked if curl attempted to make it.

Troubleshooting

Logging

If UFW is not behaving as you expect it to, you can turn on logging like this:

sudo ufw logging on

Logs are written to /var/log/ufw.log. You can boost the logging level using this command:

sudo ufw logging LEVEL

...where LEVEL is one of low, medium, high or full. low is the default which shows packets blocked by the default rules and rules that mandate logging, but not any packets blocked by specific rules. medium adds in packets matched by custom rules and all new connections. For more detail about what's included in the other levels, see the man page (man ufw). Be careful if you use a verbose log level because the log files can grow very quickly.

If you want to turn logging back off, then you can use this command:

sudo ufw logging off

Note that the logs won't show anything from the iptables rule added to after.rules.

Reports

Iptables' rule syntax is less easy to read than UFW, but sometimes it's helpful to see the full rules. To list out the "raw" iptables rules, use this command:

sudo ufw show raw

You can also list iptables rules using iptables itself:

sudo iptables -L

Questions? Ask away in the comment section below.

Type: 

Add new comment