Smaller is better

My home office (where my network and servers live) is a warm, noisy place.  So much so that I really wasn’t enjoying being in the room.  Since I don’t have an air conditioned datacenter with a raised floor in my house, I decided that I wanted to do something about this problem.  Interestingly enough, the biggest source of heat in the room turned out to be my two 24″ Apple Cinema Displays from circa 2006-2008.

One of the others was the little PC I built to be my OpenBSD router / firewall.  I did some research and discovered the APU2 board.  This little beauty is air cooled, about the size of a CD jewel case, and has 4 cores and up to 4GB of RAM with 3 gigabit Intel ethernet ports on it.  Sounds like a winner to me.

I acquired one of these from Mini-Box (http://www.mini-box.com/ALIX-APU-2C4-AMD-G-Series-GX-421TC?sc=8&category=754) along with the case and power supply.  I picked up a cheap mSATA 64GB drive for storage and spent about 3 minutes with a screw driver assembling the thing.  The end result was a small case about the size of three audio CD jewel cases stacked on top of each other.  After that, I popped a USB stick with OpenBSD 5.9-current on it and booted.

The system (which I was connected to with a serial cable to the serial port on the APU2 board) booted up, I saw the boot prompt for OpenBSD and then it booted up, I saw the boot prompt for OpenBSD, and then it booted up…  You see where this was headed.

I reached out for help on the mailing lists and very quickly had two folks clue me in.  When OpenBSD’s kernel starts to load, it looks for a console and if it can’t find one, it exits, creating this boot loop.  At the boot prompt, a few commands had be booting to the installer:

boot:  stty com0 115200
boot: set tty com0
boot: boot /bsd

I decided to do this router “right”.  Since it was just going to be a router and firewall and I wouldn’t run anything else on it, I wanted to go with a small attack surface so I chose to not install any of the X packages.  This turned out to be a “Bad Idea”(TM) since you can’t build any ports on the system if you don’t have X installed.  Since I use tarsnap and it can only be built from source currently due to the licensing model, I went back, installed the X packages from the installer and was good to go.  Make sure you disallow root logins over ssh (the default) as this is going to be internet facing in the end.

Once I logged in as root the first time, I needed to give my unprivelaged user some juice so I created an /etc/doas.conf file, allowing anyone in the “wheel” group to run commands as root (preserving their environment).

# echo "permit keepenv :wheel as root" >/etc/doas.conf

I then added my unprivelaged user to wheel, wsrc and staff and then logged out.  At this point, I shouldn’t need to log in as root at all.

# usermod -G wsrc <your user> (and so on)

Since I have my dotfiles in a github repository, I needed to now log in as my unprivelaged user and generate their SSH keys:

$ ssh-keygen -t rsa -b 4096 "<your email>"

I then copied the ~/.ssh/id_rsa.pub file’s contents and pasted it into a new SSH key in my GitHub account’s settings.  Now I’m good to clone my dotfiles repository and have it set up my environment the way I like it to be set up.  However, first I need to install git.

I temporarily export the PKG_PATH that I like to use:

$ export PKG_PATH=http://openbsd.cs.toronto.edu/pub/OpenBSD/5.9/packages/amd64

Then I install the git package:

$ doas pkg_add git

… and it didn’t work.  Ah!  I forgot to actually connect to the network after rebooting.  A simple:

$ doas ifconfig em0 up
$ doas dhclient em0

and I was good to go.  I added git and then cloned my repository:

$ git clone <your user>@github.com:/<your user>/<your dotfile repo>.git

Then I ran the shell script to set up my dotfiles, logged out and back in to pick up the changes and I was good to go.  Now I needed to actually configure this box as a router and firewall the way I like it.  To do this, I stand on the shoulders of giants and use the awesome OpenBSD FAQ.

The APU2 numbers its ethernet ports from left to right as you look at the back of the case (meaning that em0 is the port closest to the DB9 serial connector).  I’m using port 0 for my WAN interface and port 1 for my internal.  My goal is to use the third port as a private interface to a second setup just like this and use CARP to make it redundant.

So, for my router, I start by adding the following to /etc/sysctl.conf:

net.inet.ip.forwarding=1
net.inet.ip.redirect=0
kern.bufcachepercent=50
net.inet.ip.ifq.maxlen=1024
net.inet.tcp.mssdflt=1440

Then, we need to enable the dhcp daemon:

$ doas rcctl enable dhcpd
$ doas rcctl set dhcpd flags em0

Now I create this as my /etc/dhcpd.conf file:

option domain-name-servers 192.168.1.1;
subnet 192.168.1.0 netmask 255.255.255.0 {
option routers 192.168.1.1;
range 192.168.1.4 192.168.1.254;
}

Following the tutorial, we will be also using unbound for local DNS caching.  Enable it as follows:

$ doas rcctl enable unbound

Create a /var/unbound/etc/unbound.conf file like they did in the tutorial:

server:
interface: 192.168.1.1
interface: 127.0.0.1
do-ip6: no
access-control: 192.168.1.0/24 allow
do-not-query-localhost: no
hide-identity: yes
hide-version: yes
forward-zone:
name: "."
forward-addr: 127.0.0.1@40

Add dnscrypt-proxy from the packages tree and enable / configure it:

$ doas pkg_add dnscrypt-proxy
$ doas rcctl enable dnscrypt_proxy
$ doas rcctl set dnscrypt_proxy flags "-l /dev/null -R dnscrypt.eu-dk -a 127.0.0.1:40"

Finally, prevent your upstream ISP from changing your DNS resolution via DHCP:

# echo 'ignore domain-name-servers;' >> /etc/dhclient.conf

Now we need to set up the firewall.  I used this for my /etc/pf.conf file:

int_if="{ em0 em2 }"
set block-policy drop
set loginterface egress
set skip on lo0
match in all scrub (no-df random-id max-mss 1440)
match out on egress inet from !(egress:network) to any nat-to (egress:0)
antispoof quick for (egress)
block in quick inet6 all
block return out quick inet6 all
block return out quick log on egress proto { tcp udp } from any to any port 53
block return out quick log on egress from any to { no-route $broken }
block in all
pass out quick inet keep state
pass in on $int_if inet
pass in on $int_if inet proto { tcp udp } from any to ! 192.168.1.1 port 53 rdr-
to 192.168.1.1
pass in on egress inet proto tcp to (egress) port 222 rdr-to 192.168.1.2
pass in on egress inet proto tcp from any to (egress) port 2222

Finally, I added “noatime,softdep” to my /etc/fstab for my non-swapfile mount points because I’m running an SSD drive.  I also disabled the sound server to further reduce attack surface on this box:

$ doas rcctl disable dnsiod

Next, I set a static IP address for ethernet port 0 (em0) so that dhcpd could bind to it:

# echo 'inet 192.168.1.1 255.255.255.0 192.168.1.255' > /etc/hostname.em0

Finally, I told ethernet port 1 (em1) to get its IP address via dhcp from my upstream ISP’s cablemodem:

# echo 'dhcp' > /etc/hostname.em1

At this point, I did a reboot, plugged in my upstream router to ethernet port 1, plugged my home network into ethernet port 0 and tested to ensure everything was working the way I expected it to.

The one thing I did have to do was reboot each machine on the home network to pick up the new DHCP stuff.  It all worked like a champ.