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.
Installing X doesn’t really raise your attack surface, running it does. That includes ports that require X libraries.
This raises your attack surface:
“Once I logged in as root the first time, I needed to give my unprivileged 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).”
SSH defaults to no passwords for root. Root must have a key. “Make sure you disallow root logins over ssh (the default) as this is going to be internet facing in the end.” But then with doas, in your setup, this doesn’t mater, because now any “wheel” account password is automatically a remote root account. Use su instead for another level of password protection, or set doas to only allow specific commands and arguments if a user needs limited root access.
These have no bearing on a router:
kern.bufcachepercent=50
net.inet.tcp.mssdflt=1440
(you can set mss clamping in pf if necessary)
Finally, you can replace your use of ‘unbound’ with the in-tree ‘rebound’ dns proxy. It’s strictly a proxy and doesn’t require installation of additional software.
Excellent post. I am also getting ready to build an OpenBSD router, with wireless. I also ordered the apu2 board with 4 gigs of memory. This post will be helpful in doing some configuration. Thank you for this post.
No pictures?! We demand pictures!
Pingback: Diving for BSD Perls | BSD Now 142 | Jupiter Broadcasting
If your external IP is dynamic, you won’t be able to run carp on that interface at this time. It’s a known issue, but I don’t know if anyone is actually working on it.
Actually the way my cable modem works, it assigns me a private IP address. Thanks for the info though that is good to know.
What kind of serial adapter did you use to connect to the serial port on the APU?
Are all USB serial adapters created equal or are some kinds better than others?
What kind of serial adapter did you use to connect to the box? A USB serial adapter? Any advice on USB adapters which work fine with OpenBSD?
Pingback: Say my Blog’s name! | FunctionallyParanoid.com
Pingback: Say my Blog’s name! | FunctionallyParanoid.com
Hello,
Well I’m finally getting around to using my edge router lite, not quite as fast as the APU2. I figured I’d get the ERL in a good working state and try it out with my machines and then get an APU2 for my home ‘production’ use.
Your pf rule set seems to have a problem. You’re using a macro that’s not defined, $broken. Do you think you could give an update on how the apu2 is working out for you? Are you running release/stable/current on your firewall?
Pingback: Congress just sold your online privacy, now what? | FunctionallyParanoid.com
Pingback: The $25 “FREEDOM Laptop!” | FunctionallyParanoid.com
Pingback: Let’s dial it up to 11 | FunctionallyParanoid.com