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.confA user and group pdns is created.
A Systemd service
/lib/systemd/system/pdns.serviceis 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.