DEV Community

Nick Schmidt
Nick Schmidt

Posted on • Originally published at blog.engyak.co on

Automate DNS Zone Generation and Deployment with Ansible and Netbox

In a previous post, I covered a method to automatically generate DNS zones from an embedded YAML list.

This wasn't the most useful on its own, only ensuring that forward and reverse DNS entries match each other (you'll be shocked by how many places it isn't!) - and we need a good way to simplify DNS administration with tooling less expensive that, say, Infoblox.

This isn't to say that Infoblox is bad, but a fully loaded Infoblox license is a little pricy for home labs.

The Pattern

First, let's illustrate a potential design:

[

Solution Diagram

](pattern.svg)

The Code

In order to do this, we're going to need to find a good way to pull pre-filtered data for ansible to work with, and Netbox has a GraphQL API (/graphql/) that's perfect for this:

 1{
 2 ip_address_list(filters: {dns_name: {i_contains: "example.net"}, family: 4}) {
 3 dns_name
 4 address
 5 }
 6}
 7{
 8 ip_address_list(filters: {dns_name: {i_contains: "example.net"}, family: 6}) {
 9 dns_name
10 address
11 }
12}

Enter fullscreen mode Exit fullscreen mode

This will give us a separate sheet for IPv4 and IPv6 addresses attached to a given zone - and we can assemble it without any postprocessing in Ansible.

Netbox's GraphQL sandbox produces the following data output:

 1{
 2 "data": {
 3 "ip_address_list": [
 4 {
 5 "dns_name": "ns.example.net",
 6 "address": "1.1.1.1/32"
 7 }
 8 ]
 9 }
10}

Enter fullscreen mode Exit fullscreen mode

Now, this is received via a graphical interface, which means we can't consume it programmatically. In order to do that, we'll need to package the GraphQL payload in JSON. Here's an Ansible task that does just that:

 1 - name: "Try Fetching `lab.engyak.net` IPv4 GraphQL!"
 2 ansible.builtin.uri:
 3 url: "https://netbox/graphql/"
 4 method: POST
 5 body:
 6 query: "query { ip_address_list(filters: {dns_name: {i_contains: \"example.com\"}, family: 4}) { dns_name address }}"
 7 body_format: "json"
 8 headers:
 9 Authorization: "Token {{ lookup('ansible.builtin.env', 'NETBOX_TOKEN') }}"
10 Content-Type: "application/json"
11 Accept: "application/json"
12 validate_certs: false
13 register: result_example_net_v4

Enter fullscreen mode Exit fullscreen mode

There aren't any pynetbox based modules that automate this into Ansible, so here we're using the ansible.builtin.uri module (also known as the Jack of All Trades module) to pull JSON data. It also uses the environment variable NETBOX_TOKEN, which must be exposed by secrets management / CI processes.

In this case, I'm pulling IPv4 and IPv6 records separately. Jinja doesn't know the difference between types of record, so I cheat on postprocessing and let GraphQL do all the heavy lifting. IPv6 is the same, but with family: 6/result_example_net_v6.

The next step is to build Jinja templates to define the zonefiles. I created them in a previous post, but will include all of them in a Gist at the end of this post. They need to be modified to process output from GraphQL, because we don't control any of the field names with it.

The Jinja templates used in this example are unique to ansible - the custom filter ansible.utils.ipaddr is amazing, converting Netbox's {{ address }}/{{ cidr }} notation is compact and efficient, but it doesn't work as an A record target. Invocations like |ansible.utils.ipaddr('address') or |ansible.utils.ipaddr('revdns') are particularly useful here.

Finally, it's good to test the resulting zonefiles for sanity. It's included in the Gist.

Retrospective

Netbox's GraphQL API is a really effective tool for aggregating pre-filtered data and driving automation processes. I was quite impressed that I could just ask an API endpoint for this nice and tidy report, already pre-formatted for me!

Lack of field and format control is an issue with GraphQL (you're stuck with whatever data structure the application architect has in store for you) - but Ansible and Jinja2 empower you to present the back-end data in any front-end manner you prefer (in my case, as DNS data loaded into an Unbound instance).

Nearly any business reporting process can be driven from Netbox in this fashion, as long as the resulting format can be Jinjafied. Here are some ideas on how this can be used further:

  • Report on Circuits per Region
  • Report on IT-Managed assets in a given Site
  • Report on how many Sites have IPv6 coverage

The Gist

As promised, here's the raw code I created to automate DNS zonefile management from Netbox:

@import url('https://cdn.rawgit.com/lonekorean/gist-syntax-themes/d49b91b3/stylesheets/idle-fingers.css');

@import url('https://fonts.googleapis.com/css?family=Open+Sans');
body {
  font: 16px 'Open Sans', sans-serif;
}
body .gist .gist-file {
  border-color: #555 #555 #444
}
body .gist .gist-data {
  border-color: #555
}
body .gist .gist-meta {
  color: #ffffff;
  background: #373737; 
}
body .gist .gist-meta a {
  color: #ffffff
}
Enter fullscreen mode Exit fullscreen mode

GitHub Link

Top comments (0)