DNS database configuration

As this guide is intended to describe the implementation of a DNS server using PowerDNS, and not a tutorial on relational database management, we will assume that you have a fully functioning PostgreSQL database installed and running either on the same host as the DNS server or on another host on your network. If this is not the case then you may be interested in the guide.

Creating the database users

The first step in the process of creating a suitable database to store our DNS records will be to create two PostgreSQL database users called dns_server and dns_admin so that we can assign suitable permissions to any databases and tables we create later. The first of these users will be used by the PowerDNS daemon to connect to the database. This user will be mostly restricted to read access to the data contained in the database so that a subverted daemon process cannot corrupt the DNS tables. The second user will be used when we wish to update the DNS records stored in the database and consequently will have both read and write access.

As we shall be using the PostgreSQL tools repeatedly to create users, as well as databases and tables in the following sections, we can avoid being prompted every time for the password for the postgres account by switching to this user using the su command shown below.

lisa su - postgres
postgres@lisa 

Once you have switched user to the postgres account we can create the database users using the commands below. As you can see you will be prompted for passwords for these accounts and the passwords you provide should be both different and hard to guess as they will be used to secure the database component of the DNS system.

postgres@lisa createuser -S -D -R -E -P dns_admin
Enter password for new role: 
Enter it again: 
CREATE ROLE 
postgres@lisa createuser -S -D -R -E -P dns_server
Enter password for new role: 
Enter it again: 
CREATE ROLE 

When we created the above users we passed a variety of flags to the createuser command. Briefly these flags indicate that the user should not be a super-user, should not be able to create new databases, should not be able to create new roles, that the account password should be encrypted and finally that a new password should be requested from the administrator.

Creating the database

Now that we have a user to whom we can assign ownership of the DNS database we can create a new database using the following command which will create a database called dns using the UTF8 character encoding owned by the dns_admin account.

postgres@lisa createdb -O dns_admin -E UNICODE dns
CREATE DATABASE 
Information:
As the DNS system now officially allows the use of the UFT8 character set we have created the database using this encoding. If you do not intend to ever use extended character sets it is perfectly fine to use the default database encoding of SQL_ASCII instead however you will, obviously, be limited to using the ASCII character set in your DNS records.
 

Creating the tables

Now that we have created a database we can create some tables to store the different types of information which the PowerDNS daemon needs to function. Unlike users and databases, which are most easily created and manipulated using command-line tools, tables are most easily created and manipulated using the psql application. To start the psql application and connect to the dns database as the dns_admin account issue the following command.

postgres@lisa psql dns dns_admin
Welcome to psql 8.2.6, the PostgreSQL interactive terminal. 
 
Type:  \copyright for distribution terms 
       \h for help with SQL commands 
       \? for help with psql commands 
       \g or terminate with semicolon to execute query 
       \q to quit 
 
dns=> 

When you run the psql application you should see output similar to that shown above. You may now enter SQL commands to the database server by entering them at the prompt. To create a new table called domains, which will be used to store the information relating to the domains which we manage, enter the following code. All the text shown in gray is produced by the psql application and is only reproduced so you can see that you are progressing correctly.

postgres=# CREATE TABLE domains ( 
postgres(#   id              SERIAL       PRIMARY KEY, 
postgres(#   name            VARCHAR(255) NOT NULL, 
postgres(#   master          VARCHAR(128) DEFAULT NULL, 
postgres(#   last_check      INT          DEFAULT NULL, 
postgres(#   type            VARCHAR(6)   NOT NULL, 
postgres(#   notified_serial INT          DEFAULT NULL,  
postgres(#   account         VARCHAR(40)  DEFAULT NULL 
postgres(# ); 
NOTICE:  CREATE TABLE will create implicit sequence "domains_id_seq" for serial column "domains.id" 
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "domains_pkey" for table "domains" 
CREATE TABLE 
postgres=#  

Now that we have created a new database table in which we can store information about our domains it makes sense to ensure that we will only allow a single record in this table to correspond with each domain name we manage. This can be accomplished by issuing the following command.

postgres=# CREATE UNIQUE INDEX name_index ON domains(name); 
CREATE INDEX 

Next we need to create a table in which we shall store the actual DNS records which the server shall serve in response to DNS queries it receives. We can do this using the command below.

postgres=# CREATE TABLE records ( 
postgres(#   id          SERIAL       PRIMARY KEY, 
postgres(#   domain_id   INT          NOT NULL, 
postgres(#   name        VARCHAR(255) NOT NULL, 
postgres(#   type        VARCHAR(6)   NOT NULL, 
postgres(#   content     VARCHAR(255) NOT NULL, 
postgres(#   ttl         INT          DEFAULT NULL, 
postgres(#   prio        INT          DEFAULT NULL, 
postgres(#   change_date INT          NOT NULL, 
postgres(#   CONSTRAINT domain_exists 
postgres(#     FOREIGN KEY(domain_id) REFERENCES domains(id) 
postgres(#   ON DELETE CASCADE 
postgres(# ); 
NOTICE:  CREATE TABLE will create implicit sequence "records_id_seq" for serial column "records.id" 
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "records_pkey" for table "records" 
CREATE TABLE 

As this table will be very heavily used, and in some cases the DNS server will need to query the table for records which have a field containing a particular value, we should create some indexes to speed up this process by allowing the database engine to locate the rows we need without performing a table scan. This can be accomplished using the following commands.

postgres=# CREATE INDEX rec_name_index ON records(name); 
CREATE INDEX 
postgres=# CREATE INDEX nametype_index ON records(name,type); 
CREATE INDEX 
postgres=# CREATE INDEX domain_id ON records(domain_id); 
CREATE INDEX 

The final table we need to create is used by the PowerDNS daemon to perform replication as either the master or the slave. Enter the command shown below to create this table.

postgres=# CREATE TABLE supermasters ( 
postgres(#   ip         VARCHAR(25)  NOT NULL, 
postgres(#   nameserver VARCHAR(255) NOT NULL, 
postgres(#   account    VARCHAR(40)  DEFAULT NULL 
postgres(# ); 
CREATE TABLE 
Information:
If you are intending to deploy PowerDNS as a single server with no replication then this table can be omitted however this is not recommended as a single DNS server is an obvious single point of failure which would almost certainly lead to major service outages if it were so to do.
 

Setting permissions

postgres=# \dp 
          Access privileges for database "dns" 
 Schema |      Name      |   Type   | Access privileges 
--------+----------------+----------+------------------- 
 public | domains        | table    | {} 
 public | domains_id_seq | sequence | {} 
 public | records        | table    | {} 
 public | records_id_seq | sequence | {} 
 public | supermasters   | table    | {} 
(5 rows) 
postgres=# GRANT ALL ON domains, domains_id_seq, records, records_id_seq, supermasters TO dns_admin; 
GRANT 
postgres=# GRANT SELECT ON domains, records, supermasters TO dns_server; 
GRANT 
postgres=# \dp 
          Access privileges for database "dns" 
 Schema |      Name      |   Type   |                  Access privileges 
--------+----------------+----------+----------------------------------------------------- 
 public | domains        | table    | {dns_admin=arwdxt/dns_admin,dns_server=r/dns_admin} 
 public | domains_id_seq | sequence | {dns_admin=rwU/dns_admin} 
 public | records        | table    | {dns_admin=arwdxt/dns_admin,dns_server=r/dns_admin} 
 public | records_id_seq | sequence | {dns_admin=rwU/dns_admin} 
 public | supermasters   | table    | {dns_admin=arwdxt/dns_admin,dns_server=r/dns_admin} 
(5 rows) 

Database connection

Now that we have created and configured a PostgreSQL database to store our DNS records we can configure the PowerDNS daemon to load and launch the gpgsql back-end module which will enable us to connect to this database. The first part of this process requires that we create a section in the PowerDNS configuration file, located at /etc/powerdns/pdns.conf, which instructs the daemon which back-end modules to load and the order in which they should be launched.

/etc/powerdns/pdns.conf
# Back-end settings
load-modules=gpgsql
launch=gpgsql

For each back-end module which we will be using to answer authoritative DNS queries we also need to create a section in the configuration file with any parameters that it will need to function. In the case of the gpgsql module these are simply the host address of the database server, the name of the database to connect to and the user name and password with which this connection should be authenticated. In the example below we are connecting to the dns database, which we created in the previous chapter, located on host 192.168.0.6 using the user name and password dns_server. These values will obviously need to be changed so that they match those of your particular installation.

/etc/powerdns/pdns.conf
# PostgreSQL back-end settings
gpgsql-host=192.168.0.6
gpgsql-dbname=dns
gpgsql-user=dns_server
gpgsql-password=dns_server
Warning:
It is very tempting here to specify a host name for the database server so that we can easily move it around on our network and in most cases this would be a very sensible idea however as this server will be used for name resolution this is not possible unless an entry is also added to the /etc/hosts file.