Python – Manage PowerDNS Zones/Records

Python – Manage PowerDNS Zones/Records


While working on another PowerDNS project I have started working on a Python script to manage Zones/Records. Being that there is not a ton of info out there around this I wanted to begin sharing what I have put together so far.

As far as provisioning PowerDNS I have spent a good bit of time over the past year working on an Ansible role which will do a ton of the PowerDNS deployment. You can also create Zones/Records using this role which is basically just a template that creates a shell script and uses curl to leverage the PowerDNS API. You can checkout this role here.

So as I mentioned this post is purely about using Python to manage PowerDNS. And with that being said below is the latest version of the script. Or you can head over to the GitHub repo here.

Usage examples
———————

To get an idea of some of the cli parameters that you can use do a quick show help…

./pdns.py --help                                                                                                                                                                                          
usage: pdns.py [-h] [--apikey APIKEY] [--apihost APIHOST] [--apiport APIPORT]
               [--content CONTENT] [--disabled {True,False}]
               [--masters MASTERS] [--name NAME] [--nameservers NAMESERVERS]
               [--priority PRIORITY]
               [--recordType {A,AAAA,CNAME,MX,NS,SOA,SRV}]
               [--setPTR {True,False}] [--ttl TTL] [--zone ZONE]
               [--zoneType {MASTER,NATIVE,SLAVE}]
               {add_records,add_zones,delete_zones,query_zones}

PDNS Controls...

positional arguments:
  {add_records,add_zones,delete_zones,query_zones}
                        Define action to take

optional arguments:
  -h, --help            show this help message and exit
  --apikey APIKEY       PDNS API Key
  --apihost APIHOST     PDNS API Host
  --apiport APIPORT     PDNS API Port
  --content CONTENT     DNS Record content
  --disabled {True,False}
                        Define if Record is disabled
  --masters MASTERS     DNS zone masters
  --name NAME           DNS record name
  --nameservers NAMESERVERS
                        DNS nameservers for zone
  --priority PRIORITY   Define priority
  --recordType {A,AAAA,CNAME,MX,NS,SOA,SRV}
                        DNS record type
  --setPTR {True,False}
                        Define if PTR record is created
  --ttl TTL             Define TTL
  --zone ZONE           DNS zone
  --zoneType {MASTER,NATIVE,SLAVE}
                        DNS Zone Type

Create a new Master Zone with info below:

  • Zone: dev.vagrant.local
  • ZoneType: MASTER
  • Master: 172.28.128.3
  • Nameservers:
    • 172.28.128.3
    • 172.28.128.4
    • 172.28.128.5

But before we start let’s do a quick query of the existing zones defined on our master.

./pdns.py query_zones --apihost 172.28.128.3

Results…

[
    {
        "kind": "Master",
        "name": "prod.vagrant.local",
        "dnssec": false,
        "url": "/servers/localhost/zones/prod.vagrant.local.",
        "account": "",
        "last_check": 0,
        "notified_serial": 2016062816,
        "masters": [],
        "serial": 2016062816,
        "id": "prod.vagrant.local."
    },
    {
        "kind": "Master",
        "name": "test.vagrant.local",
        "dnssec": false,
        "url": "/servers/localhost/zones/test.vagrant.local.",
        "account": "",
        "last_check": 0,
        "notified_serial": 2016062879,
        "masters": [],
        "serial": 2016062879,
        "id": "test.vagrant.local."
    },
    {
        "kind": "Master",
        "name": "vagrant.local",
        "dnssec": false,
        "url": "/servers/localhost/zones/vagrant.local.",
        "account": "",
        "last_check": 0,
        "notified_serial": 2016062806,
        "masters": [],
        "serial": 2016062806,
        "id": "vagrant.local."
    },
    {
        "kind": "Master",
        "name": "128.28.172.in-addr.arpa",
        "dnssec": false,
        "url": "/servers/localhost/zones/128.28.172.in-addr.arpa.",
        "account": "",
        "last_check": 0,
        "notified_serial": 2016062842,
        "masters": [],
        "serial": 2016062842,
        "id": "128.28.172.in-addr.arpa."
    }
]

Now let’s create our new master zone…

./pdns.py add_zones --apihost 172.28.128.3 --zone dev.vagrant.local --zoneType MASTER --nameservers 172.28.128.3,172.28.128.4,172.28.128.5

Results of the above …

{
    "kind": "Master",
    "name": "dev.vagrant.local",
    "dnssec": false,
    "url": "/servers/localhost/zones/dev.vagrant.local.",
    "account": "",
    "comments": [],
    "last_check": 0,
    "records": [
        {
            "disabled": false,
            "content": "172.28.128.3",
            "type": "NS",
            "name": "dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "172.28.128.4",
            "type": "NS",
            "name": "dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "172.28.128.5",
            "type": "NS",
            "name": "dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "node0.test.vagrant.local hostmaster.test.vagrant.local 2016062901 10800 3600 604800 3600",
            "type": "SOA",
            "name": "dev.vagrant.local",
            "ttl": 3600
        }
    ],
    "soa_edit": "",
    "notified_serial": 0,
    "masters": [],
    "soa_edit_api": "INCEPTION-INCREMENT",
    "serial": 2016062901,
    "id": "dev.vagrant.local."
}

Now let’s create the slave zones on our PowerDNS slaves with the info below:

  • Zone: dev.vagrant.local
  • ZoneType: SLAVE
  • Master: 172.28.128.3
  • Slaves:
    • 172.28.128.4
    • 172.28.128.5
./pdns.py add_zones --apihost 172.28.128.4 --zone dev.vagrant.local --zoneType SLAVE --masters 172.28.128.3

And the results after running the above ….

{
	"id": "dev.vagrant.local.",
	"url": "/servers/localhost/zones/dev.vagrant.local.",
	"name": "dev.vagrant.local",
	"kind": "Slave",
	"dnssec": false,
	"account": "",
	"masters": ["172.28.128.3"],
	"serial": 0,
	"notified_serial": 0,
	"last_check": 0,
	"soa_edit_api": "",
	"soa_edit": "",
	"records": [],
	"comments": []
}

And now repeat the above process on our 172.28.128.5 slave.

./pdns.py add_zones --apihost 172.28.128.5 --zone dev.vagrant.local --zoneType SLAVE --masters 172.28.128.3

And to validate that our new zones exist on our Master…

./pdns.py query_zones --apihost 172.28.128.3

Results show…

[
    {
        "kind": "Master",
        "name": "prod.vagrant.local",
        "dnssec": false,
        "url": "/servers/localhost/zones/prod.vagrant.local.",
        "account": "",
        "last_check": 0,
        "notified_serial": 2016062816,
        "masters": [],
        "serial": 2016062816,
        "id": "prod.vagrant.local."
    },
    {
        "kind": "Master",
        "name": "test.vagrant.local",
        "dnssec": false,
        "url": "/servers/localhost/zones/test.vagrant.local.",
        "account": "",
        "last_check": 0,
        "notified_serial": 2016062879,
        "masters": [],
        "serial": 2016062879,
        "id": "test.vagrant.local."
    },
    {
        "kind": "Master",
        "name": "vagrant.local",
        "dnssec": false,
        "url": "/servers/localhost/zones/vagrant.local.",
        "account": "",
        "last_check": 0,
        "notified_serial": 2016062806,
        "masters": [],
        "serial": 2016062806,
        "id": "vagrant.local."
    },
    {
        "kind": "Master",
        "name": "128.28.172.in-addr.arpa",
        "dnssec": false,
        "url": "/servers/localhost/zones/128.28.172.in-addr.arpa.",
        "account": "",
        "last_check": 0,
        "notified_serial": 2016062842,
        "masters": [],
        "serial": 2016062842,
        "id": "128.28.172.in-addr.arpa."
    },
    {
        "kind": "Master",
        "name": "dev.vagrant.local",
        "dnssec": false,
        "url": "/servers/localhost/zones/dev.vagrant.local.",
        "account": "",
        "last_check": 0,
        "notified_serial": 2016062901,
        "masters": [],
        "serial": 2016062901,
        "id": "dev.vagrant.local."
    }
]

And as you can see the new master zone for dev.vagrant.local now exists.

And if we do a quick check on one of our slave nodes…

./pdns.py query_zones --apihost 172.28.128.4

Results…

[
    {
        "kind": "Slave",
        "name": "prod.vagrant.local",
        "dnssec": false,
        "url": "/servers/localhost/zones/prod.vagrant.local.",
        "account": "",
        "last_check": 1467154120,
        "notified_serial": 0,
        "masters": [
            "172.28.128.3"
        ],
        "serial": 2016062816,
        "id": "prod.vagrant.local."
    },
    {
        "kind": "Slave",
        "name": "test.vagrant.local",
        "dnssec": false,
        "url": "/servers/localhost/zones/test.vagrant.local.",
        "account": "",
        "last_check": 1467152860,
        "notified_serial": 0,
        "masters": [
            "172.28.128.3"
        ],
        "serial": 2016062879,
        "id": "test.vagrant.local."
    },
    {
        "kind": "Slave",
        "name": "vagrant.local",
        "dnssec": false,
        "url": "/servers/localhost/zones/vagrant.local.",
        "account": "",
        "last_check": 1467154240,
        "notified_serial": 0,
        "masters": [
            "172.28.128.3"
        ],
        "serial": 2016062806,
        "id": "vagrant.local."
    },
    {
        "kind": "Slave",
        "name": "128.28.172.in-addr.arpa",
        "dnssec": false,
        "url": "/servers/localhost/zones/128.28.172.in-addr.arpa.",
        "account": "",
        "last_check": 1467154060,
        "notified_serial": 0,
        "masters": [
            "172.28.128.3"
        ],
        "serial": 2016062842,
        "id": "128.28.172.in-addr.arpa."
    },
    {
        "kind": "Slave",
        "name": "dev.vagrant.local",
        "dnssec": false,
        "url": "/servers/localhost/zones/dev.vagrant.local.",
        "account": "",
        "last_check": 1467159949,
        "notified_serial": 0,
        "masters": [
            "172.28.128.3"
        ],
        "serial": 2016062901,
        "id": "dev.vagrant.local."
    }
]

Now that our zones are setup we are ready to create a few records. So let’s create some records using the following info:

  • Zone: dev.vagrant.local
    • name: test01.dev.vagrant.local
      • recordType: A
      • content: 172.28.128.161
    • name: development.dev.vagrant.local
      • recordType: CNAME
      • content: test01.dev.vagrant.local

So let’s create our A record.

./pdns.py add_records --zone dev.vagrant.local --name test01 --content 172.28.128.161 --recordType A

Results…

{
    "kind": "Master",
    "name": "dev.vagrant.local",
    "dnssec": false,
    "url": "/servers/localhost/zones/dev.vagrant.local.",
    "account": "",
    "comments": [],
    "last_check": 0,
    "records": [
        {
            "disabled": false,
            "content": "172.28.128.3",
            "type": "NS",
            "name": "dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "172.28.128.4",
            "type": "NS",
            "name": "dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "172.28.128.5",
            "type": "NS",
            "name": "dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "node0.test.vagrant.local hostmaster.test.vagrant.local 2016062902 10800 3600 604800 3600",
            "type": "SOA",
            "name": "dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "172.28.128.161",
            "type": "A",
            "name": "test01.dev.vagrant.local",
            "ttl": 3600
        }
    ],
    "soa_edit": "",
    "notified_serial": 2016062901,
    "masters": [],
    "soa_edit_api": "INCEPTION-INCREMENT",
    "serial": 2016062902,
    "id": "dev.vagrant.local."
}

And now we can create our CNAME record…

./pdns.py add_records --zone dev.vagrant.local --name development --content test01.dev.vagrant.local --recordType CNAME

Results…

{
    "kind": "Master",
    "name": "dev.vagrant.local",
    "dnssec": false,
    "url": "/servers/localhost/zones/dev.vagrant.local.",
    "account": "",
    "comments": [],
    "last_check": 0,
    "records": [
        {
            "disabled": false,
            "content": "172.28.128.3",
            "type": "NS",
            "name": "dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "172.28.128.4",
            "type": "NS",
            "name": "dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "172.28.128.5",
            "type": "NS",
            "name": "dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "node0.test.vagrant.local hostmaster.test.vagrant.local 2016062903 10800 3600 604800 3600",
            "type": "SOA",
            "name": "dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "test01.dev.vagrant.local",
            "type": "CNAME",
            "name": "development.dev.vagrant.local",
            "ttl": 3600
        },
        {
            "disabled": false,
            "content": "172.28.128.161",
            "type": "A",
            "name": "test01.dev.vagrant.local",
            "ttl": 3600
        }
    ],
    "soa_edit": "",
    "notified_serial": 2016062902,
    "masters": [],
    "soa_edit_api": "INCEPTION-INCREMENT",
    "serial": 2016062903,
    "id": "dev.vagrant.local."
}

So there you have it. So far this seems to be working quite nice. This is obviously no where near complete so stay tuned on that. I will also be following up another post soon on how to leverage my Ansible role mentioned above to do these same tasks at a larger scale.

Enjoy!

About Larry Smith Jr.

vExpert 2013-2016 | Old-School coder coming back around to my roots #DevOPS and #automation | #Ansible junky!

Leave a Reply

Your email address will not be published. Required fields are marked *

*