diff --git a/README.rst b/README.rst index dc7c4ae..5e9043e 100644 --- a/README.rst +++ b/README.rst @@ -24,9 +24,39 @@ Install the bind package and start the bind service. --------------- Manage the bind configuration file. -Zone files are not generated by this state -rather than taken from `salt://zones`. -See `pillar.example` for how to overwrite +This state can generate some basic zone files if a `records` entry is found in the +`available_zones`' declaration for the zone (see `pillar.example` for how to write these) + +Example Pillar +============== + +.. code:: yaml + + bind: + configured_zones: + example.com: + type: master + notify: False + available_zones: + example.com: + file: example.com.txt + soa: + ns: ns1.example.com # Required + contact: hostmaster.example.com # Required + serial: 2017041001 # Required + records: # Records for the zone, grouped by type + A: + mx1: # A RR with multiple values can + - 1.2.3.228 # be written as an array + - 1.2.3.229 + cat: 2.3.4.188 + rat: 1.2.3.231 + live: 1.2.3.236 + +See *bind/pillar.example* for a more complete example. + +On the other hand, if no `records` entry exists, the zone file is not generated by this state +rather than taken from `salt://zones`. See `pillar.example` for how to overwrite this URL. Example Pillar diff --git a/bind/config.sls b/bind/config.sls index 78698e6..86f437e 100644 --- a/bind/config.sls +++ b/bind/config.sls @@ -134,12 +134,22 @@ bind_default_zones: {% for zone, zone_data in salt['pillar.get']('bind:configured_zones', {}).items() -%} {%- set file = salt['pillar.get']("bind:available_zones:" + zone + ":file", zone_data.get('file')) %} +{%- set zone_records = salt['pillar.get']('bind:available_zones:' + zone + ':records', {}) %} +{# 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 %} {% if file and zone_data['type'] == "master" -%} zones-{{ zone }}: file.managed: - name: {{ map.named_directory }}/{{ file }} - - source: 'salt://{{ map.zones_source_dir }}/{{ file }}' + - source: {{ zone_source }} - template: jinja + {% if zone_records != {} %} + - context: + soa: {{ salt['pillar.get']("bind:available_zones:" + zone + ":soa") }} + records: {{ zone_records }} + {% 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') }} @@ -163,12 +173,22 @@ signed-{{ zone }}: {%- for view, view_data in salt['pillar.get']('bind:configured_views', {}).items() %} {% for zone, zone_data in view_data.get('configured_zones', {}).items() -%} {%- set file = salt['pillar.get']("bind:available_zones:" + zone + ":file", zone_data.get('file')) %} -{% if file and zone_data['type'] == "master" -%} +{%- set zone_records = salt['pillar.get']('bind:available_zones:' + zone + ':records', {}) %} +{# 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/zone.jinja' if zone_records != {} else 'salt://' ~ map.zones_source_dir ~ '/' ~ file %} +{% if file and zone_data['type'] == 'master' -%} zones-{{ view }}-{{ zone }}: file.managed: - name: {{ map.named_directory }}/{{ file }} - - source: 'salt://{{ map.zones_source_dir }}/{{ file }}' + - source: {{ zone_source }} - template: jinja + {% if zone_records != {} %} + - context: + soa: {{ salt['pillar.get']("bind:available_zones:" + zone + ":soa") }} + records: {{ zone_records }} + {% 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') }} diff --git a/bind/files/zone.jinja b/bind/files/zone.jinja new file mode 100644 index 0000000..c46b23e --- /dev/null +++ b/bind/files/zone.jinja @@ -0,0 +1,38 @@ +; +; This file is managed/autogenerated by Salt, do not edit by hand!! +; Modify the values passed to the bind pillar instead. +; +{%- set zone_serial = soa['serial'] %} +{%- set zone_ns = soa['ns'] %} +{%- set zone_contact = soa['contact'] %} +{%- set zone_class = soa['class'] if soa['class'] is defined else 'IN' %} +{%- set zone_refresh = soa['refresh'] if soa['refresh'] is defined else '12h' %} +{%- set zone_retry = soa['retry'] if soa['retry'] is defined else '15m' %} +{%- set zone_expiry = soa['expiry'] if soa['expiry'] is defined else '2w' %} +{%- set zone_nxdomain = soa['nxdomain'] if soa['nxdomain'] is defined else '1m' %} + +{%- if soa['ttl'] is defined -%} +$TTL {{ soa['ttl'] }} +{%- endif %} +@ {{ zone_class }} SOA {{ zone_ns }} {{ zone_contact }} ( + {{ zone_serial }} ; serial + {{ zone_refresh }} ; refresh + {{ zone_retry }} ; retry + {{ zone_expiry }} ; expiry + {{ zone_nxdomain }} ; nxdomain ttl +); + +{% for type, rrs in records.iteritems() %} +; +; {{ type }} RRs +; +{%- for host, data in rrs.iteritems() %} +{%- if data is number or data is string %} +{{ host }} {{ type }} {{ data }} +{%- elif data is iterable %} +{%- for value in data %} +{{ host }} {{ type }} {{ value }} +{%- endfor %} +{%- endif %} +{%- endfor %} +{% endfor %} diff --git a/bind/map.jinja b/bind/map.jinja index 63252e6..0a7bf19 100644 --- a/bind/map.jinja +++ b/bind/map.jinja @@ -13,7 +13,7 @@ 'named_directory': '/var/cache/bind/zones', 'log_dir': '/var/log/bind9', 'log_mode': '644', - 'user': 'root', + 'user': 'bind', 'group': 'bind', 'mode': '644' }, diff --git a/pillar.example b/pillar.example index abe8d31..b1b9948 100644 --- a/pillar.example +++ b/pillar.example @@ -103,6 +103,44 @@ bind: - 127.0.0.0/8 # And the applicable IP addresses - 10.20.0.0/16 +### Define zone records in pillar ### +bind: + available_zones: + example.com: + file: example.com.txt + soa: # Declare the SOA RRs for the zone + ns: ns1.example.com # Required + contact: hostmaster.example.com # Required + serial: 2017041001 # Required + 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: + mx1: # A RR with multiple values can + - 1.2.3.228 # be written as an array + - 1.2.3.229 + cat: 2.3.4.188 + rat: 1.2.3.231 + live: 1.2.3.236 + NS: + '@': + - rat + - cat + CNAME: + ftp: cat.example.com. + www: cat.example.com. + mail: mx1.example.com. + smtp: mx1.example.com. + TXT: # Complex records can be expressed as strings + '@': + - '"some_value"' + - '"v=spf1 mx a ip4:1.2.3.4 ~all"' + _dmarc: '"v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; fo=1:d:s; adkim=r; aspf=r; pct=100; ri=86400"' + ### Externally defined Zones ### bind: available_zones: