LinkedIn Sourceforge

Vincent's Blog

Pleasure in the job puts perfection in the work (Aristote)

How I've tuned OpenBSD to become my house's firewall

Posted on 2016-07-14 15:25:00 from Vincent in OpenBSD Firewall

In this post I'll explain what I did to filter the internet access in my house. You will see that the access is based on simple rules and is not the same for every users.


For the setup of my Firewall at home, I've used a read-only system (cfr my previous post ) on which I've installed some usual softwares: mainly squid.

Hardware:

Concerning the Hardware, I've selected a small board having 2 internet slots from minipc.de.
For power optimisation I've selected an Intel Atom board D2500. And to avoid too much noises, I've taken a fanless board.
In most of the cases you can find such board for less than 100Euro. If you add 4GB of Ram and a mini case for ITX with his power supply, the total cost is less than 200euro.

4GB of Ram is a minimum because this machine will be a "read only" system; so we will put several file system in memory (cfr previous post ).

I do not refer to the machine I'm using because it's not more existing.

But if you want to buy one, please verify that the Ethernet hardware is supported by OpenBSD. If, like me you use USB as main disk, please check that USB2 is supported too.

I'm mentioning USB2 because, none of my machines are able to boot from USB3 port. This is maybe a feature that will come in next release of OpenBSD.

In my specific case, this is an Intel D2500CC with 4GB of DDR3. The 2 ethernet ports are "Intel 82574L" recognized by the em driver.

Software

Step 1: DHCPD

I'm using the standard dhcpd provided by OpenBSD.
In my case the dhcpd.conf is defined in a way so that I have 2 types of users on my network:

  • the known machines. For them dhcpd provides a fixed address. Those machines are in the range 192.168.3-0-192.168.3.63. in CDIR format this is 192.168.3.0/26.
  • the other machines. They received an address in the range 192.168.3.64-192.168.3.250.

All visitors, but also my kids will received an IP above 64 in my IP range.
You will see why I did it like this later in this post.

The changes you have to perform in the config file (/etc/dhcpd.conf) are:

# DHCP server options.
option  domain-name "home.lan";
option  domain-name-servers 192.168.3.1;

subnet 192.168.3.0 netmask 255.255.255.0 {
    option routers 192.168.3.1;
    filename "pxeboot";

    range 192.168.3.64 192.168.3.250;

#known machines
host host1 {
    hardware ethernet xx:xx:xx:xx:xx:xx;
    fixed-address 192.168.3.2;
}
host host2 {
    hardware ethernet xx:xx:xx:xx:xx:xx;
    fixed-address       192.168.3.3;
}
...

I've identified each "known machines" by his MacAddress and I've assigned her an IP between 2 and 63.

To have the dhcpd daemon starting at reboot, do not forget to add "dhcpd_flags=" to you /etc/rc.conf.local file.
The simplest way to do that is to "enable" it with rcctl: 'rcctl enable dhcpd'

Then you can run it with the command: 'rcctl start dhcpd'

Step 2: Firewall rules

I'm using the OpenBSD PF to decide which port can be used or not.

For such configuration I strongly suggest you to read the excellent book of Peter N.M. Hansteen called: The Book of PF

Anyhow, here after my config with some explanations:

int_if="em1"  #interface pointing to the internal LAN
ext_if="em0"  #interface pointing to my internet box

#list of known machines
table <auth_ip> {192.168.1.20, 127.0.0.1, 192.168.3.0/26} #192.168.3.0-192.168.3.63
#the other machines 
table <other_ip> {192.168.3.0/24, !192.168.3.0/26}  #192.168.3.0/24 0-255

set block-policy return

# SCRUB PACKETS
match in all scrub (no-df)
match out all scrub (random-id)

#antispoof
antispoof for $ext_if inet

set skip on lo0

block log all


#for email
pass in proto tcp to self port {smtp}
pass out on $ext_if proto tcp to any port {587}


# VPN (tcp 1723 and gre 47)
pass out on $ext_if proto tcp from <auth_ip> to any port {1723}
pass in on $ext_if proto gre all keep state
pass out on $ext_if proto gre all keep state

#redircet www to squid
pass in quick inet proto tcp from <auth_ip> to port www divert-to 127.0.0.1 port 3128
pass in quick inet proto tcp from <other_ip> to port www divert-to 127.0.0.1 port 3128
pass out on $ext_if proto tcp from $ext_if to any port http

# secure forward ftp proxy
anchor "ftp-proxy/*"
pass in quick inet proto tcp to port ftp divert-to 127.0.0.1 port 8021
pass out on $ext_if proto tcp to any port ftp


#my own rules for auth_ip
pass in on $int_if all
pass out on $ext_if proto tcp from <auth_ip> to any port {domain http https ntp whois ftp imaps smtps nntp }
pass out on $ext_if proto tcp from <auth_ip> to any port {ssh 9418} #9418 is for git

#from localhost
pass out on $int_if proto tcp from 192.168.3.1 to any port {ssh tftp}
pass out on $int_if proto icmp all

#my own rules for other_ip
pass out on $ext_if proto tcp from <other_ip> to any port  {domain, http, ntp, whois, https, 9339} #9339 is for clashofclan


#for all
pass out on $ext_if proto udp to any port {domain ntp}
pass out on $ext_if proto icmp all

#Natting
match out on $ext_if from <auth_ip> nat-to ($ext_if)
match out on $ext_if from <other_ip> nat-to ($ext_if)

In short, what I've done is:

  • define 2 groups of IP addresses: the one in the range 192.168.3.0-64 and others.
  • block spoofing packets
  • block all ports
  • allowing some standard ports
  • redirecting http to a transparent squid
  • redirecting ftp to the ftp proxy
  • defining specific rules for each of the 2 groups of users.
  • and finally natting

The redirect for ftp is required because the ftp protocol is not really compatible with a firewall because it open ports at random addresses. The workaround defined by the Openbsd developers allow you to simply allow or block ftp by just specifying "port ftp".

The idea to redirect http port to squid is not really mandatory. But having kids at home, I prefer to block some web sites like adults, porn, ...
For me too, the blocking feature is interesting. Indeed, this setup allow me to block most ads. Since this is done at the firewall side, ads are now removed from all my machines running inside my network.

For persons interested in such filtering I propose you to look at what the university of Toulouse has built. They propose a wonderful Database of millions of URLs categorized: https://dsi.ut-capitole.fr/blacklists/.
I'm using a small python script to check if the url requested by the user is referenced in some of those lists. If you want I can share my setup of squid and the source code of my "URL checker".

But, I must immediately add that HTTP filtering is less and less useful because of HTTPS. Indeed, by definition HTTPS packets are encrypted between the browser and the web server. Thus a firewall in the middle cannot read what URL the user is requesting.

The solution to still perform filtering are a bit more complex to setup. Based on my readings, the most used solution consist to have a firewall which act as a fake web server, so the browser thinks he talks to a real web application. Since the firewall has received the encryption keys from the browser, he is allowed to decrypt it. So he can analyse it. If the request is valid, he send it to the effective web server, analyse the response, and transfer it back to the user's browser.

For the moment I've not implemented this solution which sounds not easy to my eyes.

Instead, I've developed my own DNS server. Indeed, whatever the protocol used (http or https or ...), the DNS server is always contacted. (OK. Not always always, but such cases are very rare).

Step 3: DNS

To be able to decide if the web site requested by the user is compliant with my policy, I've written my own DNS server.
Fortunately someone did it in python, so write it from scratch was not required. This tool is called DNSchef and it's written by Peter Kacherginsky. You can find it on https://thesprawl.org/projects/dnschef/

My modified version of DNSchef compares the requested DomainName to the one existing in a SQLite database.
This DB has black lists and white lists, depending on some simple rules, the DNS replies with a dummy IP address or it requests the IP address to my official primary dns server (can be anyone you trust) and return it back to the requestor.

The code is not yet "polished" and correctly documented. But maybe is a next future I can publish it.

This sounds a simple process. but it runs since +1 year without any troubles.
This DNS can manage without any problems for the users (delay, ...) for up to 8 very active users on their browsers.
I suppose it can manage more, because the CPU remains low. Because my limitd internet bandwidth, I cannot test it.

Step 4: Disk space

Because I'm using a read-only system where /var is in memory, I must be very careful to not exceed the space allocated to this filesystem.
Here again OpenBSD propose the right solution: 'newsyslog'.

This tool allows you to define the rules for the log file rotation.
Here after what I've implemented in newsyslog.conf:

# configuration file for newsyslog
#
# logfile_name          owner:group     mode count size when  flags
/var/log/dnsfilter.log    root:wheel     640   5   15000   *   Z "/root/dnsfilter/start_dnsfilter.sh"

The shell script just stop and restart the dns server. It looks like this:

#!/bin/sh
pkill -f dnsfilter.py
cd /root/dnsfilter
export PYTHON_EGG_CACHE=/tmp
/usr/local/bin/python2.7 dnsfilter.py -i 192.168.3.1 >> /var/log/dnsfilter.log 2>&1 &

With PYTHON_EGG_CACHE set to /tmp, I avoid that python try to write in my "read-only" folders.

Lessons learned:

Once again OpenBSD simply rocks and do what I ask him without any troubles. Thanks to the "read only" system, the setup is tolerant to power cut and can run without the need of an UPS.

Rules based on IP address is not the most secured approach, but with "dummy" users this works like a charm.
Thanks to this filtering, I'm an happy user without ads and no one is worried with what the kids are doing with their Smartphone, Laptop and tablets. And personally, thanks to the natting and the firewall rules, I'm reassured against trojan and viruses we could have at home.

This setup runs since 2012.



74, 62
displayed: 12224



What is the last letter of the word Python?