Automating DNS server configuration and Records updating with Ansible

Meher Askri
9 min readAug 2, 2024

--

Hello folks and welcome again ,

Today let’s explore one of the fundamental services in the entire IT field , one of the base that hold our Internet world, Everyone always has problem with it . Guess what ? “ It’s not DNS , There is no way it’s DNS , It was DNS 🤣 “.

Yeah, that’s actually a famous proverb used in the the tech community , the first service to blame, no one like it 😄 .

But hey , we are here to make things fun and simple not boring and complex . So , let’s start as always with a nice brief overview :

The story goes long time ago before I was even born I guess , when the internet start growing and people had difficulties remembering all those IPV4 addresses , that’s where DNS emerge and you know the rest of the story.

So, DNS in simple term is a translator of FQDN (fully qualified domain name ) to IPV4 and vice-versa ( we’ll dive deeper into records later ) .

When it comes to the main implementations of DNS , there are actually two :

The first one is what we call an internal DNS server ( also know as corporate DNS server which every organization has, especially when we talk about data center environment where the real magic happens , unlike the cloud ) . And the second one is what we call an internet facing DNS server which is a public DNS that answers queries coming from the internet .

I’m not going to go into the Hierarchy of DNS , but it’s important to know that when you buy a domain from a registrar like GoDaddy … , you typically buy an SLD ( second level domain) and they manage the registration process with the appropriate domain registry that controls the TLD ( top level domain ) such as ICANN ( this is actually beyond the scope of this article , I just wanted to mention how domain registration works) .

Now when it comes to the use cases of DNS , there are several options , we can use it for caching only purpose or we can configure it as a forwarder to another DNS or we can use it as a secondary DNS ( slave) for redundancy and load balancing … depends on the scenario .

In this demo , we will configure our server as an authoritative DNS server , which means our server will answers DNS queries based on the records in his zone . In simple terms he will act as the ultimate source of truth 😄 .

For that , we will use BIND the famous DNS service ( often enterprise use windows DNS server )and to make things more exciting , we will add the records of our clients machines dynamically , all of that with Ansible . isn’t that cool ?

So , let’s jump to our terminal and start by creating our project directory and setting up our Ansible config file and the inventory ( I’m going to use three servers for this demo , the number here is not that important , scale it in the inventory as needed ) :

Then , let’s create our config file and our inventory :

If you wonder about the sed command , I just made a typo in the word privilege, so I had to correct it ,

Again, for the inventory file , I made a typo , so I had to remove this empty line .

Okay, now let’s start writing our main playbook :

The first thing I need to do is give my main playbook a nice name “BIND project” for example , then I need to specify in which host is going to be executed ( in my case DNS_SERVER is green1 ) , then I need to define the collections because I don’t want to type this long modules name each time ( which is a new feature since Ansible 2.10 version and I’m using the latest version 2.16 , it’s a more efficient way to manage Ansible content by putting modules into categories ) .

Next , let’s start by our first task which is installing the BIND package :

Then , let’s configure our BIND service . For this task , I’ll use a template ( because it’s a lot of writing here ) :

I know we haven’t created this template file yet , we will create it later .

Now in order for DNS need to work , we need to create zones .

But what are these zones ? they are simply the database of the DNS service because to resolve queries that comes from clients , BIND will consult these zones and provide the answers ( remember, we are configuring here an authoritative DNS , which means he will not forward any query to another DNS server ) .

So , The next two tasks will be to create a forward zone ( mapping FQDN to IP addr) and a reverse zone ( mapping IP addr to FQDN ) , we’ll talk about record types later :

As you can see , the name of the reverse zone should correspond to the reverse of the subnet ( 192.168.100.0/24) , we’ll create all these templates later .

Lastly , we need to create a task that will start the BIND service and of course without forgetting to open the DNS port in the firewall ( In RedHat based distro we use the firewalld service to manage firewall configurations) :

Notice that I added a little handler ( “myhandy” ) at the end to reload the firewalld service after the change .

Okay , Next let’s move on to our templates and start creating the config file template for the BIND service :

As you can see here , the first thing to notice is that BIND is written in C style ( // is like a comment # in Unix style ) , I won’t go through all the lines here , I’ll focus on the important ones :

Firstly, I’ve defined an ACL named “trusted” which includes both localhost and the IP addr within the subnet “192.168.100.0/24” . Then, I’ve set BIND to listen on port 53 (both on localhost and on my interface IP address 192.168.100.204 ) . The “allow-query” restricts BIND to accept queries only from the specified ACL and lastly , I’ve disabled recursion by setting it to “no” . This is because as I mentioned earlier, this DNS server operates as an Internal DNS (Top Level) and doesn’t rely on upstream DNS servers for resolution ( for the purpose of keeping things simple I will not go into DNSSEC feature and singing zones with keys ) .

We’re not done yet , let’s continue by declaring the forward and reverse zones before creating their template files :

Again , I just wanted to keep things simple with a little bit of security to reduce the risk of unauthorized updates by allowing BIND to accept updates only from my trusted ACL .

Next , let’s create our zones templates and let’s start by our forwarding zone :

And then our reverse zone :

Ok , there is a lot of here , so let me explain :

The “ TTL 86400 “ this means for how long BIND should cache information from this zone , I set it to 24hours .

The “ @ IN SOA green1.meher.tn. root.green1.meher.tn. “ , this is the Start of authority record (SOA) which include the primary name server for the zone , since we’re setting up an authoritative DNS server, the primary name server is the same as the server name , followed by the admin email address ( we’re just pointing to the local root mail for simplicity ) and finally we have various parameters including serial number ( which will be incremented each time the zone file is updated) . For the refresh time , retry time , expiry time , this is are important in case of secondary DNS server , I won’t dive into them ( If you’re interested , check the BIND documentation to learn more about them ) .

The “ @ IN NS green1.meher.tn. “ , This is the name server (NS) record which indicate that green1.meher.tn is the authoritative name server for both zones .

The last line is actually when you see the diff between the forward zone and the reverse zone :

For IPV4 we have the “A” record ( addr record ) which map domain names to IPV4 addr and for reverse zone , we have the “PTR” record ( pointer record ) which map IP addresses to domain names ( the opposite of A record ) .

Now for the second part , we want to automate the updating of these zones, which means that clients will send their “A” and “PTR” records to the BIND server ( Imagine we have 100 servers , writing 200 lines of records manually seems quite boring, doesn’t it ? ) .

Luckily, we have this nice nsupdate tool , so , in order to use it in a more automated way , I’ve created a bash script :

This is actually a simple script that will use the IP add of the client to update the A record and the last octet to update the PTR record . Finally , let’s create another playbook , that will execute the script on each client machine and of course without forgetting to restart BIND to apply the changes :

Okay , let’s run our first playbook and verify :

And as you can see our first playbook ran successfully ( apparently the port is already open that’s why there was no change ) .

So, let’s run the second playbook and check if everything is fine :

And as you can the second playbook ran successfully too ( One thing I forgot to mention is that we need to point to our DNS server IP addr in the network settings on client machines ) .

And that’s it , Let’s do a final test to make sure that bind resolve queries :

Et voila , as you can see , we added the records automatically and BIND works as expected .

Thank you for taking the time to read my article. Please consider liking and sharing if you found it helpful . Au revoir .

--

--

Meher Askri
Meher Askri

Written by Meher Askri

Linux System Engineer || RHCA

No responses yet