From d387599e7763014abdf0c7806241f34cc19b9b27 Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Wed, 14 Nov 2018 11:11:07 -0500 Subject: [PATCH 01/14] Update named.conf.local.jinja Some reorganization of the format. In the for-loop that handles configured_views: - Add if-block on lines 124-128 to allow specifying a file for your view, rather than defaulting to the name of the specified zone. This allows multiple views to serve the same zone, but use a different file. --- bind/files/named.conf.local.jinja | 52 ++++++++++++++++--------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/bind/files/named.conf.local.jinja b/bind/files/named.conf.local.jinja index 3b20820..cf5675c 100644 --- a/bind/files/named.conf.local.jinja +++ b/bind/files/named.conf.local.jinja @@ -7,6 +7,22 @@ // organization //include "/etc/bind/zones.rfc1918"; +{% for name, data in salt['pillar.get']('bind:configured_acls', {})|dictsort %} +acl {{ name }} { + {%- for d in data %} + {{ d }}; + {%- endfor %} +}; +{%- endfor %} + +{%- for name, data in salt['pillar.get']('bind:configured_masters', {})|dictsort %} +masters {{ name }} { + {%- for d in data %} + {{ d }}; + {%- endfor %} +}; +{%- endfor %} + {%- macro zone(key, args, file, masters) %} zone "{{ key }}" { type {{ args['type'] }}; @@ -92,23 +108,26 @@ include "{{ map.default_zones_config }}"; {{ zone(key, args, file, masters) }} {% endfor %} -{% for view, view_data in salt['pillar.get']('bind:configured_views', {})|dictsort %} +{%- for view, view_data in salt['pillar.get']('bind:configured_views', {})|dictsort %} view {{ view }} { {%- if view == 'default' %} include "{{ map.default_zones_config }}"; {%- endif %} - -match-clients { + match-clients { {%- for acl in view_data.get('match_clients', {}) %} - {{ acl }}; + {{ acl }}; {%- endfor %} -}; + }; -{% for key, args in view_data.get('configured_zones', {})|dictsort -%} -{%- set file = salt['pillar.get']("bind:available_zones:" + key + ":file") %} +{%- for key, args in view_data.get('configured_zones', {})|dictsort -%} +{%- if 'file' in args %} +{%- set file = args.file %} +{%- else %} +{%- set file = salt['pillar.get']("bind:available_zones:" + key + ":file") %} +{%- endif %} {%- set masters = salt['pillar.get']("bind:available_zones:" + key + ":masters") %} - {{ zone(key, args, file, masters) }} + {{ zone(key, args, file, masters) }} {%- endfor %} }; {%- endfor %} @@ -163,20 +182,3 @@ statistics-channels { {%- endfor %} }; {%- endif %} - - -{%- for name, data in salt['pillar.get']('bind:configured_acls', {})|dictsort %} -acl {{ name }} { - {%- for d in data %} - {{ d }}; - {%- endfor %} -}; -{%- endfor %} - -{%- for name, data in salt['pillar.get']('bind:configured_masters', {})|dictsort %} -masters {{ name }} { - {%- for d in data %} - {{ d }}; - {%- endfor %} -}; -{%- endfor %} From 4b88c4d7977ca4b140e2b489abe1ad9e7670c804 Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Wed, 14 Nov 2018 11:17:45 -0500 Subject: [PATCH 02/14] Update pillar.example Add documentation and an example on specifying the file to be used for a view, as well as documented that you should not define the top-level 'configured_zones' key when using views. --- pillar.example | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pillar.example b/pillar.example index a2e29f0..7590294 100644 --- a/pillar.example +++ b/pillar.example @@ -231,7 +231,9 @@ bind: notify: False # Don't notify any NS RRs of any changes to zone also-notify: # Do notify these IP addresses (pointless as - 1.1.1.1 # notify has been set to no) - - 2.2.2.2 + - 2.2.2.2 # If using views, do not define configured_zones + # at this indentation level - define it using the sub-key + # of your configured_view. sub.domain2.com: # Domain zone with DNSSEC type: master # We're the master of this zone @@ -279,7 +281,16 @@ bind: configured_zones: # Zones that our view is applicable to my.zone: # We've defined a new zone in here type: master - notify: False + file: example.com.txt # Optional: specify the zone file to be used for this view. + # otherwise it will default to the file matching the name of the zone that you + # specify here. + # The file name must match what you have entered for 'file' in the zone under + # 'available_zones'. + # This allows you to define multiple views that serve the same zone, but + # serve a different record set. + # If doing this, you need to configure the zones and their record sets + # underneath the 'available_zones' section. + notify: False update_policy: # A given update policy - "grant core_dhcp name dns_entry_allowed_to_update. ANY" From a2a363819c378ed21f34588bbf0cda9f1258305f Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Wed, 14 Nov 2018 11:21:30 -0500 Subject: [PATCH 03/14] Small comment update. --- pillar.example | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pillar.example b/pillar.example index 7590294..5eb7c59 100644 --- a/pillar.example +++ b/pillar.example @@ -233,7 +233,7 @@ bind: - 1.1.1.1 # notify has been set to no) - 2.2.2.2 # If using views, do not define configured_zones # at this indentation level - define it using the sub-key - # of your configured_view. + # of your view under configured_views. sub.domain2.com: # Domain zone with DNSSEC type: master # We're the master of this zone @@ -281,13 +281,13 @@ bind: configured_zones: # Zones that our view is applicable to my.zone: # We've defined a new zone in here type: master - file: example.com.txt # Optional: specify the zone file to be used for this view. + file: example.com.txt # Optional: specify the zone file to be used for this view, # otherwise it will default to the file matching the name of the zone that you - # specify here. + # specify here (which must match a zone under 'available_zones'. # The file name must match what you have entered for 'file' in the zone under # 'available_zones'. # This allows you to define multiple views that serve the same zone, but - # serve a different record set. + # serve a different record set in each. # If doing this, you need to configure the zones and their record sets # underneath the 'available_zones' section. notify: False From d8eac23c5ddba81e7e5c960fb6e7b3820332e7a0 Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Wed, 14 Nov 2018 11:30:13 -0500 Subject: [PATCH 04/14] Add comment about using ACLs and views. --- pillar.example | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pillar.example b/pillar.example index 5eb7c59..b4a545c 100644 --- a/pillar.example +++ b/pillar.example @@ -297,7 +297,9 @@ bind: configured_acls: # And now for some ACLs my_net: # Our ACL's name - 127.0.0.0/8 # And the applicable IP addresses - - 10.20.0.0/16 + - 10.20.0.0/16 # If using views, you need to create an ACL per view to differentiate + # who accesses the view, and then specify the appropriate ACL name under + # the 'match_clients' sub-key of your view. ### Define zone records in pillar ### bind: From d67e9f86604e2f7c34bf911c715a83c9711d000f Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Thu, 15 Nov 2018 08:39:07 -0500 Subject: [PATCH 05/14] Create pillar-with-views.example An example of the bind pillar that defines multiple views for internal and external record sets. This doesn't include the other portion of the pillar the defines the bind config - this is zones, views and ACLs only. The config portion is not affected by this. --- pillar-with-views.example | 89 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 pillar-with-views.example diff --git a/pillar-with-views.example b/pillar-with-views.example new file mode 100644 index 0000000..088454c --- /dev/null +++ b/pillar-with-views.example @@ -0,0 +1,89 @@ +bind: + configured_acls: # We have an internal ACL restricted to our + internal: # private IP ranges. + - 127.0.0.0/8 # In this case, an ACL for external isn't needed + - 10.0.0.0/8 # as that view will be matched by 'any'. + + # Notice that there is no 'configured_zones' at this indentation level. + # That is because the bind service forces all zones to be served via a view when you are using + # views. + + configured_views: + external: + match_clients: + - any # This will match anything, including the public internet. + configured_zones: + mydomain.com: # Notice that this value matches on both views. + type: master + file: external.mydomain.com.txt # Specify the file to be used, which must match the file + recursion: yes # name of the zone below under available_zones. + notify: False + dnssec: False + + internal: + match_clients: + - internal # This will match only our ACL named 'internal'. + configured_zones: + mydomain.com: # Same as above - both views will serve the same zone. + type: master + file: internal.mydomain.com.txt # Different file - matches the internal zone below. + recursion: yes + notify: False + dnssec: False + + available_zones: + external.mydomain.com: # Beginning of the external zone definition. + file: external.mydomain.com.txt # The file in which to save this zone's record set - matches the file + # specified in the 'external' view. + + soa: # Declare the SOA RRs for the zone + ns: ns1.external.mydomain.com # Required + contact: hostmaster@mydomain.com # Required + serial: auto # Alternatively, autoupdate serial on each change + class: IN # Optional. Default: IN + refresh: 8600 # Optional. Default: 12h + retry: 900 # Optional. Default: 15m + expiry: 86000 # Optional. Default: 2w + nxdomain: 500 # Optional. Default: 1m + ttl: 8600 # Optional. Not set by default + records: # Records for the zone, grouped by type + A: + portal: 50.60.70.80 + gateway: 50.60.70.81 + directory: 50.60.70.82 + ns1: 50.60.70.83 + www: 50.60.70.84 + NS: + '@': + - ns1 + CNAME: + login: portal.mydomain.com. + dashboard: www.mydomain.com. + + internal.mydomain.com: # Beginning of the internal zone definition. + file: internal.mydomain.com.txt # The file in which to save this zone's record set - matches the file + # specified in the 'internal' view. + + soa: # Declare the SOA RRs for the zone + ns: ns1.mydomain.com # Required + contact: hostmaster@mydomain.com # Required + serial: auto # Alternatively, autoupdate serial on each change + class: IN # Optional. Default: IN + refresh: 8600 # Optional. Default: 12h + retry: 900 # Optional. Default: 15m + expiry: 86000 # Optional. Default: 2w + nxdomain: 500 # Optional. Default: 1m + ttl: 8600 # Optional. Not set by default + records: # Records for the zone, grouped by type + A: + portal: 10.0.0.10 + gateway: 10.0.0.11 + directory: 10.0.0.12 + ns1: 10.0.0.13 + www: 10.0.0.14 + NS: + '@': + - ns1 + CNAME: + login: portal.mydomain.com. + dashboard: www.mydomain.com. From 9b2f7836120d514d8b46fa3473e674eec16f0426 Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Thu, 15 Nov 2018 08:43:20 -0500 Subject: [PATCH 06/14] Add more comment clarification. --- pillar-with-views.example | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pillar-with-views.example b/pillar-with-views.example index 088454c..f95e5b8 100644 --- a/pillar-with-views.example +++ b/pillar-with-views.example @@ -5,8 +5,11 @@ bind: - 10.0.0.0/8 # as that view will be matched by 'any'. # Notice that there is no 'configured_zones' at this indentation level. - # That is because the bind service forces all zones to be served via a view when you are using - # views. + # That is because when you are using views, the bind service forces all zones to be served via a view. + # + # Also note - any other zones defined in any other conf files will either need to be commented out, or + # also served via a view using a file include. If you have other zones being served outside of a view, bind will + # fail to start. configured_views: external: From 2e5730c82d4b901f763ccd53182c77bb1b02a549 Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Thu, 15 Nov 2018 13:43:46 -0500 Subject: [PATCH 07/14] Add comment explaining file name requirements. The filename must match the corresponding zone name (without the .txt extension) because the config.sls jinja logic uses the filename to match to the zone when setting zone_records. It also is hardcoded to replace ".txt" with "" in order to make this match work, and so .txt extension is required for the logic to work. --- pillar-with-views.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pillar-with-views.example b/pillar-with-views.example index f95e5b8..bb5bdb9 100644 --- a/pillar-with-views.example +++ b/pillar-with-views.example @@ -20,6 +20,8 @@ bind: type: master file: external.mydomain.com.txt # Specify the file to be used, which must match the file recursion: yes # name of the zone below under available_zones. + # This filename also must match the corresponding zone name + # without the .txt extension (and be sure to use .txt as the extension). notify: False dnssec: False @@ -30,6 +32,8 @@ bind: mydomain.com: # Same as above - both views will serve the same zone. type: master file: internal.mydomain.com.txt # Different file - matches the internal zone below. + # Again, this filename must match the corresponding zone name + # without the .txt extension (and be sure to use .txt as the extension). recursion: yes notify: False dnssec: False From f0bdf1da4ec6242ab85d89ed6f1bcadaf6cb6b00 Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Thu, 15 Nov 2018 15:52:58 -0500 Subject: [PATCH 08/14] Update config.sls Add logic to detect a file specified in a view, and match it to a zone under available_zones to enable creating that zone file. --- bind/config.sls | 165 +++++++++++++++++++++--------------------------- 1 file changed, 72 insertions(+), 93 deletions(-) diff --git a/bind/config.sls b/bind/config.sls index 1394904..5bbea61 100644 --- a/bind/config.sls +++ b/bind/config.sls @@ -1,5 +1,5 @@ -{% from "bind/map.jinja" import map with context %} -{% from "bind/reverse_zone.jinja" import generate_reverse %} +{% from salt.file.join(tpldir, "map.jinja") import map with context %} +{% from salt.file.join(tpldir, "reverse_zone.jinja") import generate_reverse %} {%- set key_directory = salt['pillar.get']('bind:lookup:key_directory', map.key_directory) %} {%- set key_algorithm = salt['pillar.get']('bind:lookup:key_algorithm', map.key_algorithm) %} @@ -14,8 +14,16 @@ {%- endif %} include: - - bind + - formula.bind +######################################## +## +## Create the BIND config +## Commands are numbered only for easy reference - they are not executed in order! +## +######################################## + +# 1. Create log directory (/var/log/named). Configured in map.jinja. {{ map.chroot_dir }}{{ map.log_dir }}: file.directory: - user: root @@ -24,6 +32,7 @@ include: - require: - pkg: bind +# 2. Trigger restarts via watches on /var/log/named/query.log and bind_key_directory bind_restart: service.running: - name: {{ map.service }} @@ -32,6 +41,8 @@ bind_restart: - file: {{ map.chroot_dir }}{{ map.log_dir }}/query.log - file: bind_key_directory +# 3. Place /var/log/named/query.log (unless it exists already) +# Require creation of the directory in command 1. {{ map.chroot_dir }}{{ map.log_dir }}/query.log: file.managed: - replace: False @@ -41,6 +52,8 @@ bind_restart: - require: - file: {{ map.chroot_dir }}{{ map.log_dir }} +# 4. Create /var/named directory. Configured in map.jinja. +# Require bind pkg.installed (via the include at the top). named_directory: file.directory: - name: {{ map.named_directory }} @@ -51,6 +64,8 @@ named_directory: - require: - pkg: bind +# 5. Create a zones directory. Configured in map.jinja. +# WE DON'T DO THIS. {% if map.get('zones_directory') %} bind_zones_directory: file.directory: @@ -64,6 +79,14 @@ bind_zones_directory: - file: named_directory {% endif %} +# 6. Create the bind config file /etc/named.conf +# Uses salt://formula/bind/files/redhat/named.conf as the source template. +# Uses jinja to template it, and passes the entire map dictionary as context. +# View the context with: +# salt -G role:dns state.show_sls formula.bind.config and look for the 'bind_config' ID. +# Requires bind pkg.installed (via include at the top). +# Triggers bind service reloads via the watch_in. +# Adds an include declaration for the file created in command 7. bind_config: file.managed: - name: {{ map.config }} @@ -83,10 +106,16 @@ bind_config: - watch_in: - service: bind +# 7. Create /etc/named.conf.local. Configured in map.jinja +# Works much the same as command 6. +# Also passes zones_directory, which we don't have defined explicity, and gets set to +# map.named_directory (/var/named). +# Requires bind pkg.installed (via include at the top) and query.log file. +# Triggers bind service reloads via the watch_in. bind_local_config: file.managed: - name: {{ map.local_config }} - - source: salt://bind/files/named.conf.local.jinja + - source: salt://formula/bind/files/named.conf.local.jinja - template: jinja - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - group: {{ salt['pillar.get']('bind:config:group', map.group) }} @@ -100,6 +129,9 @@ bind_local_config: - watch_in: - service: bind +# 8. Create default config /etc/sysconfig/named +# Pass map as jinja context. +# Trigger bind service restarts via the watch_in. {% if grains['os_family'] not in ['Arch', 'FreeBSD'] %} bind_default_config: file.managed: @@ -115,64 +147,12 @@ bind_default_config: - service: bind_restart {% endif %} -{% if grains['os_family'] == 'Debian' %} -bind_key_config: - file.managed: - - name: {{ map.key_config }} - - source: 'salt://{{ map.config_source_dir }}/named.conf.key' - - template: jinja - - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - - group: {{ salt['pillar.get']('bind:config:group', map.group) }} - - mode: {{ salt['pillar.get']('bind:config:mode', '640') }} - - require: - - pkg: bind - - watch_in: - - service: bind - -bind_options_config: - file.managed: - - name: {{ map.options_config }} - - source: 'salt://{{ map.config_source_dir }}/named.conf.options' - - template: jinja - - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - - group: {{ salt['pillar.get']('bind:config:group', map.group) }} - - mode: {{ salt['pillar.get']('bind:config:mode', '644') }} - - context: - key_directory: {{ map.key_directory }} - named_directory: {{ map.named_directory }} - zones_directory: {{ zones_directory }} - - require: - - pkg: bind - - watch_in: - - service: bind - -bind_default_zones: - file.managed: - - name: {{ map.default_zones_config }} - - source: 'salt://{{ map.config_source_dir }}/named.conf.default-zones' - - template: jinja - - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - - group: {{ salt['pillar.get']('bind:config:group', map.group) }} - - mode: {{ salt['pillar.get']('bind:config:mode', '644') }} - - require: - - pkg: bind - - watch_in: - - service: bind - -/etc/logrotate.d/{{ map.service }}: - file.managed: - - source: salt://{{ map.config_source_dir }}/logrotate_bind - - template: jinja - - user: root - - group: root - - context: - map: {{ map }} - +# 9. Set up extensive logging - configured in bind pillar. {%- if salt['pillar.get']('bind:config:use_extensive_logging', False) %} bind_logging_config: file.managed: - name: {{ map.logging_config }} - - source: salt://bind/files/named.conf.logging.jinja + - source: salt://formula/bind/files/named.conf.logging.jinja - template: jinja - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - group: {{ salt['pillar.get']('bind:config:group', map.group) }} @@ -183,51 +163,44 @@ bind_logging_config: - pkg: bind - watch_in: - service: bind + - makedirs: True {%- endif %} -{%- if salt['pillar.get']('bind:rndc_client', False) %} -bind_rndc_client_config: - file.managed: - - name: {{ map.rndc_client_config }} - - source: salt://{{ map.config_source_dir }}/rndc.conf - - template: jinja - - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - - group: {{ salt['pillar.get']('bind:config:group', map.group) }} - - mode: {{ salt['pillar.get']('bind:config:mode', '640') }} - - context: - map: {{ map }} - - require: - - pkg: bind -{%- endif %} -{% endif %} +# 10. Many nested loops and if-blocks setting up the zone files and views. +# This creates the zone files for the zones set up in available_zones and configured_zones (configured in the zones pillar). {%- set views = {False: salt['pillar.get']('bind', {})} %}{# process non-view zones in the same loop #} {%- do views.update(salt['pillar.get']('bind:configured_views', {})) %} {%- for view, view_data in views|dictsort %} -{%- set dash_view = '-' + view if view else '' %} -{% for zone, zone_data in view_data.get('configured_zones', {})|dictsort -%} -{%- set file = salt['pillar.get']("bind:available_zones:" + zone + ":file", false) %} -{%- set zone_records = salt['pillar.get']('bind:available_zones:' + zone + ':records', {}) %} -{%- if salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse') %} -{%- do generate_reverse(zone_records, salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:net'), salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:for_zones'), salt['pillar.get']('bind:available_zones', {})) %} -{%- endif %} +{%- set dash_view = '-' + view if view else '' %} +{% for zone, zone_data in view_data.get('configured_zones', {})|dictsort -%} +{%- if 'file' in zone_data %} +{%- set file = zone_data.file %} +{%- set zone = zone|replace(".txt", "") %} +{%- else %} +{%- set file = salt['pillar.get']("bind:available_zones:" + zone + ":file", false) %} +{%- endif %} +{%- set zone_records = salt['pillar.get']('bind:available_zones:' + zone + ':records', {}) %} +{%- if salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse') %} +{%- do generate_reverse(zone_records, salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:net'), salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:for_zones'), salt['pillar.get']('bind:available_zones', {})) %} +{%- endif %} {# If we define RRs in pillar, we use the internal template to generate the zone file otherwise, we fallback to the old behaviour and use the declared file #} -{%- set zone_source = 'salt://bind/files/zone.jinja' if zone_records != {} else 'salt://' ~ map.zones_source_dir ~ '/' ~ file %} -{%- set serial_auto = salt['pillar.get']('bind:available_zones:' + zone + ':soa:serial', '') == 'auto' %} -{% if file and zone_data['type'] == 'master' -%} +{%- set zone_source = 'salt://formula/bind/files/zone.jinja' if zone_records != {} else 'salt://' ~ map.zones_source_dir ~ '/' ~ file %} +{%- set serial_auto = salt['pillar.get']('bind:available_zones:' + zone + ':soa:serial', '') == 'auto' %} +{% if file and zone_data['type'] == 'master' -%} zones{{ dash_view }}-{{ zone }}{{ '.include' if serial_auto else ''}}: file.managed: - - name: {{ zones_directory }}/{{ file }}{{ '.include' if serial_auto else ''}} + - name: {{ zones_directory }}/{{ file }}{{ '.include' if serial_auto else '' }} - source: {{ zone_source }} - template: jinja - {% if zone_records != {} %} + {% if zone_records != {} %} - context: - zone: zones{{ dash_view }}-{{ zone }} - soa: {{ salt['pillar.get']("bind:available_zones:" + zone + ":soa") }} - records: {{ zone_records }} - include: False - {% endif %} + zone: zones{{ dash_view }}-{{ zone }} + soa: {{ salt['pillar.get']("bind:available_zones:" + zone + ":soa") }} + records: {{ zone_records }} + include: False + {% endif %} - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - group: {{ salt['pillar.get']('bind:config:group', map.group) }} - mode: {{ salt['pillar.get']('bind:config:mode', '644') }} @@ -255,9 +228,9 @@ zones{{ dash_view }}-{{ zone }}: - template: jinja {% if zone_records != {} %} - context: - zone: zones{{ dash_view }}-{{ zone }} - soa: {{ salt['pillar.get']("bind:available_zones:" + zone + ":soa") }} - include: {{ zones_directory }}/{{ file }}.include + zone: zones{{ dash_view }}-{{ zone }} + soa: {{ salt['pillar.get']("bind:available_zones:" + zone + ":soa") }} + include: {{ zones_directory }}/{{ file }}.include {% endif %} - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - group: {{ salt['pillar.get']('bind:config:group', map.group) }} @@ -270,6 +243,9 @@ zones{{ dash_view }}-{{ zone }}: - file: bind_zones_directory {% endif %} {% endif %} + +# 11. Sign the zone file. Configured in the bind and zones pillar files, and the map.jinja file. +# Not sure of the hierarchy - turn off in all places if unwanted. {% if zone_data['dnssec'] is defined and zone_data['dnssec'] -%} signed{{ dash_view }}-{{ zone }}: cmd.run: @@ -278,8 +254,10 @@ signed{{ dash_view }}-{{ zone }}: - prereq: - file: zones{{ dash_view }}-{{ zone }} {% endif %} + {% endif %} +# 12. More DNSSEC stuff {% if zone_data['auto-dnssec'] is defined -%} zsk-{{ zone }}: cmd.run: @@ -290,6 +268,7 @@ zsk-{{ zone }}: - require: - file: bind_key_directory +# 13. More DNSSEC stuff ksk-{{ zone }}: cmd.run: - cwd: {{ key_directory }} From 33c34d928dd4f1013e53456db01fd654888688bc Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Thu, 15 Nov 2018 16:00:22 -0500 Subject: [PATCH 09/14] Revert back Made a bad commit. --- bind/config.sls | 165 +++++++++++++++++++++++++++--------------------- 1 file changed, 93 insertions(+), 72 deletions(-) diff --git a/bind/config.sls b/bind/config.sls index 5bbea61..1394904 100644 --- a/bind/config.sls +++ b/bind/config.sls @@ -1,5 +1,5 @@ -{% from salt.file.join(tpldir, "map.jinja") import map with context %} -{% from salt.file.join(tpldir, "reverse_zone.jinja") import generate_reverse %} +{% from "bind/map.jinja" import map with context %} +{% from "bind/reverse_zone.jinja" import generate_reverse %} {%- set key_directory = salt['pillar.get']('bind:lookup:key_directory', map.key_directory) %} {%- set key_algorithm = salt['pillar.get']('bind:lookup:key_algorithm', map.key_algorithm) %} @@ -14,16 +14,8 @@ {%- endif %} include: - - formula.bind + - bind -######################################## -## -## Create the BIND config -## Commands are numbered only for easy reference - they are not executed in order! -## -######################################## - -# 1. Create log directory (/var/log/named). Configured in map.jinja. {{ map.chroot_dir }}{{ map.log_dir }}: file.directory: - user: root @@ -32,7 +24,6 @@ include: - require: - pkg: bind -# 2. Trigger restarts via watches on /var/log/named/query.log and bind_key_directory bind_restart: service.running: - name: {{ map.service }} @@ -41,8 +32,6 @@ bind_restart: - file: {{ map.chroot_dir }}{{ map.log_dir }}/query.log - file: bind_key_directory -# 3. Place /var/log/named/query.log (unless it exists already) -# Require creation of the directory in command 1. {{ map.chroot_dir }}{{ map.log_dir }}/query.log: file.managed: - replace: False @@ -52,8 +41,6 @@ bind_restart: - require: - file: {{ map.chroot_dir }}{{ map.log_dir }} -# 4. Create /var/named directory. Configured in map.jinja. -# Require bind pkg.installed (via the include at the top). named_directory: file.directory: - name: {{ map.named_directory }} @@ -64,8 +51,6 @@ named_directory: - require: - pkg: bind -# 5. Create a zones directory. Configured in map.jinja. -# WE DON'T DO THIS. {% if map.get('zones_directory') %} bind_zones_directory: file.directory: @@ -79,14 +64,6 @@ bind_zones_directory: - file: named_directory {% endif %} -# 6. Create the bind config file /etc/named.conf -# Uses salt://formula/bind/files/redhat/named.conf as the source template. -# Uses jinja to template it, and passes the entire map dictionary as context. -# View the context with: -# salt -G role:dns state.show_sls formula.bind.config and look for the 'bind_config' ID. -# Requires bind pkg.installed (via include at the top). -# Triggers bind service reloads via the watch_in. -# Adds an include declaration for the file created in command 7. bind_config: file.managed: - name: {{ map.config }} @@ -106,16 +83,10 @@ bind_config: - watch_in: - service: bind -# 7. Create /etc/named.conf.local. Configured in map.jinja -# Works much the same as command 6. -# Also passes zones_directory, which we don't have defined explicity, and gets set to -# map.named_directory (/var/named). -# Requires bind pkg.installed (via include at the top) and query.log file. -# Triggers bind service reloads via the watch_in. bind_local_config: file.managed: - name: {{ map.local_config }} - - source: salt://formula/bind/files/named.conf.local.jinja + - source: salt://bind/files/named.conf.local.jinja - template: jinja - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - group: {{ salt['pillar.get']('bind:config:group', map.group) }} @@ -129,9 +100,6 @@ bind_local_config: - watch_in: - service: bind -# 8. Create default config /etc/sysconfig/named -# Pass map as jinja context. -# Trigger bind service restarts via the watch_in. {% if grains['os_family'] not in ['Arch', 'FreeBSD'] %} bind_default_config: file.managed: @@ -147,12 +115,64 @@ bind_default_config: - service: bind_restart {% endif %} -# 9. Set up extensive logging - configured in bind pillar. +{% if grains['os_family'] == 'Debian' %} +bind_key_config: + file.managed: + - name: {{ map.key_config }} + - source: 'salt://{{ map.config_source_dir }}/named.conf.key' + - template: jinja + - user: {{ salt['pillar.get']('bind:config:user', map.user) }} + - group: {{ salt['pillar.get']('bind:config:group', map.group) }} + - mode: {{ salt['pillar.get']('bind:config:mode', '640') }} + - require: + - pkg: bind + - watch_in: + - service: bind + +bind_options_config: + file.managed: + - name: {{ map.options_config }} + - source: 'salt://{{ map.config_source_dir }}/named.conf.options' + - template: jinja + - user: {{ salt['pillar.get']('bind:config:user', map.user) }} + - group: {{ salt['pillar.get']('bind:config:group', map.group) }} + - mode: {{ salt['pillar.get']('bind:config:mode', '644') }} + - context: + key_directory: {{ map.key_directory }} + named_directory: {{ map.named_directory }} + zones_directory: {{ zones_directory }} + - require: + - pkg: bind + - watch_in: + - service: bind + +bind_default_zones: + file.managed: + - name: {{ map.default_zones_config }} + - source: 'salt://{{ map.config_source_dir }}/named.conf.default-zones' + - template: jinja + - user: {{ salt['pillar.get']('bind:config:user', map.user) }} + - group: {{ salt['pillar.get']('bind:config:group', map.group) }} + - mode: {{ salt['pillar.get']('bind:config:mode', '644') }} + - require: + - pkg: bind + - watch_in: + - service: bind + +/etc/logrotate.d/{{ map.service }}: + file.managed: + - source: salt://{{ map.config_source_dir }}/logrotate_bind + - template: jinja + - user: root + - group: root + - context: + map: {{ map }} + {%- if salt['pillar.get']('bind:config:use_extensive_logging', False) %} bind_logging_config: file.managed: - name: {{ map.logging_config }} - - source: salt://formula/bind/files/named.conf.logging.jinja + - source: salt://bind/files/named.conf.logging.jinja - template: jinja - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - group: {{ salt['pillar.get']('bind:config:group', map.group) }} @@ -163,44 +183,51 @@ bind_logging_config: - pkg: bind - watch_in: - service: bind - - makedirs: True {%- endif %} +{%- if salt['pillar.get']('bind:rndc_client', False) %} +bind_rndc_client_config: + file.managed: + - name: {{ map.rndc_client_config }} + - source: salt://{{ map.config_source_dir }}/rndc.conf + - template: jinja + - user: {{ salt['pillar.get']('bind:config:user', map.user) }} + - group: {{ salt['pillar.get']('bind:config:group', map.group) }} + - mode: {{ salt['pillar.get']('bind:config:mode', '640') }} + - context: + map: {{ map }} + - require: + - pkg: bind +{%- endif %} +{% endif %} -# 10. Many nested loops and if-blocks setting up the zone files and views. -# This creates the zone files for the zones set up in available_zones and configured_zones (configured in the zones pillar). {%- set views = {False: salt['pillar.get']('bind', {})} %}{# process non-view zones in the same loop #} {%- do views.update(salt['pillar.get']('bind:configured_views', {})) %} {%- for view, view_data in views|dictsort %} -{%- set dash_view = '-' + view if view else '' %} -{% for zone, zone_data in view_data.get('configured_zones', {})|dictsort -%} -{%- if 'file' in zone_data %} -{%- set file = zone_data.file %} -{%- set zone = zone|replace(".txt", "") %} -{%- else %} -{%- set file = salt['pillar.get']("bind:available_zones:" + zone + ":file", false) %} -{%- endif %} -{%- set zone_records = salt['pillar.get']('bind:available_zones:' + zone + ':records', {}) %} -{%- if salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse') %} -{%- do generate_reverse(zone_records, salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:net'), salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:for_zones'), salt['pillar.get']('bind:available_zones', {})) %} -{%- endif %} +{%- set dash_view = '-' + view if view else '' %} +{% for zone, zone_data in view_data.get('configured_zones', {})|dictsort -%} +{%- set file = salt['pillar.get']("bind:available_zones:" + zone + ":file", false) %} +{%- set zone_records = salt['pillar.get']('bind:available_zones:' + zone + ':records', {}) %} +{%- if salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse') %} +{%- do generate_reverse(zone_records, salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:net'), salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:for_zones'), salt['pillar.get']('bind:available_zones', {})) %} +{%- endif %} {# If we define RRs in pillar, we use the internal template to generate the zone file otherwise, we fallback to the old behaviour and use the declared file #} -{%- set zone_source = 'salt://formula/bind/files/zone.jinja' if zone_records != {} else 'salt://' ~ map.zones_source_dir ~ '/' ~ file %} -{%- set serial_auto = salt['pillar.get']('bind:available_zones:' + zone + ':soa:serial', '') == 'auto' %} -{% if file and zone_data['type'] == 'master' -%} +{%- set zone_source = 'salt://bind/files/zone.jinja' if zone_records != {} else 'salt://' ~ map.zones_source_dir ~ '/' ~ file %} +{%- set serial_auto = salt['pillar.get']('bind:available_zones:' + zone + ':soa:serial', '') == 'auto' %} +{% if file and zone_data['type'] == 'master' -%} zones{{ dash_view }}-{{ zone }}{{ '.include' if serial_auto else ''}}: file.managed: - - name: {{ zones_directory }}/{{ file }}{{ '.include' if serial_auto else '' }} + - name: {{ zones_directory }}/{{ file }}{{ '.include' if serial_auto else ''}} - source: {{ zone_source }} - template: jinja - {% if zone_records != {} %} + {% if zone_records != {} %} - context: - zone: zones{{ dash_view }}-{{ zone }} - soa: {{ salt['pillar.get']("bind:available_zones:" + zone + ":soa") }} - records: {{ zone_records }} - include: False - {% endif %} + zone: zones{{ dash_view }}-{{ zone }} + soa: {{ salt['pillar.get']("bind:available_zones:" + zone + ":soa") }} + records: {{ zone_records }} + include: False + {% endif %} - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - group: {{ salt['pillar.get']('bind:config:group', map.group) }} - mode: {{ salt['pillar.get']('bind:config:mode', '644') }} @@ -228,9 +255,9 @@ zones{{ dash_view }}-{{ zone }}: - template: jinja {% if zone_records != {} %} - context: - zone: zones{{ dash_view }}-{{ zone }} - soa: {{ salt['pillar.get']("bind:available_zones:" + zone + ":soa") }} - include: {{ zones_directory }}/{{ file }}.include + zone: zones{{ dash_view }}-{{ zone }} + soa: {{ salt['pillar.get']("bind:available_zones:" + zone + ":soa") }} + include: {{ zones_directory }}/{{ file }}.include {% endif %} - user: {{ salt['pillar.get']('bind:config:user', map.user) }} - group: {{ salt['pillar.get']('bind:config:group', map.group) }} @@ -243,9 +270,6 @@ zones{{ dash_view }}-{{ zone }}: - file: bind_zones_directory {% endif %} {% endif %} - -# 11. Sign the zone file. Configured in the bind and zones pillar files, and the map.jinja file. -# Not sure of the hierarchy - turn off in all places if unwanted. {% if zone_data['dnssec'] is defined and zone_data['dnssec'] -%} signed{{ dash_view }}-{{ zone }}: cmd.run: @@ -254,10 +278,8 @@ signed{{ dash_view }}-{{ zone }}: - prereq: - file: zones{{ dash_view }}-{{ zone }} {% endif %} - {% endif %} -# 12. More DNSSEC stuff {% if zone_data['auto-dnssec'] is defined -%} zsk-{{ zone }}: cmd.run: @@ -268,7 +290,6 @@ zsk-{{ zone }}: - require: - file: bind_key_directory -# 13. More DNSSEC stuff ksk-{{ zone }}: cmd.run: - cwd: {{ key_directory }} From 342c0d7d089ede7c163c98c4825df3bbc439f38e Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Thu, 15 Nov 2018 16:07:19 -0500 Subject: [PATCH 10/14] Update with the required logic. Added an if-block to test for the file argument in the zone_data, and if found, use that view and update the zone variable to match the zone defined under available_zones. --- bind/config.sls | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/bind/config.sls b/bind/config.sls index 1394904..522f198 100644 --- a/bind/config.sls +++ b/bind/config.sls @@ -203,19 +203,24 @@ bind_rndc_client_config: {%- set views = {False: salt['pillar.get']('bind', {})} %}{# process non-view zones in the same loop #} {%- do views.update(salt['pillar.get']('bind:configured_views', {})) %} {%- for view, view_data in views|dictsort %} -{%- set dash_view = '-' + view if view else '' %} -{% for zone, zone_data in view_data.get('configured_zones', {})|dictsort -%} -{%- set file = salt['pillar.get']("bind:available_zones:" + zone + ":file", false) %} -{%- set zone_records = salt['pillar.get']('bind:available_zones:' + zone + ':records', {}) %} -{%- if salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse') %} -{%- do generate_reverse(zone_records, salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:net'), salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:for_zones'), salt['pillar.get']('bind:available_zones', {})) %} -{%- endif %} +{%- set dash_view = '-' + view if view else '' %} +{%- for zone, zone_data in view_data.get('configured_zones', {})|dictsort -%} +{%- if 'file' in zone_data %} +{%- set file = zone_data.file %} +{%- set zone = zone|replace(".txt", "") %} +{%- else %} +{%- set file = salt['pillar.get']("bind:available_zones:" + zone + ":file", false) %} +{%- endif %} +{%- set zone_records = salt['pillar.get']('bind:available_zones:' + zone + ':records', {}) %} +{%- if salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse') %} +{%- do generate_reverse(zone_records, salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:net'), salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:for_zones'), salt['pillar.get']('bind:available_zones', {})) %} +{%- endif %} {# If we define RRs in pillar, we use the internal template to generate the zone file otherwise, we fallback to the old behaviour and use the declared file #} -{%- set zone_source = 'salt://bind/files/zone.jinja' if zone_records != {} else 'salt://' ~ map.zones_source_dir ~ '/' ~ file %} -{%- set serial_auto = salt['pillar.get']('bind:available_zones:' + zone + ':soa:serial', '') == 'auto' %} -{% if file and zone_data['type'] == 'master' -%} +{%- set zone_source = 'salt://bind/files/zone.jinja' if zone_records != {} else 'salt://' ~ map.zones_source_dir ~ '/' ~ file %} +{%- set serial_auto = salt['pillar.get']('bind:available_zones:' + zone + ':soa:serial', '') == 'auto' %} +{% if file and zone_data['type'] == 'master' -%} zones{{ dash_view }}-{{ zone }}{{ '.include' if serial_auto else ''}}: file.managed: - name: {{ zones_directory }}/{{ file }}{{ '.include' if serial_auto else ''}} From e6636fe91522ec42a5a9e2bd1e13adf634f860f9 Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Fri, 16 Nov 2018 08:48:51 -0500 Subject: [PATCH 11/14] Fix variable set. Set zone based on file with the .txt extension removed. --- bind/config.sls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bind/config.sls b/bind/config.sls index 522f198..8b2658d 100644 --- a/bind/config.sls +++ b/bind/config.sls @@ -207,7 +207,7 @@ bind_rndc_client_config: {%- for zone, zone_data in view_data.get('configured_zones', {})|dictsort -%} {%- if 'file' in zone_data %} {%- set file = zone_data.file %} -{%- set zone = zone|replace(".txt", "") %} +{%- set zone = file|replace(".txt", "") %} {%- else %} {%- set file = salt['pillar.get']("bind:available_zones:" + zone + ":file", false) %} {%- endif %} From 3151899e0676fef8c7a7bec1584b7812a7d2c48b Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Fri, 16 Nov 2018 11:04:01 -0500 Subject: [PATCH 12/14] Update README.rst Add paragraph about using views. --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index dba22c4..ed64582 100644 --- a/README.rst +++ b/README.rst @@ -86,6 +86,13 @@ declared. `bind:available_zones::file` should point to an existing zone file that will be **sourced** by the formula. +Using Views +----------- + +Using views introduces some restrictions by the BIND server in that once you have views defined, ALL of your zones have to be served via a view. You cannot have any zones defined outside of a view. + +If you want multiple views to serve the same zone but with different record sets, follow the example in pillar-with-views.example to set this up. The key to this is the 'file' argument in the view configuration that allows you to set the view's configured_zone to a zone that you define underneath 'available_zones'. Without specifying this 'file' argument, your views cannot serve the same zone; they will instead serve a zone that matches the name of the view. + External zone files ------------------- From bc48510abcce660b3d8fcfbbc55768849fd3028b Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Fri, 16 Nov 2018 11:09:02 -0500 Subject: [PATCH 13/14] Update pillar-with-views.example Add some more comments for explanation. --- pillar-with-views.example | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pillar-with-views.example b/pillar-with-views.example index bb5bdb9..93f20c6 100644 --- a/pillar-with-views.example +++ b/pillar-with-views.example @@ -1,18 +1,19 @@ bind: configured_acls: # We have an internal ACL restricted to our - internal: # private IP ranges. - - 127.0.0.0/8 # In this case, an ACL for external isn't needed - - 10.0.0.0/8 # as that view will be matched by 'any'. + internal: # private IP range. + - 10.0.0.0/8 # In this case, an ACL for external isn't needed + # as that view will be matched by 'any'. # Notice that there is no 'configured_zones' at this indentation level. # That is because when you are using views, the bind service forces all zones to be served via a view. # # Also note - any other zones defined in any other conf files will either need to be commented out, or # also served via a view using a file include. If you have other zones being served outside of a view, bind will - # fail to start. + # fail to start and give you an error message indicating this. You will likely find these externally-defined zones + # in /etc/named.conf and /etc/named.conf.local configured_views: - external: + external: # A view called 'external' to match anything except the 'internal' ACL. match_clients: - any # This will match anything, including the public internet. configured_zones: @@ -25,7 +26,7 @@ bind: notify: False dnssec: False - internal: + internal: # The 'internal' view that is restricted to the 'internal' ACL. match_clients: - internal # This will match only our ACL named 'internal'. configured_zones: @@ -39,7 +40,7 @@ bind: dnssec: False available_zones: - external.mydomain.com: # Beginning of the external zone definition. + external.mydomain.com: # Beginning of the 'external' zone definition. file: external.mydomain.com.txt # The file in which to save this zone's record set - matches the file # specified in the 'external' view. @@ -67,7 +68,7 @@ bind: login: portal.mydomain.com. dashboard: www.mydomain.com. - internal.mydomain.com: # Beginning of the internal zone definition. + internal.mydomain.com: # Beginning of the 'internal' zone definition. file: internal.mydomain.com.txt # The file in which to save this zone's record set - matches the file # specified in the 'internal' view. @@ -83,8 +84,8 @@ bind: ttl: 8600 # Optional. Not set by default records: # Records for the zone, grouped by type A: - portal: 10.0.0.10 - gateway: 10.0.0.11 + portal: 10.0.0.10 # Here we serve all private IPs as opposed to the public IPs + gateway: 10.0.0.11 # in the external zone. directory: 10.0.0.12 ns1: 10.0.0.13 www: 10.0.0.14 From 7e47c7658f3ec52c80f99e5f2d7096cd02b63f08 Mon Sep 17 00:00:00 2001 From: crux-capacitor Date: Wed, 14 Nov 2018 11:11:07 -0500 Subject: [PATCH 14/14] Squash commits Update named.conf.local.jinja Some reorganization of the format. In the for-loop that handles configured_views: - Add if-block on lines 124-128 to allow specifying a file for your view, rather than defaulting to the name of the specified zone. This allows multiple views to serve the same zone, but use a different file. Update pillar.example Add documentation and an example on specifying the file to be used for a view, as well as documented that you should not define the top-level 'configured_zones' key when using views. Small comment update. Add comment about using ACLs and views. Create pillar-with-views.example An example of the bind pillar that defines multiple views for internal and external record sets. This doesn't include the other portion of the pillar the defines the bind config - this is zones, views and ACLs only. The config portion is not affected by this. Add more comment clarification. Add comment explaining file name requirements. The filename must match the corresponding zone name (without the .txt extension) because the config.sls jinja logic uses the filename to match to the zone when setting zone_records. It also is hardcoded to replace ".txt" with "" in order to make this match work, and so .txt extension is required for the logic to work. Update config.sls Add logic to detect a file specified in a view, and match it to a zone under available_zones to enable creating that zone file. Revert back Made a bad commit. Update with the required logic. Added an if-block to test for the file argument in the zone_data, and if found, use that view and update the zone variable to match the zone defined under available_zones. Fix variable set. Set zone based on file with the .txt extension removed. Update README.rst Add paragraph about using views. Update pillar-with-views.example Add some more comments for explanation. --- README.rst | 7 +++ bind/config.sls | 25 ++++---- bind/files/named.conf.local.jinja | 52 +++++++++-------- pillar-with-views.example | 97 +++++++++++++++++++++++++++++++ pillar.example | 19 +++++- 5 files changed, 162 insertions(+), 38 deletions(-) create mode 100644 pillar-with-views.example diff --git a/README.rst b/README.rst index dba22c4..ed64582 100644 --- a/README.rst +++ b/README.rst @@ -86,6 +86,13 @@ declared. `bind:available_zones::file` should point to an existing zone file that will be **sourced** by the formula. +Using Views +----------- + +Using views introduces some restrictions by the BIND server in that once you have views defined, ALL of your zones have to be served via a view. You cannot have any zones defined outside of a view. + +If you want multiple views to serve the same zone but with different record sets, follow the example in pillar-with-views.example to set this up. The key to this is the 'file' argument in the view configuration that allows you to set the view's configured_zone to a zone that you define underneath 'available_zones'. Without specifying this 'file' argument, your views cannot serve the same zone; they will instead serve a zone that matches the name of the view. + External zone files ------------------- diff --git a/bind/config.sls b/bind/config.sls index 1394904..8b2658d 100644 --- a/bind/config.sls +++ b/bind/config.sls @@ -203,19 +203,24 @@ bind_rndc_client_config: {%- set views = {False: salt['pillar.get']('bind', {})} %}{# process non-view zones in the same loop #} {%- do views.update(salt['pillar.get']('bind:configured_views', {})) %} {%- for view, view_data in views|dictsort %} -{%- set dash_view = '-' + view if view else '' %} -{% for zone, zone_data in view_data.get('configured_zones', {})|dictsort -%} -{%- set file = salt['pillar.get']("bind:available_zones:" + zone + ":file", false) %} -{%- set zone_records = salt['pillar.get']('bind:available_zones:' + zone + ':records', {}) %} -{%- if salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse') %} -{%- do generate_reverse(zone_records, salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:net'), salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:for_zones'), salt['pillar.get']('bind:available_zones', {})) %} -{%- endif %} +{%- set dash_view = '-' + view if view else '' %} +{%- for zone, zone_data in view_data.get('configured_zones', {})|dictsort -%} +{%- if 'file' in zone_data %} +{%- set file = zone_data.file %} +{%- set zone = file|replace(".txt", "") %} +{%- else %} +{%- set file = salt['pillar.get']("bind:available_zones:" + zone + ":file", false) %} +{%- endif %} +{%- set zone_records = salt['pillar.get']('bind:available_zones:' + zone + ':records', {}) %} +{%- if salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse') %} +{%- do generate_reverse(zone_records, salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:net'), salt['pillar.get']('bind:available_zones:' + zone + ':generate_reverse:for_zones'), salt['pillar.get']('bind:available_zones', {})) %} +{%- endif %} {# If we define RRs in pillar, we use the internal template to generate the zone file otherwise, we fallback to the old behaviour and use the declared file #} -{%- set zone_source = 'salt://bind/files/zone.jinja' if zone_records != {} else 'salt://' ~ map.zones_source_dir ~ '/' ~ file %} -{%- set serial_auto = salt['pillar.get']('bind:available_zones:' + zone + ':soa:serial', '') == 'auto' %} -{% if file and zone_data['type'] == 'master' -%} +{%- set zone_source = 'salt://bind/files/zone.jinja' if zone_records != {} else 'salt://' ~ map.zones_source_dir ~ '/' ~ file %} +{%- set serial_auto = salt['pillar.get']('bind:available_zones:' + zone + ':soa:serial', '') == 'auto' %} +{% if file and zone_data['type'] == 'master' -%} zones{{ dash_view }}-{{ zone }}{{ '.include' if serial_auto else ''}}: file.managed: - name: {{ zones_directory }}/{{ file }}{{ '.include' if serial_auto else ''}} diff --git a/bind/files/named.conf.local.jinja b/bind/files/named.conf.local.jinja index 3b20820..cf5675c 100644 --- a/bind/files/named.conf.local.jinja +++ b/bind/files/named.conf.local.jinja @@ -7,6 +7,22 @@ // organization //include "/etc/bind/zones.rfc1918"; +{% for name, data in salt['pillar.get']('bind:configured_acls', {})|dictsort %} +acl {{ name }} { + {%- for d in data %} + {{ d }}; + {%- endfor %} +}; +{%- endfor %} + +{%- for name, data in salt['pillar.get']('bind:configured_masters', {})|dictsort %} +masters {{ name }} { + {%- for d in data %} + {{ d }}; + {%- endfor %} +}; +{%- endfor %} + {%- macro zone(key, args, file, masters) %} zone "{{ key }}" { type {{ args['type'] }}; @@ -92,23 +108,26 @@ include "{{ map.default_zones_config }}"; {{ zone(key, args, file, masters) }} {% endfor %} -{% for view, view_data in salt['pillar.get']('bind:configured_views', {})|dictsort %} +{%- for view, view_data in salt['pillar.get']('bind:configured_views', {})|dictsort %} view {{ view }} { {%- if view == 'default' %} include "{{ map.default_zones_config }}"; {%- endif %} - -match-clients { + match-clients { {%- for acl in view_data.get('match_clients', {}) %} - {{ acl }}; + {{ acl }}; {%- endfor %} -}; + }; -{% for key, args in view_data.get('configured_zones', {})|dictsort -%} -{%- set file = salt['pillar.get']("bind:available_zones:" + key + ":file") %} +{%- for key, args in view_data.get('configured_zones', {})|dictsort -%} +{%- if 'file' in args %} +{%- set file = args.file %} +{%- else %} +{%- set file = salt['pillar.get']("bind:available_zones:" + key + ":file") %} +{%- endif %} {%- set masters = salt['pillar.get']("bind:available_zones:" + key + ":masters") %} - {{ zone(key, args, file, masters) }} + {{ zone(key, args, file, masters) }} {%- endfor %} }; {%- endfor %} @@ -163,20 +182,3 @@ statistics-channels { {%- endfor %} }; {%- endif %} - - -{%- for name, data in salt['pillar.get']('bind:configured_acls', {})|dictsort %} -acl {{ name }} { - {%- for d in data %} - {{ d }}; - {%- endfor %} -}; -{%- endfor %} - -{%- for name, data in salt['pillar.get']('bind:configured_masters', {})|dictsort %} -masters {{ name }} { - {%- for d in data %} - {{ d }}; - {%- endfor %} -}; -{%- endfor %} diff --git a/pillar-with-views.example b/pillar-with-views.example new file mode 100644 index 0000000..93f20c6 --- /dev/null +++ b/pillar-with-views.example @@ -0,0 +1,97 @@ +bind: + configured_acls: # We have an internal ACL restricted to our + internal: # private IP range. + - 10.0.0.0/8 # In this case, an ACL for external isn't needed + # as that view will be matched by 'any'. + + # Notice that there is no 'configured_zones' at this indentation level. + # That is because when you are using views, the bind service forces all zones to be served via a view. + # + # Also note - any other zones defined in any other conf files will either need to be commented out, or + # also served via a view using a file include. If you have other zones being served outside of a view, bind will + # fail to start and give you an error message indicating this. You will likely find these externally-defined zones + # in /etc/named.conf and /etc/named.conf.local + + configured_views: + external: # A view called 'external' to match anything except the 'internal' ACL. + match_clients: + - any # This will match anything, including the public internet. + configured_zones: + mydomain.com: # Notice that this value matches on both views. + type: master + file: external.mydomain.com.txt # Specify the file to be used, which must match the file + recursion: yes # name of the zone below under available_zones. + # This filename also must match the corresponding zone name + # without the .txt extension (and be sure to use .txt as the extension). + notify: False + dnssec: False + + internal: # The 'internal' view that is restricted to the 'internal' ACL. + match_clients: + - internal # This will match only our ACL named 'internal'. + configured_zones: + mydomain.com: # Same as above - both views will serve the same zone. + type: master + file: internal.mydomain.com.txt # Different file - matches the internal zone below. + # Again, this filename must match the corresponding zone name + # without the .txt extension (and be sure to use .txt as the extension). + recursion: yes + notify: False + dnssec: False + + available_zones: + external.mydomain.com: # Beginning of the 'external' zone definition. + file: external.mydomain.com.txt # The file in which to save this zone's record set - matches the file + # specified in the 'external' view. + + soa: # Declare the SOA RRs for the zone + ns: ns1.external.mydomain.com # Required + contact: hostmaster@mydomain.com # Required + serial: auto # Alternatively, autoupdate serial on each change + class: IN # Optional. Default: IN + refresh: 8600 # Optional. Default: 12h + retry: 900 # Optional. Default: 15m + expiry: 86000 # Optional. Default: 2w + nxdomain: 500 # Optional. Default: 1m + ttl: 8600 # Optional. Not set by default + records: # Records for the zone, grouped by type + A: + portal: 50.60.70.80 + gateway: 50.60.70.81 + directory: 50.60.70.82 + ns1: 50.60.70.83 + www: 50.60.70.84 + NS: + '@': + - ns1 + CNAME: + login: portal.mydomain.com. + dashboard: www.mydomain.com. + + internal.mydomain.com: # Beginning of the 'internal' zone definition. + file: internal.mydomain.com.txt # The file in which to save this zone's record set - matches the file + # specified in the 'internal' view. + + soa: # Declare the SOA RRs for the zone + ns: ns1.mydomain.com # Required + contact: hostmaster@mydomain.com # Required + serial: auto # Alternatively, autoupdate serial on each change + class: IN # Optional. Default: IN + refresh: 8600 # Optional. Default: 12h + retry: 900 # Optional. Default: 15m + expiry: 86000 # Optional. Default: 2w + nxdomain: 500 # Optional. Default: 1m + ttl: 8600 # Optional. Not set by default + records: # Records for the zone, grouped by type + A: + portal: 10.0.0.10 # Here we serve all private IPs as opposed to the public IPs + gateway: 10.0.0.11 # in the external zone. + directory: 10.0.0.12 + ns1: 10.0.0.13 + www: 10.0.0.14 + NS: + '@': + - ns1 + CNAME: + login: portal.mydomain.com. + dashboard: www.mydomain.com. diff --git a/pillar.example b/pillar.example index a2e29f0..b4a545c 100644 --- a/pillar.example +++ b/pillar.example @@ -231,7 +231,9 @@ bind: notify: False # Don't notify any NS RRs of any changes to zone also-notify: # Do notify these IP addresses (pointless as - 1.1.1.1 # notify has been set to no) - - 2.2.2.2 + - 2.2.2.2 # If using views, do not define configured_zones + # at this indentation level - define it using the sub-key + # of your view under configured_views. sub.domain2.com: # Domain zone with DNSSEC type: master # We're the master of this zone @@ -279,14 +281,25 @@ bind: configured_zones: # Zones that our view is applicable to my.zone: # We've defined a new zone in here type: master - notify: False + file: example.com.txt # Optional: specify the zone file to be used for this view, + # otherwise it will default to the file matching the name of the zone that you + # specify here (which must match a zone under 'available_zones'. + # The file name must match what you have entered for 'file' in the zone under + # 'available_zones'. + # This allows you to define multiple views that serve the same zone, but + # serve a different record set in each. + # If doing this, you need to configure the zones and their record sets + # underneath the 'available_zones' section. + notify: False update_policy: # A given update policy - "grant core_dhcp name dns_entry_allowed_to_update. ANY" configured_acls: # And now for some ACLs my_net: # Our ACL's name - 127.0.0.0/8 # And the applicable IP addresses - - 10.20.0.0/16 + - 10.20.0.0/16 # If using views, you need to create an ACL per view to differentiate + # who accesses the view, and then specify the appropriate ACL name under + # the 'match_clients' sub-key of your view. ### Define zone records in pillar ### bind: