diff --git a/README.md b/README.md index 5e2e461..5401e50 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,29 @@ class { 'powerdns': recursor => true, } ``` +### YAML config format + +Starting from Recursor `5.0` YAML syntax is supported. From release `5.2` `recursor.conf` file is parsed as YAML, unless `--enable-old-settings` flag is provided. The old configuration can be converted: + +``` +$ rec_control show-yaml path/to/recursor.conf +``` + +For detailed changes in syntax see [the official documentation](https://doc.powerdns.com/recursor/appendices/yamlconversion.html). +```yaml +powerdns::recursor_version: '5.3' +powerdns::recursor_use_yaml: true +powerdns::recursor::config: + dnssec: + validation: 'off' + incoming: + allow_from: + - 0.0.0.0/0 + distributor_threads: 1 + listen: + - 0.0.0.0:53 +``` + ### Recursor forward zones diff --git a/REFERENCE.md b/REFERENCE.md index bf12e7a..d7209b5 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -19,7 +19,8 @@ ### Defined types -* [`powerdns::config`](#powerdns--config): Manage powerdns settings +* [`powerdns::config`](#powerdns--config): Manage powerdns settings in old configuration format. +Supported up to recursor version 5.2. ### Resource types @@ -107,6 +108,9 @@ The following parameters are available in the `powerdns` class: * [`authoritative_group`](#-powerdns--authoritative_group) * [`authoritative_file_owner`](#-powerdns--authoritative_file_owner) * [`authoritative_file_group`](#-powerdns--authoritative_file_group) +* [`recursor_use_yaml`](#-powerdns--recursor_use_yaml) +* [`recursor_local_config_file`](#-powerdns--recursor_local_config_file) +* [`recursor_local_config`](#-powerdns--recursor_local_config) ##### `authoritative_package_name` @@ -152,7 +156,7 @@ Authoritative config file path ##### `authoritative_version` -Data type: `Pattern[/4\.[0-9]+/]` +Data type: `Pattern[/[4,5]\.[0-9]+/]` Authoritative server version @@ -491,11 +495,11 @@ Default value: `false` ##### `forward_zones` -Data type: `Hash` +Data type: `Optional[Variant[Hash,Tuple]]` Configures recursor forward_zones -Default value: `{}` +Default value: `undef` ##### `autoprimaries` @@ -545,6 +549,30 @@ Group of authoritative config files Default value: `$authoritative_group` +##### `recursor_use_yaml` + +Data type: `Boolean` + + + +Default value: `false` + +##### `recursor_local_config_file` + +Data type: `Optional[Stdlib::Absolutepath]` + + + +Default value: `undef` + +##### `recursor_local_config` + +Data type: `Optional[Hash]` + + + +Default value: `undef` + ### `powerdns::authoritative` powerdns::authoritative @@ -577,20 +605,60 @@ sqlite backend for powerdns powerdns recursor +* **See also** + * https://doc.powerdns.com/recursor/yamlsettings.html + #### Parameters The following parameters are available in the `powerdns::recursor` class: * [`forward_zones`](#-powerdns--recursor--forward_zones) +* [`config`](#-powerdns--recursor--config) +* [`forward_zones_file`](#-powerdns--recursor--forward_zones_file) +* [`include`](#-powerdns--recursor--include) +* [`config_includedir`](#-powerdns--recursor--config_includedir) ##### `forward_zones` -Data type: `Hash` +Data type: `Optional[Variant[Hash,Tuple]]` Hash containing zone => dns servers pairs Default value: `$powerdns::forward_zones` +##### `config` + +Data type: `Hash` + +recursor config (will be converted to YAML) +when powerdns::recursor_use_yaml is set to `true` + +Default value: `{}` + +##### `forward_zones_file` + +Data type: `String` + +filename + +Default value: `'forward_zones.conf'` + +##### `include` + +Data type: `Hash` + +Key as filename and its contents as value + +Default value: `{}` + +##### `config_includedir` + +Data type: `Stdlib::Absolutepath` + + + +Default value: `"${powerdns::recursor_configdir}/recursor.d"` + ### `powerdns::repo` powerdns::repo @@ -599,7 +667,11 @@ powerdns::repo ### `powerdns::config` -Manage powerdns settings +Manage powerdns settings in old configuration format. +Supported up to recursor version 5.2. + +* **See also** + * https://doc.powerdns.com/recursor/settings.html #### Parameters diff --git a/data/common.yaml b/data/common.yaml index 5e00312..c3401aa 100644 --- a/data/common.yaml +++ b/data/common.yaml @@ -1,4 +1,7 @@ --- +lookup_options: + powerdns::recursor_local_config: + merge: deep powerdns::authoritative_package_ensure: installed powerdns::authoritative_extra_packages_ensure: installed powerdns::authoritative_version: '4.9' @@ -8,3 +11,4 @@ powerdns::recursor_user: pdns powerdns::recursor_group: pdns powerdns::recursor_file_owner: root powerdns::recursor_file_group: "%{lookup('powerdns::recursor_group')}" +powerdns::recursor_use_yaml: false diff --git a/data/os/Debian.yaml b/data/os/Debian.yaml index c6c06c8..6830a08 100644 --- a/data/os/Debian.yaml +++ b/data/os/Debian.yaml @@ -1,5 +1,5 @@ --- -powerdns::db_file: "/var/lib/powerdns/powerdns.sqlite3" +powerdns::db_file: /var/lib/powerdns/powerdns.sqlite3 powerdns::mysql_schema_file: /usr/share/doc/pdns-backend-mysql/schema.mysql.sql powerdns::pgsql_schema_file: /usr/share/doc/pdns-backend-pgsql/schema.pgsql.sql powerdns::sqlite_schema_file: /usr/share/doc/pdns-backend-sqlite3/schema.sqlite3.sql diff --git a/data/os/Debian/12.yaml b/data/os/Debian/12.yaml new file mode 100644 index 0000000..fe7e462 --- /dev/null +++ b/data/os/Debian/12.yaml @@ -0,0 +1,3 @@ +--- +powerdns::mysql_charset: utf8mb3 +powerdns::mysql_collate: utf8mb3_general_ci diff --git a/data/os/FreeBSD.yaml b/data/os/FreeBSD.yaml index 3adf1fa..11cc58c 100644 --- a/data/os/FreeBSD.yaml +++ b/data/os/FreeBSD.yaml @@ -1,5 +1,5 @@ --- -powerdns::db_file: "/var/db/powerdns/powerdns.sqlite3" +powerdns::db_file: /var/db/powerdns/powerdns.sqlite3 powerdns::mysql_schema_file: /usr/local/share/doc/powerdns/schema.mysql.sql powerdns::pgsql_schema_file: /usr/local/share/doc/powerdns/schema.pgsql.sql powerdns::sqlite_schema_file: /usr/local/share/doc/powerdns/schema.sqlite3.sql diff --git a/data/os/RedHat.yaml b/data/os/RedHat.yaml index 11dfe60..b806938 100644 --- a/data/os/RedHat.yaml +++ b/data/os/RedHat.yaml @@ -1,5 +1,5 @@ --- -powerdns::db_file: "/var/lib/powerdns/powerdns.sqlite3" +powerdns::db_file: /var/lib/powerdns/powerdns.sqlite3 powerdns::mysql_schema_file: /usr/share/doc/pdns-backend-mysql/schema.mysql.sql powerdns::pgsql_schema_file: /usr/share/doc/pdns-backend-postgresql/schema.pgsql.sql powerdns::sqlite_schema_file: /usr/share/doc/pdns-backend-sqlite/schema.sqlite.sql diff --git a/manifests/config.pp b/manifests/config.pp index e2960b9..3fffe20 100644 --- a/manifests/config.pp +++ b/manifests/config.pp @@ -1,4 +1,6 @@ -# @summary Manage powerdns settings +# @summary Manage powerdns settings in old configuration format. +# Supported up to recursor version 5.2. +# @see https://doc.powerdns.com/recursor/settings.html # # @param setting # The setting you want to change diff --git a/manifests/init.pp b/manifests/init.pp index 61ebc6f..6f6dbbf 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -132,7 +132,7 @@ String[1] $authoritative_service_name, Stdlib::Absolutepath $authoritative_configdir, Stdlib::Absolutepath $authoritative_config, - Pattern[/4\.[0-9]+/] $authoritative_version, + Pattern[/[4,5]\.[0-9]+/] $authoritative_version, Stdlib::Absolutepath $db_file, Stdlib::Absolutepath $mysql_schema_file, Stdlib::Absolutepath $pgsql_schema_file, @@ -157,6 +157,9 @@ Optional[String[1]] $mysql_collate = undef, Boolean $authoritative = true, Boolean $recursor = false, + Boolean $recursor_use_yaml = false, + Optional[Stdlib::Absolutepath] $recursor_local_config_file = undef, + Optional[Hash] $recursor_local_config = undef, Powerdns::Backends $backend = 'mysql', Boolean $backend_install = true, Boolean $backend_create_tables = true, @@ -178,7 +181,7 @@ Powerdns::LmdbSyncMode $lmdb_sync_mode = undef, Boolean $custom_repo = false, Boolean $custom_epel = false, - Hash $forward_zones = {}, + Optional[Variant[Hash,Tuple]] $forward_zones = undef, Powerdns::Autoprimaries $autoprimaries = {}, Boolean $purge_autoprimaries = false, String[1] $authoritative_user = 'pdns', @@ -223,10 +226,12 @@ if $recursor { contain powerdns::recursor - # Set up Hiera for the recursor. - $powerdns_recursor_config = lookup('powerdns::recursor::config', Hash, 'deep', {}) - $powerdns_recursor_defaults = { 'type' => 'recursor' } - create_resources(powerdns::config, $powerdns_recursor_config, $powerdns_recursor_defaults) + # old config format + unless $recursor_use_yaml { + $powerdns_recursor_config = lookup('powerdns::recursor::config', Hash, 'deep', {}) + $powerdns_recursor_defaults = { 'type' => 'recursor' } + create_resources(powerdns::config, $powerdns_recursor_config, $powerdns_recursor_defaults) + } } if $purge_autoprimaries { diff --git a/manifests/recursor.pp b/manifests/recursor.pp index 26fdd6c..8c482ff 100644 --- a/manifests/recursor.pp +++ b/manifests/recursor.pp @@ -2,37 +2,97 @@ # # @param forward_zones # Hash containing zone => dns servers pairs +# @param config recursor config (will be converted to YAML) +# when powerdns::recursor_use_yaml is set to `true` +# @see https://doc.powerdns.com/recursor/yamlsettings.html +# @param forward_zones_file filename +# @param include Key as filename and its contents as value # class powerdns::recursor ( - Hash $forward_zones = $powerdns::forward_zones, + Optional[Variant[Hash,Tuple]] $forward_zones = $powerdns::forward_zones, + Hash $config = {}, + String $forward_zones_file = 'forward_zones.conf', + Stdlib::Absolutepath $config_includedir = "${powerdns::recursor_configdir}/recursor.d", + Hash $include = {}, ) inherits powerdns { package { $powerdns::recursor_package_name: ensure => $powerdns::recursor_package_ensure, } + $zone_config = "${powerdns::recursor_configdir}/${forward_zones_file}" - file { $powerdns::recursor_config: - ensure => file, - owner => $powerdns::recursor_file_owner, - group => $powerdns::recursor_file_group, - require => Package[$powerdns::recursor_package_name], - } + if $powerdns::recursor_use_yaml { + ## Use New YAML based configuration + $forward_block = empty($forward_zones) ? { + true => {}, + false => { 'forward_zones_file' => $zone_config }, + } + + $recursor_config = deep_merge({ + 'recursor' => { + 'include_dir' => $config_includedir, + } + $forward_block, + }, $config) + + file { $config_includedir: + ensure => directory, + owner => $powerdns::recursor_file_owner, + group => $powerdns::recursor_file_group, + require => Package[$powerdns::recursor_package_name], + } - if !empty($forward_zones) { - $zone_config = "${powerdns::recursor_configdir}/forward_zones.conf" file { $zone_config: ensure => file, owner => $powerdns::recursor_file_owner, group => $powerdns::recursor_file_group, - content => template('powerdns/forward_zones.conf.erb'), + content => stdlib::to_yaml($forward_zones), + require => Package[$powerdns::recursor_package_name], notify => Service['pdns-recursor'], } - powerdns::config { 'forward-zones-file': - value => $zone_config, - type => 'recursor', + file { $powerdns::recursor_config: + ensure => file, + owner => $powerdns::recursor_file_owner, + group => $powerdns::recursor_file_group, + content => stdlib::to_yaml($recursor_config), + require => Package[$powerdns::recursor_package_name], + notify => Service['pdns-recursor'], } - } + $include.each |String $filename, Hash $content| { + file { "${config_includedir}/${filename}": + ensure => file, + owner => $powerdns::recursor_file_owner, + group => $powerdns::recursor_file_group, + content => stdlib::to_yaml($content), + require => Package[$powerdns::recursor_package_name], + notify => Service['pdns-recursor'], + } + } + } else { + ## Use Old INI based configuration + + file { $powerdns::recursor_config: + ensure => file, + owner => $powerdns::recursor_file_owner, + group => $powerdns::recursor_file_group, + require => Package[$powerdns::recursor_package_name], + } + + if !empty($forward_zones) { + file { $zone_config: + ensure => file, + owner => $powerdns::recursor_file_owner, + group => $powerdns::recursor_file_group, + content => template('powerdns/forward_zones.conf.erb'), + notify => Service['pdns-recursor'], + } + + powerdns::config { 'forward-zones-file': + value => $zone_config, + type => 'recursor', + } + } + } service { 'pdns-recursor': ensure => running, name => $powerdns::recursor_service_name, diff --git a/metadata.json b/metadata.json index a5de57d..56383de 100644 --- a/metadata.json +++ b/metadata.json @@ -51,16 +51,16 @@ ] }, { - "operatingsystem": "Ubuntu", + "operatingsystem": "Debian", "operatingsystemrelease": [ - "24.04" + "11", + "12" ] }, { - "operatingsystem": "Debian", + "operatingsystem": "Ubuntu", "operatingsystemrelease": [ - "11", - "12" + "24.04" ] } ], diff --git a/spec/acceptance/class_spec.rb b/spec/acceptance/class_spec.rb index 69b5449..7bda8f4 100644 --- a/spec/acceptance/class_spec.rb +++ b/spec/acceptance/class_spec.rb @@ -46,7 +46,7 @@ class { 'powerdns': end describe command('/usr/bin/pdns_control version') do - its(:stdout) { is_expected.to match %r{^4\.9} } + its(:stdout) { is_expected.to match %r{^(4\.9|5\.\d)} } end end diff --git a/spec/classes/powerdns_init_spec.rb b/spec/classes/powerdns_init_spec.rb index df310b0..f50b4a2 100644 --- a/spec/classes/powerdns_init_spec.rb +++ b/spec/classes/powerdns_init_spec.rb @@ -27,6 +27,7 @@ sqlite_binary_package_name = 'sqlite' recursor_package_name = 'pdns-recursor' recursor_service_name = 'pdns-recursor' + recursor_dir = '/etc/pdns-recursor' when 'Debian' authoritative_package_name = 'pdns-server' authoritative_service_name = 'pdns' @@ -39,6 +40,7 @@ sqlite_binary_package_name = 'sqlite3' recursor_package_name = 'pdns-recursor' recursor_service_name = 'pdns-recursor' + recursor_dir = '/etc/powerdns' when 'Archlinux' authoritative_package_name = 'powerdns' authoritative_service_name = 'pdns' @@ -121,9 +123,7 @@ it { is_expected.to contain_apt__keyring('powerdns.asc') } it { is_expected.to contain_apt__pin('powerdns') } it { is_expected.to contain_apt__source('powerdns') } - it { is_expected.to contain_apt__source('powerdns').with_release(%r{auth-49}) } it { is_expected.to contain_apt__source('powerdns-recursor') } - it { is_expected.to contain_apt__source('powerdns-recursor').with_release(%r{rec-50}) } it { is_expected.to contain_package('dirmngr') } end @@ -632,11 +632,98 @@ it { is_expected.to contain_package(authoritative_package_name).with('ensure' => 'installed') } end + # if a yaml OS, test the yaml style forward zones + context 'powerdns class with the recursor with yaml forward zones' do + let(:params) do + { + recursor: true, + authoritative: false, + recursor_version: '5.3', + recursor_use_yaml: true, + forward_zones: [ + { + 'zone' => 'example.com', + 'forwarders' => ['192.0.2.1:5300'], + }, + { + 'zone' => 'example.net', + 'forwarders' => ['198.51.100.1', '198.51.100.2', '198.51.100.3', '198.51.100.4'], + 'recurse' => true, + }, + { + 'zone' => 'example.org', + 'forwarders' => ['203.0.113.5', '203.0.113.6'], + }, + { + 'zone' => '2.0.192.in-addr.arpa', # reverse for 192.0.2.0/24 + 'forwarders' => ['192.0.2.53', '192.0.2.54'], + 'recurse' => true, + }, + { + 'zone' => '100.51.198.in-addr.arpa', # reverse for 198.51.100.0/24 + 'forwarders' => ['198.51.100.53', '198.51.100.54'], + 'recurse' => true, + }, + ] + } + end + + it { is_expected.to compile.with_all_deps } + + # Check forward zones + it { is_expected.to contain_class('powerdns::recursor') } + + it 'renders forward zones yaml correctly' do + is_expected.to contain_file("#{recursor_dir}/recursor.conf").with('ensure' => 'file'). + with_content(<<~EOS + --- + recursor: + include_dir: "#{recursor_dir}/recursor.d" + forward_zones_file: "#{recursor_dir}/forward_zones.conf" + EOS + ) + end + + it 'includes forward_zones config in yaml format' do + is_expected.to contain_file("#{recursor_dir}/forward_zones.conf").with('ensure' => 'file'). + with_content(<<~EOS + --- + - zone: example.com + forwarders: + - 192.0.2.1:5300 + - zone: example.net + forwarders: + - 198.51.100.1 + - 198.51.100.2 + - 198.51.100.3 + - 198.51.100.4 + recurse: true + - zone: example.org + forwarders: + - 203.0.113.5 + - 203.0.113.6 + - zone: 2.0.192.in-addr.arpa + forwarders: + - 192.0.2.53 + - 192.0.2.54 + recurse: true + - zone: 100.51.198.in-addr.arpa + forwarders: + - 198.51.100.53 + - 198.51.100.54 + recurse: true + EOS + ) + end + end + + # if not a yaml OS, test the old style forward zones context 'powerdns class with the recursor with forward zones' do let(:params) do { recursor: true, authoritative: false, + recursor_version: '4.9', # without yaml support forward_zones: { 'example.com': '1.1.1.1', '+.': '8.8.8.8' @@ -644,16 +731,9 @@ } end - case facts[:os]['family'] - when 'RedHat' - recursor_dir = '/etc/pdns-recursor' - when 'Debian' - recursor_dir = '/etc/powerdns' - end - it { is_expected.to compile.with_all_deps } - # Check the authoritative server + # Check forward zones it { is_expected.to contain_class('powerdns::recursor') } it { is_expected.to contain_file("#{recursor_dir}/forward_zones.conf").with_ensure('file') } diff --git a/spec/classes/powerdns_recursor_spec.rb b/spec/classes/powerdns_recursor_spec.rb new file mode 100644 index 0000000..c90f820 --- /dev/null +++ b/spec/classes/powerdns_recursor_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'powerdns::recursor' do + context 'supported operating systems' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:pre_condition) do + <<-EOS + class { 'powerdns': + db_root_password => 'foobar', + db_username => 'foo', + db_password => 'bar', + recursor_use_yaml => true, + } + EOS + end + let(:facts) do + facts + end + + recursor_dir = case facts[:os]['family'] + when 'RedHat' + '/etc/pdns-recursor' + else + '/etc/powerdns' + end + + it { is_expected.to compile.with_all_deps } + + context 'custom config' do + let(:params) do + { + include: { + '00-defaults.yml': { + logging: { + loglevel: 6 + } + } + } + } + end + + it 'checks file resource based on hiera value' do + is_expected.to contain_file("#{recursor_dir}/recursor.d/00-defaults.yml").with('ensure' => 'file') + end + end + + context 'with forward zones' do + let(:params) do + { + config: { + recursor: { + threads: 2, + } + }, + forward_zones_file: 'forward_zones.yml', + forward_zones: [ + { + 'zone' => '.', + 'forwarders' => ['1.1.1.1'], + 'recurse' => true, + }, + ] + } + end + + it 'includes main config in yaml format' do + is_expected.to contain_file("#{recursor_dir}/recursor.conf").with('ensure' => 'file'). + with_content(<<~EOS + --- + recursor: + include_dir: "#{recursor_dir}/recursor.d" + forward_zones_file: "#{recursor_dir}/forward_zones.yml" + threads: 2 + EOS + ) + end + + it 'includes forward_zones config in yaml format' do + is_expected.to contain_file("#{recursor_dir}/forward_zones.yml").with('ensure' => 'file'). + with_content(<<~EOS + --- + - zone: "." + forwarders: + - 1.1.1.1 + recurse: true + EOS + ) + end + end + end + end + end +end diff --git a/spec/defines/powerdns_config_spec.rb b/spec/defines/powerdns_config_spec.rb index ca4f5dd..6453476 100644 --- a/spec/defines/powerdns_config_spec.rb +++ b/spec/defines/powerdns_config_spec.rb @@ -5,6 +5,7 @@ } require 'spec_helper' + describe 'powerdns::config' do context 'supported operating systems' do on_supported_os.each do |os, facts| @@ -18,11 +19,14 @@ end let(:pre_condition) do - 'class { "::powerdns": + 'class { "powerdns": db_root_password => "foobar", db_username => "foo", db_password => "bar", recursor => true, + recursor_use_yaml => false, # <- force INI mode + authoritative_version => "4.9", + recursor_version => "5.0", }' end @@ -53,15 +57,12 @@ end context 'powerdns::config with recursor type' do - let(:params) do - { - setting: 'foo', - value: 'bar', - type: 'recursor' - } - end + let(:params) { { setting: 'foo', value: 'bar', type: 'recursor' } } - it { is_expected.to contain_file_line(format('powerdns-config-foo-%{config}', config: recursor_config)) } + it do + # Only assert in INI mode + is_expected.to contain_file_line("powerdns-config-foo-#{recursor_config}") unless catalogue.resource('Class', 'Powerdns')[:recursor_use_yaml] + end end context 'powerdns::config with integers' do diff --git a/spec/defines/powerdns_config_yaml_spec.rb b/spec/defines/powerdns_config_yaml_spec.rb new file mode 100644 index 0000000..4298c14 --- /dev/null +++ b/spec/defines/powerdns_config_yaml_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'powerdns::config', type: :define do + on_supported_os.each do |os, facts| + context "on #{os} with recursor_use_yaml => true" do + let(:facts) { facts.merge(root_home: '/root') } + let(:pre_condition) do + <<-PUPPET + class { 'powerdns': + authoritative => true, + recursor => true, + authoritative_version => '5.0', + recursor_version => '5.3', + recursor_use_yaml => true, + db_root_password => 'rootpass', + db_username => 'pdns', + db_password => 'secret', + } + PUPPET + end + let(:title) { 'foo' } + + case facts[:os]['family'] + when 'RedHat' + authoritative_config = '/etc/pdns/pdns.conf' + recursor_dir = '/etc/pdns-recursor' + else + authoritative_config = '/etc/powerdns/pdns.conf' + recursor_dir = '/etc/powerdns' + end + recursor_config = "#{recursor_dir}/recursor.conf" + + context 'recursor type uses YAML file (with include_dir stanza)' do + let(:params) { { setting: 'foo', value: 'bar', type: 'recursor' } } + + it { is_expected.to contain_file(recursor_config).with_ensure('file') } + + it 'includes the include_dir stanza in the YAML content' do + is_expected.to contain_file(recursor_config). + with_content(%r{include_dir:\s*"?#{Regexp.escape(recursor_dir)}/recursor\.d"?}) + end + end + + context 'authoritative still writes to pdns.conf via file_line' do + let(:params) { { setting: 'foo', value: 'bar' } } + + it do + is_expected.to contain_file_line("powerdns-config-foo-#{authoritative_config}"). + with_path(authoritative_config) + end + end + end + end +end