A DNS server with Batteries Included with PowerDNS, Docker and Python

Posted on Tue 06 September 2016 in Systems

If you have to frequently create, destroy and re-configure DNS zones for testing purposes you will agree that manually editing a Bind zone file is an annoying and error-prone task.

In order to make this job simpler I worked on this Docker container: the container instantiate a PowerDNS and provides an easy-to-use CLI script interacting with the REST API.

How to use:

Inspired by the illuminating @jessfraz post about containerizing everything, I added to my .dockerfunc some helper functions:

pdns() {
    local name=pdns
    if is_running $name; then
        bailout "Container $name is already running"
        return
    fi

    del_stopped $name

    docker run -it --name $name -e API_KEY=MySecretKey -e WEB_PORT=8081 -v ${PDNS_DB}:/data/pdns.db -p 8081:8081 -p 53:53 -p 53:53/udp pbertera/${name}
}

pdns.py(){
    relies_on pdns
    docker exec -it pdns pdns.py "$@"
}

NOTE: for the first run you will need to create an empty database file: touch ${PDNS_DB}

Then I can start the container:

hank-2:~ pietro$ pdns
pdns
Error: near line 3: table domains already exists
Error: near line 13: index name_index already exists
Error: near line 16: table records already exists
Error: near line 31: index rec_name_index already exists
Error: near line 32: index nametype_index already exists
Error: near line 33: index domain_id already exists
Error: near line 34: index orderindex already exists
Error: near line 37: table supermasters already exists
Error: near line 43: index ip_nameserver_pk already exists
Error: near line 46: table comments already exists
Error: near line 57: index comments_domain_id_index already exists
Error: near line 58: index comments_nametype_index already exists
Error: near line 59: index comments_order_idx already exists
Error: near line 62: table domainmetadata already exists
Error: near line 70: index domainmetaidindex already exists
Error: near line 73: table cryptokeys already exists
Error: near line 82: index domainidindex already exists
Error: near line 85: table tsigkeys already exists
Error: near line 92: index namealgoindex already exists
Imported schema structure
Sep 06 01:24:36 Reading random entropy from '/dev/urandom'
Sep 06 01:24:36 Loading '/usr/lib/x86_64-linux-gnu/pdns/libgsqlite3backend.so'
Sep 06 01:24:36 This is a standalone pdns
Sep 06 01:24:36 Listening on controlsocket in '/var/run/pdns.controlsocket'
Sep 06 01:24:36 UDP server bound to 0.0.0.0:53
Sep 06 01:24:36 Unable to enable timestamp reporting for socket
Sep 06 01:24:36 UDPv6 server bound to [::]:53
Sep 06 01:24:36 TCP server bound to 0.0.0.0:53
Sep 06 01:24:36 TCPv6 server bound to [::]:53
Sep 06 01:24:36 PowerDNS Authoritative Server 4.0.0-alpha2 (C) 2001-2016 PowerDNS.COM BV
Sep 06 01:24:36 Using 64-bits mode. Built using gcc 5.3.1 20160330.
Sep 06 01:24:36 PowerDNS comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it according to the terms of the GPL version 2.
Sep 06 01:24:36 Listening for HTTP requests on 0.0.0.0:8081
Sep 06 01:24:36 Creating backend connection for TCP
Sep 06 01:24:36 About to create 3 backend threads for UDP
Sep 06 01:24:36 Done launching threads, ready to distribute questions

Now PowerDNS is running and listening on the port 8081 for the REST API and the port 53 UDP and TCP for the DNS queries, the database is permanently stored into the SQLite DB file ${PDNS_DB}.

Using the command pdns.py you can manage the zones:

Creating a zone:

hank-2:~ pietro$ pdns.py --zone example.com. --zoneType MASTER --nameserver ns.example.com.  add_zone
2016-09-06 01:57:09,938 pdns         INFO     DNS Zone 'example.com.' Successfully Added...
hank-2:~ pietro$ pdns.py --zone example.com. --recordType A --name ns.example.com. --content 172.16.18.15 add_record
2016-09-06 01:58:04,316 pdns         INFO     DNS Record 'ns.example.com.' Successfully Added/Updated

Digging the zone:

hank-2:~ pietro$ dig @localhost example.com NS

; <<>> DiG 9.8.3-P1 <<>> @localhost example.com NS
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57709
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;example.com.           IN  NS

;; ANSWER SECTION:
example.com.        3600    IN  NS  ns.example.com.

;; Query time: 16 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Sep  6 03:59:18 2016
;; MSG SIZE  rcvd: 46

hank-2:~ pietro$ dig @localhost ns.example.com 

; <<>> DiG 9.8.3-P1 <<>> @localhost ns.example.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49682
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;ns.example.com.            IN  A

;; ANSWER SECTION:
ns.example.com.     3600    IN  A   172.16.18.15

;; Query time: 13 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Sep  6 03:59:58 2016
;; MSG SIZE  rcvd: 48

Credits

All the credits to Larry Smith Jr. for the original pdns.py script and Renan Gonçalves for the original PowerDNS+MySQL Docker container