For those who don't know, Foreman can manage your DNS records. Built in there is support for Bind using nsupdate, Microsoft Active Directory using some Windows commands and libvirt using virsh. Previously I had written a smart-proxy compatible API for our DNS solution, but now smart-proxy provides a solution to write your own DNS provider plugin. This documents my journey and and how to use it.

Writing the plugin

Since d905c67 all DNS providers are plugins, which makes writing a new provider easy. Since we use PowerDNS at my current employer, I thought I'd give it a go even though my Ruby knowledge is limited. How hard can it be?

I started by cloning smart_proxy_dns_plugin_template and used the rename.rb script. Next I copied the from PR 48 (opened in December 2012) which I has been on my wishlist for a long time.

With a basic skeleton, I started rewriting a lot. Biggest improvements were rewriting the SQL layer to mysql2 and got rid of the SQL-injections. After the unit tests passed, I improved the Ruby code by asking Dominic to look it over.

Up till now, I hadn't actually ran the code and since all SQL-related code is mocked, there was no way to know if it actually did what it was supposed to do. Time for integration tests. After starting manually, I quickly started writing a short (python) script to verify it did what it had to. The choice for python came because of my familiarity with it, but even here I took the chance to learn pytest because I heard great things about it. Turns out they were right and I will be looking at rewriting some of my other projects to it. Note that I will gladly take patches to rewrite it to ruby.

The result can be found as theforeman/smart_proxy_dns_powerdns.

Running the code

Setting up PowerDNS

This will be a very basic config using PowerDNS 3.4 and MySQL. It's been tested on Fedora, but other distributions will be nearly identical. This will also create an example.com zone to test with.

DATABASE=powerdns
USERNAME=powerdns
PASSWORD=mypassword
ZONE=example.com

sudo dnf install pdns pdns-backend-mysql

sudo mysql <<EOF
CREATE DATABASE ${DATABASE};
CREATE USER '${USERNAME}'@'localhost' IDENTIFIED BY '${PASSWORD}';
GRANT ALL PRIVILEGES ON ${DATABASE}.* TO '${USERNAME}'@'localhost';
EOF
sudo mysql $DATABASE < /usr/share/doc/pdns-backend-mysql/schema.mysql.sql

sudo tee /etc/pdns/pdns.conf > /dev/null <<EOF
setuid=pdns
setgid=pdns
launch=gmysql
gmysql-dnssec=true
gmysql-host=localhost
gmysql-user=$USERNAME
gmysql-password=$PASSWORD
gmysql-dbname=$DATABASE
EOF

sudo systemctl start pdns
sudo systemctl enable pdns

sudo mysql <<EOF
INSERT INTO ${DATABASE}.domains (name, type) VALUES ('${ZONE}', 'master');
INSERT INTO ${DATABASE}.records (domain_id, name, type, content) VALUES (LAST_INSERT_ID(), '${ZONE}', 'SOA', 'ns1.${ZONE} hostmaster.${ZONE}. 0 3600 1800 1209600 3600');
EOF
sudo pdnssec rectify-zone $ZONE

Now you should have a running PowerDNS instance. Adjust your firewall where needed. You can verify it works using dig (from bind-utils):

dig @localhost SOA example.com

Running the smart-proxy

Currently this requires git versions, but should become part of 1.10. I hope that by then the installer will support DNS providers.

git clone https://github.com/theforeman/smart-proxy
cd smart-proxy
echo "gem 'smart_proxy_dns_powerdns', :github => 'theforeman/smart_proxy_dns_powerdns'" > bundler.d/dns_powerdns.rb
bundle install

cat > config/settings.yml <<EOF
---
:http_port: 8000
:log_file: STDOUT
EOF

cat > config/settings.d/dns.yml <<EOF
---
:enabled: true
:use_provider: dns_powerdns
EOF

cat > config/settings.d/dns_powerdns.yml <<EOF
---
:powerdns_mysql_hostname: 'localhost'
:powerdns_mysql_username: 'powerdns'
:powerdns_mysql_password: 'mypassword'
:powerdns_mysql_database: 'powerdns'
:powerdns_pdnssec: 'pdnssec'
EOF

bundle exec ./bin/smart-proxy

Note I used pdnssec as the command here. This assumes the user you're running under can read /etc/pdns/pdns.conf. You can also use sudo pdnssec if you can't.

Now you should be able to create a new record:

curl -d fqdn=host.example.com -d type=A -d value=192.168.1.1 http://localhost:8000/dns/

Verify it exists:

dig @localhost host.example.com

Congratulations, you now have a working system.

Conclusion

Even though I had little ruby experience, it wasn't hard to write the plugin. In the future it could be expanded to use the PowerDNS REST API so all databases should be supported. It should also be packaged and suppoted by the installer.