From 26a15f71d71b32fb9cd721d48f6057e3e84b833d Mon Sep 17 00:00:00 2001 From: Tim Meusel Date: Thu, 8 Feb 2024 12:40:33 +0100 Subject: [PATCH] Add systemd timer to update root.hints file By default we download a root.hints file once. That's bad. it contains IP-addresses for all root DNS servers. Those addresses can change from time to time. We should update the file every now and then. --- .fixtures.yml | 1 + REFERENCE.md | 13 +++- files/roothints.timer | 11 ++++ manifests/init.pp | 22 ++++++- metadata.json | 4 ++ spec/classes/init_spec.rb | 113 ++++++++++++++++++++++---------- templates/roothints.service.epp | 9 +++ 7 files changed, 132 insertions(+), 41 deletions(-) create mode 100644 files/roothints.timer create mode 100644 templates/roothints.service.epp diff --git a/.fixtures.yml b/.fixtures.yml index 84d77b26..76c7fdb4 100644 --- a/.fixtures.yml +++ b/.fixtures.yml @@ -2,3 +2,4 @@ fixtures: repositories: concat: "https://github.com/puppetlabs/puppetlabs-concat.git" stdlib: "https://github.com/puppetlabs/puppetlabs-stdlib.git" + systemd: "https://github.com/voxpupuli/puppet-systemd.git" diff --git a/REFERENCE.md b/REFERENCE.md index 3381041c..2b17ec93 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -6,7 +6,7 @@ ### Classes -* [`unbound`](#unbound): Class: unbound Installs and configures Unbound, the caching DNS resolver from NLnet Labs +* [`unbound`](#unbound): Installs and configures Unbound, the caching DNS resolver from NLnet Labs * [`unbound::remote`](#unbound--remote): Class: unbound::remote Configure remote control of the unbound daemon process === Parameters: [*enable*] (optional) The option is used t ### Defined types @@ -36,8 +36,6 @@ ### `unbound` -Class: unbound - Installs and configures Unbound, the caching DNS resolver from NLnet Labs #### Parameters @@ -47,6 +45,7 @@ The following parameters are available in the `unbound` class: * [`hints_file`](#-unbound--hints_file) * [`hints_file_content`](#-unbound--hints_file_content) * [`unbound_version`](#-unbound--unbound_version) +* [`update_root_hints`](#-unbound--update_root_hints) * [`manage_service`](#-unbound--manage_service) * [`verbosity`](#-unbound--verbosity) * [`statistics_interval`](#-unbound--statistics_interval) @@ -274,6 +273,14 @@ the version of the installed unbound instance. defaults to the fact, but you can Default value: `$facts['unbound_version']` +##### `update_root_hints` + +Data type: `Enum['absent','present','unmanaged']` + +If set to true (and hints_file isn't set to 'builtin') a systemd timer will be configured to update the root hints file every month + +Default value: `fact('systemd') ? { true => 'present', default => 'unmanaged'` + ##### `manage_service` Data type: `Boolean` diff --git a/files/roothints.timer b/files/roothints.timer new file mode 100644 index 00000000..5ea68f63 --- /dev/null +++ b/files/roothints.timer @@ -0,0 +1,11 @@ +# THIS FILE IS MANAGED BY PUPPET +# BASED ON https://wiki.archlinux.org/title/Unbound#Roothints_systemd_timer +[Unit] +Description=Run root.hints monthly + +[Timer] +OnCalendar=monthly +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/manifests/init.pp b/manifests/init.pp index edce29e5..5280ae6d 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,6 +1,5 @@ -# Class: unbound # -# Installs and configures Unbound, the caching DNS resolver from NLnet Labs +# @summary Installs and configures Unbound, the caching DNS resolver from NLnet Labs # # @param hints_file # File path to the root-hints. Set to 'builtin' to remove root-hint option from unbound.conf and use built-in hints. @@ -8,6 +7,9 @@ # Contents of the root hints file, if it's not remotely fetched. # @param unbound_version # the version of the installed unbound instance. defaults to the fact, but you can overwrite it. this reduces the initial puppet runs from two to one +# @param update_root_hints +# If set to true (and hints_file isn't set to 'builtin') a systemd timer will be configured to update the root hints file every month +# class unbound ( Boolean $manage_service = true, Integer[0,5] $verbosity = 1, @@ -135,7 +137,7 @@ Optional[Integer] $key_cache_slabs = undef, Optional[Unbound::Size] $neg_cache_size = undef, Boolean $unblock_lan_zones = false, - Boolean $insecure_lan_zones = false, # version 1.5.8 + Boolean $insecure_lan_zones = false, # version 1.5.8 Unbound::Local_zone $local_zone = {}, Array[String[1]] $local_data = [], Array[String[1]] $local_data_ptr = [], @@ -212,6 +214,7 @@ Integer[1] $redis_timeout = 100, Stdlib::Absolutepath $unbound_conf_d = "${confdir}/unbound.conf.d", Unbound::Hints_file $hints_file = "${confdir}/root.hints", + Enum['absent','present','unmanaged'] $update_root_hints = fact('systemd') ? { true => 'present', default => 'unmanaged' }, Optional[String[1]] $hints_file_content = undef, Hash[String[1], Unbound::Rpz] $rpzs = {}, Optional[String[1]] $unbound_version = $facts['unbound_version'], @@ -316,6 +319,19 @@ mode => '0444', content => $hints_file_content, } + if $update_root_hints == 'present' { + systemd::timer { 'roothints.timer': + timer_content => file("${module_name}/roothints.timer"), + service_content => epp("${module_name}/roothints.service.epp", { 'hints_file' => $hints_file, 'root_hints_url' => $root_hints_url, 'fetch_client' => $fetch_client }), + active => true, + enable => true, + } + } + } + if $update_root_hints == 'absent' { + systemd::timer { 'roothints.timer': + ensure => 'absent', + } } # purge unmanaged files in configuration directory diff --git a/metadata.json b/metadata.json index a58fde61..ab4383b8 100644 --- a/metadata.json +++ b/metadata.json @@ -113,6 +113,10 @@ { "name": "puppetlabs/stdlib", "version_requirement": ">= 4.25.0 < 10.0.0" + }, + { + "name": "puppet/systemd", + "version_requirement": ">= 6.3.0 < 7.0.0" } ] } diff --git a/spec/classes/init_spec.rb b/spec/classes/init_spec.rb index d6eea4d0..ef169ec2 100644 --- a/spec/classes/init_spec.rb +++ b/spec/classes/init_spec.rb @@ -18,15 +18,6 @@ pidfile = nil - if facts.dig(:os, 'family').nil? - if facts[:osfamily] - puts "Skipping tests on on platform #{facts[:osfamily]} due to missing facts[:os][:family]" - else - puts "Skipping tests on on platform #{facts[:kernel]} due to missing facts[:os][:family]" - end - next - end - case facts[:os]['family'] when 'Debian' pidfile = '/run/unbound.pid' @@ -67,6 +58,10 @@ it { is_expected.to contain_file(keys_d_dir) } it { is_expected.to contain_file(hints_file) } + context 'on Linux', if: facts[:kernel] == 'Linux' do + it { is_expected.to contain_systemd__timer('roothints.timer') } + end + it do expect(subject).to contain_file(unbound_conf_d).with( 'ensure' => 'directory', @@ -1038,41 +1033,89 @@ end end - context 'no root hints in config' do - let(:params) do - { - hints_file: 'builtin' - } + context 'roothints' do + context 'no root hints in config' do + let(:params) do + { + hints_file: 'builtin' + } + end + + it do + expect(subject).to contain_concat__fragment( + 'unbound-header' + ).without_content(%r{root-hints}) + end + + it { is_expected.not_to contain_systemd__timer('roothints.timer') } end - it do - expect(subject).to contain_concat__fragment( - 'unbound-header' - ).without_content(%r{root-hints}) + context 'no root hints in config and update_root_hints=unmanaged' do + let(:params) do + { + hints_file: 'builtin', + update_root_hints: 'unmanaged' + } + end + + it do + expect(subject).to contain_concat__fragment( + 'unbound-header' + ).without_content(%r{root-hints}) + end + + it { is_expected.not_to contain_systemd__timer('roothints.timer') } end - end - context 'hieradata root hints' do - let(:params) do - { - skip_roothints_download: true, - hints_file_content: File.read('spec/classes/expected/hieradata-root-hint.conf'), - } + context 'no root hints in config and update_root_hints=absent' do + let(:params) do + { + hints_file: 'builtin', + update_root_hints: 'absent' + } + end + + it do + expect(subject).to contain_concat__fragment( + 'unbound-header' + ).without_content(%r{root-hints}) + end + + it { is_expected.to contain_systemd__timer('roothints.timer').with_ensure('absent') } end - it do - expect(subject).to contain_file(hints_file).with( - 'ensure' => 'file', - 'mode' => '0444', - 'content' => File.read('spec/classes/expected/hieradata-root-hint.conf') - ) + context 'update_root_hints=absent' do + let(:params) do + { + update_root_hints: 'absent' + } + end + + it { is_expected.to contain_systemd__timer('roothints.timer').with_ensure('absent') } end - end - context 'with File defaults' do - let(:pre_condition) { "File { mode => '0644', owner => 'root', group => 'root' }" } + context 'hieradata root hints' do + let(:params) do + { + skip_roothints_download: true, + hints_file_content: File.read('spec/classes/expected/hieradata-root-hint.conf'), + } + end - it { is_expected.to compile.with_all_deps } + it do + expect(subject).to contain_file(hints_file).with( + 'ensure' => 'file', + 'mode' => '0444', + 'content' => File.read('spec/classes/expected/hieradata-root-hint.conf') + ) + end + end + + context 'with File defaults' do + let(:pre_condition) { "File { mode => '0644', owner => 'root', group => 'root' }" } + + it { is_expected.to compile.with_all_deps } + end end context 'RPZs config' do diff --git a/templates/roothints.service.epp b/templates/roothints.service.epp new file mode 100644 index 00000000..293d587f --- /dev/null +++ b/templates/roothints.service.epp @@ -0,0 +1,9 @@ +<%- | Stdlib::Absolutepath $hints_file, Stdlib::HTTPSUrl $root_hints_url, String[1] $fetch_client | -%> +# THIS FILE IS MANAGED BY PUPPET +# BASED ON https://wiki.archlinux.org/title/Unbound#Roothints_systemd_timer +[Unit] +Description=Update root hints for unbound +After=network.target + +[Service] +ExecStart=<%= $fetch_client %> <%= $hints_file %> <%= $root_hints_url %>