Set up Bind server with Ansible
-
Here is a role to configure and build Bind on CentOS 7. When I clone a machine, I grab the new MAC and add the reservation before I spin it up. Then in the inventory file, Ansible connects based on IP address and have a variable set up for the host name I want for the machine.
Here's the machine in our inventory
10.1.30.11 host_name=ns1.pa.jhbcomputers.com
Here's the role structure:
. ├── defaults │ └── main.yml ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── README.md ├── tasks │ └── main.yml ├── templates │ ├── db.forward.j2 │ ├── db.reverse.j2 │ ├── named.conf.j2 │ └── named.conf.local.j2 ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml
Here's our main tasks.yml file:
--- # tasks file for Bind setup - name: Install bind yum: pkg: bind state: installed - name: Set hostname hostname: name: {{ host_name }} - name: Set hostname fact set_fact: ansible_fqdn: {{ host_name }} - name: Copy named conf file template: src: named.conf.j2 dest: /etc/named.conf owner: root group: named mode: 0660 notify: restart named - name: Make named directory file: path: /etc/named state: directory owner: root group: named mode: 0750 - name: Copy named conf local file template: src: named.conf.local.j2 dest: /etc/named/named.conf.local owner: root group: named mode: 0640 notify: restart named - name: Make zones Directory file: path: /etc/named/zones state: directory owner: root group: named mode: 0750 - name: Copy forward file template: src: db.forward.j2 dest: /etc/named/zones/db.{{ domain }} owner: root group: named mode: 0640 notify: restart named - name: Copy reverse file template: src: db.reverse.j2 dest: /etc/named/zones/db.{{ rev_domain }} owner: root group: named mode: 0640 notify: restart named - name: Open firewall port firewalld: service: dns permanent: true state: enabled immediate: yes
db.forward.j2:
$TTL 604800 ; 1 week {{ domain }} IN SOA {{ ansible_fqdn }}. admin.{{ domain }}. ( 8 ; serial 604800 ; refresh (1 week) 86400 ; retry (1 day) 2419200 ; expire (4 weeks) 604800 ; minimum (1 week) ) NS {{ ansible_fqdn }}. $ORIGIN {{ domain }}. $TTL 300 ; 5 minutes $TTL 604800 ; 1 week erl A 10.1.30.1 ansible A 10.1.30.5 ns1 A 10.1.30.11
db.reverse.j2:
$ORIGIN . $TTL 604800 ; 1 week {{ rev_domain }} IN SOA {{ ansible_fqdn }}. admin.{{ domain }}. ( 7 ; serial 604800 ; refresh (1 week) 86400 ; retry (1 day) 2419200 ; expire (4 weeks) 604800 ; minimum (1 week) ) NS {{ ansible_fqdn }}. $ORIGIN {{ rev_domain }}. $TTL 604800 ; 1 week 1 PTR erl.30.1.10.in-addr.arpa. 5 PTR ansible.30.1.10.in-addr.arpa. 11 PTR ns1.30.1.10.in-addr.arpa.
named.conf.j2:
// // named.conf // // Provided by Red Hat bind package to configure the ISC BIND named(8) DNS // server as a caching only nameserver (as a localhost DNS resolver only). // // See /usr/share/doc/bind*/sample/ for example named configuration files. // include "/etc/rndc.key"; options { listen-on port 53 { 127.0.0.1; {{ ansible_eth0.ipv4.address }}; }; # listen-on-v6 port 53 { ::1; }; directory "/var/named"; dump-file "/var/named/data/cache_dump.db"; statistics-file "/var/named/data/named_stats.txt"; memstatistics-file "/var/named/data/named_mem_stats.txt"; allow-query { any; }; /* - If you are building an AUTHORITATIVE DNS server, do NOT enable recursion. - If you are building a RECURSIVE (caching) DNS server, you need to enable recursion. - If your recursive DNS server has a public IP address, you MUST enable access control to limit queries to your legitimate users. Failing to do so will cause your server to become part of large scale DNS amplification attacks. Implementing BCP38 within your network would greatly reduce such attack surface */ recursion yes; dnssec-enable yes; dnssec-validation yes; forwarders { 8.8.8.8; }; /* Path to ISC DLV key */ bindkeys-file "/etc/named.iscdlv.key"; managed-keys-directory "/var/named/dynamic"; pid-file "/run/named/named.pid"; session-keyfile "/run/named/session.key"; }; logging { channel default_debug { file "data/named.run"; severity dynamic; }; }; zone "." IN { type hint; file "named.ca"; }; include "/etc/named/named.conf.local";
named.conf.local.j2:
zone "{{ domain }}" IN { type master; file "/etc/named/zones/db.{{ domain }}"; allow-update { key rndc-key; }; }; zone "{{ rev_domain }}" IN { type master; file "/etc/named/zones/db.{{ rev_domain }}"; allow-update {key rndc-key; }; }; controls { inet 127.0.0.1 port 953 allow { 127.0.0.1; } keys { "rndc-key"; }; };
Here's the single simple handler main.yml:
--- # handlers file for Bind setup - name: restart named service: name: named state: restarted
And the vars main.yml file:
--- # vars file for Bind setup domain: pa.jhbcomputers.com rev_domain: 30.1.10.in-addr.arpa
Then you can call the role from your playbook:
--- - name: Set up Bind hosts: 10.1.30.11 gather_facts: true user: jhooks become: true roles: - bind
-
For this to be truly modular, I would have to make variables for all of the options in each template file, but since this is pretty much just for me I didn't worry about it.
-
I may also set up a yaml dictionary for the actual DNS entries. This way everything is in one place vs editing different files.
-
So if you would want to use a YAML dictionary for your records here's a sample that goes in vars.yml:
records: ns1: forward: 10.1.30.11 type: A last: 11 rev: 30.1.10.in-addr.arpa. ansible: forward: 10.1.30.5 type: A last: 5 rev: 30.1.10.in-addr.arpa.
Then in your templates you would have this:
reverse:
$ORIGIN . $TTL 604800 ; 1 week {{ rev_domain }} IN SOA {{ ansible_fqdn }}. admin.{{ domain }}. ( 7 ; serial 604800 ; refresh (1 week) 86400 ; retry (1 day) 2419200 ; expire (4 weeks) 604800 ; minimum (1 week) ) NS {{ ansible_fqdn }}. $ORIGIN {{ rev_domain }}. $TTL 604800 ; 1 week {% for key, value in records.iteritems() %} {{ value.last }} PTR {{ value.rev }} {% endfor %}
forward:
$ORIGIN . $TTL 604800 ; 1 week {{ domain }} IN SOA {{ ansible_fqdn }}. admin.{{ domain }}. ( 8 ; serial 604800 ; refresh (1 week) 86400 ; retry (1 day) 2419200 ; expire (4 weeks) 604800 ; minimum (1 week) ) NS {{ ansible_fqdn }}. $ORIGIN {{ domain }}. $TTL 300 ; 5 minutes $TTL 604800 ; 1 week {% for key, value in records.iteritems() %} {{ key }} {{ value.type }} {{ value.forward }} {% endfor %}
And then the tasks would look like this:
- name: Copy forward file template: src: db.forward.j2 dest: /etc/named/zones/db.{{ domain }} owner: root group: named mode: 0640 with_dict: "{{ records }}" notify: restart named - name: Copy reverse file template: src: db.reverse.j2 dest: /etc/named/zones/db.{{ rev_domain }} owner: root group: named mode: 0640 with_dict: "{{ records }}" notify: restart named
-
Nice, moving DNS to a YAML file and making it platform agnostic is awesome.
-
So I also realized that if you have even a small number of records, a dictionary will become super long. So you can compact the dictionary like this:
records: ns1: {forward: 10.1.30.11, type: A, last: 11, rev: 30.1.10.in-addr.arpa.} ansible: {forward: 10.1.30.5, type: A, last: 5, rev: 30.1.10.in-addr.arpa.}
So while the other way may be easier to read, this saves a TON of space.