PowerDNS
PowerDNS is a versatile nameserver which supports a large number of different backends ranging from simple zonefiles to relational databases and load balancing/failover algorithms. PowerDNS tries to emphasize speed and security.
Prerequisites
IP Addresses
The server will need a dedicated private IPv4 address and global IPv6 address. See Network.
Troughout this document we will use 192.0.2.41 as IPv4 address and 2001:db8::41 as IPv6 address.
Also you need to know your public IPv4 address on your gateway. In this document we will use 198.51.100.240 as an example address.
Firewall-Gateway
The Router needs to forward incoming external IPv4 traffic on TCP and UDP port 53 to the private IPv4 address of this DNS server. IPv6 traffic on TCP and UDP port 53 to this servers IP address must be allowed to pass the firewall.
Database
Database Server must be installed and running.
Domain Registration
You need to register your domain with one of the official DNS registrars. The registrar must support IPv6, DNSSEC and allow you to define your own name servers.
Secondary Domain Name Servers
You will need a 3rd-Party providing you with secondary servers. The provider must support IPv6 records, NOTIFY, AXFR and DNSSEC on its servers.
Their DNS servers must be reachable over IPv6 and be able to receive DNS NOTIFY messages and transfer zones from the master over IPv6.
Software Installation
The PowerDNS server software is in the Ubuntu software package repository. We install the server and the MySQL database backend.
$ sudo apt-get install pdns-server pdns-backend-mysql
You will be asked for the password of the MySQL root user, so the database can be created.
The following happens during installation:
The following configuration file are created:
/etc/powerdns/pdns.conf
.
/etc/default/pdns
.
/etc/powerdns/pdns.d/pdns.local.conf
/etc/powerdns/pdns.d/pdns.simplebind.conf
/etc/powerdns/pdns.d/pdns.local.gmysql.conf
A user and group pdns is created.
A Systemd service
/lib/systemd/system/pdns.service
is created and started.
Systemd Service Dependencies
Since we use MariaDB as database backend for PowerDNS, we want to ensure that PowerDNS is always able to connect to the database server before running.
The Systemd services file for the PowerDNS server
/lib/systemd/system/pdns.service
is created as part of the software
package installation. The recommended method is to create a Systemd
override-file and not change anything in the provided service file, as it
would be lost on software package updates.
You can create a override file easily with the help of the systemctl command:
$ sudo systemctl edit pdns.service
This will start your editor with an empty file, where you can add your own custom Systemd service configuration options.
[Unit]
BindsTo=mariadb.service
The configuration statement After=mariadb.service
ensures that
mariadb.service
is fully started up before the pdns.service
is
started. It is already included in the service file installed by the software
package.
The line BindsTo=mariadb.service
ensures that if the Database
service is stopped, the PowerDNS server will be stopped too.
After you save and exit of the editor, the file will be saved as
/etc/systemd/system/pdns.service.d/override.conf
and Systemd will
reload its configuration:
systemctl show pdns.service |grep -E "After=|BindsTo="
DNS Server Database
All our DNS data will stored in a MySQL database.
Remove BIND Backend
By default PowerDNS uses BIND style zone-files to store DNS data.
As only one backend can be active at any time and we installed the MySQL backend, we need to remove the default “simple BIND backend”. This is done simply by deleting its configuration file.
$ sudo rm /etc/powerdns/pdns.d/pdns.simplebind.conf
Database Server
The following setting needs to changed in
/etc/powerdns/pdns.d/pdns.local.gmysql.conf
:
# MySQL Configuration
#
# Launch gmysql backend
launch=gmysql
# gmysql parameters
#gmysql-host=localhost
#gmysql-port=
gmysql-dbname=pdns
gmysql-user=pdns
gmysql-password=********
gmysql-dnssec=yes
gmysql-socket=/var/run/mysqld/mysqld.sock
Preparing the database
Create a new emtpy database called pdns on the MySQL server:
$ mysqladmin -u root -p create pdns
Create a database user for PowerDNS-server to access the database:
$ mysql -u root -p
GRANT SELECT ON pdns.* TO 'pdns'@'127.0.0.1'
IDENTIFIED BY '********';
FLUSH PRIVILEGES;
EXIT;
The file powerdns.sql
contains the PowerDNS
database structure as shown in the PoweDNS documentation
Basic setup: configuring database connectivity:
CREATE TABLE domains (
id INT AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
master VARCHAR(128) DEFAULT NULL,
last_check INT DEFAULT NULL,
type VARCHAR(6) NOT NULL,
notified_serial INT DEFAULT NULL,
account VARCHAR(40) DEFAULT NULL,
PRIMARY KEY (id)
) Engine=InnoDB;
CREATE UNIQUE INDEX name_index ON domains(name);
CREATE TABLE records (
id INT AUTO_INCREMENT,
domain_id INT DEFAULT NULL,
name VARCHAR(255) DEFAULT NULL,
type VARCHAR(10) DEFAULT NULL,
content VARCHAR(64000) DEFAULT NULL,
ttl INT DEFAULT NULL,
prio INT DEFAULT NULL,
change_date INT DEFAULT NULL,
disabled TINYINT(1) DEFAULT 0,
ordername VARCHAR(255) BINARY DEFAULT NULL,
auth TINYINT(1) DEFAULT 1,
PRIMARY KEY (id),
) Engine=InnoDB;
CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX recordorder ON records (domain_id, ordername);
CREATE TABLE supermasters (
ip VARCHAR(64) NOT NULL,
nameserver VARCHAR(255) NOT NULL,
account VARCHAR(40) NOT NULL,
PRIMARY KEY (ip, nameserver)
) Engine=InnoDB;
CREATE TABLE comments (
id INT AUTO_INCREMENT,
domain_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(10) NOT NULL,
modified_at INT NOT NULL,
account VARCHAR(40) NOT NULL,
comment VARCHAR(64000) NOT NULL,
PRIMARY KEY (id)
) Engine=InnoDB;
CREATE INDEX comments_domain_id_idx ON comments (domain_id);
CREATE INDEX comments_name_type_idx ON comments (name, type);
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
CREATE TABLE domainmetadata (
id INT AUTO_INCREMENT,
domain_id INT NOT NULL,
kind VARCHAR(32),
content TEXT,
PRIMARY KEY (id)
) Engine=InnoDB;
CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind);
CREATE TABLE cryptokeys (
id INT AUTO_INCREMENT,
domain_id INT NOT NULL,
flags INT NOT NULL,
active BOOL,
content TEXT,
PRIMARY KEY(id)
) Engine=InnoDB;
CREATE INDEX domainidindex ON cryptokeys(domain_id);
CREATE TABLE tsigkeys (
id INT AUTO_INCREMENT,
name VARCHAR(255),
algorithm VARCHAR(50),
secret VARCHAR(255),
PRIMARY KEY (id)
) Engine=InnoDB;
CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
ALTER TABLE `records` ADD CONSTRAINT `records_ibfk_1` FOREIGN KEY (`domain_id`)
REFERENCES `domains` (`id`) ON DELETE CASCADE;
To create this table structures in our new PowerDNS database:
$ mysql -u root -p pdns < powerdns.sql
Configuration
The following settings need to be changed in
/etc/powerdns/pdns.conf
:
REST API
Create a random string to be used as API key to access the server by other apps:
$ pwgen -cns 64 1
# cache-ttl=20
#################################
# chroot If set, chroot to this directory for more security
#
# chroot=/var/spool/powerdns
#################################
# config-dir Location of configuration directory (pdns.conf)
#
config-dir=/etc/powerdns
#################################
# config-name Name of this virtual configuration - will rename the binary image
#
# config-name=
#################################
# control-console Debugging switch - don't use
Allowed Zone Transfers
#################################
# allow-axfr-ips If enabled, restrict zonetransfers to originate from these
# IP addresses
allow-axfr-ips=127.0.0.1 ::1 192.0.2.0/24 2001:db8::/64
#################################
Enable Zone Transfers
#################################
# disable-axfr Disable zonetransfers but do allow TCP queries
#
disable-axfr=no
#################################
Server IP Address
#################################
# local-address Local IP address to which we bind
#
local-address=192.0.2.41
#################################
# local-ipv6 Local IP address to which we bind
#
local-ipv6=2001:db8::41
#################################
Act as Master Server
#################################
# master Act as a master
#
master=yes
#################################
Source Address
By default PowerDNS will use the last defined IP address as source address to send out DNS NOTIFY messages to slaves.
The slave servers, will not accept any NOTIFY messages, if they are not coming from the defined master server of a domain. Here is how we tell PowerDNS to use its dedicated IPv4 and IPv6 addresses for outgoing connections:
#################################
# query-local-address The IP address to use as a source address for sending
# queries.
query-local-address=192.0.2.41
query-local-address6=2001:db8::41
#################################
Server Restart
$ sudo service pdns restart
Import Zone-Files
If you already have zone files, from previous DNS servers or 3rd-party providers, you can import them as follows:
$ zone2sql --zone=example.net.zone \
--zone-name=example.net \
--gmysql --transactions --verbose \
> example.net.zone.sql
1 domains were fully parsed, containing 49 records
$ mysql -u root -p pdns < example.net.zone.sql
Enter password:
And done. Very easy.
Secondary Server
Let’s assume our master server has the IP address 2001:db8::41 and the new slave will have the IP address 2001:db8::42.
In the real world a DNS slave would be on entirely another subnet.
To set up a PowerDNS as secondary slave DNS server.
Install MariaDB and PowerDNS
See above. Also add the MySQL tables as above.
Copy the configuration file from the master and change following things:
Slave Server IP Addresses
#################################
# local-address Local IP address to which we bind
#
local-address=192.0.2.42
#################################
# local-ipv6 Local IP address to which we bind
#
local-ipv6=2001:db8::42
Setup PowerDNS as a Slave
#################################
# master Act as a master
#
master=no
#################################
# slave Act as a slave
#
slave=yes
Restart the slave server:
$ sudo service pdns restart
Add Domain Record on Slave Server
Open a MySQL database server sesssion:
slave$ mysql -u root -p pdns
Add the the domain along with the IP address of the master server as follows:
INSERT INTO `domains` (`name`, `master`, `type`) VALUES('example.net', '2001:db8::41', 'SLAVE');
Add Slave Record on Master Server
Open a MySQL database server sesssion:
master$ mysql -u root -p pdns
Add a NS record and IP addresses of the new slave to the domain:
INSERT INTO `records` (`domain_id`, `name`, `type`, `content`) VALUES( (SELECT `id` FROM `domains` WHERE `name` = 'example.net'), 'example.net', 'NS', 'ns2.example.net' ); INSERT INTO `records` (`domain_id`, `name`, `type`, `content`) VALUES( (SELECT `id` FROM `domains` WHERE `name` = 'example.net'), 'ns2.example.net', 'A', '192.0.2.42' ); INSERT INTO `records` (`domain_id`, `name`, `type`, `content`) VALUES( (SELECT `id` FROM `domains` WHERE `name` = 'example.net'), 'ns2.example.net', 'AAAA', '2001:db8::42' );
Delete a Domain
Let say you want to remove the domain example.org completely.
DELETE FROM `domainmetadata` WHERE `domain_id` = ( SELECT `id` FROM `domains` WHERE `name` = "example.org" ); DELETE FROM `records` WHERE `domain_id` = ( SELECT `id` FROM `domains` WHERE `name` = "example.org" ); DELETE FROM `comments` WHERE `domain_id` = ( SELECT `id` FROM `domains` WHERE `name` = "example.org" ); DELETE FROM `cryptokeys` WHERE `domain_id` = ( SELECT `id` FROM `domains` WHERE `name` = "example.org" ); DELETE FROM `domains` WHERE `name` = "example.org";
This same procedure needs to be done on every master or slave sever.