We are successfully using PowerDNS as an in-line DNSSEC signer between a BIND master and a number of slave servers. When a zone on the BIND master is updated, in our case via RFC 2136 DDNS updates, the SOA serial number is incremented automatically and BIND sends out a DNS NOTIFY to the PowerDNS “slave”. PowerDNS then transfers the zone via an AXFR and stores it in its back-end database. So far nothing particularly exciting. What I wanted was for the zone transfer to be “intercepted”. I wanted to be able to change or add a resource record on the fly, and I’ll tell you why, in a moment. I spoke to Bert Hubert and proposed a solution involving a dynamically loadable library containing a C function to perform such on-the-fly modifications. Bert didn’t like the idea of a shared object messing with PowerDNS and suggested the functionality be implemented in Lua instead to which I agreed. (You may recall I wrote about the Lua functionality of PowerDNS Recursor in Appendix H of my book.) A few days passed, and since this morning, build 2065 to be precise, AXFR filtering in PowerDNS with a Lua script is implemented. A beaut!
What does this do? PowerDNS can invoke a Lua script on an incoming AXFR zone
transfer. The user-defined Lua function
axfrfilter within your script is
invoked for each resource record read during the transfer, and the outcome of
the function defines what PowerDNS does with the resulting records. More
precisely, your function defines which records, with what content are stored
by PowerDNS in the back-end database at the end of the zone transfer.
What you can accomplish with this Lua interception script:
- Ensure consistent values on SOA
- Change incoming SOA serial number to a YYYYMMDDnn format
- Ensure consistent NS RRset
- Timestamp the zone transfer with a TXT record
- Add one or more records to a zone
To enable a Lua script for a particular slave zone, determine the
for the zone from the
domains table, and add a row to the
table for the domain. Supposing the domain we want has an
id of 3, the
following SQL statement will enable the Lua script
my.lua for that domain:
The Lua script must both exist and be syntactically correct or the server logs an error. PowerDNS continues to try loading the script until it succeeds; in other words the AXFR is attempted again and again until the error-condition is cleared. Your script is reloaded for every incoming AXFR; you don’t have to restart PowerDNS after modifying your Lua script.
Your Lua functions have access to the query codes through a pre-defined Lua
pdns. For example if you want to check for a CNAME record you
can either compare
qtype to the numeric constant 5 or the value
– they are equivalent.
If your function decides to handle a resource record it must return a result code of 0 together with a Lua table containing one or more replacement records to be stored in the back-end database. If, on the other hand, your function decides not to modify a record, it must return -1 and an empty table indicating that PowerDNS should handle the incoming record as normal.
Consider the following simple example:
Upon an incoming AXFR, PowerDNS calls our
axfrfilter function for each
record. All HINFO records are replaced by a TXT record with a TTL of 99
seconds and the specified string. TXT Records with names starting with
_tstamp. get their value (rdata) set to the current time stamp. All other
records are not handled by the script which means PowerDNS simply copies them
from the zone transfer to the back-end database.
Individual records can also be deleted from the transfer; a use case I may elaborate on at a later time is mechanically updating a “counter” in a zone to force an SOA serial update and transfer, without wanting that particular counter record to be seen on the slaves. Do note that messing with a zone transfer in this way is not necessarily for the faint of heart; it can break all sorts of things for you, and it may even skin your cat. I’ve warned you, and I’d be interested in ideas you have for the Lua AXFR-filter-functionality in PowerDNS.