Skip to main content

You are here

BIND

Updating BIND DNS records using Ansible

This is a follow up to the post. Configure BIND to support DDNS updates
Now, that I'm able to dynamically update DNS records, this is where Ansible comes in. Ansible is hands down my favorite orchestration/automation tool. So I choose to use it to update my local DNS records going forward.

I'll be using the community.general.nsupdate module.

I structured my records on my nameserver's corresponding Ansible group_vars using the following structured:

all_dns_records:
  - zone: DNS-NAME
    records:
      - record: (@ for $ORIGIN or normal record name )
        ttl: TTL-VALUE
        state: (present or absent)
        type: DNS-TYPE
        value: VALUE-OF-DNS-RECORD

Example

---
all_dns_records:
  - zone: "rubyninja.org"
    records:
      - record: "@"
        ttl: "10800"
        state: "present"
        type: "A"
        value: "192.168.1.63"
      - record: "shit"
        ttl: "10800"
        state: "present"
        type: "A"
        value: "192.168.1.64"
  - zone: "alpha.org"
    records:
      - record: "@"
        ttl: "10800"
        state: "present"
        type: "A"
        value: "192.168.1.63"
      - record: "test"
        ttl: "10800"
        state: "present"
        type: "A"
        value: "192.168.1.64"
[...]

Deployment Ansible playbook:

---
- hosts: ns1.rubyninja.org
  pre_tasks:
    - name: Get algorithm from vault
      ansible.builtin.set_fact:
        vault_algorithm: "{{ lookup('community.general.hashi_vault', 'secret/systems/bind:algorithm') }}"
      delegate_to: localhost

    - name: Get rndckey from vault
      ansible.builtin.set_fact:
        vault_rndckey: "{{ lookup('community.general.hashi_vault', 'secret/systems/bind:rndckey') }}"
      delegate_to: localhost

  tasks:
    - name: Sync $ORIGIN records"
      community.general.nsupdate:
        key_name: "rndckey"
        key_secret: "{{ vault_rndckey }}"
        key_algorithm: "{{ vault_algorithm }}"
        server: "ns1.rubyninja.org"
        port: "53"
        protocol: "tcp"
        ttl: "{{ item.1.ttl }}"
        record: "{{ item.0.zone }}."
        state: "{{ item.1.state }}"
        type: "{{ item.1.type }}"
        value: "{{ item.1.value }}"
      when: item.1.record == "@"
      with_subelements:
        - "{{ all_dns_records }}"
        - records
      notify: Sync zone files
      delegate_to: localhost

    - name: Sync DNS records"
      community.general.nsupdate:
        key_name: "rndckey"
        key_secret: "{{ vault_rndckey }}"
        key_algorithm: "{{ vault_algorithm }}"
        server: "ns1.rubyninja.org"
        port: "53"
        protocol: "tcp"
        zone: "{{ item.0.zone }}"
        ttl: "{{ item.1.ttl }}"
        record: "{{ item.1.record }}"
        state: "{{ item.1.state }}"
        type: "{{ item.1.type }}"
        value: "{{ item.1.value }}"
      when: item.1.record != "@"
      with_subelements:
        - "{{ all_dns_records }}"
        - records
      notify: Sync zone files
      delegate_to: localhost

  post_tasks:
    - name: Check master config
      command: named-checkconf /var/named/chroot/etc/named.conf
      delegate_to: ns1.rubyninja.org
      changed_when: false

    - name: Check zone config
      command: "named-checkzone {{ item }} /var/named/chroot/etc/zones/db.{{ item }}"
      with_items:
        - "{{ all_dns_records | map(attribute='zone') | list }}"
      delegate_to: ns1.rubyninja.org
      changed_when: false

  handlers:
    - name: Sync zone files
      command: rndc -c /var/named/chroot/etc/rndc.conf sync -clean
      delegate_to: ns1.rubyninja.org

My DNS deployment a playbook breakdown:
1). Grabs the Dynamic DNS update keys from HashiCorp Vault
2). Syncs all of @ $ORIGIN records for all zone.
3). Syncs all of the records.
4). For good measure, but necessary: Checks named.conf file
5). For good measure, but necessary: Checks each individual zone file
6). Force dynamic changes to be applied to disk.

Given that in my environment I have roughly a couple of dozen DNS records, the structured for DNS records works in my environment. Thus said, my group_vars file with all my DNS records is almost 600 lines long. The playbook executing run takes around 1-2 minutes to complete. If I were to be in an environment where I had thousands of DNS records, the approached that I described here might not be the most efficient.

Awesome Applications: 

Configure BIND to support DDNS updates

I use BIND on my home network for both authoritative and forwarding name resolution. In it I have a few private DNS zones I use for testing and for my homelab setup. The main primary dns zone I use for my homelab is rubyninja.org. Previously when I wanted to make DNS changes, I just ssh into my master nameserver, I update the zone file, and reload. While this worked great for me these last 10+ years that I've running BIND. It obviously doesn't follow good DevOps practices.

If you're in a normal BIND environment where you already using rndc, to administer your server, then you're almost quite there.

BIND Configuration
1). Secret Key Transaction Authentication (TSIG) key. (Where ddnskey. is the name of the key)
Approach A: Using dnssec-keygen

mkdir ddns
dnssec-keygen -a hmac-md5 -b 512 -n HOST -r /dev/urandom ddnskey.

The above command will create two Kddnskey.* files. One ending *.private while the other *.key.

Approach A: Using tsig-keygen

tsig-keygen -a hmac-md5 ddnskey.

Either approach is fine, for this example I opted to use dnssec-keygen since I'll be using the created key file to test a dynamic dns update.

2). Update named.conf file.
Include the newly created key configuration:

key "ddnskey." {
        algorithm      "hmac-md5";
        secret          "PRIVATEKEYHERE==";
};

Now, it's just a matter of setting the allow-update configuration to allow updates using our newly created key.

zone "rubyninja.org." IN {
        type master;
        file "etc/zones/db.rubyninja.org";
        allow-transfer { trusted-servers; };
        allow-query { any; };
        allow-update { key rndckey; };
};

zone "k8s.rubyninja.org." IN {
        type master;
        file "etc/zones/db.k8s.rubyninja.org";
        allow-transfer { trusted-servers; };
        allow-query { any; };
        allow-update { key "ddnskey."; };
};

It is worth indicating that BIND also includes the update-policy option for more finer-grained options for the type of updates that we want to allow.

3). Testing
Using the tool dnsupdate (part of bind-utils) we can easily test doing an update to verify the setup works as expected.

$ nsupdate -d -k Kddnskey.+157+06602.key
Creating key...
> server ns1.rubyninja.org
> zone k8s.rubyninja.org.
> update add tonytest.k8s.rubyninja.org. 3600 A 192.168.1.25
> send

Resources:
https://docs.netgate.com/pfsense/en/latest/recipes/bind-rfc2136.html
https://www.thegeekdiary.com/how-to-use-rndc-command-command-line-admini...

Awesome Applications: 

BIND - Slave zone transfer stopped working

I was surprised to see that a typo fucked up caused all slave transfers to shit themselves. I came across a situation where a new slave zone was specified to a non-existing location in the file system and that caused the rest of the slave zones to get permission denied errors when trying to update.

Logs

Jul 12 03:23:27 ns2 named[1184]: dumping master file: etc/zones/tmp-Zbk9acg9uv: open: permission denied
Jul 12 03:27:50 ns2 named[1184]: dumping master file: etc/zenos/tmp-4yxBXaUMTq: open: file not found
Jul 12 03:29:46 ns2 named[1184]: dumping master file: etc/zones/tmp-KPqzHa9ev9: open: permission denied
Jul 12 03:38:02 ns2 named[1184]: dumping master file: etc/zones/tmp-kuhtUPjcAi: open: permission denied

Awesome Applications: 

Reverse DNS Slave Setup

So a few months back, I enabled reverse DNS on my home BIND server (https://www.rubysecurity.org/bind_rerverse-dns). One thing that I forgot to implement was the additional slave DNS reverse setup. Like many things in BIND, the slave revserse DNS setup was dead simple.

It's simply just a matter of adding the following entry to the slave's named.conf with the updated master's DNS IP specified in the masters directive and reload BIND.

zone "1.168.192.in-addr.arpa" IN {
        type slave;
        file "etc/zones/db.192.168.1.255.bak";
        allow-query { any; };
        masters { MasterDNSIP; };
};

Awesome Applications: 

Reverse DNS in BIND 9.8

I use BIND on my home network, and giving the bast amount of virtual machines I have online, I've always find myself wanting to easily look up which machine is using which IP address without having to ssh into the actual vm or check the zone file. Configuring reverse DNS in BIND 9.8 is actually a dead simple process.
First, a separate zone file for PTR records needs to be created, I named mine db.192.168.1.255.
Note: since my network address space is 192.168.1, the actual PTR record will be the network address backgrounds followed by in-addr.arpa..

$TTL 3h
@       IN SOA  ns1.rubyninja.org. dnsadmin.rubysecurity.org. (
                                        2013090701      ; serial
                                        3h              ; refresh after 3 hours
                                        1h              ; retry after 1 hour
                                        1w              ; expire after 1 week
                                        1H )            ; negative caching TTL of 1 hour

              IN      NS      ns1.rubyninja.org.
              IN      NS      ns2.rubyninja.org.

14.1.168.192.in-addr.arpa.      IN      PTR     email.rubyninja.org.

Lastly, the zone entry needs to be added to the master named.conf file. Mine looks like this

zone "1.168.192.in-addr.arpa" IN {
        type master;
        file "etc/zones/db.192.168.1.255";
        allow-query { any; };
};

After reloading Bind, you verify reverse DNS works by using the utility of your choice; ie dig, host, nslookup, etc..

alpha03:~ tony$ nslookup 192.168.1.14
Server:		192.168.1.10
Address:	192.168.1.10#53

14.1.168.192.in-addr.arpa	name = email.rubyninja.org.

Linux: 

Awesome Applications: 

BIND 9.7.3

On my new CentOS 6 powered BIND DNS server, it took a while to figure out why my custom jailed BIND configuration was not able to load any zone data files, even though the zone data files did not had any sort of syntax errors. Of which I verified using the named-checkzone utility.

Syslog errors:

Dec 29 21:29:04 centos6 named[17311]: etc/db.rubysecurity.org:2: ignoring out-of-zone data (rubysecurity.org)
Dec 29 21:29:04 centos6 named[17311]: etc/db.rubysecurity.org:9: ignoring out-of-zone data (rubysecurity.org)
Dec 29 21:29:04 centos6 named[17311]: etc/db.rubysecurity.org:10: ignoring out-of-zone data (rubysecurity.org)
Dec 29 21:29:04 centos6 named[17311]: etc/db.rubysecurity.org:11: ignoring out-of-zone data (rubysecurity.org)
Dec 29 21:29:04 centos6 named[17311]: etc/db.rubysecurity.org:12: ignoring out-of-zone data (www.rubysecurity.org)
Dec 29 21:29:04 centos6 named[17311]: zone db.rubysecurity.org/IN: has 0 SOA records
Dec 29 21:29:04 centos6 named[17311]: zone db.rubysecurity.org/IN: has no NS records
Dec 29 21:29:04 centos6 named[17311]: zone db.rubysecurity.org/IN: not loaded due to errors.
Dec 29 21:29:04 centos6 named[17311]: etc/db.ubuntu:2: ignoring out-of-zone data (ubuntu)
Dec 29 21:29:04 centos6 named[17311]: zone db.ubuntu/IN: has 0 SOA records
Dec 29 21:29:04 centos6 named[17311]: zone db.ubuntu/IN: not loaded due to errors.

I came to realize the issue was within my named.conf master config file. Since I'm using BIND 9.7.3 (and newer versions), it turns out that the zone name needs to have a dot (.) at the end of the domain name. This was really annoying since it appears that earlier versions didn't tagged this an error and were able to load up zone files perfectly fine without the addition of the dot character at the end of the zone file name. Luckily, I was able to fix the issue, which by the way, the named-checkconf utility was not able to detect this problem.

Broken:

zone "rubysecurity.org" IN {
        type master;
        file "etc/db.rubysecurity.org";
        allow-update { key rndc-key; };
        allow-query { any; };

};

Fix:

zone "rubysecurity.org." IN {
        type master;
        file "etc/db.rubysecurity.org";
        allow-update { key rndc-key; };
        allow-query { any; };

};

Awesome Applications: 

Premium Drupal Themes by Adaptivethemes