From 20d60098ea876a5fce5c2aebc455859fca65fca2 Mon Sep 17 00:00:00 2001 From: magior <2691798+magior@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:46:55 +0100 Subject: [PATCH 1/2] Add per-zone allow_transfer for primary zones --- REFERENCE.md | 21 +++++++++++- manifests/zone/primary.pp | 35 +++++++++++++------ spec/defines/zone/primary_spec.rb | 57 +++++++++++++++++++++++++++++++ templates/zone-primary.epp | 8 +++++ 4 files changed, 109 insertions(+), 12 deletions(-) diff --git a/REFERENCE.md b/REFERENCE.md index 51a1456..44a2404 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -2472,6 +2472,16 @@ bind::zone::primary { 'example.com': } ``` +##### Restrict zone transfers for a primary zone + +```puppet + +bind::zone::primary { 'example.com': + source => 'puppet:///modules/profile/example.com.zone', + allow_transfer => ['192.0.2.42'], +} +``` + ##### Use DNSSEC signing for a primary zone using a DNSSEC policy ```puppet @@ -2499,6 +2509,7 @@ bind::zone::primary { '_acme-challenge.example.com': The following parameters are available in the `bind::zone::primary` defined type: * [`also_notify`](#-bind--zone--primary--also_notify) +* [`allow_transfer`](#-bind--zone--primary--allow_transfer) * [`update_policy`](#-bind--zone--primary--update_policy) * [`dnssec_enable`](#-bind--zone--primary--dnssec_enable) * [`dnssec_dnskey_kskonly`](#-bind--zone--primary--dnssec_dnskey_kskonly) @@ -2530,6 +2541,15 @@ nameservers that are listed in the zone file. Default value: `[]` +##### `allow_transfer` + +Data type: `Array[String]` + +An array of ACL names or networks that are allowed to transfer zone +information for this zone. + +Default value: `[]` + ##### `update_policy` Data type: `Variant[Enum['local'],Array[String]]` @@ -3372,4 +3392,3 @@ Alias of `Enum['critical', 'error', 'warning', 'notice', 'info', 'debug', 'dynam Type to match allowed values for the zone class Alias of `Enum['IN', 'HS', 'CH']` - diff --git a/manifests/zone/primary.pp b/manifests/zone/primary.pp index 2a4554e..62f48a0 100644 --- a/manifests/zone/primary.pp +++ b/manifests/zone/primary.pp @@ -13,6 +13,13 @@ # source => 'puppet:///modules/profile/example.com.zone', # } # +# @example Restrict zone transfers for a primary zone +# +# bind::zone::primary { 'example.com': +# source => 'puppet:///modules/profile/example.com.zone', +# allow_transfer => ['192.0.2.42'], +# } +# # @example Use DNSSEC signing for a primary zone using a DNSSEC policy # # bind::zone::primary { 'example.com': @@ -33,6 +40,10 @@ # Secondary servers that should be notified in addition to the # nameservers that are listed in the zone file. # +# @param allow_transfer +# An array of ACL names or networks that are allowed to transfer zone +# information for this zone. +# # @param update_policy # Enable dynamic updates for the zone and define the update policy. This # can either be the string `local` or an array of strings. Using the string @@ -124,6 +135,7 @@ # define bind::zone::primary ( Array[String] $also_notify = [], + Array[String] $allow_transfer = [], Variant[Enum['local'],Array[String]] $update_policy = [], Optional[Boolean] $dnssec_enable = undef, Optional[Boolean] $dnssec_dnskey_kskonly = undef, @@ -257,17 +269,18 @@ } $params = { - 'zone' => $zone, - 'file' => $zonefile, - 'also_notify' => $also_notify, - 'notify' => $notify_secondaries, - 'statistics' => $zone_statistics, - 'update_policy' => $update_policy, - 'class' => $class, - 'comment' => $comment, - 'indent' => bool2str($bind::views_enable, ' ', ''), - 'zone_in_view' => ($view =~ NotUndef), - 'dnssec_params' => !empty(delete_undef_values($params_dnssec)), + 'zone' => $zone, + 'file' => $zonefile, + 'also_notify' => $also_notify, + 'allow_transfer' => $allow_transfer, + 'notify' => $notify_secondaries, + 'statistics' => $zone_statistics, + 'update_policy' => $update_policy, + 'class' => $class, + 'comment' => $comment, + 'indent' => bool2str($bind::views_enable, ' ', ''), + 'zone_in_view' => ($view =~ NotUndef), + 'dnssec_params' => !empty(delete_undef_values($params_dnssec)), } if $bind::views_enable { diff --git a/spec/defines/zone/primary_spec.rb b/spec/defines/zone/primary_spec.rb index ca11b8a..27b5c5d 100644 --- a/spec/defines/zone/primary_spec.rb +++ b/spec/defines/zone/primary_spec.rb @@ -375,6 +375,44 @@ } end + context 'with source => "/file", allow_transfer => ["any"]' do + let(:params) do + { source: '/file', allow_transfer: ['any'] } + end + + it { + is_expected.to contain_file('/var/lib/bind/primary/com') + is_expected.to contain_file('/var/lib/bind/primary/com/example') + is_expected.to contain_file('/var/lib/bind/primary/com/example/db.example.com') + + is_expected.to contain_exec('bind::reload::example.com') + + is_expected.to contain_concat__fragment('named.conf.zones-example.com') + .with_target('named.conf.zones') + .with_order('20') + .with_content(%r{allow-transfer {\n\s+any;\n\s+};}) + } + end + + context 'with source => "/file", allow_transfer => ["acl1", "192.0.2.42"]' do + let(:params) do + { source: '/file', allow_transfer: ['acl1', '192.0.2.42'] } + end + + it { + is_expected.to contain_file('/var/lib/bind/primary/com') + is_expected.to contain_file('/var/lib/bind/primary/com/example') + is_expected.to contain_file('/var/lib/bind/primary/com/example/db.example.com') + + is_expected.to contain_exec('bind::reload::example.com') + + is_expected.to contain_concat__fragment('named.conf.zones-example.com') + .with_target('named.conf.zones') + .with_order('20') + .with_content(%r{allow-transfer {\n\s+acl1;\n\s+192.0.2.42;\n\s+};}) + } + end + context 'with source => "/file", zone_statistics => true' do let(:params) do { source: '/file', zone_statistics: true } @@ -815,6 +853,25 @@ } end + context 'with view => "internal", source => "/file", allow_transfer => ["any"]' do + let(:params) do + { view: 'internal', source: '/file', allow_transfer: ['any'] } + end + + it { + is_expected.to contain_file('/var/lib/bind/primary/com') + is_expected.to contain_file('/var/lib/bind/primary/com/example') + is_expected.to contain_file('/var/lib/bind/primary/com/example/db.example.com') + + is_expected.to contain_exec('bind::reload::internal::example.com') + + is_expected.to contain_concat__fragment('named.conf.views-internal-50-example.com') + .with_target('named.conf.views') + .with_order('10') + .with_content(%r{allow-transfer {\n \s+any;\n \s+};}) + } + end + context 'with view => "internal", source => "/file", zone_statistics => true' do let(:params) do { view: 'internal', source: '/file', zone_statistics: true } diff --git a/templates/zone-primary.epp b/templates/zone-primary.epp index 64f7e91..5c8d8a5 100644 --- a/templates/zone-primary.epp +++ b/templates/zone-primary.epp @@ -29,6 +29,14 @@ <% } -%> <%= $indent %> }; <% } -%> +<% unless empty($allow_transfer) { -%> + +<%= $indent %> allow-transfer { +<% $allow_transfer.each |$item| { -%> +<%= $indent %> <%= $item -%>; +<% } -%> +<%= $indent %> }; +<% } -%> <% if $dnssec_params { -%> <% if $dnssec_enable =~ NotUndef { -%> From e0062761de9508d13d437921661eb6f237d6d76d Mon Sep 17 00:00:00 2001 From: magior <2691798+magior@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:35:31 +0100 Subject: [PATCH 2/2] Add custom view options support --- README.md | 24 ++++++++++++++++++++++ REFERENCE.md | 15 ++++++++++++++ manifests/view.pp | 10 ++++++++++ spec/defines/view_spec.rb | 42 +++++++++++++++++++++++++++++++++++++++ templates/view.epp | 4 ++++ 5 files changed, 95 insertions(+) diff --git a/README.md b/README.md index d5744ce..b73d37e 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,30 @@ bind::views: The defined types `bind::zone::primary` and `bind::zone::secondary` can be used to add zones to this view. +Global options that are not modeled as dedicated class parameters can be +set with `custom_options`. + +```puppet +class { 'bind': + custom_options => { + 'minimal-responses' => 'no-auth-recursive', + }, +} +``` + +The `bind::view` defined type supports the same pattern for per-view options: + +```puppet +bind::view { 'internal': + match_clients => [ 'localnets', ], + allow_query => [ 'localnets', ], + allow_recursion => [ 'localnets', ], + custom_options => { + 'minimal-responses' => true, + }, +} +``` + ## Reference See [REFERENCE.md](https://github.com/smoeding/puppet-bind/blob/master/REFERENCE.md) diff --git a/REFERENCE.md b/REFERENCE.md index 44a2404..f05aa1e 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -1997,6 +1997,7 @@ The following parameters are available in the `bind::view` defined type: * [`view`](#-bind--view--view) * [`order`](#-bind--view--order) * [`response_policies`](#-bind--view--response_policies) +* [`custom_options`](#-bind--view--custom_options) ##### `match_clients` @@ -2156,6 +2157,19 @@ An array of response policy zones. Default value: `[]` +##### `custom_options` + +Data type: `Hash[String,Data]` + +Additional config options that are not implemented as parameters of this +defined type can be set by a hash of custom options. Each key of the hash +will be added to the view block of the configuration. For string or numeric +values the value will be added as a normal option value. If the value is a +hash or an array it will be included as an additional block enclosed in +braces. + +Default value: `{}` + ### `bind::zone::forward` Manage a forward zone @@ -3392,3 +3406,4 @@ Alias of `Enum['critical', 'error', 'warning', 'notice', 'info', 'debug', 'dynam Type to match allowed values for the zone class Alias of `Enum['IN', 'HS', 'CH']` + diff --git a/manifests/view.pp b/manifests/view.pp index 80a4c43..77a7721 100644 --- a/manifests/view.pp +++ b/manifests/view.pp @@ -75,6 +75,14 @@ # @param response_policies # An array of response policy zones. # +# @param custom_options +# Additional config options that are not implemented as parameters of this +# defined type can be set by a hash of custom options. Each key of the hash +# will be added to the view block of the configuration. For string or numeric +# values the value will be added as a normal option value. If the value is a +# hash or an array it will be included as an additional block enclosed in +# braces. +# # define bind::view ( Array[String] $match_clients = ['any',], @@ -93,6 +101,7 @@ String $view = $name, String $order = '10', Array[String] $response_policies = [], + Hash[String,Data] $custom_options = {}, Optional[Boolean] $localhost_forward_enable = undef, Optional[Boolean] $localhost_reverse_enable = undef, ) { @@ -120,6 +129,7 @@ 'allow_query_cache_on' => $allow_query_cache_on, 'allow_transfer' => $allow_transfer, 'response_policies' => $response_policies, + 'custom_options' => bind::gencfg($custom_options, 2), } concat::fragment { "named.conf.views-${view}-00": diff --git a/spec/defines/view_spec.rb b/spec/defines/view_spec.rb index f54e7ff..aefc315 100644 --- a/spec/defines/view_spec.rb +++ b/spec/defines/view_spec.rb @@ -205,6 +205,48 @@ } end + context 'with custom_options => { "minimal-responses" => true }' do + let(:params) do + { custom_options: { 'minimal-responses' => true } } + end + + it { + is_expected.to contain_concat__fragment('named.conf.views-internal-00') + .with_target('named.conf.views') + .with_content("\nview \"internal\" {\n match-clients {\n any;\n };\n\n allow-query {\n any;\n };\n\n recursion yes;\n\n minimal-responses yes;\n") + .with_order('10') + + is_expected.to contain_concat__fragment('named.conf.views-internal-99') + .with_content('};') + } + end + + context 'with custom_options => { "minimal-responses" => "no-auth-recursive" }' do + let(:params) do + { custom_options: { 'minimal-responses' => 'no-auth-recursive' } } + end + + it { + is_expected.to contain_concat__fragment('named.conf.views-internal-00') + .with_target('named.conf.views') + .with_content("\nview \"internal\" {\n match-clients {\n any;\n };\n\n allow-query {\n any;\n };\n\n recursion yes;\n\n minimal-responses no-auth-recursive;\n") + .with_order('10') + } + end + + context 'with custom_options => { "sortlist" => ["localnets", "localhost"] }' do + let(:params) do + { custom_options: { 'sortlist' => ['localnets', 'localhost'] } } + end + + it { + is_expected.to contain_concat__fragment('named.conf.views-internal-00') + .with_target('named.conf.views') + .with_content("\nview \"internal\" {\n match-clients {\n any;\n };\n\n allow-query {\n any;\n };\n\n recursion yes;\n\n sortlist {\n localnets;\n localhost;\n };\n") + .with_order('10') + } + end + context 'with root_hints_enable => true' do let(:params) do { root_hints_enable: true } diff --git a/templates/view.epp b/templates/view.epp index 0a285a4..b9b6f60 100644 --- a/templates/view.epp +++ b/templates/view.epp @@ -83,3 +83,7 @@ view "<%= $view -%>" { <% } -%> }; <% } -%> +<% unless empty($custom_options) { -%> + +<%= $custom_options -%> +<% } -%>