Self Hosted: Episode 3 - DNS/DHCP/NTP

Used Software:

  • FreeBSD 14.2 (as jail)
  • ISC Bind 9.2.0 (from ports)
  • ISC DHCP 4.4 (from ports)

Introduction

It all begins with DNS...

Building the Jail environment

The DNS/DHCP server will be built using a FreeBSD jail. This is done for several reason. First, FreeBSD Jails come up nearly as quick as the hypervisor itself, meaning if everything is (re)started at the same time the DNS server should be the first service up an running. Second, is that FreeBSD jails provide isolation from other processes on the same system. Third, they are light weight.

What is a FreeBSD Jail (referred to as just a 'jail' from now on)? A jail is an isolated virtual space to run processes in that is separate from the host OS, and all other jails. From the Linux perspective jails are very close to Docker containers (although they pre-date Docker by a decade).

Setup the Jail sever

To setup the jail server first we need a space to store all the jails. Jails consist of the base FreeBSD OS version of our choice. We will be making use of ZFS snapshots to make deploying and updating jails quickly.

Create Jail from template

To create a jail from the template use the ZFS clone command.

zfs clone data01/jails/templates/14.2-RELEASE@base data01/jails/dns

command to clone a jail drive

Setup the DNS/DHCP server jail

Next we will create a jail configuration file. These files will be stored in the /etc/jails.conf.d directory. Each jail will have its own file. The file is used to setup the properties of the jail, what it is allowed to do within the host OS and any pre/port commands that need to be run in the host OS when the jail is started/stopped.

First we need to add/update the /etc/devfs.conf file. This file controls what resources are available to the jail. In this case we will need to expose the BPF devices to listen for DHCP requests. The following lines need to be added to the /etc/devfs.conf file

[devfsrules_jail_bpf=10]
add include $devfsrules_jail
add path bpf* unhide

partial /etc/devfs.conf

Now create the /etc/jail.conf.d/dns.conf file for defining and starting the jail.

# DNS/DHCP server jail file

dns {
	# STARTUP / LOGGING
	exec.stop = "/bin/sh /etc/rc.shutdown";
	exec.consolelog = "/var/log/jail_console_${name}.log";

	# PERMISSIONS
	allow.raw_sockets;
	exec.clean;
	mount.devfs;
    devfs_ruleset = 10;
    allow.reserved_ports = true;


	# HOSTNAME / PATH
	host.hostname = "${name}";
	path = "/data01/jails/${name}";

	# NETWORK
	vnet;
	vnet.interface = "${epair}b";

	# NETWORKS/INTERFACES
	$id = "10";
	$ip = "192.168.30.${id}";
	$mask = "255.255.255.0";
	$gateway = "192.168.30.1";
	$bridge = "net-dmz";
	$epair = "epair${id}";

	exec.prestart += "ifconfig ${epair} create up";
	exec.prestart += "ifconfig ${epair}a up descr jail:${name}";
	exec.prestart += "ifconfig ${bridge} addm ${epair}a up";
	exec.start    += "ifconfig ${epair}b ${ip} netmask ${mask} up";
	exec.start    += "route add default ${gateway}";
  	exec.start    += "/bin/sh /etc/rc";
	exec.poststop += "ifconfig ${bridge} deletem ${epair}a";
	exec.poststop += "ifconfig ${epair}a destroy";
}

/etc/jails.conf.d/dns.conf

Start the jail

Once the configuration file has been complete the jail should be started.

service jail start dns

command to start the jail

Install the DNS/DHCP service in the jail

Once the jail has been started we need to update the FreeBSD package system and install the DNS and DHCP packages into the jail.

pkg -j dns -y update

command to update the packages in the jail

Setup the new jail

To enter the jail from the host OS use the jexec(8) command and specify the command to run inside the jail.

jexec -l dns /bin/sh

command to connect to the jail

Once in the jail you will be the root user. The jail itself will have no service running by default, not even SSH. Let's enable the SSH service... but first we need to create a user account because we don't want to SSH into the server as root... (or at least we shouldn't do this... it is up to you).

jexec -l dns pw user add alfred -m -s /bin/sh -u 1001 -G wheel
jexec -l dns passwd alfred
jexec -l dns passwd root

commands to create a jail user and set the passwords

Fix sshd to disallow root to login via SSH. Edit the /etc/ssh/sshd_config and explicitly disable root login.

jexec -l dns sed -i -r -e 's/^#?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config

command to edit sshd_config to prevent root logins

Now lets enable and start the SSH service within the jail

service -j dns sshd enable
service -j dns sshd start

command to enable and start sshd

Now we test. SSH to 192.168.30.10 (remember no DNS yet). Once that is working we can move on to the DNS configuration.

DNS server setup discussion...

Before we configure the DNS settings lets create a list of items we need to accomplish to be successful.

  • Resolve internal names from internal sources
  • Resolve external names from internal sources
  • Resolve DMZ names from external sources (future episode)
  • Filter external names from internal sources (bad domains, advertisements, etc) (future episode)
  • Provide Dynamic DNS updates from internal hosts

We can accomplish this is three different ways (there are probably more... but only three I'll mention). Each way has it's own complications.

The first way is to use DNS views (or DNS split view). Based on where the client request comes from the client will be presented with one view or another view of the domain. This can be tricky to troubleshoot.

The second method, that is easier to troubleshoot, is to use two different domains. One domain is used for internal applications and the other domain is used for external applications. This is easier to troubleshoot, but become complicated when we need to expose an internal resource externally.

The third method is to use a single domain but two authoritative name servers, and internal and external name server. This method requires the most work of all since we will need to maintain two separate servers, but from the client standpoint, it is very easy to understand. External clients connect to the external name server, Internal clients use the internal name server.

In series I will present the third option. While the goal is to "self host" everything, we will require internet facing services at predictable addresses. This will allow us to utilize one of the many Internet DNS providers out there while maintaining maximum control.

Configuring DNS server

(this section assumes that we are ssh'd into the jail)

On FreeBSD the configuration files are generally stored in the /usr/local/etc/namedb directory.

First we need to create the rndc.conf file.

rndc-confgen -a -k DNS_UPDATE

command to create the rndc.conf file

This will create a file in the /usr/local/etc/namedb/ directory.

Next we will crate the named.conf file.

//
options {
        // All file and path names are relative to the chroot directory,
        // if any, and should be fully qualified.
        directory       "/usr/local/etc/namedb/working";
        pid-file        "/var/run/named/pid";
        dump-file       "/var/dump/named_dump.db";
        statistics-file "/var/stats/named.stats";
        listen-on       {
                                127.0.0.1;
                                192.168.30.10;
                         };
// These zones are already covered by the empty zones listed below.
// If you remove the related empty zones below, comment these lines out.
        disable-empty-zone "255.255.255.255.IN-ADDR.ARPA";
        disable-empty-zone "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA";
        disable-empty-zone "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA";

        # allow recursion
        allow-recursion { 0.0.0.0/0; };
};

include "/usr/local/etc/namedb/rndc.key";
controls {
        inet 127.0.0.1 port 953 allow { 127.0.0.1; } keys { "DNS_UPDATE"; };
};

zone "." { type hint; file "/usr/local/etc/namedb/named.root"; };

zone "example.org" {
        type primary;
        file "/usr/local/etc/namedb/primary/example.org";
        allow-update { key DNS_UPDATE; };
};

zone "10.168.192.in-addr.arpa" {
        type primary;
        file "/usr/local/etc/namedb/primary/10.168.192.in-addr.arpa";
        allow-update { key DNS_UPDATE; };
};
zone "20.168.192.in-addr.arpa" {
        type primary;
        file "/usr/local/etc/namedb/primary/20.168.192.in-addr.arpa";
        allow-update { key DNS_UPDATE; };
};
zone "30.168.192.in-addr.arpa" {
        type primary;
        file "/usr/local/etc/namedb/primay/30.168.192.in-addr.arpa";
        allow-update { key DNS_UPDATE; };
};
zone "40.168.192.in-addr.arpa" {
        type primary;
        file "/usr/local/etc/namedb/primary/40.168.192.in-addr.arpa";
        allow-update { key DNS_UPDATE; };
};

/usr/local/etc/namedb/named.conf

Finally we create the zone files stored in the /usr/local/etc/namedb/primary/ directory.

; 
$TTL 60
$ORIGIN example.org.
@       IN      SOA     dns.example.org. noone.example.org. (
                10
                1h
                3h
                2h
                4h
)
;
; NS records
        IN      NS      dns.example.org.
; MX records
; A records
justatest       IN      A       192.168.10.254
dns     IN      A       192.168.30.10
firewall-dmz      IN      A      192.168.30.1
firewall-iot      IN      A      192.168.20.1
firewall      IN      A      192.168.10.1
; CNAME records

/usr/local/etc/namedb/primary/example.org

; 
$TTL 60
$ORIGIN 10.168.192.in-addr.arpa.
@       IN      SOA     dns.example.org. noone.example.org. (
        10
        1h
        3h
        2h
        4h
)
; NS records
        IN      NS      dns.example.org.
; PTR records
254     IN      PTR     justatest.example.org.
1       IN      PTR     firewall.example.org.

/usr/local/etc/namedb/primary/10.168.192.in-addr.arpa

; 
$TTL 60
$ORIGIN 20.168.192.in-addr.arpa.
@       IN      SOA     dns.example.org. noone.example.org. (
        10
        1h
        3h
        2h
        4h
)
; NS records
        IN      NS      dns.example.org.
; PTR records
1       IN      PTR     firewall-iot.example.org.

/usr/local/etc/namedb/primary/20.168.192.in-addr.arpa

; 
$TTL 60
$ORIGIN 30.168.192.in-addr.arpa.
@       IN      SOA     dns.example.org. noone.example.org. (
        10
        1h
        3h
        2h
        4h
)
; NS records
        IN      NS      dns.example.org.
; PTR records
10      IN      PTR     dns.example.org.
1       IN      PTR     firewall-dmz.example.org.

/usr/local/etc/namedb/primary/30.168.192.in-addr.arpa

; 
$TTL 60
$ORIGIN 40.168.192.in-addr.arpa.
@       IN      SOA     dns.example.org. noone.example.org. (
        10
        1h
        3h
        2h
        4h
)
; NS records
        IN      NS      dns.example.org.

/usr/local/etc/namedb/primary/40.168.192.in-addr.arpa

Once bind has been configured we need to enable it so that it starts automatically.

service named enable
service named start

command to enable and start named service

Confirm DNS server is working

To confirm that DNS is working the following commands can be run.

dig @127.0.0.1 google.com
dig @127.0.0.1 justatest.example.org

commands to test recursive and authoritative resolution

Both commands should succeed. Assuming it is working, update the /etc/resolv.conf file

nameserver 127.0.0.1

/etc/resolv.conf

DHCP server discussion

As with DNS server, let's take a moment to discuss what we want to accomplish with the DHCP server.

  • Provide DHCP for the COMMON segment.
  • Provide DHCP for the IOT segment.
  • Dynamically update DNS records for the assigned IPs

One important thing to note is that DHCP clients use a broadcast to find a DHCP server. This broadcast does not extend past the local subnet (without help).

Install DHCP server software

First step is to install the DHCP server software

pkg install isc-dhcp44-server

command to install the dhcp server software

Configuring DHCP server

Use the following config for the DHCP server

#

# option definitions common to all supported networks...
option domain-name "example.org";

default-lease-time 86400;
max-lease-time 86400;

ddns-update-style standard;
deny client-updates;
ddns-updates on;

authoritative;

log-facility local7;

include "/usr/local/etc/namedb/rndc.key";


zone example.org. {
        primary 127.0.0.1;
        key DNS_UPDATE;
}

zone 10.168.192.in-addr.arpa. {
        primary 127.0.0.1;
        key DNS_UPDATE;
}
zone 20.168.192.in-addr.arpa. {
        primary 127.0.0.1;
        key DNS_UPDATE;
}
zone 30.168.192.in-addr.arpa. {
        primary 127.0.0.1;
        key DNS_UPDATE;
}
zone 40.168.192.in-addr.arpa. {
        primary 127.0.0.1;
        key DNS_UPDATE;
}

subnet 192.168.10.0 netmask 255.255.255.0 {
        range 192.168.10.10 192.168.10.254;
        option routers 192.168.10.1;
        option domain-name-servers 192.168.30.10;
}
subnet 192.168.20.0 netmask 255.255.255.0 {
        range 192.168.20.10 192.168.20.254;
        option routers 192.168.20.1;
        option domain-name-servers 192.168.30.10;
}
subnet 192.168.30.0 netmask 255.255.255.0 {
        range 192.168.30.10 192.168.30.254;
        option routers 192.168.30.1;
        option domain-name-servers 192.168.30.10;
}
subnet 192.168.40.0 netmask 255.255.255.0 {
        range 192.168.40.10 192.168.40.254;
        option routers 192.168.40.1;
        option domain-name-servers 192.168.30.10;
}

/usr/local/etc/dhcpd.conf

Next we enable the DHCP service

service isc-dhcpd enable
service isc-dhcpd start

commands to enable and start the dhcp server

Setup DHCP relay on Firewall

As mentioned above DHCP uses a link local broadcast to find a DHCP server. In order to provide the help necessary to forward the broadcasts from different LAN segments we will setup the firewall to DHCP relaying.

On the firewall we will enable and start the DHCP relay service.

ln -s /etc/rc.d/dhcrelay /etc/rc.d/dhcrelay_iot
ln -s /etc/rc.d/dhcrelay /etc/rc.d/dhcrelay_common
rcctl enable dhcrelay_common dhcrelay_iot
rcctl set dhcrelay_common flags -i vio2 192.168.30.10
rcctl set dhcrelay_iot flags -i vio1 192.168.30.10
rcctl start dhcrelay_common dhcrelay_iot

Conclusion

This setups up the initial DNS and DHCP services. However, as this series progresses we will add DNS records.