Running services online without domain is hard. More services you run, more DNS entries you need to manage. More services you run, more servers you need to manage. And when you manage several servers, it’s time to use some orchestration. But what about all those domains associated with those servers and services? Can’t that be also part of the orchestration? Somehow automated? Of-course it can. Let me tell you how am I handling it for my domains and servers.
Software stack
To be able to do that and much more, I run my own DNS master that I manage. This comes with great flexibility. In my early attempts to automate my DNS records, I used to use PowerDNS as it had all the records stored in the SQL database. That sounds cool, right? Databases are easy to query, replicate, modify, … Sounds much easier than parsing weird zone files, right? It turned out that it wasn’t the best choice, at least for me. I sometimes broke the database, sometimes connection to it. And even when it was working, changing the records in backend and not telling PowerDNS about it lead to various issues. In the end, I decided that I actually don’t need to read the zone files and modify them. It is much easier to keep all the relevant data somewhere else and just generate the zone files. Therefor PowerDNS lost its main appeal to me and I switched to Knot that is mainly known for being extremely fast. But my main reason all those years back was that I knew that there will be a great support for DNSSEC and other new and cool technologies. Since then my zone is signed well and I no longer have to have monitoring to check whether I broke my DNSSEC.
Other part of software stack is orchestration. I wrote about that a lot in my last post. Reasoning aside, as the title suggests, for orchestration I use SaltStack.
Knot
Let’s take a look at Knot configuration that enabled me to what I need. Basically I don’t need much. I need to generate new zone file from the master data somewhere else and when that happens, I need to increase serial, reload the zone and resign it. In reality it is even simpler. What I’m using is the following configuration:
serial-policy: "unixtime" zonefile-sync: -1 zonefile-load: "difference-no-serial" journal-content: all
Therefore I don’t need to change SOA record manually and Knot will use timestamp as a serial. And cool part about Knot is that DNSSEC is really easy and well integrated, so on zone reload, it get’s signed automatically. So in reality I just need to generate the zone file and reload Knot.
SalStack configuration
Where is the ultimate source of truth about my domains? Answer is easy - in SaltStack! I could just enter the data in there manually and pretend that I improved things by replacing obscure zone file format with not much better yaml. But that was not my goal. I don’t want to keep tract of my servers manually. Let them keep track of them themselves!
Salt Mine
One cool feature that SaltStack has thanks to it’s architecture is that minions (orchestrated servers) can share information about themselves with the master and master can use this data to configure other minions.
What interesting data can we gather? Well, for DNS, we need hostnames and IP addresses, right? That is actually one of the examples mentioned in the documentation. With a little change, we can collect public IP addresses of all our servers using the following configuration in the SaltStack pillar:
public_ip4:
- mine_function: network.ip_addrs
- type: 'public'
public_ip6:
- mine_function: network.ip_addrs6
- type: 'public'
Once we have them, we can start using them to setup Knot. For that, I’m using knot formula. Formula takes care of reloading the zone whenever its zone file is changed. That simplifies what I need to do. Final basic configuration (including firewall setup thanks to firewalld formula) will look something like this:
#Enable and configure knot
knot:
server:
enabled: True
# Global configuration
policy:
default:
nsec3: true
template:
default:
dnssec-signing: on
dnssec-policy: default
# Magic to use unixtime as SOA serial
serial-policy: unixtime
zonefile-sync: -1
zonefile-load: difference-no-serial
journal-content: all
# Zone configuration
zone:
example.com:
soa:
master: ns1.nic.cz
email: michal.hrusecky.nic.cz
records:
- name: '@'
type: NS
content: "ns1.nic.cz"
{# We are getting A records from public_ip4 records and AAAA from public_ip6 ones #}
{%- for fun_tpe, tpe in ( ('public_ip4', 'A'), ('public_ip6', 'AAAA') ) %}
{# Get the actual server addresses #}
{%- for srv, addrs in salt.saltutil.runner('mine.get', tgt='*', fun=fun_tpe, tgt_type='compound').items() %}
{# Name of the server is the first part of the minion ID #}
- name: '{{ srv.split('.', 1)[0] }}'
content: {{ addrs[0]|yaml_dquote }}
type: {{ tpe|yaml_dquote }}
{%- endif %}
{%- endfor %}
{%- endfor %}
# Setup firewall as well
firewalld:
zones:
public:
dns_services:
- dns
As can bee seen, I use the Knot configuration mentioned before. Then Jinja
comes to do its magic. By calling mine.get
with fun
set to either
public_ip4
or public_ip6
I get minion ids and their IP addresses collected
in the mine. I use the minion id
to construct the hostname. Namely first
part of the id
. As I put a short hostname into the zonefile without the
ending dot, fully qualified hostname is constructed by appending the domain.
This way all my minions with public IP addresses have a record in my domain.
They can even have the same name and IPs in multiple domains. And whenever I
add a new server, it will end up in the domain automatically. At the same time,
whenever IP address of any device changes, it’s domain record changes as well.
So far it is quite a simple setup and useful if servers come and go or change their IPs. But I hope that it demonstrates the endless possibilities that managing domain in SaltStack and that it was at least a bit interesting and inspirational. I’ll expand on this idea and show you what other interesting data can be easily gathered automatically and put into the zone in the following post.