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: