diff --git a/.fixtures.yml b/.fixtures.yml index 76c7fdb4..98776782 100644 --- a/.fixtures.yml +++ b/.fixtures.yml @@ -3,3 +3,4 @@ fixtures: concat: "https://github.com/puppetlabs/puppetlabs-concat.git" stdlib: "https://github.com/puppetlabs/puppetlabs-stdlib.git" systemd: "https://github.com/voxpupuli/puppet-systemd.git" + extlib: "https://github.com/voxpupuli/puppet-extlib.git" diff --git a/REFERENCE.md b/REFERENCE.md index 6c5c78fc..62a1a3ac 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -20,6 +20,7 @@ ### Functions * [`unbound::print_config`](#unbound--print_config): Print a configuration value if it is defined and the version is supported +* [`unbound::split_txt`](#unbound--split_txt): function to split TXT records. Long TXT records must be broken into strings of 255 characters as per RFC 4408 ### Data types @@ -2357,7 +2358,7 @@ Custom type Unbound::Local_zone_type. ##### `config_file` -Data type: `Any` +Data type: `Stdlib::Absolutepath` name of configuration file. @@ -2543,7 +2544,7 @@ Type: Puppet Language Print a configuration value if it is defined and the version is supported -#### `unbound::print_config(String[1] $name, Optional[Variant[Boolean, Integer, String, Array[String, 1]]] $value = undef, Optional[String[1]] $version = undef)` +#### `unbound::print_config(String[1] $name, Optional[Variant[Boolean, Integer, String, Array[String]]] $value = undef, Optional[String[1]] $version = undef)` The unbound::print_config function. @@ -2557,7 +2558,7 @@ the config item name ##### `value` -Data type: `Optional[Variant[Boolean, Integer, String, Array[String, 1]]]` +Data type: `Optional[Variant[Boolean, Integer, String, Array[String]]]` the config item value @@ -2567,6 +2568,24 @@ Data type: `Optional[String[1]]` the version when the config item was introduced +### `unbound::split_txt` + +Type: Puppet Language + +function to split TXT records. Long TXT records must be broken into strings of 255 characters as per RFC 4408 + +#### `unbound::split_txt(String[1] $data)` + +The unbound::split_txt function. + +Returns: `String[1]` A string of 255 character strings + +##### `data` + +Data type: `String[1]` + +A TXT record to split + ## Data types ### `Unbound::Access_control` diff --git a/functions/print_config.pp b/functions/print_config.pp index c659143e..40be065d 100644 --- a/functions/print_config.pp +++ b/functions/print_config.pp @@ -5,13 +5,16 @@ # @return the config item as a string or an empty string if the version is not supported function unbound::print_config ( String[1] $name, - Optional[Variant[Boolean, Integer, String, Array[String, 1]]] $value = undef, + Optional[Variant[Boolean, Integer, String, Array[String]]] $value = undef, Optional[String[1]] $version = undef, ) >> String { $unbound_version = $facts['unbound_version'].lest || { '0.a' } if ($value =~ Undef or ($version =~ NotUndef and versioncmp($unbound_version, $version) < 0)) { return '' } + if $value =~ Array and $value.empty { + return '' + } $value ? { String => " ${name}: \"${value}\"", Integer => " ${name}: ${value}", diff --git a/functions/split_txt.pp b/functions/split_txt.pp new file mode 100644 index 00000000..60e61f2a --- /dev/null +++ b/functions/split_txt.pp @@ -0,0 +1,8 @@ +# @summary function to split TXT records. Long TXT records must be broken into strings of 255 characters as per RFC 4408 +# @param data A TXT record to split +# @return A string of 255 character strings +function unbound::split_txt( + String[1] $data +) >> String[1] { + $data.slice(255).map |$slice| { "\"${slice.join}\"" }.join +} diff --git a/manifests/dnstap.pp b/manifests/dnstap.pp index 5ec6da77..44abea7b 100644 --- a/manifests/dnstap.pp +++ b/manifests/dnstap.pp @@ -98,7 +98,7 @@ concat::fragment { 'unbound-dnstap': order => '20', target => $unbound::config_file, - content => $config.split("\n").filter |$x| { !$x.empty }.join("\n"), + content => $config.extlib::remove_blank_lines(), } } } diff --git a/manifests/forward.pp b/manifests/forward.pp index f383bb05..8308101a 100644 --- a/manifests/forward.pp +++ b/manifests/forward.pp @@ -28,9 +28,18 @@ Pattern[/yes|no/] $forward_tls_upstream = 'no', $config_file = $unbound::config_file, ) { + $content = @("CONTENT") + forward-zone: + ${unbound::print_config('name', $zone)} + ${unbound::print_config('forward-addr', $address)} + ${unbound::print_config('forward-host', $host)} + ${unbound::print_config('forward-first', $forward_first)} + ${unbound::print_config('forward-ssl-upstream', $forward_ssl_upstream)} + ${unbound::print_config('forward-tls-upstream', $forward_tls_upstream)} + | CONTENT concat::fragment { "unbound-forward-${name}": order => '20', target => $config_file, - content => template('unbound/forward.erb'), + content => $content.extlib::remove_blank_lines(), } } diff --git a/manifests/localzone.pp b/manifests/localzone.pp index 2f50cc89..ccfc16fb 100644 --- a/manifests/localzone.pp +++ b/manifests/localzone.pp @@ -26,15 +26,34 @@ # @param template_name Use a custom template. # define unbound::localzone ( - Unbound::Local_zone_type $type, - String $zone = $name, - $config_file = $unbound::config_file, - Array[Unbound::Resource_record_type] $local_data = [], - String $template_name = 'unbound/local_zone.erb' + Unbound::Local_zone_type $type, + String $zone = $name, + Stdlib::Absolutepath $config_file = $unbound::config_file, + String $template_name = 'unbound/local_zone.erb', + Array[Unbound::Resource_record_type] $local_data = [], ) { + # Loop through the local_data hash and create ablock of local-data strings with the correctly formated + # local-data lines which are ued in the final content block below. + # local-data: 'api.test.com 15 300 IN A 192.0.2.1 + $records = $local_data.map |$record| { + $data = $record['data'] + $_data = $record['type'] ? { + 'TXT' => $data.unbound::split_txt(), + default => $data, + } + $record_txt = "${record['name']} ${record['ttl']} ${record['class']} ${record['type']} ${_data}".regsubst( + /\s+/, ' ', 'G' + ) # Remove multiple spaces + " local-data: '${record_txt}'" + }.join("\n") + $content = @("CONTENT") + server: + local-zone: "${zone}" ${type} + ${records} + | CONTENT concat::fragment { "unbound-localzone-${name}": order => '06', target => $config_file, - content => template($template_name), + content => $content, } } diff --git a/manifests/remote.pp b/manifests/remote.pp index ed11a669..48f9dc73 100644 --- a/manifests/remote.pp +++ b/manifests/remote.pp @@ -50,10 +50,24 @@ $config_file = $unbound::config_file, $control_setup_path = $unbound::control_setup_path, ) { + $_tls_config = @("CONFIG") + ${unbound::print_config('server-key-file', $server_key_file)} + ${unbound::print_config('server-cert-file', $server_cert_file)} + ${unbound::print_config('control-key-file', $control_key_file)} + ${unbound::print_config('control-cert-file', $control_cert_file)} + | CONFIG + $tls_config = $control_use_cert.bool2str($_tls_config, '') + $content = @("CONFIG") + remote-control: + ${unbound::print_config('control-enable', $enable)} + ${unbound::print_config('control-interface', $interface)} + ${unbound::print_config('control-port', $port)} + ${tls_config} + | CONFIG concat::fragment { 'unbound-remote': order => '10', target => $config_file, - content => template('unbound/remote.erb'), + content => $content.extlib::remove_blank_lines(), } unless $control_setup_path.empty { diff --git a/manifests/stub.pp b/manifests/stub.pp index bb619c29..735cd74a 100644 --- a/manifests/stub.pp +++ b/manifests/stub.pp @@ -20,7 +20,7 @@ # Controls 'stub-first' stub zone option. # If true, a query that fails with the stub clause is attempted again # without the stub clause. -# @param type +# @param type # can be 'deny', 'refuse', 'static', 'transparent', 'typetransparent', 'redirect' or 'nodefault'. # @param config_file Name of the unbound config file # @@ -37,10 +37,18 @@ ) { include unbound $_config_file = pick($config_file, $unbound::config_file) + $content = @("CONFIG") + stub-zone: + ${unbound::print_config('name', $name)} + ${unbound::print_config('stub-addr', $address)} + ${unbound::print_config('stub-host', $nameservers)} + ${unbound::print_config('stub-first', $stub_first)} + ${unbound::print_config('stub-no-cache', $no_cache)} + | CONFIG concat::fragment { "unbound-stub-${name}": order => '15', target => $_config_file, - content => template('unbound/stub.erb'), + content => $content.extlib::remove_blank_lines(), } if str2bool($insecure) == true { diff --git a/metadata.json b/metadata.json index d92f131e..e14fe7f7 100644 --- a/metadata.json +++ b/metadata.json @@ -123,6 +123,10 @@ { "name": "puppet/systemd", "version_requirement": ">= 6.3.0 < 9.0.0" + }, + { + "name": "puppet/extlib", + "version_requirement": ">= 7.4.0 < 8.0.0" } ] } diff --git a/spec/classes/init_spec.rb b/spec/classes/init_spec.rb index abd5d4b9..f47fe4ee 100644 --- a/spec/classes/init_spec.rb +++ b/spec/classes/init_spec.rb @@ -829,7 +829,14 @@ it do expect(subject).to contain_concat__fragment('unbound-stub-example-stub.com').with_content( - %r{^stub-zone:\n name: "example-stub.com"\n stub-addr: 10.0.0.1\n stub-addr: 10.0.0.2} + %r{ + ^stub-zone: + \s+name:\s"example-stub.com" + \s+stub-addr:\s"10.0.0.1" + \s+stub-addr:\s"10.0.0.2" + \s+stub-first:\sno + \s+stub-no-cache:\sno + }x ) end end @@ -843,7 +850,15 @@ it do expect(subject).to contain_concat__fragment('unbound-forward-example-forward.com').with_content( - %r{^forward-zone:\n name: "example-forward.com"\n forward-addr: 10.0.0.1\n forward-addr: 10.0.0.2\n forward-first: yes\n forward-ssl-upstream: yes} + %r{ + ^forward-zone: + \s+name:\s"example-forward.com" + \s+forward-addr:\s"10.0.0.1" + \s+forward-addr:\s"10.0.0.2" + \s+forward-first:\s"yes" + \s+forward-ssl-upstream:\s"yes" + \s+forward-tls-upstream:\s"no" + }x ) end end diff --git a/spec/classes/remote_spec.rb b/spec/classes/remote_spec.rb index 8cbcbb34..0d9b03c8 100644 --- a/spec/classes/remote_spec.rb +++ b/spec/classes/remote_spec.rb @@ -29,13 +29,13 @@ %r{ ^remote-control: \s+control-enable:\sno - \s+control-interface:\s::1 - \s+control-interface:\s127.0.0.1 + \s+control-interface:\s"::1" + \s+control-interface:\s"127.0.0.1" \s+control-port:\s8953 - \s+server-key-file:\s/etc/unbound/unbound_server.key - \s+server-cert-file:\s/etc/unbound/unbound_server.pem - \s+control-key-file:\s/etc/unbound/unbound_control.key - \s+control-cert-file:\s/etc/unbound/unbound_control.pem + \s+server-key-file:\s"/etc/unbound/unbound_server.key" + \s+server-cert-file:\s"/etc/unbound/unbound_server.pem" + \s+control-key-file:\s"/etc/unbound/unbound_control.key" + \s+control-cert-file:\s"/etc/unbound/unbound_control.pem" }x ) end @@ -62,7 +62,7 @@ it do is_expected.to contain_concat__fragment('unbound-remote').with_content( - %r{control-interface:\s192.0.2.42} + %r{control-interface:\s"192.0.2.42"} ) end end diff --git a/spec/defines/stub_spec.rb b/spec/defines/stub_spec.rb index 097b4022..aa54c3f4 100644 --- a/spec/defines/stub_spec.rb +++ b/spec/defines/stub_spec.rb @@ -20,11 +20,13 @@ it { is_expected.to contain_unbound__stub('lab.example.com') } it { - expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with( - content: <<~ZONE + expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with_content( + <<~ZONE stub-zone: name: "lab.example.com" - stub-addr: ::1 + stub-addr: "::1" + stub-first: no + stub-no-cache: no ZONE ) } @@ -42,13 +44,15 @@ it { is_expected.to contain_unbound__stub('lab.example.com') } it { - expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with( - content: <<~ZONE + expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with_content( + <<~ZONE stub-zone: name: "lab.example.com" - stub-addr: 10.0.0.10@10053 - stub-host: ns1.example.com - stub-host: ns2.example.com + stub-addr: "10.0.0.10@10053" + stub-host: "ns1.example.com" + stub-host: "ns2.example.com" + stub-first: no + stub-no-cache: no ZONE ) } @@ -66,11 +70,12 @@ it { is_expected.to contain_unbound__stub('lab.example.com') } it { - expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with( - content: <<~ZONE + expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with_content( + <<~ZONE stub-zone: name: "lab.example.com" - stub-addr: ::1 + stub-addr: "::1" + stub-first: no stub-no-cache: yes ZONE ) @@ -89,12 +94,13 @@ it { is_expected.to contain_unbound__stub('lab.example.com') } it { - expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with( - content: <<~ZONE + expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with_content( + <<~ZONE stub-zone: name: "lab.example.com" - stub-addr: ::1 + stub-addr: "::1" stub-first: yes + stub-no-cache: no ZONE ) } @@ -112,11 +118,12 @@ it { is_expected.to contain_unbound__stub('lab.example.com') } it { - expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with( - content: <<~ZONE + expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with_content( + <<~ZONE stub-zone: name: "lab.example.com" - stub-addr: ::1 + stub-addr: "::1" + stub-first: no stub-no-cache: yes ZONE ) diff --git a/spec/functions/split_txt_spec.rb b/spec/functions/split_txt_spec.rb new file mode 100644 index 00000000..a795058c --- /dev/null +++ b/spec/functions/split_txt_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' +input = "Long TXT Record #{'X' * 255}" +output = "\"Long TXT Record #{'X' * 239}\"\"#{'X' * 16}\"" + +describe 'unbound::split_txt' do + it { is_expected.to run.with_params(input).and_return(output) } +end diff --git a/templates/forward.erb b/templates/forward.erb deleted file mode 100644 index 9312ce74..00000000 --- a/templates/forward.erb +++ /dev/null @@ -1,17 +0,0 @@ -forward-zone: - name: "<%= @zone %>" -<% Array(@address).each do |addr| -%> - forward-addr: <%= addr %> -<% end -%> -<% Array(@host).each do |h| -%> - forward-host: <%= h %> -<% end -%> -<% if @forward_first != 'no' -%> - forward-first: <%= @forward_first %> -<% end -%> -<% if @forward_ssl_upstream != 'no' -%> - forward-ssl-upstream: <%= @forward_ssl_upstream %> -<% end -%> -<% if @forward_tls_upstream != 'no' -%> - forward-tls-upstream: <%= @forward_tls_upstream %> -<% end -%> diff --git a/templates/local_zone.erb b/templates/local_zone.erb deleted file mode 100644 index d4fd789f..00000000 --- a/templates/local_zone.erb +++ /dev/null @@ -1,15 +0,0 @@ -server: - local-zone: "<%= @zone %>" <%= @type %> - <%- @local_data.each do |resource_record| -%> - <%- rr = resource_record['name'] -%> - <%- rr = "#{rr} #{resource_record['ttl']}" if resource_record['ttl'] -%> - <%- rr = "#{rr} #{resource_record['class']}" if resource_record['class'] -%> - <%- rr = "#{rr} #{resource_record['type']}" -%> - <%- if resource_record['type'] != 'TXT' -%> - <%- rr = "#{rr} #{resource_record['data']}" -%> - local-data: "<%= rr %>" - <%- else -%> - <%- rr = "#{rr} #{(resource_record['data'].scan /.{1,255}/).inject(''){|r, s| "#{r}\"#{s}\""}}" -%> - local-data: '<%= rr %>' - <%- end -%> - <%- end -%> diff --git a/templates/remote.erb b/templates/remote.erb deleted file mode 100644 index 40b676ac..00000000 --- a/templates/remote.erb +++ /dev/null @@ -1,25 +0,0 @@ -remote-control: -<% if @enable -%> - control-enable: yes -<% else -%> - control-enable: no -<% end -%> -<% @interface.each do |int| -%> - control-interface: <%= int %> -<% end -%> - control-port: <%= @port %> - -<% if @control_use_cert -%> -<% if @server_key_file -%> - server-key-file: <%= @server_key_file %> -<% end -%> -<% if @server_cert_file -%> - server-cert-file: <%= @server_cert_file %> -<% end -%> -<% if @control_key_file -%> - control-key-file: <%= @control_key_file %> -<% end -%> -<% if @control_cert_file -%> - control-cert-file: <%= @control_cert_file %> -<% end -%> -<% end -%> diff --git a/templates/stub.erb b/templates/stub.erb deleted file mode 100644 index fbc9ccc2..00000000 --- a/templates/stub.erb +++ /dev/null @@ -1,14 +0,0 @@ -stub-zone: - name: "<%= @name %>" -<% [@address].flatten.each do |addr| -%> - stub-addr: <%= addr %> -<% end -%> -<% @nameservers.each do |host| -%> - stub-host: <%= host %> -<% end -%> -<% if @stub_first == 'true' or @stub_first == true -%> - stub-first: yes -<% end -%> -<% if @no_cache == 'true' or @no_cache == true -%> - stub-no-cache: yes -<% end -%>