Redundant OpenBSD Firewalls
Part 3 - Network Services
We will now add some additional services to our single firewall. These are basic configurations which work as-is, but you will almost certainly need to modify and expand them to meet your needs.
Time server
I prefer the reference NTP implementation, but it can only send packets to other time servers from port 123. This won’t work for our use case. If we configure the packet filter on fw1 to accept all UDP traffic on port 123, then the time server on fw2 won’t work.
OpenBSD comes with OpenNTPD, which isn’t as full featured, but it does send outbound UDP packets on high ports. This allows the packet filter to track state on those outbound packets, and send the incoming ones back to the originator, making it possible for us to run two time servers.
Fortunately the NTP protocol design makes it super easy to have redundant servers. We need zero additional software support or configuration to make it happen. We need a NTP server listening on 192.168.13.4.
Make /etc/ntpd.conf
look something like this:
#
# configuration file for ntpd
# serve time to our internal network
listen on 127.0.0.1
listen on 192.168.13.4
# time servers
server 0.us.pool.ntp.org
server 1.us.pool.ntp.org
server 2.us.pool.ntp.org
server 3.us.pool.ntp.org
# TLS constraint to mitigate MITM NTP attacks
constraint from "https://9.9.9.9" "2620:fe::9"
And start it up:
fw1# rcctl enable ntpd
fw1# rcctl start ntpd
It may take a few minutes for the daemon to contact all the servers and sync the clock. To check the status of the time service:
fw1$ ntpctl -s all
4/4 peers valid, constraint offset -1s, clock synced, stratum 4
peer
wt tl st next poll offset delay jitter
162.159.200.123 from pool pool.ntp.org
* 1 10 3 1292s 1529s -0.217ms 14.496ms 6.526ms
162.159.200.1 from pool pool.ntp.org
1 10 3 1507s 1525s -1.301ms 12.377ms 2.488ms
173.0.48.220 from pool pool.ntp.org
1 10 2 1414s 1503s 22.369ms 46.584ms 4.257ms
23.31.21.164 from pool pool.ntp.org
1 10 2 1576s 1587s 16.927ms 186.044ms 116.632ms
If you see “clock synced”, then you are in good shape.
Authoritative name server for internal network
I have some internal hosts that I want to use friendly host names for, but I don’t want to publish these hosts in a public name server. So I need an internal only name server, which my internal caching resolver is aware of. The internal authoritative name server only needs to be queried by our internal caching resolver.
OpenBSD uses nsd
as an authoritative name server. I won’t go into full
details of how to configure a name server and zone files. For this exercise,
the key configuration detail is to only listen on localhost port 5353.
Let’s set up nsd
:
fw1# nsd-control-setup
fw1# rcctl enable nsd
Then install a basic configuration file in /var/nsd/etc/nsd.conf
:
#
# /var/nsd/etc/nsd.conf - configuration for authoritative name server
server:
hide-version: yes
verbosity: 1
database: "" # disable database
# only listen on localhost
# the only resolver we care that can access this service
# is the unbound server running on this same box
interface: 127.0.0.1@5353
interface: ::1@5353
remote-control:
control-enable: yes
control-interface: 127.0.0.1
control-port: 8952
server-key-file: /var/nsd/etc/nsd_server.key
server-cert-file: /var/nsd/etc/nsd_server.pem
control-key-file: /var/nsd/etc/nsd_control.key
control-cert-file: /var/nsd/etc/nsd_control.pem
zone:
name: kotfu.net
zonefile: kotfu.net
zone:
name: 13.168.192.in-addr.arpa
zonefile: 192.168.13.0
That’s it. Start it up and we are done:
fw1# rcctl start nsd
Caching DNS resolver
In theory, we only need the resolver to run on one IP address. In practice, I have seen stupid network configuration user interfaces on wireless routers that require two name server IP addresses. I have also had cases where I needed to move the resolver to a different piece of hardware. If the resolvers have their own IP addresses, the service can be moved without having to have every DHCP client renew their lease. Finally, most clients that query a resolver will wait a few seconds for it to time out before they try the second resolver in their list. We want to avoid that timeout.
Therefore, we will create a new CARP network interface, and assign it two IP address to be used only for name services. When we add our redundant hardware, we’ll just add it to the CARP group for these IP addresses, and be good to go.
We already have carp0
, so let’s create carp1
with two IP
addresses.
Put the following into /etc/hostname.carp1
:
inet 192.168.13.2 255.255.255.0 192.168.13.255 vhid 132 carpdev em0 pass vhid132passwd description "name servers"
inet alias 192.168.13.3 255.255.255.0
Bring up the interface:
fw1# sh /etc/netstart carp1
Configure unbound and get curl
(to be used shortly):
fw1# rcctl enable unbound
fw1# unbound-control-setup
fw1# pkg-add -vv curl
Use curl
to get a list of root name servers:
fw1# curl https://www.internic.net/domain/named.root > /var/unbound/etc/root.hints
Create the unbound configuration file /var/unbound/etc/unbound.conf
:
#
# /var/unbound/etc/unbound.conf - caching resolver configuration
server:
interface: 127.0.0.1
interface: 192.168.13.2
interface: 192.168.13.3
interface: 192.168.13.4
interface: ::1
access-control: 0.0.0.0/0 refuse
access-control: 127.0.0.0/8 allow
access-control: 192.168.13.0/24 allow
access-control: ::0/0 refuse
access-control: ::1 allow
# by default, unbound won't query localhost, let's change that
do-not-query-localhost: no
hide-identity: yes
hide-version: yes
# root hints
# curl https://www.internic.net/domain/named.root > /var/unbound/etc/root.hints
root-hints: /var/unbound/etc/root.hints
# enable dnssec by specifying a root key file
auto-trust-anchor-file: /var/unbound/db/root.key
# allow RFC1918 addresses to be returned
local-zone: 13.168.192.in-addr.arpa transparent
# don't use dnssec for locally hosted zones
domain-insecure: kotfu.net
domain-insecure: 13.168.192.in-addr.arpa
# enable remote control, but only on localhost
remote-control:
control-enable: yes
control-interface: 127.0.0.1
control-port: 8953
server-key-file: /var/unbound/etc/unbound_server.key
server-cert-file: /var/unbound/etc/unbound_server.pem
control-key-file: /var/unbound/etc/unbound_control.key
control-cert-file: /var/unbound/etc/unbound_control.pem
# stub out our internal zones
stub-zone:
name: kotfu.net
stub-addr: 127.0.0.1@5353
stub-zone:
name: 13.168.192.in-addr.arpa
stub-addr: 127.0.0.1@5353
Notice that the stub zones are queried from localhost port 5353, which is
where we set up nsd
to listen. Start it up and test it out:
fw1# rcctl start unbound
fw1# dig @localhost eff.org
Notice that we are also listening on 192.168.13.4. That’s intentional, and we’ll need it later.
DHCP Server
There are two ways to have redundant DHCP servers. One method requires no software support in the servers for any type of failover. Assign two blocks of dynamic IP addresses, configure the first server to hand out addresses in the first block, and configure the second server to hand out addresses in the second block.
The second approach is to use a DHCP server that knows how to sync leases between two instances. You could install Kea or ISC DHCP from the OpenBSD packages repository; both of these DHCP servers support lease synchonization.
You can also use the DHCP server that ships stock with OpenBSD, which also
supports lease synchronization. I haven’t used Kea,
but the ISC DHCP server requires the
synchronization information to be provided in the /etc/dhcpd.conf
file, which
often requires you to split the configuration information into two files, one
that’s the same between your failover servers, and one that’s not. The OpenBSD
stock DHCP server uses the command line for the synchronization parameters,
simplifying the configuration file and making it the same between the failover
servers.
We will use the DHCP server installed by default with OpenBSD. First enable the server:
fw1# rcctl enable dhcpd
Create a configuration file in /etc/dhcpd.conf
:
#
# /etc/dhcpd.conf
authoritative;
# set our options
option domain-name "kotfu.net";
option domain-name-servers 192.168.13.2,192.168.13.3;
option ntp-servers 192.168.13.4;
default-lease-time 216000; # 1 hr
max-lease-time 648000; # 3 hr
subnet 192.168.13.0 netmask 255.255.255.0 {
option routers 192.168.13.1;
option broadcast-address 192.168.13.255;
range 192.168.13.32 192.168.13.127;
}
Notice we have used the two IP addresses assigned to our resolver, as well as the NTP server. Your DHCP configuration will likely be more involved than this one (mine is), but it is sufficient for our current example. Finally, we start up the DHCP service:
fw1# rcctl start dhcpd
Test it out with any device on your network to make sure it can dynamically acquire a working network configuration.
Part 2 is now complete. We have a single server which provides:
- routing
- packet filtering
- authoritative name server
- caching DNS resolver
- NTP server
- DHCP server
Along the way, we have done some preparatory work for Part 3, where we add in our second piece of firewall hardware and configure it for redundant routing and packet filtering.