From d3c9a0830a5f93bbb95d089ed65e9610df07617d Mon Sep 17 00:00:00 2001 From: David Galloway Date: Thu, 8 Dec 2016 15:13:26 -0500 Subject: [PATCH] nameserver: Support dynamic DNS zone in records task Signed-off-by: David Galloway --- roles/nameserver/README.rst | 16 ++++++++- roles/nameserver/tasks/records.yml | 58 ++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/roles/nameserver/README.rst b/roles/nameserver/README.rst index d30806c..3c403ea 100644 --- a/roles/nameserver/README.rst +++ b/roles/nameserver/README.rst @@ -74,7 +74,7 @@ Most variables are defined in ``roles/nameserver/defaults/main.yml`` and values **named_domains: []** -The ``named_domains`` dictionary is the bread and butter of creating zone files. It is in standard YAML syntax. Each domain (key) must have ``forward`` and ``ipvar`` defined although ``ipvar`` can be set to ``NULL``. Optional values include ``miscrecords`` and ``reverse``. +The ``named_domains`` dictionary is the bread and butter of creating zone files. It is in standard YAML syntax. Each domain (key) must have ``forward``, ``ipvar``, and ``dynamic`` defined. ``ipvar`` can be set to ``NULL``. Optional values include ``miscrecords``, ``reverse``, and ``ddns_hostname_prefixes``. ``forward`` The domain of the forward lookup zone for each domain (key) @@ -82,6 +82,12 @@ The ``named_domains`` dictionary is the bread and butter of creating zone files. ``ipvar`` The variable assigned to a system in the Ansible inventory. This allows systems to have multiple IPs assigned for a front and ipmi network, for example. See **Inventory Example** below. +``dynamic`` + Specifies whether the parent zone/domain should allow Dynamic DNS records. See **Dynamic DNS** below for more information. + +``ddns_hostname_prefixes`` + This should be a list of dynamic hostname prefixes you don't want overwritten if a zone/domain has static and dynamic records. See **Dynamic DNS** below. + ``miscrecords`` Records to add to corresponding ``forward`` zone file. This is a good place for CNAMEs and MX records and records for hosts you don't have in your Ansible inventory. If your main nameserver is in a subdomain, you should create its glue record here. See example. @@ -93,6 +99,7 @@ The ``named_domains`` dictionary is the bread and butter of creating zone files. named_domains: example.com: ipvar: NULL + dynamic: false forward: example.com miscrecords: - www IN A 8.8.8.8 @@ -100,6 +107,9 @@ The ``named_domains`` dictionary is the bread and butter of creating zone files. - ns1.private IN A 192.168.0.1 private.example.com: ipvar: ip + dynamic: true + ddns_hostname_prefixes: + - dyn forward: private.example.com miscrecords: - mail IN MX 192.168.0.2 @@ -110,11 +120,15 @@ The ``named_domains`` dictionary is the bread and butter of creating zone files. - 192.168.2.0 mgmt.example.com: ipvar: mgmt + dynamic: false forward: mgmt.example.com reverse: - 192.168.10.0 - 192.168.11.0 - 192.168.12.0 + ddns.example.com: + ipvar: NULL + dynamic: true Inventory +++++++++ diff --git a/roles/nameserver/tasks/records.yml b/roles/nameserver/tasks/records.yml index c66b09c..de154c5 100644 --- a/roles/nameserver/tasks/records.yml +++ b/roles/nameserver/tasks/records.yml @@ -15,6 +15,44 @@ set_fact: named_serial: "{{ ansible_date_time.epoch }}" +- name: Create non-existent forward zone files for dynamic domains + template: + src: forward.j2 + dest: "{{ named_conf_zones_path }}/{{ item.key }}" + validate: named-checkzone {{ item.key }} %s + # only write if zone file doesn't already exist + # this makes sure we don't clobber ddns records + force: no + with_dict: "{{ named_domains }}" + notify: reload named + when: item.value.dynamic == true + +# This makes sure dynamic DNS records in the journal files are in sync with the +# actual zone files so we can store them in the next step. +- name: Sync Dynamic DNS journals with zone files + command: "rndc sync -clean {{ item.key }}" + with_dict: "{{ named_domains }}" + when: item.value.dynamic == true and + item.value.ddns_hostname_prefixes is defined + # Don't fail if there is no journal file + failed_when: false + +- name: Create temporary directory for dynamic A records + command: "mktemp -d" + register: named_tempdir + +# We need to store existing DNS records in a temp file so we can spit +# them back out into the zone file after static records are written. +# Given hostname prefix(es) to expect, this task greps for those records +# and stores them in a temporary file named after the domain. +- name: Store existing dynamic A records + shell: "grep -E '^({% for prefix in item.value.ddns_hostname_prefixes %}{{ prefix }}{% if not loop.last %}|{% endif %}{% endfor %})[0-9]+\\s+A' {{ named_conf_zones_path }}/{{ item.key }} > {{ named_tempdir.stdout }}/{{ item.key }}" + with_dict: "{{ named_domains }}" + when: item.value.dynamic == true and + item.value.ddns_hostname_prefixes is defined + # Don't fail if there are no records to store + failed_when: false + - name: Write forward zone files template: src: forward.j2 @@ -22,6 +60,9 @@ validate: named-checkzone {{ item.key }} %s with_dict: "{{ named_domains }}" notify: reload named + # Don't write zone files for pure dynamic zones + when: (item.value.dynamic != true) or + (item.value.dynamic == true and item.value.ddns_hostname_prefixes is defined) - name: Write reverse zone files template: @@ -34,3 +75,20 @@ - flags: skip_missing: True notify: reload named + +- name: Restore dynamic A records from temp file(s) + shell: "cat {{ named_tempdir.stdout }}/{{ item.key }} >> {{ named_conf_zones_path }}/{{ item.key }}" + with_dict: "{{ named_domains }}" + when: item.value.dynamic == true and + item.value.ddns_hostname_prefixes is defined + # Don't fail if there are no records to restore + failed_when: false + +# This gets rid of any cached dynamic records that we didn't just restore +- name: Freeze, reload, thaw dynamic zone files + shell: "rndc freeze; rndc reload; rndc thaw" + +- name: Clean up dynamic A records temp dir + file: + path: "{{ named_tempdir.stdout }}" + state: absent -- 2.39.5