Chapter 7

Install and configure powerdns to serve HTTP API

MAAS is full of stateful services, and this is a very big problem. Ideally, MAAS is composed by multiple stateless and statefull services and they are not boundled together.

One of the biggest and most annoying, complicated and overlooked problem is DNS. Currently MAAS uses BIND, which is a very good piece of software of course, but the way MAAS is configuring it is very messy, buggy and hard to maintain.

My wish is to move out DNS to powerdns, and have MAAS only creating the records through HTTP API.

So, here are some notes to setup powerdns and configure it to serve HTTP API. This is just the first step of the POC journey.

Preerequisites

Deploy a LXD container with Ubuntu 24.04 and install the following packages:

sudo apt-get update 
sudo apt-get install -y pdns-server sqlite3 pdns-backend-sqlite3

Createe a database for powerdns

sudo mkdir /var/lib/powerdns
sudo sqlite3 /var/lib/powerdns/pdns.sqlite3 < /usr/share/doc/pdns-backend-sqlite3/schema.sqlite3.sql
sudo chown -R pdns:pdns /var/lib/powerdns

Configure powerdns

Edit the configuration /etc/powerdns/pdns.conf

launch=gsqlite3
gsqlite3-database=/var/lib/powerdns/pdns.sqlite3

local-port=5300

api=yes
api-key=changeme

webserver=no
webserver-address=0.0.0.0
webserver-allow-from=0.0.0.0/0
webserver-port=8081

Test!

Check the zones

curl -X GET -H "X-API-Key: changeme" http://10.0.1.16:8081/api/v1/servers/localhost/zones

Create a new zone

curl -X POST \
  -H "X-API-Key: changeme" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "mytestzone.com.",
        "kind": "Native",
        "masters": [],
        "nameservers": ["ns1.mytestzone.com."],
        "soa_edit_api": "INCEPTION-INCREMENT"
      }' \
  http://10.0.1.16:8081/api/v1/servers/localhost/zones

Create a new record

curl -X PATCH \
  -H "X-API-Key: changeme" \
  -H "Content-Type: application/json" \
  -d '{
        "rrsets": [{
          "name": "pippo.mytestzone.com.",
          "type": "A",
          "ttl": 3600,
          "changetype": "REPLACE",
          "records": [{
            "content": "192.168.1.1",
            "disabled": false
          }]
        }]
      }' \
  http://10.0.1.16:8081/api/v1/servers/localhost/zones/mytestzone.com.

another one

curl -X PATCH \
  -H "X-API-Key: changeme" \
  -H "Content-Type: application/json" \
  -d '{
        "rrsets": [{
          "name": "fuffa.mytestzone.com.",
          "type": "A",
          "ttl": 3600,
          "changetype": "REPLACE",
          "records": [{
            "content": "192.168.1.2",
            "disabled": false
          }]
        }]
      }' \
  http://10.0.1.16:8081/api/v1/servers/localhost/zones/mytestzone.com.

and then check!

$ dig @10.0.1.16 -p 5300 pippo.mytestzone.com.

; <<>> DiG 9.18.30-0ubuntu0.24.04.2-Ubuntu <<>> @10.0.1.16 -p 5300 pippo.mytestzone.com.
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57829
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;pippo.mytestzone.com.		IN	A

;; ANSWER SECTION:
pippo.mytestzone.com.	3600	IN	A	192.168.1.1

;; Query time: 0 msec
;; SERVER: 10.0.1.16#5300(10.0.1.16) (UDP)
;; WHEN: Fri May 09 23:29:33 UTC 2025
;; MSG SIZE  rcvd: 65
$ dig @10.0.1.16 -p 5300 fuffa.mytestzone.com.

; <<>> DiG 9.18.30-0ubuntu0.24.04.2-Ubuntu <<>> @10.0.1.16 -p 5300 fuffa.mytestzone.com.
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29537
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;fuffa.mytestzone.com.		IN	A

;; ANSWER SECTION:
fuffa.mytestzone.com.	3600	IN	A	192.168.1.2

;; Query time: 0 msec
;; SERVER: 10.0.1.16#5300(10.0.1.16) (UDP)
;; WHEN: Fri May 09 23:29:18 UTC 2025
;; MSG SIZE  rcvd: 65