Get up and running on with your own DNS and DHCP server from scratch (PowerDNS + isc-dhcp-server)

This article covers how to get up and running with your own authoritative DNS server, DHCP server, and have dhcp clients automatically register there host name in dns.

Prerequisites

  • Static IP address has already been configured. (In Debian you can set this in the /etc/network/interfaces file)
  • The machine is connected to the internet (for package installation)

Goals

  • PowerDNS with a mysql backend (The DNS server)
  • MariaDB for the database (A database for the DNS Server)
  • isc-dhcp-server for providing DHCP requests.
  • A authoritative zone running to serve DNS requests
  • DHCP server for a internal subnet e.g. ‘10.88.77.0/27’
  • DHCP server updating DNS records based on the hostname of a dhcp client when connecting to the network

Part 1. Install Required Packages

# Ensure we are running the latest version of Debian and all packages are up to date. 
apt update && apt upgrade -y && apt dist-upgrade -y
# Install MariaDB for the PowerDNS backend
apt install mariadb-server -y
# Install PowerDNS with MySQL support for the backend
apt-get install pdns-server pdns-backend-mysql pdns-tools dnsutils -y
# By default PowerDNS installs support for bind. We can remove this as we are using MySQL
apt remove pdns-backend-bind -y
# Install isc-dhcp-server
apt install isc-dhcp-server -y
# Note: Ignore the errors during installation
# Install a firewall to restrict access and unattended-upgrades to for automatic security patching.
apt install ufw unattended-upgrades -y
# Next lets stop both services while we are configuring them
service isc-dhcp-server stop
service pdns stop

Step 2. Setup MariaDB for PowerDNS

  • Then set a root password using a strong password
  • Then select Yes for remove anonymous users, yes to disallow remote access, and yes to disallow remote root login.
root@dns1:~# mysql_secure_installationNOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
SERVERS IN PRODUCTION USE! PLEASE READ EACH STEP CAREFULLY!
In order to log into MariaDB to secure it, we’ll need the current
password for the root user. If you’ve just installed MariaDB, and
you haven’t set the root password yet, the password will be blank,
so you should just press enter here.
Enter current password for root (enter for none):
OK, successfully used password, moving on…
Setting the root password ensures that nobody can log into the MariaDB
root user without the proper authorisation.
Set root password? [Y/n] y
New password:
Re-enter new password:
Password updated successfully!
Reloading privilege tables..
… Success!
By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them. This is intended only for testing, and to make the installation
go a bit smoother. You should remove them before moving into a
production environment.
Remove anonymous users? [Y/n] y
… Success!
Normally, root should only be allowed to connect from ‘localhost’. This
ensures that someone cannot guess at the root password from the network.
Disallow root login remotely? [Y/n] y
… Success!
By default, MariaDB comes with a database named ‘test’ that anyone can
access. This is also intended only for testing, and should be removed
before moving into a production environment.
Remove test database and access to it? [Y/n] y
— Dropping test database…
… Success!
— Removing privileges on test database…
… Success!
Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.
Reload privilege tables now? [Y/n] y
… Success!
Cleaning up…All done! If you’ve completed all of the above steps, your MariaDB
installation should now be secure.
Thanks for using MariaDB!
  • Next we need to connect to the mariadb instances, create a database, create a user, and allow access from the localhost to that database with a predefined password.
# Connect to MariaDB 
root@dns1:~# mysql -u root -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
# Create the database
CREATE DATABASE pdns;
Query OK, 1 row affected (0.000 sec)
# Create a user called pdns that connects from the localhost using the password specified below (change the password for your case)
GRANT ALL ON pdns.* TO 'pdns'@'localhost' IDENTIFIED BY 'ReallyStrongPassword';
Query OK, 0 rows affected (0.000 sec)
# Flush the privileges and quit
FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.000 sec)
QUIT;
  • Next we need to load the schema

Part 3. Database Schema

root@dns1:~# apt search pdns-server
Sorting… Done
Full Text Search… Done
pdns-server/stable,now 4.1.6–3 amd64 [installed]
extremely powerful and versatile nameserver
# Use the newly created database
USE pdns;
  • Copy and paste the schema from the link above into the console.
  • Then quit the mysql client (quit;)

Part 4. Configure the mysql backend for PowerDNS

# Remove bind.conf as we are using mysql as our bandend. 
rm /etc/powerdns/pdns.d/bind.conf
# Create a mysql config file and specify your sql settings
nano /etc/powerdns/pdns.d/pdns.local.gmysql.conf
# Add the following lines to the MySQL Configuration file
launch=gmysql
gmysql-host=localhost
gmysql-dbname=pdns
gmysql-user=pdns
gmysql-password=ReallyStrongPassword #<< Change to your own pass
  • We are now ready to start our PowerDNS server!
root@dns1:/# service pdns restart
root@dns1:/# service pdns status
● pdns.service — PowerDNS Authoritative Server
Loaded: loaded (/lib/systemd/system/pdns.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2020–06–14 12:18:37 JST; 30s ago
Docs: man:pdns_server(1)
man:pdns_control(1)
https://doc.powerdns.com
Main PID: 5651 (pdns_server)
Tasks: 8 (limit: 4700)
Memory: 4.8M
CGroup: /system.slice/pdns.service
└─5651 /usr/sbin/pdns_server — guardian=no — daemon=no — disable-syslog — log-timestamp=no — write-pid=no
Jun 14 12:18:37 dns1 pdns_server[5651]: UDPv6 server bound to [::]:53
Jun 14 12:18:37 dns1 pdns_server[5651]: TCP server bound to 0.0.0.0:53
Jun 14 12:18:37 dns1 pdns_server[5651]: TCPv6 server bound to [::]:53
Jun 14 12:18:37 dns1 pdns_server[5651]: PowerDNS Authoritative Server 4.1.6 © 2001–2018 PowerDNS.COM BV
Jun 14 12:18:37 dns1 pdns_server[5651]: Using 64-bits mode. Built using gcc 8.3.0.
Jun 14 12:18:37 dns1 pdns_server[5651]: PowerDNS comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribu
Jun 14 12:18:37 dns1 pdns_server[5651]: Creating backend connection for TCP
Jun 14 12:18:37 dns1 pdns_server[5651]: About to create 3 backend threads for UDP
Jun 14 12:18:37 dns1 systemd[1]: Started PowerDNS Authoritative Server.
Jun 14 12:18:37 dns1 pdns_server[5651]: Done launching threads, ready to distribute questions

Other guides on the internet might show you installing a web based GUI to help with administration of your DNS server however as of PowerDNS version 4.0 and on-wards there is now a new text based tool called ‘pdnsutil’ to interact directly with PowerDNS from the command line. I found the tool to be fast, simple and easy to use.
There is also the added advantage of not having to deploy and maintain a web stack, and maintain certificates. This also hepps in reducing the attack surface.
Further reading of pdnsutil can be found here.

  • pdnsutil is already installed with powerdns-server but to make things a little easier we can add bash completion file.
# Edit your bash_completion and make sure the following line is added:
nano ~/.bash_completion
for bcfile in ~/.bash_completion.d/* ; do
. $bcfile
done
# Make a user level bash_completion directory
mkdir ~/.bash_completion.d/
# Download the bash_completion file provided by PowerDNS
cd ~/.bash_completion.d/
wget
https://raw.githubusercontent.com/PowerDNS/pdns/rel/auth-4.1.x/contrib/pdnsutil.bash_completion.d

Step 5. Zone Creation

Note: I don’t own this domain and I am not affiliated in anyway

# Create your first zone with PowerDNS
root@dns1:~# pdnsutil create-zone expresso-addict.com
Creating empty zone ‘expresso-addict.com’
  • Edit the zone using ‘pdnsutil edit-zone expresso-addict.com’. This will bring up a nano session.
  • You will need to correct your SOA record, add a NS record and a A record for your own DNS server and IP address. I have posted a simple example below to get you started.
pdnsutil edit-zone {zone name}
  • The nice thing about pdnsutil edit-zone is you are editing a copy of the zone file. After you have completed your edits pdnsutil will check the differences in the file and validate the configuration before writing back to the database.
(a)pply these changes, (e)dit again, (r)etry with original zone, (q)uit: e
Checked 3 records of ‘expresso-addict.com’, 0 errors, 0 warnings.
Detected the following changes:
+expresso-addict.com 3600 IN NS dns1.expresso-addict.com
-expresso-addict.com 3600 IN SOA a.misconfigured.powerdns.server hostmaster.expresso-addict.com 1 10800 3600 604800 3600
+expresso-addict.com 3600 IN SOA dns1.expresso-addict.com hostmaster.expresso-addict.com 1 10800 3600 604800 3600
+dns1.expresso-addict.com 3600 IN A 172.23.220.200
(a)pply these changes, (e)dit again, (r)etry with original zone, (q)uit: a
Adding empty non-terminals for non-DNSSEC zone
root@dns1:~#
  • How can we test the configuration? Simply run a dig command against the local host.
root@dns1:~# dig @localhost expresso-addict.com; <<>> DiG 9.11.5-P4–5.1+deb10u1-Debian <<>> @localhost expresso-addict.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51625
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1680
;; QUESTION SECTION:
;expresso-addict.com. IN A
;; AUTHORITY SECTION:
expresso-addict.com. 3600 IN SOA dns1.expresso-addict.com. hostmaster.expresso-addict.com. 1 10800 3600 604800 3600
;; Query time: 0 msec
;; SERVER: ::1#53(::1)
;; WHEN: Sun Jun 14 14:27:46 JST 2020
;; MSG SIZE rcvd: 100
  • You now have your own DNS server running and serving dns requests!

Step 6. Creating a TSIG key

Further reader on TSIG can be found here.

# Create a key called 'dhcp-key' using hmac-sha256
root@dns1:~# pdnsutil generate-tsig-key dhcp-key hmac-sha256
Generating new key with 64 bytes
Create new TSIG key dhcp-key hmac-sha256 eNgfPSPxyN3xX4Z91Ba+UfQaBXYa7l67Nw/F4iuKOEnOllI+A1ZB5MK8qpemjSThO3riCtnJc01HPMKDFMqADA==
# Enable the key on your DNS zone
root@dns1:~# pdnsutil activate-tsig-key expresso-addict.com dhcp-key master
Enabled TSIG key dhcp-key for expresso-addict.com
# To verify you can list your keys
root@dns1:~# pdnsutil list-tsig-keys
dhcp-key. hmac-sha256. eNgfPSPxyN3xX4Z91Ba+UfQaBXYa7l67Nw/F4iuKOEnOllI+A1ZB5MK8qpemjSThO3riCtnJc01HPMKDFMqADA==
# Verify the key is enabled for your domain
root@dns1:~# pdnsutil show-zone expresso-addict.com
This is a Native zone
Zone is not actively secured
Zone has following allowed TSIG key(s): dhcp-key
Metadata items:
TSIG-ALLOW-AXFR dhcp-key
No keys for zone 'expresso-addict.com'.

Step 7. Enable DNS updates

The pdns.conf file is well laid out and provides explanations of each parameter.

root@dns1:/etc/dhcp# nano /etc/powerdns/pdns.conf#Allow dnsupdates from localhost
allow-dnsupdate-from=127.0.0.0/8
# Enable DNS updates
dnsupdate=yes

Once completed restart pdns using ‘service pdns restart’ for the new settings to take.

8. Setup isc-dhcp-server

  • To get isc-dhcp-server up and running we first need to get the name of the network interface. (In this example ens18). We also need to stop the service before we start.
root@dns1:~# ip a
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether ca:fe:42:9d:1f:1d brd ff:ff:ff:ff:ff:ff
inet 10.88.77.30/27 brd 10.88.77.31 scope global dynamic ens18
root@dns1:/etc/dhcp# service isc-dhcp-server stop
  • Then edit the isc-dhcp-server defaults and add the interface name to INTERFACESv4
# Edit the following file
root@dns1:~# nano /etc/default/isc-dhcp-server
# Add the interface name to INTERFACESv4
INTERFACESv4=”ens18"
  • Now we can create a new dhcpd.conf file and specify our options, and scope.
# Move the original dhcpd.conf (Useful as a future reference)
mv /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.backup
nano /etc/dhcp/dhcpd.conf
  • Below is an example of the dhcp scope we are using. Note that we add the TSIG key previously generated with pdnsutil to allow for dynamic updates as well as define a network range, and options like default gateway, domain name, dns servers etc.
authoritative;
default-lease-time 720000;
max-lease-time 2160000;
ping-check true;
update-static-leases on;

ddns-updates on;
ddns-update-style standard;
ddns-ttl 300;
ddns-domainname "expresso-addict.com";

# Add the TSIG key generated with pdnsutil
key "dhcp-key" {
algorithm hmac-sha256;
secret "eNgfPSPxyN3xX4Z91Ba+UfQaBXYa7l67Nw/F4iuKOEnOllI+A1ZB5MK8qpemjSThO3riCtnJc01HPMKDFMqADA==";
};

# Specify the zone name and the DNS server on the localhost
zone expresso-addict.com {
primary 127.0.0.1;
key dhcp-key;
}
#-------------------------------
# Internal Network Scope
#-------------------------------
subnet 10.88.77.0 netmask 255.255.255.224 {
option domain-name-servers 172.23.220.30, 10.88.77.66;
option domain-search "expresso-addicts.com";
option routers 10.88.77.1;

pool {
range 10.88.77.20 10.88.77.29;
}
}
  • PowerDNS also provides a more detailed explanation of DynamicDNS here.
  • I also recommend to download the the latest OUI list so your dhcp server can show you the manufacture of MAC address of your client devices. This can help to identify what type of client is on your network.
# Download the latest OUI list
wget -O /usr/local/etc/oui.txt http://standards-oui.ieee.org/oui.txt
# included with isc-dhcp-server is a command that shows your dhcp clients, and the manufacturer of their MAC addresses. root@dns1:~# dhcp-lease-list
Reading leases from /var/lib/dhcp/dhcpd.leases
MAC IP hostname manufacturer
====================================================
00:0c:1.. 10.2. ups1 CyberPower Systems, Inc.
00:0c:1.. 10.2. ups2 CyberPower Systems, Inc.
18:66:d.. 10.2. idrac-srv1 Dell Inc.
  • Now that everything is in place on the dhcp server side lets start the dhcp server.
root@dns1:~# service isc-dhcp-server start
root@dns1:~# service isc-dhcp-server status
● isc-dhcp-server.service — LSB: DHCP server
Loaded: loaded (/etc/init.d/isc-dhcp-server; generated)
Active: active (running) since Mon 2020–06–15 11:31:36 JST; 3s ago
Docs: man:systemd-sysv-generator(8)
Process: 10794 ExecStart=/etc/init.d/isc-dhcp-server start (code=exited, status=0/SUCCESS)
Tasks: 1 (limit: 4700)
Memory: 12.0M
CGroup: /system.slice/isc-dhcp-server.service
└─10806 /usr/sbin/dhcpd -4 -q -cf /etc/dhcp/dhcpd.conf ens18
Jun 15 11:31:34 dns1 systemd[1]: Starting LSB: DHCP server…
Jun 15 11:31:34 dns1 isc-dhcp-server[10794]: Launching IPv4 server only.
Jun 15 11:31:34 dns1 dhcpd[10806]: Wrote 2 leases to leases file.
Jun 15 11:31:34 dns1 dhcpd[10806]: Server starting service.
Jun 15 11:31:36 dns1 isc-dhcp-server[10794]: Starting ISC DHCPv4 server: dhcpd.
Jun 15 11:31:36 dns1 systemd[1]: Started LSB: DHCP server.

Verify

To verify everything is working add a dhcp client to the network and monitor the logs on your new dns/dhcp server.

# We can also verify the DNS record is updated from the syslog
root@dns1:/etc/dhcp# grep dhcpd /var/log/syslog
dns1 dhcpd[8861]: DHCPRELEASE of 10.88.77.21 from ca:fe:ff:81:4c:33 (test2) via ens18 (found)
dns1 dhcpd[8861]: DHCPDISCOVER from ca:fe:ff:81:4c:33 via ens18
dns1 dhcpd[8861]: DHCPOFFER on 10.88.77.21 to ca:fe:ff:81:4c:33 (test2) via ens18
dns1 dhcpd[8861]: DHCPREQUEST for 10.88.77.21 (10.88.77.30) from ca:fe:ff:81:4c:33 (test2) via ens18
dns1 dhcpd[8861]: DHCPACK on 10.88.77.21 to ca:fe:ff:81:4c:33 (test2) via ens18
dns1 dhcpd[8861]: Added new forward map from test2.expresso-addict.com to 10.88.77.21
# pdnsutil list-zone will start to show records for dhcp clients.
root@dns1:/etc/dhcp# pdnsutil list-zone expresso-addict.com
$ORIGIN .
dns1.expresso-addict.com 3600 IN A 10.88.77.30
expresso-addict.com 3600 IN NS dns1.expresso-addict.com.
expresso-addict.com 3600 IN SOA dns1.expresso-addict.com. hostmaster.expresso-addict.com. 2020061501 10800 3600 6048
00 3600
test2.expresso-addict.com 300 IN A 10.88.77.21
test2.expresso-addict.com 300 IN DHCID AAIBhkP+9vwMjeINBpbOj+uAI3l7fClXxGksBGRsmSror6Y=

9. Firewall

dns1:~# ufw allow dns
dns1:~# ufw allow ssh
dns1:~# ufw allow 67/udp comment “allow isc-dhcp-server respones”
dns1:~# ufw enable
dns1:~# ufw status
Status: active

To Action From
-- ------ ----
DNS ALLOW Anywhere
67/udp ALLOW Anywhere
22/tcp ALLOW Anywhere
DNS (v6) ALLOW Anywhere (v6)
67/udp (v6) ALLOW Anywhere (v6)
22/tcp (v6) ALLOW Anywhere (v6)

10. Additional Considerations

  • Implement a second powerdns server in native mode with MariaDB master/slave replication for redundancy.
master / slave replication
  • On the topic of redundancy isc-dhcp-server supports fail-over peers as well. Have a look here.
  • If you need to have external DNS (internet lookup) as well as providing your internal DNS records then look into powerdns-recursor or even setting up a pihole for ad filtering that forwards looksup for your zone to your pdns-server.
Example of running both pdns-server and pdns-recursor on the same PC
pihole settings page to allow you to specify a conditional forwarder
  • If you are still interested in a GUI for PowerDNS then I would recommend to look at PowerDNS-Admin
  • If you are interest in a GUI for DHCP then glass-isc-dhcp is worth checking out as well.

Do you have any suggestions to improve this guide? Feel free to let me know down in the comments.

Cloud and Infrastructure Engineer