From 9908a1c5e0d39b760fab51713558cdc2e61ebe34 Mon Sep 17 00:00:00 2001 From: jbond Date: Fri, 17 Jan 2025 16:34:47 +0100 Subject: [PATCH 1/2] dnstap: add print_config functions Add a new function to print function lines. this is similar to the ruby function in unbound.conf.erb and should allow us to move the other templates to epp. --- REFERENCE.md | 36 +++++++++++++++++++++++++++++ functions/print_config.pp | 21 +++++++++++++++++ spec/functions/print_config_spec.rb | 23 ++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 functions/print_config.pp create mode 100644 spec/functions/print_config_spec.rb diff --git a/REFERENCE.md b/REFERENCE.md index ee0f90c..2205fba 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -16,6 +16,10 @@ * [`unbound::record`](#unbound--record): Create an unbound static DNS record override * [`unbound::stub`](#unbound--stub): Create an unbound stub zone for caching upstream name resolvers +### Functions + +* [`unbound::print_config`](#unbound--print_config): Print a configuration value if it is defined and the version is supported + ### Data types * [`Unbound::Access_control`](#Unbound--Access_control): custom type for access control lists @@ -2341,6 +2345,38 @@ Name of the unbound config file Default value: `undef` +## Functions + +### `unbound::print_config` + +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)` + +The unbound::print_config function. + +Returns: `String` the config item as a string or an empty string if the version is not supported + +##### `name` + +Data type: `String[1]` + +the config item name + +##### `value` + +Data type: `Optional[Variant[Boolean, Integer, String, Array[String, 1]]]` + +the config item value + +##### `version` + +Data type: `Optional[String[1]]` + +the version when the config item was introduced + ## Data types ### `Unbound::Access_control` diff --git a/functions/print_config.pp b/functions/print_config.pp new file mode 100644 index 0000000..c659143 --- /dev/null +++ b/functions/print_config.pp @@ -0,0 +1,21 @@ +# @summary Print a configuration value if it is defined and the version is supported +# @param name the config item name +# @param value the config item value +# @param version the version when the config item was introduced +# @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[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 '' + } + $value ? { + String => " ${name}: \"${value}\"", + Integer => " ${name}: ${value}", + Boolean => " ${name}: ${value.bool2str('yes', 'no')}", + Array => $value.map |$v| { " ${name}: \"${v}\"" }.join("\n"), + } +} diff --git a/spec/functions/print_config_spec.rb b/spec/functions/print_config_spec.rb new file mode 100644 index 0000000..3724620 --- /dev/null +++ b/spec/functions/print_config_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'unbound::print_config' do + it { is_expected.to run.with_params('string_name', 'string_value').and_return(' string_name: "string_value"') } + it { is_expected.to run.with_params('int_name', 42).and_return(' int_name: 42') } + it { is_expected.to run.with_params('true_name', true).and_return(' true_name: yes') } + it { is_expected.to run.with_params('false_name', false).and_return(' false_name: no') } + + it do + is_expected.to run.with_params('list_name', %w[value1 value2]). + and_return(" list_name: \"value1\"\n list_name: \"value2\"") + end + + context 'with version' do + let(:facts) { { 'unbound_version' => '1.21.0' } } + + it { is_expected.to run.with_params('supported', 42, '1.11.0').and_return(' supported: 42') } + it { is_expected.to run.with_params('supported', 42, '1.21.0').and_return(' supported: 42') } + it { is_expected.to run.with_params('unsupported', 42, '1.22.0').and_return('') } + end +end From 69948caf70ec6db168a5140cbe2cadd5f1b1fc1d Mon Sep 17 00:00:00 2001 From: jbond Date: Fri, 17 Jan 2025 16:36:01 +0100 Subject: [PATCH 2/2] dnstap: add new class to add dnstap configuration --- REFERENCE.md | 190 ++++++++++++++++++++++++++++++++++++ manifests/dnstap.pp | 104 ++++++++++++++++++++ spec/classes/dnstap_spec.rb | 123 +++++++++++++++++++++++ 3 files changed, 417 insertions(+) create mode 100644 manifests/dnstap.pp create mode 100644 spec/classes/dnstap_spec.rb diff --git a/REFERENCE.md b/REFERENCE.md index 2205fba..6c5c78f 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -7,6 +7,7 @@ ### Classes * [`unbound`](#unbound): Installs and configures Unbound, the caching DNS resolver from NLnet Labs +* [`unbound::dnstap`](#unbound--dnstap) * [`unbound::remote`](#unbound--remote): Configure remote control of the unbound daemon process ### Defined types @@ -1920,6 +1921,195 @@ specifying reduces the number of puppet runs Default value: `$facts['unbound_version']` +### `unbound::dnstap` + +The unbound::dnstap class. + +#### Parameters + +The following parameters are available in the `unbound::dnstap` class: + +* [`enable`](#-unbound--dnstap--enable) +* [`bidirectional`](#-unbound--dnstap--bidirectional) +* [`socket_path`](#-unbound--dnstap--socket_path) +* [`ip`](#-unbound--dnstap--ip) +* [`tls`](#-unbound--dnstap--tls) +* [`tls_host`](#-unbound--dnstap--tls_host) +* [`tls_cert_bundle`](#-unbound--dnstap--tls_cert_bundle) +* [`tls_cert_key_file`](#-unbound--dnstap--tls_cert_key_file) +* [`tls_cert_cert_file`](#-unbound--dnstap--tls_cert_cert_file) +* [`send_identity`](#-unbound--dnstap--send_identity) +* [`send_version`](#-unbound--dnstap--send_version) +* [`identity`](#-unbound--dnstap--identity) +* [`version`](#-unbound--dnstap--version) +* [`sample_rate`](#-unbound--dnstap--sample_rate) +* [`log_resolver_query_messages`](#-unbound--dnstap--log_resolver_query_messages) +* [`log_resolver_response_messages`](#-unbound--dnstap--log_resolver_response_messages) +* [`log_client_query_messages`](#-unbound--dnstap--log_client_query_messages) +* [`log_client_response_messages`](#-unbound--dnstap--log_client_response_messages) +* [`log_forwarder_query_messages`](#-unbound--dnstap--log_forwarder_query_messages) +* [`log_forwarder_response_messages`](#-unbound--dnstap--log_forwarder_response_messages) + +##### `enable` + +Data type: `Boolean` + +Whether to enable dnstap. + +Default value: `true` + +##### `bidirectional` + +Data type: `Boolean` + +Whether to enable bidirectional dnstap. + +Default value: `true` + +##### `socket_path` + +Data type: `Optional[Stdlib::Absolutepath]` + +The path to the dnstap socket. + +Default value: `undef` + +##### `ip` + +Data type: `Optional[Unbound::Address]` + +The IP address for dnstap. + +Default value: `undef` + +##### `tls` + +Data type: `Boolean` + +Whether to enable TLS for dnstap. + +Default value: `true` + +##### `tls_host` + +Data type: `Optional[Stdlib::Host]` + +The TLS host for dnstap. + +Default value: `undef` + +##### `tls_cert_bundle` + +Data type: `Optional[Stdlib::Absolutepath]` + +The path to the TLS certificate bundle. + +Default value: `undef` + +##### `tls_cert_key_file` + +Data type: `Optional[Stdlib::Absolutepath]` + +The path to the TLS certificate key file. + +Default value: `undef` + +##### `tls_cert_cert_file` + +Data type: `Optional[Stdlib::Absolutepath]` + +The path to the TLS certificate file. + +Default value: `undef` + +##### `send_identity` + +Data type: `Boolean` + +Whether to send the identity in dnstap messages. + +Default value: `false` + +##### `send_version` + +Data type: `Boolean` + +Whether to send the version in dnstap messages. + +Default value: `false` + +##### `identity` + +Data type: `Optional[String[1]]` + +The identity to send in dnstap messages. + +Default value: `undef` + +##### `version` + +Data type: `Optional[String[1]]` + +The version to send in dnstap messages. + +Default value: `undef` + +##### `sample_rate` + +Data type: `Integer[0,1000]` + +The sample rate for dnstap messages. + +Default value: `0` + +##### `log_resolver_query_messages` + +Data type: `Boolean` + +Whether to log resolver query messages. + +Default value: `false` + +##### `log_resolver_response_messages` + +Data type: `Boolean` + +Whether to log resolver response messages. + +Default value: `false` + +##### `log_client_query_messages` + +Data type: `Boolean` + +Whether to log client query messages. + +Default value: `false` + +##### `log_client_response_messages` + +Data type: `Boolean` + +Whether to log client response messages. + +Default value: `false` + +##### `log_forwarder_query_messages` + +Data type: `Boolean` + +Whether to log forwarder query messages. + +Default value: `false` + +##### `log_forwarder_response_messages` + +Data type: `Boolean` + +Whether to log forwarder response messages. + +Default value: `false` + ### `unbound::remote` Configure remote control of the unbound daemon process diff --git a/manifests/dnstap.pp b/manifests/dnstap.pp new file mode 100644 index 0000000..5ec6da7 --- /dev/null +++ b/manifests/dnstap.pp @@ -0,0 +1,104 @@ +# @summary +# @param enable +# Whether to enable dnstap. +# @param bidirectional +# Whether to enable bidirectional dnstap. +# @param socket_path +# The path to the dnstap socket. +# @param ip +# The IP address for dnstap. +# @param tls +# Whether to enable TLS for dnstap. +# @param tls_host +# The TLS host for dnstap. +# @param tls_cert_bundle +# The path to the TLS certificate bundle. +# @param tls_cert_key_file +# The path to the TLS certificate key file. +# @param tls_cert_cert_file +# The path to the TLS certificate file. +# @param send_identity +# Whether to send the identity in dnstap messages. +# @param send_version +# Whether to send the version in dnstap messages. +# @param identity +# The identity to send in dnstap messages. +# @param version +# The version to send in dnstap messages. +# @param sample_rate +# The sample rate for dnstap messages. +# @param log_resolver_query_messages +# Whether to log resolver query messages. +# @param log_resolver_response_messages +# Whether to log resolver response messages. +# @param log_client_query_messages +# Whether to log client query messages. +# @param log_client_response_messages +# Whether to log client response messages. +# @param log_forwarder_query_messages +# Whether to log forwarder query messages. +# @param log_forwarder_response_messages +# Whether to log forwarder response messages. +class unbound::dnstap ( + Boolean $enable = true, # version 1.11 + Boolean $bidirectional = true, # version 1.11 + Optional[Stdlib::Absolutepath] $socket_path = undef, # version 1.11 + Optional[Unbound::Address] $ip = undef, # version 1.11 + Boolean $tls = true, # version 1.11 + Optional[Stdlib::Host] $tls_host = undef, # version 1.11 + Optional[Stdlib::Absolutepath] $tls_cert_bundle = undef, # version 1.11 + Optional[Stdlib::Absolutepath] $tls_cert_key_file = undef, # version 1.11 + Optional[Stdlib::Absolutepath] $tls_cert_cert_file = undef, # version 1.11 + Boolean $send_identity = false, # version 1.11 + Boolean $send_version = false, # version 1.11 + Optional[String[1]] $identity = undef, # version 1.11 + Optional[String[1]] $version = undef, # version 1.11 + Integer[0,1000] $sample_rate = 0, # version 1.21 + Boolean $log_resolver_query_messages = false, # version 1.11 + Boolean $log_resolver_response_messages = false, # version 1.11 + Boolean $log_client_query_messages = false, # version 1.11 + Boolean $log_client_response_messages = false, # version 1.11 + Boolean $log_forwarder_query_messages = false, # version 1.11 + Boolean $log_forwarder_response_messages = false, # version 1.11 + +) { + include unbound + if $enable and $socket_path == undef and $ip == undef { + fail('Either ip or socket_path is required when dnstap is enabled') + } + if $enable { + $ip_config = $ip.then |$v| { + @("CONFIG") + ${unbound::print_config('dnstap-ip', $v, '1.11')} + ${unbound::print_config('dnstap-tls', $tls, '1.11')} + ${unbound::print_config('dnstap-tls-host', $tls_host, '1.11')} + ${unbound::print_config('dnstap-tls-cert-bundle', $tls_cert_bundle, '1.11')} + ${unbound::print_config('dnstap-tls-cert-key-file', $tls_cert_key_file, '1.11')} + ${unbound::print_config('dnstap-tls-cert-cert-file', $tls_cert_cert_file, '1.11')} + | CONFIG + } + $config = @("CONFIG") + dnstap: + ${unbound::print_config('dnstap-enable', $enable, '1.11')} + ${unbound::print_config('dnstap-bidirectional', $bidirectional, '1.11')} + ${unbound::print_config('dnstap-socket-path', $socket_path, '1.11')} + ${$ip_config} + ${unbound::print_config('dnstap-send-identity', $send_identity, '1.11')} + ${unbound::print_config('dnstap-send-version', $send_version, '1.11')} + ${unbound::print_config('dnstap-identity', $identity, '1.11')} + ${unbound::print_config('dnstap-version', $version, '1.11')} + ${unbound::print_config('dnstap-sample-rate', $sample_rate, '1.21')} + ${unbound::print_config('dnstap-log-resolver-query-messages', $log_resolver_query_messages, '1.11')} + ${unbound::print_config('dnstap-log-resolver-response-messages', $log_resolver_response_messages, '1.11')} + ${unbound::print_config('dnstap-log-client-query-messages', $log_client_query_messages, '1.11')} + ${unbound::print_config('dnstap-log-client-response-messages', $log_client_response_messages, '1.11')} + ${unbound::print_config('dnstap-log-forwarder-query-messages', $log_forwarder_query_messages, '1.11')} + ${unbound::print_config('dnstap-log-forwarder-response-messages', $log_forwarder_response_messages, '1.11')} + | CONFIG + concat::fragment { 'unbound-dnstap': + order => '20', + target => $unbound::config_file, + content => $config.split("\n").filter |$x| { !$x.empty }.join("\n"), + } + } +} diff --git a/spec/classes/dnstap_spec.rb b/spec/classes/dnstap_spec.rb new file mode 100644 index 0000000..4bb7451 --- /dev/null +++ b/spec/classes/dnstap_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'unbound::dnstap' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { facts.merge(concat_basedir: '/dne', unbound_version: '1.21.0') } + + case facts[:os]['family'] + when 'FreeBSD' + let(:config_file) { '/usr/local/etc/unbound/unbound.conf' } + when 'OpenBSD' + let(:config_file) { '/var/unbound/etc/unbound.conf' } + else + let(:config_file) { '/etc/unbound/unbound.conf' } + end + + context 'with disabled params' do + let(:params) { { enable: false } } + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_class('unbound::dnstap') } + it { is_expected.not_to contain_concat__fragment('unbound-dnstap') } + end + + context 'with enable and socket' do + let(:params) { { socket_path: '/var/run/dnstap.sock' } } + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_class('unbound::dnstap') } + + it do + is_expected.to contain_concat__fragment('unbound-dnstap').with_content( + %r{ + ^dnstap: + \s+dnstap-enable:\syes + \s+dnstap-bidirectional:\syes + \s+dnstap-socket-path:\s"/var/run/dnstap.sock" + \s+dnstap-send-identity:\sno + \s+dnstap-send-version:\sno + \s+dnstap-sample-rate:\s0 + \s+dnstap-log-resolver-query-messages:\sno + \s+dnstap-log-resolver-response-messages:\sno + \s+dnstap-log-client-query-messages:\sno + \s+dnstap-log-client-response-messages:\sno + \s+dnstap-log-forwarder-query-messages:\sno + \s+dnstap-log-forwarder-response-messages:\sno + }x + ) + end + end + + context 'with enable and ip' do + let(:params) { { ip: '192.0.2.1' } } + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_class('unbound::dnstap') } + + it do + is_expected.to contain_concat__fragment('unbound-dnstap').with_content( + %r{ + ^dnstap: + \s+dnstap-enable:\syes + \s+dnstap-bidirectional:\syes + \s+dnstap-ip:\s"192\.0\.2\.1" + \s+dnstap-tls:\syes + \s+dnstap-send-identity:\sno + \s+dnstap-send-version:\sno + \s+dnstap-sample-rate:\s0 + \s+dnstap-log-resolver-query-messages:\sno + \s+dnstap-log-resolver-response-messages:\sno + \s+dnstap-log-client-query-messages:\sno + \s+dnstap-log-client-response-messages:\sno + \s+dnstap-log-forwarder-query-messages:\sno + \s+dnstap-log-forwarder-response-messages:\sno + }x + ) + end + + context 'with tls_host' do + let(:params) { super().merge(tls_host: 'dnstap.example.com') } + + it do + is_expected.to contain_concat__fragment('unbound-dnstap').with_content( + %r{^ dnstap-tls-host:\s"dnstap.example.com"} + ) + end + end + + context 'with tls_cert_bundle' do + let(:params) { super().merge(tls_cert_bundle: '/etc/ssl/cert.pem') } + + it do + is_expected.to contain_concat__fragment('unbound-dnstap').with_content( + %r{^ dnstap-tls-cert-bundle:\s"/etc/ssl/cert.pem"} + ) + end + end + + context 'with tls_cert_key_file' do + let(:params) { super().merge(tls_cert_key_file: '/etc/ssl/key.pem') } + + it do + is_expected.to contain_concat__fragment('unbound-dnstap').with_content( + %r{^ dnstap-tls-cert-key-file:\s"/etc/ssl/key.pem"} + ) + end + end + + context 'with tls_cert_cert_file' do + let(:params) { super().merge(tls_cert_cert_file: '/etc/ssl/cert.pem') } + + it do + is_expected.to contain_concat__fragment('unbound-dnstap').with_content( + %r{^ dnstap-tls-cert-cert-file:\s"/etc/ssl/cert.pem"} + ) + end + end + end + end + end +end