├── data └── os │ ├── Suse.yaml │ ├── Debian │ ├── 12.yaml │ └── 13.yaml │ ├── Archlinux.yaml │ ├── Debian.yaml │ ├── RedHat.yaml │ ├── Darwin.yaml │ ├── OpenBSD.yaml │ ├── FreeBSD.yaml │ └── Solaris │ └── SmartOS.yaml ├── spec ├── classes │ ├── expected │ │ └── hieradata-root-hint.conf │ ├── remote_spec.rb │ ├── dnstap_spec.rb │ └── init_spec.rb ├── spec_helper_acceptance.rb ├── type_aliases │ ├── access_control_spec.rb │ ├── local_zone_type_spec.rb │ ├── unbound_address.rb │ └── rpz_spec.rb ├── spec_helper.rb ├── functions │ └── print_config_spec.rb ├── acceptance │ └── unbound_spec.rb ├── unit │ └── unbound │ │ └── facter │ │ └── unbound_version_spec.rb └── defines │ ├── localzone_spec.rb │ ├── record_spec.rb │ └── stub_spec.rb ├── .sync.yml ├── types ├── size.pp ├── range.pp ├── local_zone.pp ├── hints_file.pp ├── rpz │ └── action.pp ├── chroot.pp ├── local_zone_override.pp ├── module.pp ├── resource_record_type.pp ├── access_control.pp ├── local_zone_type.pp ├── rpz.pp └── address.pp ├── .msync.yml ├── templates ├── interfaces.txt.erb ├── stub.erb ├── roothints.service.epp ├── forward.erb ├── remote.erb ├── local_zone.erb ├── unbound.modules.conf.erb └── unbound.conf.erb ├── .github ├── labeler.yml ├── workflows │ ├── labeler.yml │ ├── ci.yml │ ├── release.yml │ └── prepare_release.yml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── release.yml ├── .puppet-lint.rc ├── .rubocop.yml ├── .fixtures.yml ├── files └── roothints.timer ├── .editorconfig ├── hiera.yaml ├── lib └── facter │ └── unbound_version.rb ├── .gitignore ├── .rubocop_todo.yml ├── .pmtignore ├── examples └── init.pp ├── Gemfile ├── Rakefile ├── functions └── print_config.pp ├── manifests ├── localzone.pp ├── record.pp ├── forward.pp ├── stub.pp ├── remote.pp ├── dnstap.pp └── init.pp ├── .overcommit.yml ├── metadata.json ├── README.md └── LICENSE /data/os/Suse.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | unbound::runtime_dir: '/var/lib/unbound' 3 | -------------------------------------------------------------------------------- /spec/classes/expected/hieradata-root-hint.conf: -------------------------------------------------------------------------------- 1 | test 2 | root 3 | hints -------------------------------------------------------------------------------- /.sync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spec/spec_helper_acceptance.rb: 3 | unmanaged: false 4 | -------------------------------------------------------------------------------- /data/os/Debian/12.yaml: -------------------------------------------------------------------------------- 1 | unbound::package_name: ['unbound','unbound-anchor'] 2 | -------------------------------------------------------------------------------- /data/os/Debian/13.yaml: -------------------------------------------------------------------------------- 1 | unbound::package_name: ['unbound','unbound-anchor'] 2 | -------------------------------------------------------------------------------- /types/size.pp: -------------------------------------------------------------------------------- 1 | # @summary custom type for size 2 | type Unbound::Size = Pattern[/\d+([kmg])?/] 3 | -------------------------------------------------------------------------------- /data/os/Archlinux.yaml: -------------------------------------------------------------------------------- 1 | unbound::fetch_client: 'curl --silent --output' 2 | unbound::owner: 'root' 3 | -------------------------------------------------------------------------------- /types/range.pp: -------------------------------------------------------------------------------- 1 | # @summary custom type for ranges 2 | type Unbound::Range = Pattern[/\d+(-\d+)?/] 3 | -------------------------------------------------------------------------------- /types/local_zone.pp: -------------------------------------------------------------------------------- 1 | # custom enum type for local-zone types 2 | type Unbound::Local_zone = Hash[String, Unbound::Local_zone_type] 3 | -------------------------------------------------------------------------------- /types/hints_file.pp: -------------------------------------------------------------------------------- 1 | # @summary custom type for hints file 2 | type Unbound::Hints_file = Variant[Enum['builtin'], Stdlib::Absolutepath] 3 | -------------------------------------------------------------------------------- /data/os/Debian.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | unbound::pidfile: '/run/unbound.pid' 3 | unbound::runtime_dir: '/var/lib/unbound' 4 | unbound::purge_unbound_conf_d: true 5 | -------------------------------------------------------------------------------- /types/rpz/action.pp: -------------------------------------------------------------------------------- 1 | # @summary list of valid rpz actions 2 | type Unbound::Rpz::Action = Enum['nxdomain', 'nodata', 'passthru', 'drop', 'disabled', 'cname'] 3 | -------------------------------------------------------------------------------- /types/chroot.pp: -------------------------------------------------------------------------------- 1 | # @summary custom type for access chroot dir to allow support for empty string 2 | type Unbound::Chroot = Variant[Enum[''], Stdlib::Absolutepath] 3 | -------------------------------------------------------------------------------- /.msync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | modulesync_config_version: '10.4.0' 6 | -------------------------------------------------------------------------------- /templates/interfaces.txt.erb: -------------------------------------------------------------------------------- 1 | # Used by puppet-unbound 2 | <% unless @interface.empty? -%> 3 | <%= @interface.join("\n") %> 4 | <% else -%> 5 | <%= String.new %> 6 | <% end -%> 7 | 8 | -------------------------------------------------------------------------------- /data/os/RedHat.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | unbound::chroot: '' 3 | unbound::runtime_dir: '/var/lib/unbound' 4 | unbound::fetch_client: 'curl -o' 5 | unbound::validate_cmd: '/usr/sbin/unbound-checkconf %' 6 | -------------------------------------------------------------------------------- /types/local_zone_override.pp: -------------------------------------------------------------------------------- 1 | # @summary custom type for local zone overrides 2 | type Unbound::Local_zone_override = Struct[{ 3 | netblock => String, 4 | type => Unbound::Local_zone_type 5 | }] 6 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | skip-changelog: 6 | - head-branch: ['^release-*', 'release'] 7 | -------------------------------------------------------------------------------- /.puppet-lint.rc: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | --fail-on-warnings 5 | --no-parameter_documentation-check 6 | --no-parameter_types-check 7 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | inherit_from: .rubocop_todo.yml 6 | inherit_gem: 7 | voxpupuli-test: rubocop.yml 8 | -------------------------------------------------------------------------------- /types/module.pp: -------------------------------------------------------------------------------- 1 | # @summary list of valid modules 2 | type Unbound::Module = Enum[ 3 | 'validator', 4 | 'iterator', 5 | 'python', 6 | 'dns64', 7 | 'subnetcache', 8 | 'ipsecmod', 9 | 'cachedb', 10 | 'respip', 11 | ] 12 | -------------------------------------------------------------------------------- /.fixtures.yml: -------------------------------------------------------------------------------- 1 | --- 2 | fixtures: 3 | repositories: 4 | concat: https://github.com/puppetlabs/puppetlabs-concat.git 5 | stdlib: https://github.com/puppetlabs/puppetlabs-stdlib.git 6 | systemd: https://github.com/voxpupuli/puppet-systemd.git 7 | -------------------------------------------------------------------------------- /data/os/Darwin.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | unbound::confdir: '/opt/local/etc/unbound' 3 | unbound::logdir: '/opt/local/var/log/unbound' 4 | unbound::service_name: 'org.macports.unbound' 5 | unbound::package_provider: 'macports' 6 | unbound::fetch_client: 'curl -o' 7 | 8 | -------------------------------------------------------------------------------- /files/roothints.timer: -------------------------------------------------------------------------------- 1 | # THIS FILE IS MANAGED BY PUPPET 2 | # BASED ON https://wiki.archlinux.org/title/Unbound#Roothints_systemd_timer 3 | [Unit] 4 | Description=Run root.hints monthly 5 | 6 | [Timer] 7 | OnCalendar=monthly 8 | Persistent=true 9 | 10 | [Install] 11 | WantedBy=timers.target 12 | -------------------------------------------------------------------------------- /types/resource_record_type.pp: -------------------------------------------------------------------------------- 1 | # custom type for resource record used for local-data 2 | type Unbound::Resource_record_type = Struct[{ 3 | 'name' => String, 4 | 'ttl' => Optional[Integer], 5 | 'class' => Optional[String], 6 | 'type' => String, 7 | 'data' => String, 8 | }] 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | # Managed by modulesync - DO NOT EDIT 4 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 5 | 6 | root = true 7 | 8 | [*] 9 | charset = utf-8 10 | end_of_line = lf 11 | indent_size = 2 12 | tab_width = 2 13 | indent_style = space 14 | insert_final_newline = true 15 | trim_trailing_whitespace = true 16 | -------------------------------------------------------------------------------- /hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 5 3 | defaults: 4 | datadir: 'data' 5 | data_hash: 'yaml_data' 6 | hierarchy: 7 | - name: "release" 8 | path: "os/%{facts.os.family}/%{facts.os.release.major}.yaml" 9 | - name: "variant" 10 | path: "os/%{facts.os.family}/%{facts.os.name}.yaml" 11 | - name: "family" 12 | path: "os/%{facts.os.family}.yaml" 13 | 14 | -------------------------------------------------------------------------------- /spec/spec_helper_acceptance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Managed by modulesync - DO NOT EDIT 4 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 5 | 6 | require 'voxpupuli/acceptance/spec_helper_acceptance' 7 | 8 | configure_beaker(modules: :metadata) 9 | 10 | Dir['./spec/support/acceptance/**/*.rb'].sort.each { |f| require f } 11 | -------------------------------------------------------------------------------- /types/access_control.pp: -------------------------------------------------------------------------------- 1 | # @summary custom type for access control lists 2 | type Unbound::Access_control = Struct[{ 3 | action => Optional[Enum['deny', 'refuse', 'allow', 'allow_setrd', 'allow_snoop', 'allow_cookie', 'deny_non_local', 'refuse_non_local']], 4 | tags => Optional[Array[String]], 5 | rr_string => Optional[String], 6 | view => Optional[String], 7 | }] 8 | -------------------------------------------------------------------------------- /lib/facter/unbound_version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Facter.add(:unbound_version) do 4 | confine { Facter.value(:kernel) != 'windows' } 5 | setcode do 6 | if Facter::Util::Resolution.which('unbound') 7 | unbound_version = Facter::Util::Resolution.exec('unbound -V 2>&1') 8 | %r{Version\s+(\d+(?:\.\d+){2})\s+}.match(unbound_version)[1] 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /templates/stub.erb: -------------------------------------------------------------------------------- 1 | stub-zone: 2 | name: "<%= @name %>" 3 | <% [@address].flatten.each do |addr| -%> 4 | stub-addr: <%= addr %> 5 | <% end -%> 6 | <% @nameservers.each do |host| -%> 7 | stub-host: <%= host %> 8 | <% end -%> 9 | <% if @stub_first == 'true' or @stub_first == true -%> 10 | stub-first: yes 11 | <% end -%> 12 | <% if @no_cache == 'true' or @no_cache == true -%> 13 | stub-no-cache: yes 14 | <% end -%> 15 | -------------------------------------------------------------------------------- /templates/roothints.service.epp: -------------------------------------------------------------------------------- 1 | <%- | Stdlib::Absolutepath $hints_file, Stdlib::HTTPSUrl $root_hints_url, String[1] $fetch_client | -%> 2 | # THIS FILE IS MANAGED BY PUPPET 3 | # BASED ON https://wiki.archlinux.org/title/Unbound#Roothints_systemd_timer 4 | [Unit] 5 | Description=Update root hints for unbound 6 | After=network.target 7 | 8 | [Service] 9 | ExecStart=<%= $fetch_client %> <%= $hints_file %> <%= $root_hints_url %> 10 | -------------------------------------------------------------------------------- /data/os/OpenBSD.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | unbound::confdir: '/var/unbound/etc' 3 | unbound::pidfile: '/var/run/unbound.pid' 4 | unbound::logdir: '/var/log/unbound' 5 | unbound::owner: '_unbound' 6 | unbound::group: '_unbound' 7 | unbound::service_name: 'unbound' 8 | unbound::fetch_client: 'ftp -o' 9 | unbound::validate_cmd: '/usr/sbin/unbound-checkconf %' 10 | unbound::package_name: '' 11 | unbound::restart_cmd: "/usr/sbin/rcctl restart %{lookup('unbound::service_name')}" 12 | -------------------------------------------------------------------------------- /types/local_zone_type.pp: -------------------------------------------------------------------------------- 1 | # custom enum type for local-zone types 2 | type Unbound::Local_zone_type = Enum[ 3 | 'deny', 4 | 'refuse', 5 | 'static', 6 | 'transparent', 7 | 'redirect', 8 | 'nodefault', 9 | 'typetransparent', 10 | 'inform', 11 | 'inform_deny', 12 | 'inform_redirect', 13 | 'always_transparent', 14 | 'block_a', 15 | 'always_refuse', 16 | 'always_nxdomain', 17 | 'always_null', 18 | 'noview', 19 | 'nodefault', 20 | ] 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | /pkg/ 5 | /Gemfile.lock 6 | /Gemfile.local 7 | /vendor/ 8 | /.vendor/ 9 | /spec/fixtures/manifests/ 10 | /spec/fixtures/modules/ 11 | /.vagrant/ 12 | /.bundle/ 13 | /.ruby-version 14 | /coverage/ 15 | /log/ 16 | /.idea/ 17 | /.dependencies/ 18 | /.librarian/ 19 | /Puppetfile.lock 20 | *.iml 21 | .*.sw? 22 | /.yardoc/ 23 | /Guardfile 24 | bolt-debug.log 25 | .rerun.json 26 | -------------------------------------------------------------------------------- /data/os/FreeBSD.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | unbound::confdir: '/usr/local/etc/unbound' 3 | unbound::pidfile: '/usr/local/etc/unbound/unbound.pid' 4 | unbound::logdir: '/var/log/unbound' 5 | unbound::fetch_client: 'fetch -o' 6 | unbound::control_enable: true 7 | unbound::control_setup_path: '/usr/local/sbin/unbound-control-setup' 8 | unbound::control_path: '/usr/local/sbin/unbound-control' 9 | unbound::validate_cmd: '/usr/local/sbin/unbound-checkconf %' 10 | unbound::restart_cmd: "/usr/sbin/service %{hiera('unbound::service_name')} restart" 11 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: "Pull Request Labeler" 6 | 7 | # yamllint disable-line rule:truthy 8 | on: 9 | pull_request_target: {} 10 | 11 | permissions: 12 | contents: read 13 | pull-requests: write 14 | 15 | jobs: 16 | labeler: 17 | permissions: 18 | contents: read 19 | pull-requests: write 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/labeler@v5 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: CI 6 | 7 | # yamllint disable-line rule:truthy 8 | on: 9 | pull_request: {} 10 | push: 11 | branches: 12 | - main 13 | - master 14 | 15 | concurrency: 16 | group: ${{ github.ref_name }} 17 | cancel-in-progress: true 18 | 19 | permissions: 20 | contents: read 21 | 22 | jobs: 23 | puppet: 24 | name: Puppet 25 | uses: voxpupuli/gha-puppet/.github/workflows/beaker.yml@v4 26 | -------------------------------------------------------------------------------- /templates/forward.erb: -------------------------------------------------------------------------------- 1 | forward-zone: 2 | name: "<%= @zone %>" 3 | <% Array(@address).each do |addr| -%> 4 | forward-addr: <%= addr %> 5 | <% end -%> 6 | <% Array(@host).each do |h| -%> 7 | forward-host: <%= h %> 8 | <% end -%> 9 | <% if @forward_first != 'no' -%> 10 | forward-first: <%= @forward_first %> 11 | <% end -%> 12 | <% if @forward_ssl_upstream != 'no' -%> 13 | forward-ssl-upstream: <%= @forward_ssl_upstream %> 14 | <% end -%> 15 | <% if @forward_tls_upstream != 'no' -%> 16 | forward-tls-upstream: <%= @forward_tls_upstream %> 17 | <% end -%> 18 | -------------------------------------------------------------------------------- /data/os/Solaris/SmartOS.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | unbound::confdir: '/opt/local/etc/unbound' 3 | unbound::pidfile: '/usr/local/etc/unbound/unbound.pid' 4 | unbound::logdir: '/var/log/unbound' 5 | unbound::fetch_client: 'wget -O' 6 | unbound::control_setup_path: '/opt/local/sbin/unbound-control-setup' 7 | unbound::control_path: '/opt/local/sbin/unbound-control' 8 | unbound::validate_cmd: '/opt/local/sbin/unbound-checkconf %' 9 | unbound::restart_cmd: "/usr/sbin/svcadm restart %{hiera('unbound::service_name')}" 10 | unbound::anchor_fetch_command: "/opt/local/sbin/unbound-anchor -a %{hiera('unbound::auto_trust_anchor_file')}" 11 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2023-08-17 21:39:21 UTC using RuboCop version 1.50.2. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 2 10 | # This cop supports unsafe autocorrection (--autocorrect-all). 11 | RSpec/BeEq: 12 | Exclude: 13 | - 'spec/unit/unbound/facter/unbound_version_spec.rb' 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | #### Pull Request (PR) description 10 | 13 | 14 | #### This Pull Request (PR) fixes the following issues 15 | 21 | -------------------------------------------------------------------------------- /.pmtignore: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | /docs/ 5 | /pkg/ 6 | /Gemfile 7 | /Gemfile.lock 8 | /Gemfile.local 9 | /vendor/ 10 | /.vendor/ 11 | /spec/ 12 | /Rakefile 13 | /.vagrant/ 14 | /.bundle/ 15 | /.ruby-version 16 | /coverage/ 17 | /log/ 18 | /.idea/ 19 | /.dependencies/ 20 | /.github/ 21 | /.librarian/ 22 | /Puppetfile.lock 23 | /Puppetfile 24 | *.iml 25 | /.editorconfig 26 | /.fixtures.yml 27 | /.gitignore 28 | /.msync.yml 29 | /.overcommit.yml 30 | /.pmtignore 31 | /.rspec 32 | /.rspec_parallel 33 | /.rubocop.yml 34 | /.sync.yml 35 | .*.sw? 36 | /.yardoc/ 37 | /.yardopts 38 | /Dockerfile 39 | /HISTORY.md 40 | -------------------------------------------------------------------------------- /templates/remote.erb: -------------------------------------------------------------------------------- 1 | remote-control: 2 | <% if @enable -%> 3 | control-enable: yes 4 | <% else -%> 5 | control-enable: no 6 | <% end -%> 7 | <% @interface.each do |int| -%> 8 | control-interface: <%= int %> 9 | <% end -%> 10 | control-port: <%= @port %> 11 | 12 | <% if @control_use_cert -%> 13 | <% if @server_key_file -%> 14 | server-key-file: <%= @server_key_file %> 15 | <% end -%> 16 | <% if @server_cert_file -%> 17 | server-cert-file: <%= @server_cert_file %> 18 | <% end -%> 19 | <% if @control_key_file -%> 20 | control-key-file: <%= @control_key_file %> 21 | <% end -%> 22 | <% if @control_cert_file -%> 23 | control-cert-file: <%= @control_cert_file %> 24 | <% end -%> 25 | <% end -%> 26 | -------------------------------------------------------------------------------- /templates/local_zone.erb: -------------------------------------------------------------------------------- 1 | server: 2 | local-zone: "<%= @zone %>" <%= @type %> 3 | <%- @local_data.each do |resource_record| -%> 4 | <%- rr = resource_record['name'] -%> 5 | <%- rr = "#{rr} #{resource_record['ttl']}" if resource_record['ttl'] -%> 6 | <%- rr = "#{rr} #{resource_record['class']}" if resource_record['class'] -%> 7 | <%- rr = "#{rr} #{resource_record['type']}" -%> 8 | <%- if resource_record['type'] != 'TXT' -%> 9 | <%- rr = "#{rr} #{resource_record['data']}" -%> 10 | local-data: "<%= rr %>" 11 | <%- else -%> 12 | <%- rr = "#{rr} #{(resource_record['data'].scan /.{1,255}/).inject(''){|r, s| "#{r}\"#{s}\""}}" -%> 13 | local-data: '<%= rr %>' 14 | <%- end -%> 15 | <%- end -%> 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: Release 6 | 7 | # yamllint disable-line rule:truthy 8 | on: 9 | push: 10 | tags: 11 | - '*' 12 | 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | release: 18 | name: Release 19 | uses: voxpupuli/gha-puppet/.github/workflows/release.yml@v3 20 | with: 21 | allowed_owner: 'voxpupuli' 22 | secrets: 23 | # Configure secrets here: 24 | # https://docs.github.com/en/actions/security-guides/encrypted-secrets 25 | username: ${{ secrets.PUPPET_FORGE_USERNAME }} 26 | api_key: ${{ secrets.PUPPET_FORGE_API_KEY }} 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ## Affected Puppet, Ruby, OS and module versions/distributions 12 | 13 | - Puppet: 14 | - Ruby: 15 | - Distribution: 16 | - Module version: 17 | 18 | ## How to reproduce (e.g Puppet code you use) 19 | 20 | ## What are you seeing 21 | 22 | ## What behaviour did you expect instead 23 | 24 | ## Output log 25 | 26 | ## Any additional information you'd like to impart 27 | -------------------------------------------------------------------------------- /spec/type_aliases/access_control_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Unbound::Access_control' do 6 | describe 'valid modes' do 7 | values = [ 8 | { 9 | 'action' => 'deny', 10 | }, 11 | { 12 | 'action' => 'allow', 13 | 'tags' => %w[fuu foo], 14 | 'rr_string' => 'a string', 15 | 'view' => 'another string :o', 16 | }, 17 | { 18 | 'action' => 'allow', 19 | 'rr_string' => '0.0.0.0/0', 20 | }, 21 | { 22 | 'action' => 'allow', 23 | 'rr_string' => '::/0', 24 | } 25 | ] 26 | values.each do |value| 27 | describe value.inspect do 28 | it { is_expected.to allow_value(value) } 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /examples/init.pp: -------------------------------------------------------------------------------- 1 | class { 'unbound': 2 | interface => ['::0','0.0.0.0'], 3 | access => ['fe80::/10','10.0.0.0/24', '127.0.0.1/32 allow_snoop'], 4 | } 5 | 6 | unbound::stub { 'example.com': 7 | address => '10.0.0.10', 8 | insecure => true, 9 | } 10 | 11 | unbound::stub { '0.0.0.10.in-addr.arpa.': 12 | address => '10.0.0.10', 13 | insecure => true, 14 | } 15 | 16 | unbound::stub { '0.0.0.10.in-addr.arpa.': 17 | address => '10.0.0.10@10053', 18 | insecure => true, 19 | } 20 | 21 | unbound::stub { '10.0.10.in-addr.arpa.': 22 | address => ['ns1.example.com', '10.0.0.10@10053', 'ns2.example.com'], 23 | } 24 | 25 | unbound::localzone { '10.in-addr.arpa.': 26 | type => 'nodefault', 27 | } 28 | 29 | unbound::forward { '10.in-addr.arpa.': 30 | address => '10.0.0.10', 31 | forward_first => 'yes', 32 | } 33 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | source ENV['GEM_SOURCE'] || 'https://rubygems.org' 5 | 6 | group :test do 7 | gem 'voxpupuli-test', '~> 13.0', :require => false 8 | gem 'puppet_metadata', '~> 5.0', :require => false 9 | end 10 | 11 | group :development do 12 | gem 'guard-rake', :require => false 13 | gem 'overcommit', '>= 0.39.1', :require => false 14 | end 15 | 16 | group :system_tests do 17 | gem 'voxpupuli-acceptance', '~> 4.0', :require => false 18 | end 19 | 20 | group :release do 21 | gem 'voxpupuli-release', '~> 5.0', :require => false 22 | end 23 | 24 | gem 'rake', :require => false 25 | 26 | gem 'openvox', ENV.fetch('OPENVOX_GEM_VERSION', [">= 7", "< 9"]), :require => false, :groups => [:test] 27 | 28 | # vim: syntax=ruby 29 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Managed by modulesync - DO NOT EDIT 4 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 5 | 6 | # puppetlabs_spec_helper will set up coverage if the env variable is set. 7 | # We want to do this if lib exists and it hasn't been explicitly set. 8 | ENV['COVERAGE'] ||= 'yes' if Dir.exist?(File.expand_path('../lib', __dir__)) 9 | 10 | require 'voxpupuli/test/spec_helper' 11 | 12 | RSpec.configure do |c| 13 | c.facterdb_string_keys = false 14 | end 15 | 16 | add_mocked_facts! 17 | 18 | if File.exist?(File.join(__dir__, 'default_module_facts.yml')) 19 | facts = YAML.safe_load(File.read(File.join(__dir__, 'default_module_facts.yml'))) 20 | facts&.each do |name, value| 21 | add_custom_fact name.to_sym, value 22 | end 23 | end 24 | Dir['./spec/support/spec/**/*.rb'].sort.each { |f| require f } 25 | -------------------------------------------------------------------------------- /.github/workflows/prepare_release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: 'Prepare Release' 6 | 7 | on: 8 | workflow_dispatch: 9 | inputs: 10 | version: 11 | description: 'Module version to be released. Must be a valid semver string without leading v. (1.2.3)' 12 | required: false 13 | 14 | permissions: 15 | contents: write 16 | pull-requests: write 17 | 18 | jobs: 19 | release_prep: 20 | uses: 'voxpupuli/gha-puppet/.github/workflows/prepare_release.yml@v3' 21 | with: 22 | version: ${{ github.event.inputs.version }} 23 | allowed_owner: 'voxpupuli' 24 | secrets: 25 | # Configure secrets here: 26 | # https://docs.github.com/en/actions/security-guides/encrypted-secrets 27 | github_pat: '${{ secrets.PCCI_PAT_RELEASE_PREP }}' 28 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | # https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes 6 | 7 | changelog: 8 | exclude: 9 | labels: 10 | - duplicate 11 | - invalid 12 | - modulesync 13 | - question 14 | - skip-changelog 15 | - wont-fix 16 | - wontfix 17 | 18 | categories: 19 | - title: Breaking Changes 🛠 20 | labels: 21 | - backwards-incompatible 22 | 23 | - title: New Features 🎉 24 | labels: 25 | - enhancement 26 | 27 | - title: Bug Fixes 🐛 28 | labels: 29 | - bug 30 | 31 | - title: Documentation Updates 📚 32 | labels: 33 | - documentation 34 | - docs 35 | 36 | - title: Dependency Updates ⬆️ 37 | labels: 38 | - dependencies 39 | 40 | - title: Other Changes 41 | labels: 42 | - "*" 43 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | begin 5 | require 'voxpupuli/test/rake' 6 | rescue LoadError 7 | # only available if gem group test is installed 8 | end 9 | 10 | begin 11 | require 'voxpupuli/acceptance/rake' 12 | rescue LoadError 13 | # only available if gem group acceptance is installed 14 | end 15 | 16 | begin 17 | require 'voxpupuli/release/rake_tasks' 18 | rescue LoadError 19 | # only available if gem group releases is installed 20 | else 21 | GCGConfig.user = 'voxpupuli' 22 | GCGConfig.project = 'puppet-unbound' 23 | end 24 | 25 | desc "Run main 'test' task and report merged results to coveralls" 26 | task test_with_coveralls: [:test] do 27 | if Dir.exist?(File.expand_path('../lib', __FILE__)) 28 | require 'coveralls/rake/task' 29 | Coveralls::RakeTask.new 30 | Rake::Task['coveralls:push'].invoke 31 | else 32 | puts 'Skipping reporting to coveralls. Module has no lib dir' 33 | end 34 | end 35 | 36 | # vim: syntax=ruby 37 | -------------------------------------------------------------------------------- /functions/print_config.pp: -------------------------------------------------------------------------------- 1 | # @summary Print a configuration value if it is defined and the version is supported 2 | # @param name the config item name 3 | # @param value the config item value 4 | # @param version the version when the config item was introduced 5 | # @return the config item as a string or an empty string if the version is not supported 6 | function unbound::print_config ( 7 | String[1] $name, 8 | Optional[Variant[Boolean, Integer, String, Array[String, 1]]] $value = undef, 9 | Optional[String[1]] $version = undef, 10 | ) >> String { 11 | $unbound_version = $facts['unbound_version'].lest || { '0.a' } 12 | if ($value =~ Undef or ($version =~ NotUndef and versioncmp($unbound_version, $version) < 0)) { 13 | return '' 14 | } 15 | $value ? { 16 | String => " ${name}: \"${value}\"", 17 | Integer => " ${name}: ${value}", 18 | Boolean => " ${name}: ${value.bool2str('yes', 'no')}", 19 | Array => $value.map |$v| { " ${name}: \"${v}\"" }.join("\n"), 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spec/functions/print_config_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'unbound::print_config' do 6 | it { is_expected.to run.with_params('string_name', 'string_value').and_return(' string_name: "string_value"') } 7 | it { is_expected.to run.with_params('int_name', 42).and_return(' int_name: 42') } 8 | it { is_expected.to run.with_params('true_name', true).and_return(' true_name: yes') } 9 | it { is_expected.to run.with_params('false_name', false).and_return(' false_name: no') } 10 | 11 | it do 12 | is_expected.to run.with_params('list_name', %w[value1 value2]). 13 | and_return(" list_name: \"value1\"\n list_name: \"value2\"") 14 | end 15 | 16 | context 'with version' do 17 | let(:facts) { { 'unbound_version' => '1.21.0' } } 18 | 19 | it { is_expected.to run.with_params('supported', 42, '1.11.0').and_return(' supported: 42') } 20 | it { is_expected.to run.with_params('supported', 42, '1.21.0').and_return(' supported: 42') } 21 | it { is_expected.to run.with_params('unsupported', 42, '1.22.0').and_return('') } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/type_aliases/local_zone_type_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 6 | describe 'Unbound::Local_zone_type' do 7 | describe 'valid modes' do 8 | %w[ 9 | deny refuse static transparent redirect nodefault typetransparent 10 | inform inform_deny always_transparent always_refuse always_nxdomain 11 | ].each do |value| 12 | describe value.inspect do 13 | it { is_expected.to allow_value(value) } 14 | end 15 | end 16 | end 17 | 18 | describe 'invalid modes' do 19 | context 'with garbage inputs' do 20 | [ 21 | nil, 22 | [nil], 23 | [nil, nil], 24 | { 'foo' => 'bar' }, 25 | {}, 26 | '', 27 | 'ネット', 28 | '644', 29 | '7777', 30 | '1', 31 | '22', 32 | '333', 33 | '55555', 34 | '0x123', 35 | '0649', 36 | 'deNy', 37 | 'refse', 38 | 'sta tic' 39 | ].each do |value| 40 | describe value.inspect do 41 | it { is_expected.not_to allow_value(value) } 42 | end 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/type_aliases/unbound_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 6 | describe 'Unbound::Address' do 7 | describe 'accepts ipv4 and ipv6 addresses' do 8 | [ 9 | '224.0.0.0', 10 | '255.255.255.255', 11 | '0.0.0.0', 12 | '192.88.99.0', 13 | '2001:0db8:85a3:0000:0000:8a2e:0370:7334', 14 | 'fa76:8765:34ac:0823:ab76:eee9:0987:1111', 15 | '127.0.0.1', 16 | '8.8.4.4', 17 | '10.1.240.4@5353', 18 | '52.10.10.141', 19 | 'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210', 20 | 'FF01:0:0:0:0:0:0:101', 21 | 'FF01::101', 22 | 'FF01:0:0:0:0:0:0:101@5353', 23 | 'FF01::101@5353', 24 | '::', 25 | '12AB::CD30:192.168.0.1' 26 | ].each do |value| 27 | describe value.inspect do 28 | it { is_expected.to allow_value(value) } 29 | end 30 | end 31 | end 32 | 33 | describe 'rejects other values' do 34 | [ 35 | 'nope', 36 | '77', 37 | '4.4.4', 38 | '2001:0db8:85a3:000000:0000:8a2e:0370:7334', 39 | '4.4.4.4:5353', 40 | '4.4.4@4' 41 | ].each do |value| 42 | describe value.inspect do 43 | it { is_expected.not_to allow_value(value) } 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /types/rpz.pp: -------------------------------------------------------------------------------- 1 | # @summary Type used to validate rzp configueration 2 | # @param primary the primary name server 3 | # @param master another name for the primary name server 4 | # @param url to download the rpz zone 5 | # @param allow_notify list of hosts allowed to notify 6 | # @param zonefile path to zonefile 7 | # @param rpz_action_override Always use this RPZ action for matching triggers 8 | # from this zone. Possible action are: nxdomain, nodata, passthru, drop, 9 | # disabled and cname. 10 | # @param rpz_cname_override The CNAME target domain to use if the cname action 11 | # is configured for rpz-action-override. 12 | # @param rpz_log Log all applied RPZ actions for this RPZ zone 13 | # @param rpz_log_name Specify a string to be part of the log line, for easy referencing. 14 | # @param tags Limit the policies from this RPZ clause to clients with a matching tag 15 | type Unbound::Rpz = Struct[{ 16 | primary => Optional[Array[Stdlib::Host]], 17 | master => Optional[Array[Stdlib::Host]], 18 | url => Optional[Array[Stdlib::HTTPUrl]], 19 | allow_notify => Optional[Array[Stdlib::Host]], 20 | zonefile => Optional[Stdlib::Unixpath], 21 | rpz_action_override => Optional[Unbound::Rpz::Action], 22 | rpz_cname_override => Optional[Stdlib::Fqdn], 23 | rpz_log => Optional[Boolean], 24 | rpz_log_name => Optional[String], 25 | tags => Optional[Array[String]], 26 | }] 27 | -------------------------------------------------------------------------------- /manifests/localzone.pp: -------------------------------------------------------------------------------- 1 | # @summary Configures a local zone. 2 | # The default zones are localhost, reverse 127.0.0.1 and ::1, and the 3 | # AS112 zones. The AS112 zones are reverse DNS zones for private use and 4 | # reserved IP addresses for which the servers on the internet cannot pro- 5 | # vide correct answers. 6 | # 7 | # === Parameters: 8 | # 9 | # @param zone String. Zone name. 10 | # @param type Custom type Unbound::Local_zone_type. 11 | # @param config_file name of configuration file. 12 | # @param local_data 13 | # Define local data which should be rendered into configuration file. Required 14 | # value is an Array of the custom type Unbond::Resource_record_type. 15 | # Default value: []. 16 | # Example: 17 | # unbound::localzone::local_data: 18 | # - name: 'api.test.com' 19 | # ttl: 15 20 | # class: IN 21 | # type: A 22 | # data: '1.1.1.1' 23 | # - name: 'backend.test.com' 24 | # type: A 25 | # data: '2.2.2.2' 26 | # @param template_name Use a custom template. 27 | # 28 | define unbound::localzone ( 29 | Unbound::Local_zone_type $type, 30 | String $zone = $name, 31 | $config_file = $unbound::config_file, 32 | Array[Unbound::Resource_record_type] $local_data = [], 33 | String $template_name = 'unbound/local_zone.erb' 34 | ) { 35 | concat::fragment { "unbound-localzone-${name}": 36 | order => '06', 37 | target => $config_file, 38 | content => template($template_name), 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spec/type_aliases/rpz_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Unbound::Rpz' do 6 | describe 'valid modes' do 7 | values = [ 8 | { 9 | 'primary' => ['primary.example.org'], 10 | 'master' => ['mastr.example.org'], 11 | 'url' => ['http://rpz.example.org/zone'], 12 | 'allow_notify' => ['192.0.2.1'], 13 | 'zonefile' => '/foo/zone', 14 | 'rpz_action_override' => 'passthru', 15 | 'rpz_cname_override' => 'cname.example.org', 16 | 'rpz_log' => true, 17 | 'rpz_log_name' => 'rpzlog', 18 | 'tags' => %w[foo bar] 19 | }, 20 | { 21 | 'primary' => ['primary.example.org'], 22 | }, 23 | { 24 | 'master' => ['mastr.example.org'], 25 | }, 26 | { 27 | 'url' => ['http://rpz.example.org/zone'], 28 | }, 29 | { 30 | 'allow_notify' => ['192.0.2.1'], 31 | }, 32 | { 33 | 'zonefile' => '/foo/zone', 34 | }, 35 | { 36 | 'rpz_action_override' => 'passthru', 37 | }, 38 | { 39 | 'rpz_cname_override' => 'cname.example.org', 40 | }, 41 | { 42 | 'rpz_log' => true, 43 | }, 44 | { 45 | 'rpz_log_name' => 'rpzlog', 46 | }, 47 | { 48 | 'tags' => %w[foo bar] 49 | } 50 | ] 51 | values.each do |value| 52 | describe value.inspect do 53 | it { is_expected.to allow_value(value) } 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /manifests/record.pp: -------------------------------------------------------------------------------- 1 | # @summary Create an unbound static DNS record override 2 | # @param content The name of the record (ip address) 3 | # @param ttl The time to live for this record, defaults to '14400' 4 | # @param type Type or the record 5 | # @param reverse Reverse record or not, defaults to false 6 | # @param entry Name entry for the record (name) 7 | # @param config_file name of configuration file 8 | # 9 | define unbound::record ( 10 | Variant[Array[String[1]], String[1]] $content, 11 | $ttl = '14400', 12 | $type = 'A', 13 | $reverse = false, 14 | $entry = $name, 15 | $config_file = $unbound::config_file, 16 | ) { 17 | $local_data = [$content].flatten.map |$_content| { 18 | if $type != 'TXT' { 19 | " local-data: \"${entry} ${ttl} IN ${type} ${_content}\"" 20 | } else { 21 | # Long TXT records must be broken into strings of 255 characters as per RFC 4408 22 | $real_content = $_content.slice(255) 23 | .reduce('') |String $record, Array $s| { 24 | "${record}\"${s.join()}\"" 25 | } 26 | " local-data: '${entry} ${ttl} IN ${type} ${real_content}'" 27 | } 28 | }.join("\n") 29 | $local_data_ptr = " local-data-ptr: \"${content} ${entry}\"" 30 | 31 | $config = $reverse ? { 32 | true => "${local_data}\n${local_data_ptr}\n", 33 | default => "${local_data}\n", 34 | } 35 | 36 | concat::fragment { "unbound-stub-${title}-local-record": 37 | order => '07', 38 | target => $config_file, 39 | content => $config, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | # 4 | # Hooks are only enabled if you take action. 5 | # 6 | # To enable the hooks run: 7 | # 8 | # ``` 9 | # bundle exec overcommit --install 10 | # # ensure .overcommit.yml does not harm to you and then 11 | # bundle exec overcommit --sign 12 | # ``` 13 | # 14 | # (it will manage the .git/hooks directory): 15 | # 16 | # Examples howto skip a test for a commit or push: 17 | # 18 | # ``` 19 | # SKIP=RuboCop git commit 20 | # SKIP=PuppetLint git commit 21 | # SKIP=RakeTask git push 22 | # ``` 23 | # 24 | # Don't invoke overcommit at all: 25 | # 26 | # ``` 27 | # OVERCOMMIT_DISABLE=1 git commit 28 | # ``` 29 | # 30 | # Read more about overcommit: https://github.com/brigade/overcommit 31 | # 32 | # To manage this config yourself in your module add 33 | # 34 | # ``` 35 | # .overcommit.yml: 36 | # unmanaged: true 37 | # ``` 38 | # 39 | # to your modules .sync.yml config 40 | --- 41 | PreCommit: 42 | RuboCop: 43 | enabled: true 44 | description: 'Runs rubocop on modified files only' 45 | command: ['bundle', 'exec', 'rubocop'] 46 | RakeTarget: 47 | enabled: true 48 | description: 'Runs lint on modified files only' 49 | targets: 50 | - 'lint' 51 | command: ['bundle', 'exec', 'rake'] 52 | YamlSyntax: 53 | enabled: true 54 | JsonSyntax: 55 | enabled: true 56 | TrailingWhitespace: 57 | enabled: true 58 | 59 | PrePush: 60 | RakeTarget: 61 | enabled: true 62 | description: 'Run rake targets' 63 | targets: 64 | - 'validate' 65 | - 'test' 66 | - 'rubocop' 67 | command: ['bundle', 'exec', 'rake'] 68 | -------------------------------------------------------------------------------- /manifests/forward.pp: -------------------------------------------------------------------------------- 1 | # @summary Configures a zone for DNS forwarding 2 | # @param zone the name of the zone. 3 | # @param address 4 | # IP address of server to forward queries to. Can be IP 4 or IP 6 (and an 5 | # array or a single value. To use a nondefault port for DNS communication 6 | # append '@' with the port number. 7 | # @param host 8 | # Hostname of server to forward queries to. Can be IP 4 or IP 6 (and an array 9 | # or a single value. To use a nondefault port for DNS communication append 10 | # '@' with the port number. 11 | # @param forward_first 12 | # If enabled, a query is attempted without the forward clause if 13 | # it fails. The data could not be retrieved and would have caused SERVFAIL 14 | # because the servers are unreachable, instead it is tried without this 15 | # clause. The default is 'no'. 16 | # @param forward_ssl_upstream 17 | # If enabled, unbound will query the forward DNS server via TLS. 18 | # @param forward_tls_upstream 19 | # If enabled, unbound will query the forward DNS server via TLS. 20 | # @param config_file name of configuration file 21 | # 22 | define unbound::forward ( 23 | Array $address = [], 24 | Array $host = [], 25 | $zone = $name, 26 | Pattern[/yes|no/] $forward_first = 'no', 27 | Pattern[/yes|no/] $forward_ssl_upstream = 'no', 28 | Pattern[/yes|no/] $forward_tls_upstream = 'no', 29 | $config_file = $unbound::config_file, 30 | ) { 31 | concat::fragment { "unbound-forward-${name}": 32 | order => '20', 33 | target => $config_file, 34 | content => template('unbound/forward.erb'), 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spec/acceptance/unbound_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | if fact('osfamily') == 'FreeBSD' 5 | apply_manifest("package{'dns/bind-tools': ensure => 'present'}") 6 | else 7 | if fact('osfamily') == 'RedHat' 8 | apply_manifest("package{'bind-utils': ensure => 'present'}") 9 | else 10 | apply_manifest("package{'dnsutils': ensure => 'present'}") 11 | end 12 | 13 | shell('sysctl net.ipv6.conf.all.disable_ipv6=0') 14 | end 15 | describe 'unbound class' do 16 | describe 'running puppet code' do 17 | it 'work with no errors' do 18 | pp = "class {'unbound': }" 19 | apply_manifest(pp, catch_failures: true) 20 | apply_manifest(pp, catch_failures: true) 21 | expect(apply_manifest(pp, catch_failures: true).exit_code).to eq 0 22 | end 23 | 24 | if fact('osfamily') == 'Archlinux' 25 | describe command('systemctl restart unbound') do 26 | its(:exit_status) { is_expected.to eq 0 } 27 | end 28 | else 29 | describe command('service unbound restart') do 30 | its(:exit_status) { is_expected.to eq 0 } 31 | end 32 | end 33 | 34 | describe service('unbound') do 35 | it { is_expected.to be_running } 36 | end 37 | 38 | describe port(53) do 39 | it { is_expected.to be_listening } 40 | end 41 | 42 | describe command('dig +dnssec . soa @localhost') do 43 | its(:stdout) { is_expected.to match %r{\.\s+\d+\s+IN\s+SOA\s+a\.root-servers\.net\.\snstld\.verisign-grs\.com\.\s\d+\s1800\s900\s604800\s86400} } 44 | its(:stdout) { is_expected.to match %r{\.\s+\d+\s+IN\s+RRSIG\s+SOA} } 45 | its(:stdout) { is_expected.to match %r{flags: qr rd ra ad;} } 46 | end 47 | 48 | describe command('dig +dnssec SOA servfail.nl @localhost') do 49 | its(:stdout) { is_expected.to match %r{status: SERVFAIL} } 50 | end 51 | 52 | describe command('dig +dnssec +cd SOA servfail.nl @localhost') do 53 | its(:stdout) { is_expected.to match %r{status: NOERROR} } 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/unit/unbound/facter/unbound_version_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | version_string = <<~VERSION 6 | unbound: invalid option -- 'V' 7 | usage: unbound [options] 8 | start unbound daemon DNS resolver. 9 | -h this help 10 | -c file config file to read instead of /etc/unbound/unbound.conf 11 | file format is described in unbound.conf(5). 12 | -d do not fork into the background. 13 | -v verbose (more times to increase verbosity) 14 | Version %s 15 | linked libs: libevent 2.0.21-stable (it uses epoll), OpenSSL 1.1.0l 10 Sep 2019 16 | linked modules: dns64 python validator iterator 17 | BSD licensed, see LICENSE in source package for details. 18 | Report bugs to unbound-bugs@nlnetlabs.nl 19 | VERSION 20 | 21 | tests = { 22 | 'valid' => ['1.1.1', '1.90.24', '1.0.404', '2.5.6'], 23 | 'invalid' => ['1', '1.1', '1.1.1.1', '1,1,1', '2:5.1', 'foobar'] 24 | } 25 | describe Facter::Util::Fact.to_s do 26 | before { Facter.clear } 27 | 28 | context 'unbound not in path' do 29 | before do 30 | allow(Facter::Util::Resolution).to receive(:which).with('unbound').and_return(false) 31 | end 32 | 33 | it { expect(Facter.fact(:unbound_version).value).to be_nil } 34 | end 35 | 36 | tests.each_pair do |test, versions| 37 | describe "test #{test} versions" do 38 | before do 39 | allow(Facter::Util::Resolution).to receive(:which).with('unbound').and_return(true) 40 | end 41 | 42 | versions.each do |version| 43 | context "test version #{version}" do 44 | before do 45 | allow(Facter::Util::Resolution).to receive(:exec).with('unbound -V 2>&1') do 46 | version_string % version 47 | end 48 | end 49 | 50 | if test == 'valid' 51 | it { expect(Facter.fact(:unbound_version).value).to eq(version) } 52 | else 53 | it { expect(Facter.fact(:unbound_version).value).to be_nil } 54 | end 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/defines/localzone_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'unbound::localzone' do 6 | let(:title) { 'example.com' } 7 | let(:pre_condition) { 'class { "unbound": }' } 8 | 9 | on_supported_os.each do |os, os_facts| 10 | context "on #{os}" do 11 | let(:facts) { os_facts } 12 | 13 | context 'with a TXT record (<255 characters)' do 14 | let(:params) do 15 | { 16 | type: 'transparent', 17 | local_data: [ 18 | { 19 | name: 'txt.example.com', 20 | type: 'TXT', 21 | data: 'Short TXT Record' 22 | } 23 | ] 24 | } 25 | end 26 | 27 | it { is_expected.to contain_unbound__localzone('example.com') } 28 | 29 | it { 30 | expect(subject).to contain_concat__fragment('unbound-localzone-example.com').with( 31 | content: <<~ZONE 32 | server: 33 | local-zone: "example.com" transparent 34 | local-data: 'txt.example.com TXT "Short TXT Record"' 35 | ZONE 36 | ) 37 | } 38 | end 39 | 40 | context 'with a TXT record (>255 characters)' do 41 | long_txt_record = "Long TXT Record #{'X' * 255}" 42 | long_txt_record_chunked = "Long TXT Record #{'X' * 239}\"\"#{'X' * 16}" 43 | let(:params) do 44 | { 45 | type: 'transparent', 46 | local_data: [ 47 | { 48 | name: 'txt.example.com', 49 | type: 'TXT', 50 | data: long_txt_record 51 | } 52 | ] 53 | } 54 | end 55 | 56 | it { is_expected.to contain_unbound__localzone('example.com') } 57 | 58 | it { 59 | expect(subject).to contain_concat__fragment('unbound-localzone-example.com').with( 60 | content: <<~ZONE 61 | server: 62 | local-zone: "example.com" transparent 63 | local-data: 'txt.example.com TXT "#{long_txt_record_chunked}"' 64 | ZONE 65 | ) 66 | } 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/defines/record_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'unbound::record' do 6 | let(:title) { 'record.example.com' } 7 | let(:pre_condition) { 'class { "unbound": }' } 8 | 9 | on_supported_os.each do |os, os_facts| 10 | context "on #{os}" do 11 | let(:facts) { os_facts } 12 | 13 | context 'with a TXT record (<255 characters)' do 14 | let(:params) do 15 | { 16 | type: 'TXT', 17 | content: 'Short TXT Record', 18 | reverse: false 19 | } 20 | end 21 | 22 | it { is_expected.to contain_unbound__record('record.example.com') } 23 | 24 | it { 25 | expect(subject).to contain_concat__fragment('unbound-stub-record.example.com-local-record').with( 26 | content: " local-data: 'record.example.com 14400 IN TXT \"Short TXT Record\"'\n" 27 | ) 28 | } 29 | end 30 | 31 | context 'with a TXT record (>255 characters)' do 32 | long_txt_record = "Long TXT Record #{'X' * 255}" 33 | long_txt_record_chunked = "Long TXT Record #{'X' * 239}\"\"#{'X' * 16}" 34 | let(:params) do 35 | { 36 | type: 'TXT', 37 | content: long_txt_record, 38 | reverse: false 39 | } 40 | end 41 | 42 | it { is_expected.to contain_unbound__record('record.example.com') } 43 | 44 | it { 45 | expect(subject).to contain_concat__fragment('unbound-stub-record.example.com-local-record').with( 46 | content: " local-data: 'record.example.com 14400 IN TXT \"#{long_txt_record_chunked}\"'\n" 47 | ) 48 | } 49 | end 50 | 51 | context 'Multiple contents (answers)' do 52 | let(:params) do 53 | { 54 | content: ['192.0.2.53', '192.0.2.42'], 55 | reverse: false, 56 | } 57 | end 58 | 59 | it { is_expected.to contain_unbound__record('record.example.com') } 60 | 61 | it do 62 | is_expected.to contain_concat__fragment('unbound-stub-record.example.com-local-record'). 63 | with_content( 64 | %r{ 65 | \s+local-data:\s"record.example.com\s14400\sIN\sA\s192.0.2.53" 66 | \s+local-data:\s"record.example.com\s14400\sIN\sA\s192.0.2.42" 67 | }x 68 | ) 69 | end 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /types/address.pp: -------------------------------------------------------------------------------- 1 | # Patterns copied from Stdlib::IP 2 | type Unbound::Address = Variant[ 3 | Stdlib::IP::Address::Nosubnet, 4 | Pattern[ 5 | /\A([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}@\d{1,5}\z/, 6 | /\A[[:xdigit:]]{1,4}(:[[:xdigit:]]{1,4}){7}(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 7 | /\A([[:xdigit:]]{1,4}:){6}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 8 | /\A([[:xdigit:]]{1,4}:){5}:([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 9 | /\A([[:xdigit:]]{1,4}:){4}(:[[:xdigit:]]{1,4}){0,1}:([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 10 | /\A([[:xdigit:]]{1,4}:){3}(:[[:xdigit:]]{1,4}){0,2}:([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 11 | /\A([[:xdigit:]]{1,4}:){2}(:[[:xdigit:]]{1,4}){0,3}:([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 12 | /\A([[:xdigit:]]{1,4}:){1}(:[[:xdigit:]]{1,4}){0,4}:([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 13 | /\A:(:[[:xdigit:]]{1,4}){0,5}:([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 14 | /\A:(:|(:[[:xdigit:]]{1,4}){1,7})(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 15 | /\A([[:xdigit:]]{1,4}:){1}(:|(:[[:xdigit:]]{1,4}){1,6})(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 16 | /\A([[:xdigit:]]{1,4}:){2}(:|(:[[:xdigit:]]{1,4}){1,5})(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 17 | /\A([[:xdigit:]]{1,4}:){3}(:|(:[[:xdigit:]]{1,4}){1,4})(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 18 | /\A([[:xdigit:]]{1,4}:){4}(:|(:[[:xdigit:]]{1,4}){1,3})(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 19 | /\A([[:xdigit:]]{1,4}:){5}(:|(:[[:xdigit:]]{1,4}){1,2})(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 20 | /\A([[:xdigit:]]{1,4}:){6}(:|(:[[:xdigit:]]{1,4}){1,1})(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 21 | /\A([[:xdigit:]]{1,4}:){7}:(\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9]))?@\d{1,5}\z/, 22 | ], 23 | ] 24 | -------------------------------------------------------------------------------- /manifests/stub.pp: -------------------------------------------------------------------------------- 1 | # @summary Create an unbound stub zone for caching upstream name resolvers 2 | # @param address 3 | # IP address of server to forward to. Can be IP 4 or IP 6 (and an 4 | # array or a single value. To use a nondefault port for DNS communication 5 | # append '@' with the port number. 6 | # @param nameservers Name of stub zone nameserver. Is itself resolved before it is used. 7 | # @param insecure 8 | # Sets domain name to be insecure, DNSSEC chain 9 | # of trust is ignored towards the domain name. So a trust anchor above the 10 | # domain name can not make the domain secure with a DS record, such a DS 11 | # record is then ignored. Also keys from DLV are ignored for the domain. 12 | # Can be given multiple times to specify multiple domains that are treated 13 | # as if unsigned. If you set trust anchors for the domain they override 14 | # this setting (and the domain is secured). 15 | # This can be useful if you want to make sure a trust anchor for external 16 | # lookups does not affect an (unsigned) internal domain. A DS record 17 | # externally can create validation failures for that internal domain. 18 | # @param no_cache don't cache 19 | # @param stub_first 20 | # Controls 'stub-first' stub zone option. 21 | # If true, a query that fails with the stub clause is attempted again 22 | # without the stub clause. 23 | # @param type 24 | # can be 'deny', 'refuse', 'static', 'transparent', 'typetransparent', 'redirect' or 'nodefault'. 25 | # @param config_file Name of the unbound config file 26 | # 27 | define unbound::stub ( 28 | Variant[Array[Unbound::Address], Unbound::Address] $address, 29 | Array[Stdlib::Host] $nameservers = [], 30 | # lint:ignore:quoted_booleans 31 | Variant[Boolean, Enum['true', 'false']] $insecure = false, 32 | Variant[Boolean, Enum['true', 'false']] $no_cache = false, 33 | Variant[Boolean, Enum['true', 'false']] $stub_first = false, 34 | # lint:endignore 35 | Unbound::Local_zone_type $type = 'transparent', 36 | Optional[Stdlib::Unixpath] $config_file = undef, 37 | ) { 38 | include unbound 39 | $_config_file = pick($config_file, $unbound::config_file) 40 | concat::fragment { "unbound-stub-${name}": 41 | order => '15', 42 | target => $_config_file, 43 | content => template('unbound/stub.erb'), 44 | } 45 | 46 | if str2bool($insecure) == true { 47 | concat::fragment { "unbound-stub-${name}-insecure": 48 | order => '01', 49 | target => $_config_file, 50 | content => " domain-insecure: \"${name}\"\n", 51 | } 52 | } 53 | 54 | concat::fragment { "unbound-stub-${name}-local-zone": 55 | order => '02', 56 | target => $_config_file, 57 | content => " local-zone: \"${name}\" ${type} \n", 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /spec/classes/remote_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'unbound::remote' do 6 | on_supported_os.each do |os, os_facts| 7 | context "on #{os}" do 8 | let(:facts) { os_facts.merge(concat_basedir: '/dne', unbound_version: '1.21.0') } 9 | let(:params) do 10 | { 11 | enable: false, 12 | server_key_file: '/etc/unbound/unbound_server.key', 13 | server_cert_file: '/etc/unbound/unbound_server.pem', 14 | control_key_file: '/etc/unbound/unbound_control.key', 15 | control_cert_file: '/etc/unbound/unbound_control.pem', 16 | group: 'unbound', 17 | confdir: '/etc/unbound', 18 | config_file: '/etc/unbound/unbound.conf', 19 | control_setup_path: '/usr/sbin/unbound-control-setup', 20 | } 21 | end 22 | 23 | context 'with disabled params' do 24 | it { is_expected.to compile.with_all_deps } 25 | it { is_expected.to contain_class('unbound::remote') } 26 | 27 | it do 28 | is_expected.to contain_concat__fragment('unbound-remote').with_content( 29 | %r{ 30 | ^remote-control: 31 | \s+control-enable:\sno 32 | \s+control-interface:\s::1 33 | \s+control-interface:\s127.0.0.1 34 | \s+control-port:\s8953 35 | \s+server-key-file:\s/etc/unbound/unbound_server.key 36 | \s+server-cert-file:\s/etc/unbound/unbound_server.pem 37 | \s+control-key-file:\s/etc/unbound/unbound_control.key 38 | \s+control-cert-file:\s/etc/unbound/unbound_control.pem 39 | }x 40 | ) 41 | end 42 | 43 | it { is_expected.to contain_exec('unbound-control-setup') } 44 | 45 | %w[server.key server.pem control.key control.pem].each do |file| 46 | it { is_expected.to contain_file("/etc/unbound/unbound_#{file}") } 47 | end 48 | end 49 | 50 | context 'with enable true' do 51 | let(:params) { super().merge(enable: true) } 52 | 53 | it do 54 | is_expected.to contain_concat__fragment('unbound-remote').with_content( 55 | %r{control-enable:\syes} 56 | ) 57 | end 58 | end 59 | 60 | context 'with control_interface' do 61 | let(:params) { super().merge(interface: ['192.0.2.42']) } 62 | 63 | it do 64 | is_expected.to contain_concat__fragment('unbound-remote').with_content( 65 | %r{control-interface:\s192.0.2.42} 66 | ) 67 | end 68 | end 69 | 70 | context 'with control_use_cert false' do 71 | let(:params) { super().merge(control_use_cert: false) } 72 | 73 | it do 74 | is_expected.to contain_concat__fragment('unbound-remote'). 75 | without_content(%r{(server|control)-(key|cert)-file}) 76 | end 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppet-unbound", 3 | "version": "8.0.1-rc0", 4 | "author": "Vox Pupuli", 5 | "summary": "A module to deploy and manage the Unbound caching resolver", 6 | "license": "Apache-2.0", 7 | "source": "https://github.com/voxpupuli/puppet-unbound", 8 | "project_page": "https://github.com/voxpupuli/puppet-unbound", 9 | "issues_url": "https://github.com/voxpupuli/puppet-unbound/issues", 10 | "operatingsystem_support": [ 11 | { 12 | "operatingsystem": "AlmaLinux", 13 | "operatingsystemrelease": [ 14 | "8", 15 | "9" 16 | ] 17 | }, 18 | { 19 | "operatingsystem": "Rocky", 20 | "operatingsystemrelease": [ 21 | "8", 22 | "9" 23 | ] 24 | }, 25 | { 26 | "operatingsystem": "RedHat", 27 | "operatingsystemrelease": [ 28 | "8", 29 | "9" 30 | ] 31 | }, 32 | { 33 | "operatingsystem": "CentOS", 34 | "operatingsystemrelease": [ 35 | "9" 36 | ] 37 | }, 38 | { 39 | "operatingsystem": "OracleLinux", 40 | "operatingsystemrelease": [ 41 | "8", 42 | "9" 43 | ] 44 | }, 45 | { 46 | "operatingsystem": "Scientific", 47 | "operatingsystemrelease": [ 48 | "9" 49 | ] 50 | }, 51 | { 52 | "operatingsystem": "Debian", 53 | "operatingsystemrelease": [ 54 | "11", 55 | "12", 56 | "13" 57 | ] 58 | }, 59 | { 60 | "operatingsystem": "Ubuntu", 61 | "operatingsystemrelease": [ 62 | "22.04" 63 | ] 64 | }, 65 | { 66 | "operatingsystem": "Solaris", 67 | "operatingsystemrelease": [ 68 | "10", 69 | "11" 70 | ] 71 | }, 72 | { 73 | "operatingsystem": "AIX", 74 | "operatingsystemrelease": [ 75 | "5.3", 76 | "6.1", 77 | "7.1" 78 | ] 79 | }, 80 | { 81 | "operatingsystem": "FreeBSD", 82 | "operatingsystemrelease": [ 83 | "12", 84 | "13", 85 | "14" 86 | ] 87 | }, 88 | { 89 | "operatingsystem": "OpenBSD", 90 | "operatingsystemrelease": [ 91 | "6.0", 92 | "5.9" 93 | ] 94 | }, 95 | { 96 | "operatingsystem": "NetBSD", 97 | "operatingsystemrelease": [ 98 | "6.1.4", 99 | "5.2.2" 100 | ] 101 | }, 102 | { 103 | "operatingsystem": "Archlinux" 104 | } 105 | ], 106 | "requirements": [ 107 | { 108 | "name": "openvox", 109 | "version_requirement": ">= 8.19.0 < 9.0.0" 110 | } 111 | ], 112 | "dependencies": [ 113 | { 114 | "name": "puppetlabs/concat", 115 | "version_requirement": ">= 4.1.0 < 10.0.0" 116 | }, 117 | { 118 | "name": "puppetlabs/stdlib", 119 | "version_requirement": ">= 4.25.0 < 10.0.0" 120 | }, 121 | { 122 | "name": "puppet/systemd", 123 | "version_requirement": ">= 6.3.0 < 9.0.0" 124 | } 125 | ] 126 | } 127 | -------------------------------------------------------------------------------- /manifests/remote.pp: -------------------------------------------------------------------------------- 1 | # @summary Configure remote control of the unbound daemon process 2 | # @param enable 3 | # The option is used to enable remote control, default is false. 4 | # If turned off, the server does not listen for control. 5 | # @param interface 6 | # Give IPv4 or IPv6 addresses to listen on for control commands. 7 | # By default localhost (127.0.0.1 and ::1) is listened. 8 | # @param port 9 | # The port number to listen on for control commands, default is 10 | # 8953. If you change this port number, and permissions have been dropped, 11 | # a reload is not sufficient to open the port again, you must then restart. 12 | # @param server_key_file 13 | # Path to the server private key, by default unbound_server.key. 14 | # This file is generated by the unbound-control-setup utility. This file is 15 | # used by the unbound server, but not by unbound-control. 16 | # @param control_use_cert if we should use certs for the control channel 17 | # @param server_cert_file 18 | # Path to the server self signed certificate, by default 19 | # unbound_server.pem. This file is generated by the unbound-control-setup 20 | # utility. This file is used by the unbound server, and also by 21 | # unbound-control. 22 | # @param control_key_file 23 | # Path to the control client private key, by default 24 | # unbound_control.key. This file is generated by the unbound-control-setup 25 | # utility. This file is used by unbound-control. 26 | # @param control_cert_file 27 | # Path to the control client certificate, by default 28 | # unbound_control.pem. This certificate has to be signed with the server 29 | # certificate. This file is generated by the unbound-control-setup utility. 30 | # This file is used by unbound-control. 31 | # @param group 32 | # Name of the group for unbound files and directory 33 | # @param confdir 34 | # Name of the directory where configuration files are stored 35 | # @param config_file 36 | # Name of the unbound config file 37 | # @param control_setup_path the path to nsd-control-setup 38 | # 39 | class unbound::remote ( 40 | Boolean $enable = $unbound::control_enable, 41 | Array $interface = ['::1', '127.0.0.1'], 42 | Integer $port = 8953, 43 | Boolean $control_use_cert = true, 44 | String $server_key_file = "${unbound::confdir}/unbound_server.key", 45 | String $server_cert_file = "${unbound::confdir}/unbound_server.pem", 46 | String $control_key_file = "${$unbound::confdir}/unbound_control.key", 47 | String $control_cert_file = "${$unbound::confdir}/unbound_control.pem", 48 | $group = $unbound::group, 49 | $confdir = $unbound::confdir, 50 | $config_file = $unbound::config_file, 51 | $control_setup_path = $unbound::control_setup_path, 52 | ) { 53 | concat::fragment { 'unbound-remote': 54 | order => '10', 55 | target => $config_file, 56 | content => template('unbound/remote.erb'), 57 | } 58 | 59 | unless $control_setup_path.empty { 60 | exec { 'unbound-control-setup': 61 | command => "${control_setup_path} -d ${confdir}", 62 | creates => $server_key_file, 63 | } 64 | 65 | file { [$server_key_file, $server_cert_file, $control_key_file, $control_cert_file]: 66 | owner => 'root', 67 | group => $group, 68 | mode => '0640', 69 | require => Exec['unbound-control-setup'], 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /spec/defines/stub_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'unbound::stub' do 6 | let(:title) { 'lab.example.com' } 7 | 8 | on_supported_os.each do |os, os_facts| 9 | context "on #{os}" do 10 | let(:facts) { os_facts } 11 | 12 | context 'basic' do 13 | let(:params) do 14 | { 15 | address: ['::1'] 16 | } 17 | end 18 | 19 | it { is_expected.to compile.with_all_deps } 20 | it { is_expected.to contain_unbound__stub('lab.example.com') } 21 | 22 | it { 23 | expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with( 24 | content: <<~ZONE 25 | stub-zone: 26 | name: "lab.example.com" 27 | stub-addr: ::1 28 | ZONE 29 | ) 30 | } 31 | end 32 | 33 | context 'unbound::address' do 34 | let(:params) do 35 | { 36 | address: '10.0.0.10@10053', 37 | nameservers: ['ns1.example.com', 'ns2.example.com'], 38 | } 39 | end 40 | 41 | it { is_expected.to compile.with_all_deps } 42 | it { is_expected.to contain_unbound__stub('lab.example.com') } 43 | 44 | it { 45 | expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with( 46 | content: <<~ZONE 47 | stub-zone: 48 | name: "lab.example.com" 49 | stub-addr: 10.0.0.10@10053 50 | stub-host: ns1.example.com 51 | stub-host: ns2.example.com 52 | ZONE 53 | ) 54 | } 55 | end 56 | 57 | context 'with no_cache set' do 58 | let(:params) do 59 | { 60 | address: ['::1'], 61 | no_cache: true 62 | } 63 | end 64 | 65 | it { is_expected.to compile.with_all_deps } 66 | it { is_expected.to contain_unbound__stub('lab.example.com') } 67 | 68 | it { 69 | expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with( 70 | content: <<~ZONE 71 | stub-zone: 72 | name: "lab.example.com" 73 | stub-addr: ::1 74 | stub-no-cache: yes 75 | ZONE 76 | ) 77 | } 78 | end 79 | 80 | context 'with stub_first set' do 81 | let(:params) do 82 | { 83 | address: ['::1'], 84 | stub_first: true 85 | } 86 | end 87 | 88 | it { is_expected.to compile.with_all_deps } 89 | it { is_expected.to contain_unbound__stub('lab.example.com') } 90 | 91 | it { 92 | expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with( 93 | content: <<~ZONE 94 | stub-zone: 95 | name: "lab.example.com" 96 | stub-addr: ::1 97 | stub-first: yes 98 | ZONE 99 | ) 100 | } 101 | end 102 | 103 | context 'with address set as string' do 104 | let(:params) do 105 | { 106 | address: '::1', 107 | no_cache: true 108 | } 109 | end 110 | 111 | it { is_expected.to compile.with_all_deps } 112 | it { is_expected.to contain_unbound__stub('lab.example.com') } 113 | 114 | it { 115 | expect(subject).to contain_concat__fragment('unbound-stub-lab.example.com').with( 116 | content: <<~ZONE 117 | stub-zone: 118 | name: "lab.example.com" 119 | stub-addr: ::1 120 | stub-no-cache: yes 121 | ZONE 122 | ) 123 | } 124 | end 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /templates/unbound.modules.conf.erb: -------------------------------------------------------------------------------- 1 | # Managed by Puppet 2 | # 3 | <%- 4 | def unbound_version 5 | @unbound_version ? @unbound_version : '0.a' 6 | end 7 | def print_config(name, value, version=false ) 8 | if version and scope.call_function('versioncmp', [unbound_version, version]) < 0 9 | return 10 | end 11 | if value.is_a?(TrueClass) 12 | return " #{name}: yes\n" 13 | elsif value.is_a?(FalseClass) 14 | return " #{name}: no\n" 15 | elsif not value 16 | return 17 | elsif value.is_a?(String) 18 | return " #{name}: \"#{value}\"\n" 19 | elsif value.is_a?(Integer) 20 | return " #{name}: #{value}\n" 21 | elsif value.is_a?(Array) 22 | str = '' 23 | value.each { |v| str << " #{name}: \"#{v}\"\n" } 24 | return str 25 | end 26 | end 27 | -%> 28 | <%- if @module_config and @module_config.include?('dns64') -%> 29 | <%= print_config('dns64-prefix', @dns64_prefix) -%> 30 | <%= print_config('dns64-synthall', @dns64_synthall) -%> 31 | <%- end -%> 32 | <%- if @module_config and @module_config.include?('subnetcache') -%> 33 | <%= print_config('send-client-subnet', @send_client_subnet, '1.6.1') -%> 34 | <%= print_config('client-subnet-zone', @client_subnet_zone, '1.6.1') -%> 35 | <%= print_config('client-subnet-always-forward', @client_subnet_always_forward, '1.6.1') -%> 36 | <%= print_config('max-client-subnet-ipv6', @max_client_subnet_ipv6, '1.6.1') -%> 37 | <%= print_config('max-client-subnet-ipv4', @max_client_subnet_ipv4, '1.6.1') -%> 38 | <%= print_config('min-client-subnet-ipv6', @min_client_subnet_ipv6, '1.8.2') -%> 39 | <%= print_config('min-client-subnet-ipv4', @min_client_subnet_ipv4, '1.8.2') -%> 40 | <%= print_config('max-ecs-tree-size-ipv4', @max_ecs_tree_size_ipv4, '1.8.2') -%> 41 | <%= print_config('max-ecs-tree-size-ipv6', @max_ecs_tree_size_ipv6, '1.8.2') -%> 42 | <%- end -%> 43 | <%- if @module_config and @module_config.include?('ipsecmod') -%> 44 | <%- raise Puppet::Error, 'you must set ipsecmod_hook if using ipsecmod' unless @ipsecmod_hook -%> 45 | <%= print_config('ipsecmod-enabled', @ipsecmod_enabled, '1.6.4') -%> 46 | <%= print_config('ipsecmod-hook', @ipsecmod_hook, '1.6.4') -%> 47 | <%= print_config('ipsecmod-strict', @ipsecmod_strict, '1.6.4') -%> 48 | <%= print_config('ipsecmod-max-ttl', @ipsecmod_max_ttl, '1.6.4') -%> 49 | <%= print_config('ipsecmod-ignore-bogus', @ipsecmod_ignore_bogus, '1.6.4') -%> 50 | <%= print_config('ipsecmod-whitelist', @ipsecmod_whitelist, '1.6.4') -%> 51 | <%- end -%> 52 | <%- if @module_config and @module_config.include?('python') -%> 53 | <%- raise Puppet::Error, 'you must set python_script if using python' unless @python_script -%> 54 | python: 55 | <%= print_config('python-script', @python_script) -%> 56 | <%- end -%> 57 | <%- if @module_config and @module_config.include?('cachedb') -%> 58 | cachedb: 59 | <%= print_config('backend', @backend) -%> 60 | <%= print_config('secret-seed', @secret_seed) -%> 61 | <%- if @backend == 'redis' -%> 62 | <%= print_config('redis-server-host', @redis_server_host) -%> 63 | <%= print_config('redis-server-port', @redis_server_port) -%> 64 | <%= print_config('redis-timeout', @redis_timeout) -%> 65 | <%- end -%> 66 | <%- end -%> 67 | <%- if @module_config and @module_config.include?('respip') -%> 68 | <%- @rpzs.each_pair do |name, rpz| -%> 69 | rpz: 70 | name: <%= name %> 71 | <%= print_config('primary', rpz['primary']) -%> 72 | <%= print_config('primary', rpz['master']) -%> 73 | <%= print_config('url', rpz['url']) -%> 74 | <%= print_config('allow-notify', rpz['allow_notify']) -%> 75 | <%= print_config('zonefile', rpz['zonefile']) -%> 76 | <%= print_config('rpz-action-overrude', rpz['rpz_action_override']) -%> 77 | <%= print_config('rpz-cname-override', rpz['rpz_cname_override']) -%> 78 | <%= print_config('rpz-log', rpz['rpz_log']) -%> 79 | <%= print_config('rpz-log-name', rpz['rpz_log_name']) -%> 80 | <%= print_config('tags', rpz['tags']) -%> 81 | <%- end -%> 82 | <%- end -%> 83 | -------------------------------------------------------------------------------- /spec/classes/dnstap_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'unbound::dnstap' do 6 | on_supported_os.each do |os, os_facts| 7 | context "on #{os}" do 8 | let(:facts) { os_facts.merge(concat_basedir: '/dne', unbound_version: '1.21.0') } 9 | 10 | case os_facts[:os]['family'] 11 | when 'FreeBSD' 12 | let(:config_file) { '/usr/local/etc/unbound/unbound.conf' } 13 | when 'OpenBSD' 14 | let(:config_file) { '/var/unbound/etc/unbound.conf' } 15 | else 16 | let(:config_file) { '/etc/unbound/unbound.conf' } 17 | end 18 | 19 | context 'with disabled params' do 20 | let(:params) { { enable: false } } 21 | 22 | it { is_expected.to compile.with_all_deps } 23 | it { is_expected.to contain_class('unbound::dnstap') } 24 | it { is_expected.not_to contain_concat__fragment('unbound-dnstap') } 25 | end 26 | 27 | context 'with enable and socket' do 28 | let(:params) { { socket_path: '/var/run/dnstap.sock' } } 29 | 30 | it { is_expected.to compile.with_all_deps } 31 | it { is_expected.to contain_class('unbound::dnstap') } 32 | 33 | it do 34 | is_expected.to contain_concat__fragment('unbound-dnstap').with_content( 35 | %r{ 36 | ^dnstap: 37 | \s+dnstap-enable:\syes 38 | \s+dnstap-bidirectional:\syes 39 | \s+dnstap-socket-path:\s"/var/run/dnstap.sock" 40 | \s+dnstap-send-identity:\sno 41 | \s+dnstap-send-version:\sno 42 | \s+dnstap-sample-rate:\s0 43 | \s+dnstap-log-resolver-query-messages:\sno 44 | \s+dnstap-log-resolver-response-messages:\sno 45 | \s+dnstap-log-client-query-messages:\sno 46 | \s+dnstap-log-client-response-messages:\sno 47 | \s+dnstap-log-forwarder-query-messages:\sno 48 | \s+dnstap-log-forwarder-response-messages:\sno 49 | }x 50 | ) 51 | end 52 | end 53 | 54 | context 'with enable and ip' do 55 | let(:params) { { ip: '192.0.2.1' } } 56 | 57 | it { is_expected.to compile.with_all_deps } 58 | it { is_expected.to contain_class('unbound::dnstap') } 59 | 60 | it do 61 | is_expected.to contain_concat__fragment('unbound-dnstap').with_content( 62 | %r{ 63 | ^dnstap: 64 | \s+dnstap-enable:\syes 65 | \s+dnstap-bidirectional:\syes 66 | \s+dnstap-ip:\s"192\.0\.2\.1" 67 | \s+dnstap-tls:\syes 68 | \s+dnstap-send-identity:\sno 69 | \s+dnstap-send-version:\sno 70 | \s+dnstap-sample-rate:\s0 71 | \s+dnstap-log-resolver-query-messages:\sno 72 | \s+dnstap-log-resolver-response-messages:\sno 73 | \s+dnstap-log-client-query-messages:\sno 74 | \s+dnstap-log-client-response-messages:\sno 75 | \s+dnstap-log-forwarder-query-messages:\sno 76 | \s+dnstap-log-forwarder-response-messages:\sno 77 | }x 78 | ) 79 | end 80 | 81 | context 'with tls_host' do 82 | let(:params) { super().merge(tls_host: 'dnstap.example.com') } 83 | 84 | it do 85 | is_expected.to contain_concat__fragment('unbound-dnstap').with_content( 86 | %r{^ dnstap-tls-host:\s"dnstap.example.com"} 87 | ) 88 | end 89 | end 90 | 91 | context 'with tls_cert_bundle' do 92 | let(:params) { super().merge(tls_cert_bundle: '/etc/ssl/cert.pem') } 93 | 94 | it do 95 | is_expected.to contain_concat__fragment('unbound-dnstap').with_content( 96 | %r{^ dnstap-tls-cert-bundle:\s"/etc/ssl/cert.pem"} 97 | ) 98 | end 99 | end 100 | 101 | context 'with tls_cert_key_file' do 102 | let(:params) { super().merge(tls_cert_key_file: '/etc/ssl/key.pem') } 103 | 104 | it do 105 | is_expected.to contain_concat__fragment('unbound-dnstap').with_content( 106 | %r{^ dnstap-tls-cert-key-file:\s"/etc/ssl/key.pem"} 107 | ) 108 | end 109 | end 110 | 111 | context 'with tls_cert_cert_file' do 112 | let(:params) { super().merge(tls_cert_cert_file: '/etc/ssl/cert.pem') } 113 | 114 | it do 115 | is_expected.to contain_concat__fragment('unbound-dnstap').with_content( 116 | %r{^ dnstap-tls-cert-cert-file:\s"/etc/ssl/cert.pem"} 117 | ) 118 | end 119 | end 120 | end 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /manifests/dnstap.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # @param enable 3 | # Whether to enable dnstap. 4 | # @param bidirectional 5 | # Whether to enable bidirectional dnstap. 6 | # @param socket_path 7 | # The path to the dnstap socket. 8 | # @param ip 9 | # The IP address for dnstap. 10 | # @param tls 11 | # Whether to enable TLS for dnstap. 12 | # @param tls_host 13 | # The TLS host for dnstap. 14 | # @param tls_cert_bundle 15 | # The path to the TLS certificate bundle. 16 | # @param tls_cert_key_file 17 | # The path to the TLS certificate key file. 18 | # @param tls_cert_cert_file 19 | # The path to the TLS certificate file. 20 | # @param send_identity 21 | # Whether to send the identity in dnstap messages. 22 | # @param send_version 23 | # Whether to send the version in dnstap messages. 24 | # @param identity 25 | # The identity to send in dnstap messages. 26 | # @param version 27 | # The version to send in dnstap messages. 28 | # @param sample_rate 29 | # The sample rate for dnstap messages. 30 | # @param log_resolver_query_messages 31 | # Whether to log resolver query messages. 32 | # @param log_resolver_response_messages 33 | # Whether to log resolver response messages. 34 | # @param log_client_query_messages 35 | # Whether to log client query messages. 36 | # @param log_client_response_messages 37 | # Whether to log client response messages. 38 | # @param log_forwarder_query_messages 39 | # Whether to log forwarder query messages. 40 | # @param log_forwarder_response_messages 41 | # Whether to log forwarder response messages. 42 | class unbound::dnstap ( 43 | Boolean $enable = true, # version 1.11 44 | Boolean $bidirectional = true, # version 1.11 45 | Optional[Stdlib::Absolutepath] $socket_path = undef, # version 1.11 46 | Optional[Unbound::Address] $ip = undef, # version 1.11 47 | Boolean $tls = true, # version 1.11 48 | Optional[Stdlib::Host] $tls_host = undef, # version 1.11 49 | Optional[Stdlib::Absolutepath] $tls_cert_bundle = undef, # version 1.11 50 | Optional[Stdlib::Absolutepath] $tls_cert_key_file = undef, # version 1.11 51 | Optional[Stdlib::Absolutepath] $tls_cert_cert_file = undef, # version 1.11 52 | Boolean $send_identity = false, # version 1.11 53 | Boolean $send_version = false, # version 1.11 54 | Optional[String[1]] $identity = undef, # version 1.11 55 | Optional[String[1]] $version = undef, # version 1.11 56 | Integer[0,1000] $sample_rate = 0, # version 1.21 57 | Boolean $log_resolver_query_messages = false, # version 1.11 58 | Boolean $log_resolver_response_messages = false, # version 1.11 59 | Boolean $log_client_query_messages = false, # version 1.11 60 | Boolean $log_client_response_messages = false, # version 1.11 61 | Boolean $log_forwarder_query_messages = false, # version 1.11 62 | Boolean $log_forwarder_response_messages = false, # version 1.11 63 | 64 | ) { 65 | include unbound 66 | if $enable and $socket_path == undef and $ip == undef { 67 | fail('Either ip or socket_path is required when dnstap is enabled') 68 | } 69 | if $enable { 70 | $ip_config = $ip.then |$v| { 71 | @("CONFIG") 72 | ${unbound::print_config('dnstap-ip', $v, '1.11')} 73 | ${unbound::print_config('dnstap-tls', $tls, '1.11')} 74 | ${unbound::print_config('dnstap-tls-host', $tls_host, '1.11')} 75 | ${unbound::print_config('dnstap-tls-cert-bundle', $tls_cert_bundle, '1.11')} 76 | ${unbound::print_config('dnstap-tls-cert-key-file', $tls_cert_key_file, '1.11')} 77 | ${unbound::print_config('dnstap-tls-cert-cert-file', $tls_cert_cert_file, '1.11')} 78 | | CONFIG 79 | } 80 | $config = @("CONFIG") 81 | dnstap: 82 | ${unbound::print_config('dnstap-enable', $enable, '1.11')} 83 | ${unbound::print_config('dnstap-bidirectional', $bidirectional, '1.11')} 84 | ${unbound::print_config('dnstap-socket-path', $socket_path, '1.11')} 85 | ${$ip_config} 86 | ${unbound::print_config('dnstap-send-identity', $send_identity, '1.11')} 87 | ${unbound::print_config('dnstap-send-version', $send_version, '1.11')} 88 | ${unbound::print_config('dnstap-identity', $identity, '1.11')} 89 | ${unbound::print_config('dnstap-version', $version, '1.11')} 90 | ${unbound::print_config('dnstap-sample-rate', $sample_rate, '1.21')} 91 | ${unbound::print_config('dnstap-log-resolver-query-messages', $log_resolver_query_messages, '1.11')} 92 | ${unbound::print_config('dnstap-log-resolver-response-messages', $log_resolver_response_messages, '1.11')} 93 | ${unbound::print_config('dnstap-log-client-query-messages', $log_client_query_messages, '1.11')} 94 | ${unbound::print_config('dnstap-log-client-response-messages', $log_client_response_messages, '1.11')} 95 | ${unbound::print_config('dnstap-log-forwarder-query-messages', $log_forwarder_query_messages, '1.11')} 96 | ${unbound::print_config('dnstap-log-forwarder-response-messages', $log_forwarder_response_messages, '1.11')} 97 | | CONFIG 98 | concat::fragment { 'unbound-dnstap': 99 | order => '20', 100 | target => $unbound::config_file, 101 | content => $config.split("\n").filter |$x| { !$x.empty }.join("\n"), 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Puppet powered DNS with Unbound 2 | 3 | [![Build Status](https://github.com/voxpupuli/puppet-unbound/workflows/CI/badge.svg)](https://github.com/voxpupuli/puppet-unbound/actions?query=workflow%3ACI) 4 | [![Release](https://github.com/voxpupuli/puppet-unbound/actions/workflows/release.yml/badge.svg)](https://github.com/voxpupuli/puppet-unbound/actions/workflows/release.yml) 5 | [![Puppet Forge](https://img.shields.io/puppetforge/v/puppet/unbound.svg)](https://forge.puppetlabs.com/puppet/unbound) 6 | [![Puppet Forge - downloads](https://img.shields.io/puppetforge/dt/puppet/unbound.svg)](https://forge.puppetlabs.com/puppet/unbound) 7 | [![Puppet Forge - endorsement](https://img.shields.io/puppetforge/e/puppet/unbound.svg)](https://forge.puppetlabs.com/puppet/unbound) 8 | [![Puppet Forge - scores](https://img.shields.io/puppetforge/f/puppet/unbound.svg)](https://forge.puppetlabs.com/puppet/unbound) 9 | [![puppetmodule.info docs](http://www.puppetmodule.info/images/badge.png)](http://www.puppetmodule.info/m/puppet-unbound) 10 | [![Apache-2.0 License](https://img.shields.io/github/license/voxpupuli/puppet-unbound.svg)](LICENSE) 11 | 12 | A puppet module for the Unbound caching resolver. 13 | 14 | ## Supported Platforms 15 | 16 | * Debian 17 | * FreeBSD 18 | * OpenBSD 19 | * OS X (macports) 20 | * RHEL clones (with EPEL) 21 | * openSUSE (local repo or obs://server:dns) 22 | * Archlinux 23 | 24 | For an up2date list of supported operating systems and their versions, please 25 | check the metadata.json. 26 | 27 | ## Requirements 28 | 29 | To use this module requires at least unbound 1.6.6. Please also consult 30 | metadata.json to understand the minimum puppet version and any other module 31 | dependencies. 32 | 33 | ## Usage 34 | 35 | ### Server Setup 36 | 37 | At minimum you should setup the interfaces to listen on and allow access to a 38 | few subnets. This will tell unbound which interfaces to listen on, and which 39 | networks to allow queries from. 40 | 41 | ```puppet 42 | class { "unbound": 43 | interface => ["::0","0.0.0.0"], 44 | access => ["10.0.0.0/20","::1"], 45 | } 46 | ``` 47 | 48 | Or, using hiera 49 | 50 | ```yaml 51 | unbound::interface: 52 | - '::0' 53 | - '0.0.0.0' 54 | unbound::access: 55 | - '10.0.0.0/20' 56 | - '::1' 57 | ``` 58 | 59 | ### Stub Zones 60 | 61 | These are zones for which you have an authoritative name server and want to 62 | direct queries. 63 | 64 | ```puppet 65 | unbound::stub { "lan.example.com": 66 | address => '10.0.0.10', 67 | insecure => true, 68 | } 69 | 70 | unbound::stub { "0.0.10.in-addr.arpa.": 71 | address => '10.0.0.10', 72 | insecure => true, 73 | } 74 | 75 | # port can be specified 76 | unbound::stub { "0.0.10.in-addr.arpa.": 77 | address => '10.0.0.10@10053', 78 | insecure => true, 79 | } 80 | 81 | # address can be an array along with nameservers. 82 | # in the following case, generated conf would be as follows: 83 | # 84 | # stub-addr: 10.0.0.53 85 | # stub-addr: 10.0.0.10@10053 86 | # stub-host: ns1.example.com 87 | # stub-host: ns2.example.com 88 | # 89 | # note that conf will be generated in the same order provided. 90 | unbound::stub { "10.0.10.in-addr.arpa.": 91 | address => [ '10.0.0.53', '10.0.0.10@10053'], 92 | nameservers => [ 'ns1.example.com', 'ns2.example.com' ], 93 | } 94 | ``` 95 | 96 | Or, using hiera 97 | 98 | ```yaml 99 | unbound::stub: 100 | '10.0.10.in-addr.arpa.': 101 | address: 102 | - '10.0.0.53 103 | - '10.0.0.10@10053' 104 | nameserveres: 105 | - 'ns1.example.com' 106 | - 'ns2.example.com' 107 | ``` 108 | 109 | Unless you have DNSSEC for your private zones, they are considered insecure, 110 | noted by `insecure => true`. 111 | 112 | ### Static DNS records 113 | 114 | For overriding DNS record in zone. 115 | 116 | ```puppet 117 | unbound::record { 'test.example.tld': 118 | type => 'A', 119 | content => '10.0.0.1', 120 | ttl => '14400', 121 | } 122 | ``` 123 | 124 | Or, using hiera 125 | 126 | ```yaml 127 | unbound::record: 128 | 'test.example.tld': 129 | type: 'A' 130 | content: '10.0.0.1' 131 | ttl: '14400' 132 | ``` 133 | 134 | ### Forward Zones 135 | 136 | Setup a forward zone with a list of address from which you should resolve 137 | queries. You can configure a forward zone with something like the following: 138 | 139 | ```puppet 140 | unbound::forward { '.': 141 | address => [ 142 | '8.8.8.8', 143 | '8.8.4.4' 144 | ] 145 | } 146 | ``` 147 | 148 | Or, using hiera 149 | 150 | ```yaml 151 | unbound::forward: 152 | '.': 153 | address: 154 | - '8.8.8.8' 155 | - '8.8.4.4' 156 | ``` 157 | 158 | This means that your server will use the Google DNS servers for any 159 | zones that it doesn't know how to reach and cache the result. 160 | 161 | ### Domain Insecure 162 | 163 | Sets domain name to be insecure, DNSSEC chain of trust is 164 | ignored towards the domain name. So a trust anchor above the 165 | domain name can not make the domain secure with a DS record, 166 | such a DS record is then ignored. Also keys from DLV are 167 | ignored for the domain. Can be given multiple times to specify 168 | multiple domains that are treated as if unsigned. If you set 169 | trust anchors for the domain they override this setting (and the 170 | domain is secured). 171 | 172 | ```puppet 173 | class {'unbound:' 174 | domain_insecure => ['example.com', example.org'] 175 | } 176 | ``` 177 | 178 | Or, using hiera 179 | 180 | ```yaml 181 | unbound::domain_insecure: 182 | - example.com 183 | - example.org 184 | ``` 185 | 186 | ### Local Zones 187 | 188 | Configure a local zone. The type determines the answer to give 189 | if there is no match from local-data. The types are deny, 190 | refuse, static, transparent, redirect, nodefault, typetranspar- 191 | ent, inform, inform\_deny, always\_transparent, always\_refuse, 192 | always\_nxdomain. See local-zone in the [unbound documentation](https://unbound.net/documentation/unbound.conf.html) 193 | for more information. You can configure a local-zone with something like the 194 | following. 195 | 196 | ```puppet 197 | class {'unbound:' 198 | local_zone => { '10.0.10.in-addr.arpa.' => 'nodefault'} 199 | } 200 | ``` 201 | 202 | Or, using unbound::localzone 203 | 204 | ```puppet 205 | unbound::localzone { '10.0.10.in-addr.arpa.': 206 | type => 'nodefault' 207 | } 208 | ``` 209 | 210 | Or, using hiera 211 | 212 | ```yaml 213 | unbound::local_zone: 214 | 10.0.10.in-addr.arpa.: nodefault 215 | 11.0.10.in-addr.arpa.: nodefault 216 | ``` 217 | 218 | ### Fine grain access-control 219 | 220 | ```puppet 221 | class { "unbound": 222 | interface => ["::0","0.0.0.0"], 223 | access => ["10.0.0.0/20", "10.0.0.5/32 reject", "::1 allow_snoop"], 224 | } 225 | ``` 226 | 227 | The access option allows to pass the action for each subnets, if the action is 228 | not provided we assume it’s 'allow'. 229 | 230 | ### Adding arbitrary unbound configuration parameters 231 | 232 | ```puppet 233 | class { "unbound": 234 | interface => ["::0","0.0.0.0"], 235 | access => ["10.0.0.0/20","::1"], 236 | custom_server_conf => [ 'include: "/etc/unbound/conf.d/*.conf"' ], 237 | } 238 | ``` 239 | 240 | The _custom_server_conf_ option allows the addition of arbitrary configuration 241 | parameters to your server configuration. It expects an array, and each element 242 | gets added to the configuration file on a separate line. In the example above, 243 | we instruct Unbound to load other configuration files from a subdirectory. 244 | 245 | ### Remote Control 246 | 247 | The Unbound remote controls the use of the unbound-control utility to issue 248 | commands to the Unbound daemon process. 249 | 250 | ```puppet 251 | class { "unbound::remote": 252 | enable => true, 253 | } 254 | ``` 255 | 256 | On some platforms this is needed to function correctly for things like service 257 | reloads. 258 | 259 | ### Skipping hints download 260 | 261 | In the case you're only building a caching forwarder and don't do iterative 262 | lookups you might not want to download the hints file containing the root 263 | nameservers because you don't need it, or you also might not be able to 264 | download it anyway because your server is firewalled which would cause the 265 | module would hang on trying to download the hints file. To skip the download 266 | set the skip_roothints_download parameter to true. 267 | 268 | ```puppet 269 | class { "unbound": 270 | skip_roothints_download => true, 271 | } 272 | ``` 273 | 274 | ## More information 275 | 276 | You can find more information about Unbound and its configuration items at 277 | [unbound.net](http://unbound.net). 278 | 279 | ## Contribute 280 | 281 | Please help me make this module awesome! Send pull requests and file issues. 282 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2013 Puppet Labs 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /templates/unbound.conf.erb: -------------------------------------------------------------------------------- 1 | # Managed by Puppet 2 | # 3 | <%- 4 | def unbound_version 5 | @unbound_version ? @unbound_version : '0.a' 6 | end 7 | def print_config(name, value, version=false ) 8 | if version and scope.call_function('versioncmp', [unbound_version, version]) < 0 9 | return 10 | end 11 | if value.is_a?(TrueClass) 12 | return " #{name}: yes\n" 13 | elsif value.is_a?(FalseClass) 14 | return " #{name}: no\n" 15 | elsif not value 16 | return 17 | elsif value.is_a?(String) 18 | return " #{name}: \"#{value}\"\n" 19 | elsif value.is_a?(Integer) 20 | return " #{name}: #{value}\n" 21 | elsif value.is_a?(Array) and !value.empty? 22 | str = '' 23 | value.each { |v| str << " #{name}: #{v}\n" } 24 | return str 25 | end 26 | end 27 | -%> 28 | server: 29 | <%= print_config('verbosity', @verbosity) -%> 30 | <%= print_config('statistics-interval', @statistics_interval) -%> 31 | <%= print_config('statistics-cumulative', @statistics_cumulative) -%> 32 | <%= print_config('extended-statistics', @extended_statistics) -%> 33 | <%= print_config('num-threads', @num_threads) -%> 34 | <%= print_config('port', @port) -%> 35 | <%= print_config('interface', @interface) -%> 36 | <%= print_config('interface-automatic', @interface_automatic) -%> 37 | <% if @interface_automatic_ports -%> 38 | <%= print_config('interface-automatic-ports', @interface_automatic_ports) -%> 39 | <% end -%> 40 | <%= print_config('outgoing-interface', @outgoing_interface) -%> 41 | <%= print_config('outgoing-range', @outgoing_range) -%> 42 | <%- if @outgoing_port_permit_first -%> 43 | <%= print_config('outgoing-port-permit', @outgoing_port_permit) -%> 44 | <%= print_config('outgoing-port-avoid', @outgoing_port_avoid) -%> 45 | <%- else -%> 46 | <%= print_config('outgoing-port-avoid', @outgoing_port_permit) -%> 47 | <%= print_config('outgoing-port-permit', @outgoing_port_avoid) -%> 48 | <%- end -%> 49 | <%= print_config('outgoing-num-tcp', @outgoing_num_tcp) -%> 50 | <%= print_config('incoming-num-tcp', @incoming_num_tcp) -%> 51 | <%= print_config('edns-buffer-size', @edns_buffer_size) -%> 52 | <%= print_config('max-udp-size', @max_udp_size) -%> 53 | <%= print_config('stream-wait-size', @stream_wait_size, '1.9.0') -%> 54 | <%= print_config('msg-cache-size', @msg_cache_size) -%> 55 | <%= print_config('msg-cache-slabs', @msg_cache_slabs) -%> 56 | <%= print_config('num-queries-per-thread', @num_queries_per_thread) -%> 57 | <%= print_config('jostle-timeout', @jostle_timeout) -%> 58 | <%= print_config('delay-close', @delay_close) -%> 59 | <%= print_config('unknown-server-time-limit', @unknown_server_time_limit, '1.8.2') -%> 60 | <%= print_config('so-rcvbuf', @so_rcvbuf) -%> 61 | <%= print_config('so-sndbuf', @so_sndbuf) -%> 62 | <%= print_config('so-reuseport', @so_reuseport) -%> 63 | <%= print_config('ip-transparent', @ip_transparent) -%> 64 | <%= print_config('ip-freebind', @ip_freebind) -%> 65 | <%= print_config('rrset-cache-slabs', @rrset_cache_slabs) -%> 66 | <%= print_config('rrset-cache-size', @rrset_cache_size) -%> 67 | <%= print_config('cache-max-ttl', @cache_max_ttl) -%> 68 | <%= print_config('cache-max-negative-ttl', @cache_max_negative_ttl) -%> 69 | <%= print_config('cache-min-ttl', @cache_min_ttl) -%> 70 | <%= print_config('infra-host-ttl', @infra_host_ttl) -%> 71 | <%= print_config('infra-cache-numhosts', @infra_cache_numhosts) -%> 72 | <%= print_config('infra-cache-slabs', @infra_cache_slabs) -%> 73 | <%= print_config('infra-cache-min-rtt', @infra_cache_min_rtt) -%> 74 | <% if !@define_tag.empty? -%> 75 | define-tag: "<%= @define_tag.join(' ') %>" 76 | <%- end -%> 77 | <%= print_config('do-ip4', @do_ip4) -%> 78 | <%= print_config('do-ip6', @do_ip6) -%> 79 | <%= print_config('prefer-ip6', @prefer_ip6) -%> 80 | <%= print_config('do-udp', @do_udp) -%> 81 | <%= print_config('do-tcp', @do_tcp) -%> 82 | <%= print_config('tcp-mss', @tcp_mss) -%> 83 | <%= print_config('outgoing-tcp-mss', @outgoing_tcp_mss) -%> 84 | <%= print_config('tcp-idle-timeout', @tcp_idle_timeout, '1.8.0') -%> 85 | <%= print_config('edns-tcp-keepalive', @edns_tcp_keepalive, '1.8.0') -%> 86 | <%= print_config('edns-tcp-keepalive-timeout', @edns_tcp_keepalive_timeout, '1.8.0') -%> 87 | <%= print_config('tcp-upstream', @tcp_upstream) -%> 88 | <%= print_config('udp-upstream-without-downstream', @udp_upstream_without_downstream, '1.6.7') -%> 89 | <%= print_config('tls-upstream', @tls_upstream, '1.7.0') -%> 90 | <%= print_config('tls-cert-bundle', @tls_cert_bundle, '1.7.0') -%> 91 | <%= print_config('ssl-upstream', @ssl_upstream, '1.7.0') -%> 92 | <%= print_config('ssl-service-key', @ssl_service_key, '1.7.0') -%> 93 | <%= print_config('ssl-service-pem', @ssl_service_pem, '1.7.0') -%> 94 | <%= print_config('ssl-port', @ssl_port, '1.7.0') -%> 95 | <%= print_config('tls-ciphers', @tls_ciphers, '1.9.0') -%> 96 | <%= print_config('tls-ciphersuites', @tls_ciphersuites, '1.9.0') -%> 97 | <%= print_config('use-systemd', @use_systemd) -%> 98 | <%= print_config('do-daemonize', @do_daemonize) -%> 99 | <% @access.each do |acc| -%> 100 | <%- if acc.include? " " -%> 101 | access-control: <%= acc %> 102 | <%- else -%> 103 | access-control: <%= acc %> allow 104 | <%- end -%> 105 | <%- end -%> 106 | <%- @access_control.each_pair do |prefix, config| -%> 107 | <%- if config.include?('tags') -%> 108 | <%- unless config.include?('action') or config.include?('rr_string') -%> 109 | access-control-tag: <%= prefix %> "<%= tags.join(' ') %>" 110 | <%- else -%> 111 | <%- config['tags'].each do |tag| -%> 112 | <%- if config.include?('action') -%> 113 | access-control-tag-action: <%= prefix %> <%= tag %> <%= config['action'] %> 114 | <%- end -%> 115 | <%- if config.include?('rr_string') -%> 116 | access-control-tag-action: <%= prefix %> <%= tag %> <%= config['rr_string'] %> 117 | <%- end -%> 118 | <%- end -%> 119 | <%- end -%> 120 | <%- end -%> 121 | <%- if config.include?('view') -%> 122 | access-control-view: <%= prefix %> <% config['view'] %> 123 | <%- end -%> 124 | <%- end -%> 125 | <%= print_config('chroot', @chroot) -%> 126 | <%= print_config('username', @username) -%> 127 | <%= print_config('directory', @directory) -%> 128 | <% if @logfile -%> 129 | logfile: <%= @logfile %> 130 | use-syslog: no 131 | <% else -%> 132 | use-syslog: yes 133 | <% end -%> 134 | <%= print_config('log-identity', @log_identity) -%> 135 | <%= print_config('log-time-ascii', @log_time_ascii) -%> 136 | <%= print_config('log-queries', @log_queries) -%> 137 | <%= print_config('log-replies', @log_replies) -%> 138 | <%= print_config('log-tag-queryreply', @log_tag_queryreply, '1.9.0') -%> 139 | <%= print_config('log-local-actions', @log_local_actions, '1.8.0') -%> 140 | <%= print_config('log-servfail', @log_servfail, '1.8.0') -%> 141 | <%= print_config('hide-identity', @hide_identity) -%> 142 | <%= print_config('pidfile', @pidfile) -%> 143 | <%- unless @hints_file == 'builtin' -%> 144 | <%= print_config('root-hints', @hints_file) -%> 145 | <%- end -%> 146 | <%= print_config('identity', @identity) -%> 147 | <%= print_config('hide-version', @hide_version) -%> 148 | <%= print_config('version', @version) -%> 149 | <%= print_config('hide-trustanchor', @hide_trustanchor) -%> 150 | <%- unless @target_fetch_policy.empty? -%> 151 | <%= print_config('target-fetch-policy', @target_fetch_policy.join(' ')) -%> 152 | <%- end -%> 153 | <%= print_config('harden-short-bufsize', @harden_short_bufsize) -%> 154 | <%= print_config('harden-large-queries', @harden_large_queries) -%> 155 | <%= print_config('harden-glue', @harden_glue) -%> 156 | <%= print_config('harden-dnssec-stripped', @harden_dnssec_stripped) -%> 157 | <%= print_config('harden-below-nxdomain', @harden_below_nxdomain) -%> 158 | <%= print_config('harden-referral-path', @harden_referral_path) -%> 159 | <%= print_config('harden-algo-downgrade', @harden_algo_downgrade) -%> 160 | <%= print_config('use-caps-for-id', @use_caps_for_id) -%> 161 | <%= print_config('caps-whitlist', @caps_whitlist) -%> 162 | <%= print_config('qname-minimisation', @qname_minimisation) -%> 163 | <%= print_config('qname-minimisation-strict', @qname_minimisation_strict) -%> 164 | <%= print_config('private-address', @private_address) -%> 165 | <%= print_config('private-domain', @private_domain) -%> 166 | <%= print_config('unwanted-reply-threshold', @unwanted_reply_threshold) -%> 167 | <%= print_config('do-not-query-address', @do_not_query_address) -%> 168 | <%= print_config('do-not-query-localhost', @do_not_query_localhost) -%> 169 | <%= print_config('prefetch', @prefetch) -%> 170 | <%= print_config('prefetch-key', @prefetch_key) -%> 171 | <%= print_config('deny-any', @deny_any, '1.8.2') -%> 172 | <%= print_config('rrset-roundrobin', @rrset_roundrobin) -%> 173 | <%= print_config('minimal-responses', @minimal_responses) -%> 174 | <%= print_config('disable-dnssec-lame-check', @disable_dnssec_lame_check) -%> 175 | <%- unless @module_config.empty? %> 176 | module-config: "<%= @module_config.join(' ') %>" 177 | <%- end -%> 178 | <%- if @trust_anchor_file -%> 179 | <%= print_config('trust-anchor-file', @trust_anchor_file) -%> 180 | <%- elsif @auto_trust_anchor_file -%> 181 | <%= print_config('auto-trust-anchor-file', @auto_trust_anchor_file) -%> 182 | <%- end -%> 183 | <%= print_config('trust-anchor', @trust_anchor) -%> 184 | <%= print_config('trusted-keys-file', @trusted_keys_file) -%> 185 | <%= print_config('trust-anchor-signaling', @trust_anchor_signaling) -%> 186 | <%= print_config('domain-insecure', @domain_insecure) -%> 187 | <%= print_config('val-sig-skew-min', @val_sig_skew_min) -%> 188 | <%= print_config('val-sig-skew-max', @val_sig_skew_max) -%> 189 | <%= print_config('val-bogus-ttl', @val_bogus_ttl) -%> 190 | <%= print_config('val-clean-additional', @val_clean_additional) -%> 191 | <%= print_config('val-log-level', @val_log_level) -%> 192 | <%= print_config('val-permissive-mode', @val_permissive_mode) -%> 193 | <%= print_config('ignore-cd-flag', @ignore_cd_flag) -%> 194 | <%= print_config('serve-expired', @serve_expired) -%> 195 | <%= print_config('serve-expired-ttl', @serve_expired_ttl, '1.8.0') -%> 196 | <%= print_config('serve-expired-ttl-reset', @serve_expired_ttl_reset, '1.8.0') -%> 197 | <%= print_config('serve-expired-reply-ttl', @serve_expired_reply_ttl, '1.8.0') -%> 198 | <%= print_config('serve-expired-client-timeout', @serve_expired_client_timeout, '1.8.0') -%> 199 | <%= print_config('val-nsec3-keysize-iterations', @val_nsec3_keysize_iterations) -%> 200 | <%= print_config('add-holddown', @add_holddown) -%> 201 | <%= print_config('del-holddown', @del_holddown) -%> 202 | <%= print_config('keep-missing', @keep_missing) -%> 203 | <%= print_config('permit-small-holddown', @permit_small_holddown) -%> 204 | <%= print_config('key-cache-size', @key_cache_size) -%> 205 | <%= print_config('key-cache-slabs', @key_cache_slabs) -%> 206 | <%= print_config('neg-cache-size', @neg_cache_size) -%> 207 | <%= print_config('unblock-lan-zones', @unblock_lan_zones) -%> 208 | <%= print_config('insecure-lan-zones', @insecure_lan_zones) -%> 209 | <%- @local_zone.each_pair do |zone, type| -%> 210 | local-zone: "<%= zone %>" <%= type %> 211 | <%- end -%> 212 | <%= print_config('local-data', @local_data) -%> 213 | <%= print_config('local-data-ptr', @local_data_ptr) -%> 214 | <%- @local_zone_tag.each_pair do |zone, tags| -%> 215 | local-zone-tag: <%= zone %> "<%= tags.join(' ') %> 216 | <%- end -%> 217 | <%- @local_zone_override.each_pair do |zone, config| -%> 218 | local-zone-tag: <%= zone %> <%= config['netblock'] %> <%= config['type'] %> 219 | <%- end -%> 220 | <%= print_config('ratelimit', @ratelimit) -%> 221 | <%= print_config('ratelimit-size', @ratelimit_size) -%> 222 | <%= print_config('ratelimit-slabs', @ratelimit_slabs) -%> 223 | <%= print_config('ratelimit-factor', @ratelimit_factor) -%> 224 | <%- @ratelimit_for_domain.each_pair do |domain, qps| %> 225 | ratelimit-for-domain: <%= domain %> <%= qps %> 226 | <%- end -%> 227 | <%- @ratelimit_below_domain.each_pair do |domain, qps| %> 228 | ratelimit-below-domain: <%= domain %> <%= qps %> 229 | <%- end -%> 230 | <%= print_config('ip-ratelimit', @ip_ratelimit) -%> 231 | <%= print_config('ip-ratelimit-size', @ip_ratelimit_size) -%> 232 | <%= print_config('ip-ratelimit-slabs', @ip_ratelimit_slabs) -%> 233 | <%= print_config('ip-ratelimit-factor', @ip_ratelimit_factor) -%> 234 | <%= print_config('fast-server-permil', @fast_server_permil, '1.8.2') -%> 235 | <%= print_config('fast-server-num', @fast_server_num, '1.8.2') -%> 236 | <% @custom_server_conf.each do |c| -%> 237 | <%= c %> 238 | <% end -%> 239 | -------------------------------------------------------------------------------- /spec/classes/init_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'unbound' do 6 | let(:params) { {} } 7 | 8 | # rubocop:disable RSpec/MultipleMemoizedHelpers 9 | on_supported_os.each do |os, os_facts| 10 | context "on #{os}" do 11 | let(:facts) { os_facts.merge(concat_basedir: '/dne') } 12 | let(:package) { 'unbound' } 13 | let(:conf_file) { "#{conf_dir}/unbound.conf" } 14 | let(:conf_d_dir) { "#{conf_dir}/conf.d" } 15 | let(:unbound_conf_d) { "#{conf_dir}/unbound.conf.d" } 16 | let(:keys_d_dir) { "#{conf_dir}/keys.d" } 17 | let(:hints_file) { "#{conf_dir}/root.hints" } 18 | 19 | pidfile = nil 20 | 21 | case os_facts[:os]['family'] 22 | when 'Debian' 23 | pidfile = '/run/unbound.pid' 24 | let(:service) { 'unbound' } 25 | let(:conf_dir) { '/etc/unbound' } 26 | let(:purge_unbound_conf_d) { true } 27 | let(:control_path) { '/usr/sbin/unbound-control' } 28 | when 'OpenBSD' 29 | pidfile = '/var/run/unbound.pid' 30 | let(:service) { 'unbound' } 31 | let(:conf_dir) { '/var/unbound/etc' } 32 | let(:purge_unbound_conf_d) { false } 33 | let(:control_path) { '/usr/sbin/unbound-control' } 34 | when 'FreeBSD' 35 | pidfile = '/usr/local/etc/unbound/unbound.pid' 36 | let(:service) { 'unbound' } 37 | let(:conf_dir) { '/usr/local/etc/unbound' } 38 | let(:purge_unbound_conf_d) { false } 39 | let(:control_path) { '/usr/local/sbin/unbound-control' } 40 | when 'Darwin' 41 | pidfile = '/var/run/unbound.pid' 42 | let(:service) { 'org.macports.unbound' } 43 | let(:conf_dir) { '/opt/local//etc/unbound' } 44 | let(:purge_unbound_conf_d) { false } 45 | else 46 | pidfile = '/var/run/unbound/unbound.pid' 47 | let(:service) { 'unbound' } 48 | let(:conf_dir) { '/etc/unbound' } 49 | let(:purge_unbound_conf_d) { false } 50 | let(:control_path) { '/usr/sbin/unbound-control' } 51 | end 52 | 53 | context 'with default params' do 54 | it { is_expected.to compile.with_all_deps } 55 | it { is_expected.to contain_class('unbound') } 56 | 57 | it { is_expected.to contain_package(package) } if os_facts[:os]['family'] != 'OpenBSD' 58 | it { is_expected.to contain_service(service) } 59 | it { is_expected.to contain_concat(conf_file) } 60 | it { is_expected.to contain_file(conf_dir) } 61 | it { is_expected.to contain_file(conf_d_dir) } 62 | it { is_expected.to contain_file(keys_d_dir) } 63 | it { is_expected.to contain_file(hints_file) } 64 | 65 | context 'on Linux', if: os_facts[:kernel] == 'Linux' do 66 | it { is_expected.to contain_systemd__timer('roothints.timer') } 67 | end 68 | 69 | it do 70 | expect(subject).to contain_file(unbound_conf_d).with( 71 | 'ensure' => 'directory', 72 | 'owner' => 'root', 73 | 'group' => '0', 74 | 'purge' => purge_unbound_conf_d, 75 | 'recurse' => purge_unbound_conf_d 76 | ) 77 | end 78 | 79 | it { is_expected.not_to contain_file('/run') } 80 | it { is_expected.not_to contain_file('/var/run') } 81 | 82 | it { is_expected.to contain_file(File.dirname(pidfile)) } if pidfile =~ %r{unbound/unbound\.pid\Z} 83 | it do 84 | expect(subject).to contain_concat__fragment( 85 | 'unbound-header' 86 | ).with_content( 87 | %r{\s+root-hints:\s"#{hints_file}"} 88 | ).with_content( 89 | %r{\s+pidfile:\s"#{pidfile}"} 90 | ).without_content( 91 | %r{infra-cache-slabs} 92 | ).without_content( 93 | %r{key-cache-slabs} 94 | ).without_content( 95 | %r{msg-cache-slabs} 96 | ).without_content( 97 | %r{rrset-cache-slabs} 98 | ).without_content( 99 | %r{num-queries-per-thread} 100 | ) 101 | end 102 | 103 | it do 104 | expect(subject).to contain_concat__fragment( 105 | 'unbound-modules' 106 | ).without_content( 107 | %r{python:} 108 | ).without_content( 109 | %r{cachedb:} 110 | ).without_content( 111 | %r{ipsecmod:} 112 | ).without_content( 113 | %r{dns64:} 114 | ).without_content( 115 | %r{subnetcache:} 116 | ) 117 | end 118 | end 119 | 120 | context 'with access control configured' do 121 | let(:facts) { os_facts.merge(unbound_version: '1.6.1') } 122 | let :params do 123 | { 124 | access_control: { 125 | 'foobar' => { 126 | 'view' => 'allow' 127 | }, 128 | 'foobaz' => { 129 | 'action' => 'allow', 130 | 'rr_string' => '::/0', 131 | 'tags' => %w[123 456] 132 | } 133 | } 134 | } 135 | end 136 | 137 | it { is_expected.to compile.with_all_deps } 138 | 139 | it { 140 | expect(subject).to contain_concat__fragment( 141 | 'unbound-header' 142 | ).with_content( 143 | %r{\s+access-control-view: foobar} 144 | ).with_content( 145 | %r{\s+access-control-tag-action: foobaz 123 allow} 146 | ).with_content( 147 | %r{\s+access-control-tag-action: foobaz 123 ::/0} 148 | ).with_content( 149 | %r{\s+access-control-tag-action: foobaz 456 allow} 150 | ).with_content( 151 | %r{\s+access-control-tag-action: foobaz 456 ::/0} 152 | ) 153 | } 154 | end 155 | 156 | context 'module config' do 157 | context 'dns64' do 158 | before { params.merge!(module_config: %w[dns64]) } 159 | 160 | it do 161 | expect(subject).to contain_concat__fragment( 162 | 'unbound-modules' 163 | ).with_content( 164 | %r{dns64-prefix: "64:ff9b::/96"} 165 | ).with_content( 166 | %r{dns64-synthall: no} 167 | ) 168 | end 169 | end 170 | 171 | context 'dns64 dns64-prefix' do 172 | before do 173 | params.merge!( 174 | module_config: %w[dns64], 175 | dns64_prefix: '42:ff9b::/96' 176 | ) 177 | end 178 | 179 | it do 180 | expect(subject).to contain_concat__fragment( 181 | 'unbound-modules' 182 | ).with_content( 183 | %r{dns64-prefix: "42:ff9b::/96"} 184 | ).with_content( 185 | %r{dns64-synthall: no} 186 | ) 187 | end 188 | end 189 | 190 | context 'dns64 dns64-synthall' do 191 | before do 192 | params.merge!( 193 | module_config: %w[dns64], 194 | dns64_synthall: true 195 | ) 196 | end 197 | 198 | it do 199 | expect(subject).to contain_concat__fragment( 200 | 'unbound-modules' 201 | ).with_content( 202 | %r{dns64-prefix: "64:ff9b::/96"} 203 | ).with_content( 204 | %r{dns64-synthall: yes} 205 | ) 206 | end 207 | end 208 | 209 | context 'subnetcache not supported' do 210 | before { params.merge!(module_config: %w[subnetcache]) } 211 | 212 | it do 213 | expect(subject).to contain_concat__fragment( 214 | 'unbound-modules' 215 | ).without_content( 216 | %r{send-client-subnet:} 217 | ).without_content( 218 | %r{client-subnet-zone:} 219 | ).without_content( 220 | %r{client-subnet-always-forward:} 221 | ).without_content( 222 | %r{max-client-subnet-ipv6:} 223 | ).without_content( 224 | %r{max-client-subnet-ipv4:} 225 | ) 226 | end 227 | end 228 | 229 | context 'subnetcache' do 230 | let(:facts) { os_facts.merge(unbound_version: '1.6.1') } 231 | 232 | before { params.merge!(module_config: %w[subnetcache]) } 233 | 234 | it do 235 | expect(subject).to contain_concat__fragment( 236 | 'unbound-modules' 237 | ).without_content( 238 | %r{send-client-subnet:} 239 | ).without_content( 240 | %r{client-subnet-zone:} 241 | ).with_content( 242 | %r{client-subnet-always-forward: no} 243 | ).with_content( 244 | %r{max-client-subnet-ipv6: 56} 245 | ).with_content( 246 | %r{max-client-subnet-ipv4: 24} 247 | ) 248 | end 249 | end 250 | 251 | context 'subnetcache send-client-subnet' do 252 | let(:facts) { os_facts.merge(unbound_version: '1.6.1') } 253 | 254 | before do 255 | params.merge!( 256 | module_config: %w[subnetcache], 257 | send_client_subnet: ['192.0.2.0/24', '2001::db8:/48'] 258 | ) 259 | end 260 | 261 | it do 262 | expect(subject).to contain_concat__fragment( 263 | 'unbound-modules' 264 | ).with_content( 265 | %r{send-client-subnet: "192.0.2.0/24"} 266 | ).with_content( 267 | %r{send-client-subnet: "2001::db8:/48"} 268 | ).without_content( 269 | %r{client-subnet-zone:} 270 | ).with_content( 271 | %r{client-subnet-always-forward: no} 272 | ).with_content( 273 | %r{max-client-subnet-ipv6: 56} 274 | ).with_content( 275 | %r{max-client-subnet-ipv4: 24} 276 | ) 277 | end 278 | end 279 | 280 | context 'subnetcache client_subnet_zone' do 281 | before do 282 | params.merge!( 283 | module_config: %w[subnetcache], 284 | client_subnet_zone: ['example.com', 'example.net'] 285 | ) 286 | end 287 | 288 | it do 289 | expect(subject).to contain_concat__fragment( 290 | 'unbound-modules' 291 | ).without_content( 292 | %r{send-client-subnet:} 293 | ).without_content( 294 | %r{client-subnet-zone: "example.com"} 295 | ).without_content( 296 | %r{client-subnet-zone: "example.net"} 297 | ).without_content( 298 | %r{client-subnet-always-forward: no} 299 | ).without_content( 300 | %r{max-client-subnet-ipv6: 56} 301 | ).without_content( 302 | %r{max-client-subnet-ipv4: 24} 303 | ) 304 | end 305 | end 306 | 307 | context 'subnetcache client_subnet_always_forward' do 308 | before do 309 | params.merge!( 310 | module_config: %w[subnetcache], 311 | client_subnet_always_forward: true 312 | ) 313 | end 314 | 315 | it do 316 | expect(subject).to contain_concat__fragment( 317 | 'unbound-modules' 318 | ).without_content( 319 | %r{send-client-subnet:} 320 | ).without_content( 321 | %r{client-subnet-zone:} 322 | ).without_content( 323 | %r{client-subnet-always-forward: yes} 324 | ).without_content( 325 | %r{max-client-subnet-ipv6: 56} 326 | ).without_content( 327 | %r{max-client-subnet-ipv4: 24} 328 | ) 329 | end 330 | end 331 | 332 | context 'subnetcache max_client_subnet_ipv6' do 333 | before do 334 | params.merge!( 335 | module_config: %w[subnetcache], 336 | max_client_subnet_ipv6: 42 337 | ) 338 | end 339 | 340 | it do 341 | expect(subject).to contain_concat__fragment( 342 | 'unbound-modules' 343 | ).without_content( 344 | %r{send-client-subnet:} 345 | ).without_content( 346 | %r{client-subnet-zone:} 347 | ).without_content( 348 | %r{client-subnet-always-forward: no} 349 | ).without_content( 350 | %r{max-client-subnet-ipv6: 42} 351 | ).without_content( 352 | %r{max-client-subnet-ipv4: 24} 353 | ) 354 | end 355 | end 356 | 357 | context 'subnetcache max_client_subnet_ipv4' do 358 | before do 359 | params.merge!( 360 | module_config: %w[subnetcache], 361 | max_client_subnet_ipv4: 21 362 | ) 363 | end 364 | 365 | it do 366 | expect(subject).to contain_concat__fragment( 367 | 'unbound-modules' 368 | ).without_content( 369 | %r{send-client-subnet:} 370 | ).without_content( 371 | %r{client-subnet-zone:} 372 | ).without_content( 373 | %r{client-subnet-always-forward: no} 374 | ).without_content( 375 | %r{max-client-subnet-ipv6: 56} 376 | ).without_content( 377 | %r{max-client-subnet-ipv4: 42} 378 | ) 379 | end 380 | end 381 | 382 | context 'ipsecmod not supported' do 383 | before do 384 | params.merge!( 385 | module_config: %w[ipsecmod], 386 | ipsecmod_hook: '/foo/bar' 387 | ) 388 | end 389 | 390 | it do 391 | expect(subject).to contain_concat__fragment( 392 | 'unbound-modules' 393 | ).without_content( 394 | %r{ipsecmod-enabled:} 395 | ).without_content( 396 | %r{ipsecmod-hook:} 397 | ).without_content( 398 | %r{ipsecmod-strict:} 399 | ).without_content( 400 | %r{ipsecmod-max-ttl:} 401 | ).without_content( 402 | %r{ipsecmod-ignore-bogus:} 403 | ).without_content( 404 | %r{ipsecmod-whitelist:} 405 | ) 406 | end 407 | end 408 | 409 | context 'ipsecmod disable' do 410 | let(:facts) { os_facts.merge(unbound_version: '1.6.4') } 411 | 412 | before do 413 | params.merge!( 414 | module_config: %w[ipsecmod], 415 | ipsecmod_hook: '/foo/bar', 416 | ipsecmod_enabled: false 417 | ) 418 | end 419 | 420 | it do 421 | expect(subject).to contain_concat__fragment( 422 | 'unbound-modules' 423 | ).with_content( 424 | %r{ipsecmod-enabled: no} 425 | ).with_content( 426 | %r{ipsecmod-hook: "/foo/bar"} 427 | ).with_content( 428 | %r{ipsecmod-strict: no} 429 | ).with_content( 430 | %r{ipsecmod-max-ttl: 3600} 431 | ).with_content( 432 | %r{ipsecmod-ignore-bogus: no} 433 | ).without_content( 434 | %r{ipsecmod-whitelist:} 435 | ) 436 | end 437 | end 438 | 439 | context 'ipsecmod default' do 440 | let(:facts) { os_facts.merge(unbound_version: '1.6.4') } 441 | 442 | before do 443 | params.merge!( 444 | module_config: %w[ipsecmod], 445 | ipsecmod_hook: '/foo/bar' 446 | ) 447 | end 448 | 449 | it do 450 | expect(subject).to contain_concat__fragment( 451 | 'unbound-modules' 452 | ).with_content( 453 | %r{ipsecmod-enabled: yes} 454 | ).with_content( 455 | %r{ipsecmod-hook: "/foo/bar"} 456 | ).with_content( 457 | %r{ipsecmod-strict: no} 458 | ).with_content( 459 | %r{ipsecmod-max-ttl: 3600} 460 | ).with_content( 461 | %r{ipsecmod-ignore-bogus: no} 462 | ).without_content( 463 | %r{ipsecmod-whitelist:} 464 | ) 465 | end 466 | end 467 | 468 | context 'ipsecmod ipsecmod-hook' do 469 | let(:facts) { os_facts.merge(unbound_version: '1.6.4') } 470 | 471 | before do 472 | params.merge!( 473 | module_config: %w[ipsecmod], 474 | ipsecmod_hook: '/foo/bar/42' 475 | ) 476 | end 477 | 478 | it do 479 | expect(subject).to contain_concat__fragment( 480 | 'unbound-modules' 481 | ).with_content( 482 | %r{ipsecmod-enabled: yes} 483 | ).with_content( 484 | %r{ipsecmod-hook: "/foo/bar/42"} 485 | ).with_content( 486 | %r{ipsecmod-strict: no} 487 | ).with_content( 488 | %r{ipsecmod-max-ttl: 3600} 489 | ).with_content( 490 | %r{ipsecmod-ignore-bogus: no} 491 | ).without_content( 492 | %r{ipsecmod-whitelist:} 493 | ) 494 | end 495 | end 496 | 497 | context 'ipsecmod ipsecmod_strict' do 498 | let(:facts) { os_facts.merge(unbound_version: '1.6.4') } 499 | 500 | before do 501 | params.merge!( 502 | module_config: %w[ipsecmod], 503 | ipsecmod_hook: '/foo/bar', 504 | ipsecmod_strict: true 505 | ) 506 | end 507 | 508 | it do 509 | expect(subject).to contain_concat__fragment( 510 | 'unbound-modules' 511 | ).with_content( 512 | %r{ipsecmod-enabled: yes} 513 | ).with_content( 514 | %r{ipsecmod-hook: "/foo/bar"} 515 | ).with_content( 516 | %r{ipsecmod-strict: yes} 517 | ).with_content( 518 | %r{ipsecmod-max-ttl: 3600} 519 | ).with_content( 520 | %r{ipsecmod-ignore-bogus: no} 521 | ).without_content( 522 | %r{ipsecmod-whitelist:} 523 | ) 524 | end 525 | end 526 | 527 | context 'ipsecmod ipsecmod_max_ttl' do 528 | let(:facts) { os_facts.merge(unbound_version: '1.6.4') } 529 | 530 | before do 531 | params.merge!( 532 | module_config: %w[ipsecmod], 533 | ipsecmod_hook: '/foo/bar', 534 | ipsecmod_max_ttl: 42 535 | ) 536 | end 537 | 538 | it do 539 | expect(subject).to contain_concat__fragment( 540 | 'unbound-modules' 541 | ).with_content( 542 | %r{ipsecmod-enabled: yes} 543 | ).with_content( 544 | %r{ipsecmod-hook: "/foo/bar"} 545 | ).with_content( 546 | %r{ipsecmod-strict: no} 547 | ).with_content( 548 | %r{ipsecmod-max-ttl: 42} 549 | ).with_content( 550 | %r{ipsecmod-ignore-bogus: no} 551 | ).without_content( 552 | %r{ipsecmod-whitelist:} 553 | ) 554 | end 555 | end 556 | 557 | context 'ipsecmod ipsecmod-ignore-bogus' do 558 | let(:facts) { os_facts.merge(unbound_version: '1.6.4') } 559 | 560 | before do 561 | params.merge!( 562 | module_config: %w[ipsecmod], 563 | ipsecmod_hook: '/foo/bar', 564 | ipsecmod_ignore_bogus: true 565 | ) 566 | end 567 | 568 | it do 569 | expect(subject).to contain_concat__fragment( 570 | 'unbound-modules' 571 | ).with_content( 572 | %r{ipsecmod-enabled: yes} 573 | ).with_content( 574 | %r{ipsecmod-hook: "/foo/bar"} 575 | ).with_content( 576 | %r{ipsecmod-strict: no} 577 | ).with_content( 578 | %r{ipsecmod-max-ttl: 3600} 579 | ).with_content( 580 | %r{ipsecmod-ignore-bogus: yes} 581 | ).without_content( 582 | %r{ipsecmod-whitelist:} 583 | ) 584 | end 585 | end 586 | 587 | context 'ipsecmod ipsecmod-whitelist' do 588 | let(:facts) { os_facts.merge(unbound_version: '1.6.4') } 589 | 590 | before do 591 | params.merge!( 592 | module_config: %w[ipsecmod], 593 | ipsecmod_hook: '/foo/bar', 594 | ipsecmod_whitelist: ['example.com', 'example.net'] 595 | ) 596 | end 597 | 598 | it do 599 | expect(subject).to contain_concat__fragment( 600 | 'unbound-modules' 601 | ).with_content( 602 | %r{ipsecmod-enabled: yes} 603 | ).with_content( 604 | %r{ipsecmod-hook: "/foo/bar"} 605 | ).with_content( 606 | %r{ipsecmod-strict: no} 607 | ).with_content( 608 | %r{ipsecmod-max-ttl: 3600} 609 | ).with_content( 610 | %r{ipsecmod-ignore-bogus: no} 611 | ).with_content( 612 | %r{ipsecmod-whitelist: "example.com"} 613 | ).with_content( 614 | %r{ipsecmod-whitelist: "example.net"} 615 | ) 616 | end 617 | end 618 | 619 | context 'python' do 620 | before do 621 | params.merge!( 622 | module_config: %w[python], 623 | python_script: '/foo/bar' 624 | ) 625 | end 626 | 627 | it do 628 | expect(subject).to contain_concat__fragment( 629 | 'unbound-modules' 630 | ).with_content( 631 | %r{python:} 632 | ).with_content( 633 | %r{\s+python-script: "/foo/bar"} 634 | ) 635 | end 636 | end 637 | 638 | context 'cachedb' do 639 | before do 640 | params.merge!( 641 | module_config: %w[cachedb] 642 | ) 643 | end 644 | 645 | it do 646 | expect(subject).to contain_concat__fragment( 647 | 'unbound-modules' 648 | ).with_content( 649 | %r{cachedb:} 650 | ).without_content( 651 | %r{\s+backend:} 652 | ).with_content( 653 | %r{\s+secret-seed: "default"} 654 | ).without_content( 655 | %r{\s+redis-server-host:} 656 | ).without_content( 657 | %r{\s+redis-server-port:} 658 | ).without_content( 659 | %r{\s+redis-timeout:} 660 | ) 661 | end 662 | end 663 | 664 | context 'cachedb backend redis' do 665 | before do 666 | params.merge!( 667 | module_config: %w[cachedb], 668 | backend: 'redis' 669 | ) 670 | end 671 | 672 | it do 673 | expect(subject).to contain_concat__fragment( 674 | 'unbound-modules' 675 | ).with_content( 676 | %r{cachedb:} 677 | ).with_content( 678 | %r{\s+backend: "redis"} 679 | ).with_content( 680 | %r{\s+secret-seed: "default"} 681 | ).with_content( 682 | %r{\s+redis-server-host: "127.0.0.1"} 683 | ).with_content( 684 | %r{\s+redis-server-port: 6379} 685 | ).with_content( 686 | %r{\s+redis-timeout: 100} 687 | ) 688 | end 689 | end 690 | 691 | context 'cachedb backend foobar' do 692 | before do 693 | params.merge!( 694 | module_config: %w[cachedb], 695 | backend: 'foobar' 696 | ) 697 | end 698 | 699 | it do 700 | expect(subject).to contain_concat__fragment( 701 | 'unbound-modules' 702 | ).with_content( 703 | %r{cachedb:} 704 | ).with_content( 705 | %r{\s+backend: "foobar"} 706 | ).with_content( 707 | %r{\s+secret-seed: "default"} 708 | ).without_content( 709 | %r{\s+redis-server-host:} 710 | ).without_content( 711 | %r{\s+redis-server-port:} 712 | ).without_content( 713 | %r{\s+redis-timeout:} 714 | ) 715 | end 716 | end 717 | 718 | context 'cachedb redis_server_host' do 719 | before do 720 | params.merge!( 721 | module_config: %w[cachedb], 722 | backend: 'redis', 723 | redis_server_host: '192.0.2.1' 724 | ) 725 | end 726 | 727 | it do 728 | expect(subject).to contain_concat__fragment( 729 | 'unbound-modules' 730 | ).with_content( 731 | %r{cachedb:} 732 | ).with_content( 733 | %r{\s+backend: "redis"} 734 | ).with_content( 735 | %r{\s+secret-seed: "default"} 736 | ).with_content( 737 | %r{\s+redis-server-host: "192.0.2.1"} 738 | ).with_content( 739 | %r{\s+redis-server-port: 6379} 740 | ).with_content( 741 | %r{\s+redis-timeout: 100} 742 | ) 743 | end 744 | end 745 | 746 | context 'cachedb redis_server_port' do 747 | before do 748 | params.merge!( 749 | module_config: %w[cachedb], 750 | backend: 'redis', 751 | redis_server_port: 42 752 | ) 753 | end 754 | 755 | it do 756 | expect(subject).to contain_concat__fragment( 757 | 'unbound-modules' 758 | ).with_content( 759 | %r{cachedb:} 760 | ).with_content( 761 | %r{\s+backend: "redis"} 762 | ).with_content( 763 | %r{\s+secret-seed: "default"} 764 | ).with_content( 765 | %r{\s+redis-server-host: "127.0.0.1"} 766 | ).with_content( 767 | %r{\s+redis-server-port: 42} 768 | ).with_content( 769 | %r{\s+redis-timeout: 100} 770 | ) 771 | end 772 | end 773 | 774 | context 'cachedb redis-timeout' do 775 | before do 776 | params.merge!( 777 | module_config: %w[cachedb], 778 | backend: 'redis', 779 | redis_timeout: 42 780 | ) 781 | end 782 | 783 | it do 784 | expect(subject).to contain_concat__fragment( 785 | 'unbound-modules' 786 | ).with_content( 787 | %r{cachedb:} 788 | ).with_content( 789 | %r{\s+backend: "redis"} 790 | ).with_content( 791 | %r{\s+secret-seed: "default"} 792 | ).with_content( 793 | %r{\s+redis-server-host: "127.0.0.1"} 794 | ).with_content( 795 | %r{\s+redis-server-port: 6379} 796 | ).with_content( 797 | %r{\s+redis-timeout: 42} 798 | ) 799 | end 800 | end 801 | end 802 | 803 | context 'with modified access' do 804 | let(:params) do 805 | { 806 | access: ['10.21.30.0/24 allow', '10.21.30.5/32 reject', '127.0.0.1/32 allow_snoop', '123.123.123.0/20'] 807 | } 808 | end 809 | 810 | it do 811 | expect(subject).to contain_concat__fragment('unbound-header').with_content( 812 | %r{^ access-control: 10.21.30.0/24 allow\n access-control: 10.21.30.5/32 reject\n access-control: 127.0.0.1/32 allow_snoop\n access-control: 123.123.123.0/20 allow} 813 | ) 814 | end 815 | end 816 | 817 | context 'with a different config file' do 818 | let(:params) { { config_file: '/etc/unbound/unbound.conf.d/foobar.conf' } } 819 | 820 | it { is_expected.to contain_concat('/etc/unbound/unbound.conf.d/foobar.conf') } 821 | end 822 | 823 | context 'stub passed to class' do 824 | let(:params) do 825 | { 826 | stub: { 'example-stub.com' => { 'address' => ['10.0.0.1', '10.0.0.2'], 'insecure' => 'true' } } 827 | } 828 | end 829 | 830 | it do 831 | expect(subject).to contain_concat__fragment('unbound-stub-example-stub.com').with_content( 832 | %r{^stub-zone:\n name: "example-stub.com"\n stub-addr: 10.0.0.1\n stub-addr: 10.0.0.2} 833 | ) 834 | end 835 | end 836 | 837 | context 'forward passed to class' do 838 | let(:params) do 839 | { 840 | forward: { 'example-forward.com' => { 'address' => ['10.0.0.1', '10.0.0.2'], 'forward_first' => 'yes', 'forward_ssl_upstream' => 'yes' } } 841 | } 842 | end 843 | 844 | it do 845 | expect(subject).to contain_concat__fragment('unbound-forward-example-forward.com').with_content( 846 | %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} 847 | ) 848 | end 849 | end 850 | 851 | context 'local_zone passed to class' do 852 | let(:params) do 853 | { 854 | domain_insecure: ['0.0.10.in-addr.arpa.', 'example.com.'] 855 | } 856 | end 857 | 858 | it do 859 | expect(subject).to contain_concat__fragment('unbound-header').with_content( 860 | %r{^\s+domain-insecure: 0.0.10.in-addr.arpa.$} 861 | ).with_content( 862 | %r{^\s+domain-insecure: example.com.$} 863 | ) 864 | end 865 | end 866 | 867 | context 'local_zone passed to class with nodefault' do 868 | let(:params) do 869 | { 870 | local_zone: { '0.0.10.in-addr.arpa.' => 'nodefault' } 871 | } 872 | end 873 | 874 | it do 875 | expect(subject).to contain_concat__fragment('unbound-header').with_content( 876 | %r{^\s+local-zone: "0.0.10.in-addr.arpa." nodefault$} 877 | ) 878 | end 879 | end 880 | 881 | context 'custom extended_statistics passed to class' do 882 | let(:params) do 883 | { 884 | extended_statistics: true 885 | } 886 | end 887 | 888 | it do 889 | expect(subject).to contain_concat__fragment('unbound-header').with_content( 890 | %r{^ extended-statistics: yes\n} 891 | ) 892 | end 893 | end 894 | 895 | context 'custom log_identity passed to class' do 896 | let(:facts) { os_facts.merge(unbound_version: '1.6.1') } 897 | let(:params) { { log_identity: 'bind' } } 898 | 899 | it do 900 | expect(subject).to contain_concat__fragment('unbound-header').with_content( 901 | %r{^ log-identity: "bind"\n} 902 | ) 903 | end 904 | end 905 | 906 | context 'custom log_time_ascii passed to class' do 907 | let(:params) { { log_time_ascii: true } } 908 | 909 | it do 910 | expect(subject).to contain_concat__fragment('unbound-header').with_content( 911 | %r{^ log-time-ascii: yes\n} 912 | ) 913 | end 914 | end 915 | 916 | context 'custom log_queries passed to class' do 917 | let(:params) { { log_queries: true } } 918 | 919 | it do 920 | expect(subject).to contain_concat__fragment('unbound-header').with_content( 921 | %r{^ log-queries: yes\n} 922 | ) 923 | end 924 | end 925 | 926 | context 'custom log_replies passed to class' do 927 | let(:facts) { os_facts.merge(unbound_version: '1.6.1') } 928 | let(:params) { { log_replies: true } } 929 | 930 | it do 931 | expect(subject).to contain_concat__fragment('unbound-header').with_content( 932 | %r{^ log-replies: yes\n} 933 | ) 934 | end 935 | end 936 | 937 | context 'platform control enablement' do 938 | let(:params) do 939 | { 940 | control_enable: true 941 | } 942 | end 943 | 944 | it { is_expected.to contain_class('unbound::remote') } 945 | 946 | it do 947 | expect(subject).to contain_concat__fragment('unbound-remote').with_content( 948 | %r{^ control-enable: yes\n} 949 | ) 950 | end 951 | 952 | it { is_expected.to contain_service(service).with_restart("#{control_path} reload") } 953 | 954 | case os_facts[:os]['family'] 955 | when 'FreeBSD' 956 | it { is_expected.to contain_exec('unbound-control-setup').with_command('/usr/local/sbin/unbound-control-setup -d /usr/local/etc/unbound') } 957 | when 'OpenBSD' 958 | it { is_expected.to contain_exec('unbound-control-setup').with_command('/usr/sbin/unbound-control-setup -d /var/unbound/etc') } 959 | it { is_expected.to contain_exec('restart unbound').with_command('/usr/sbin/rcctl restart unbound') } 960 | else 961 | it { is_expected.to contain_exec('unbound-control-setup').with_command('/usr/sbin/unbound-control-setup -d /etc/unbound') } 962 | it { is_expected.to contain_exec('restart unbound').with_command('/bin/systemctl restart unbound') } 963 | end 964 | end 965 | 966 | context 'service management diabled' do 967 | let(:params) { { manage_service: false, } } 968 | 969 | it { is_expected.not_to contain_service(service) } 970 | end 971 | 972 | context 'arbitrary control enablement' do 973 | let(:params) do 974 | { 975 | control_enable: true, 976 | control_setup_path: '/no/bin/unbound-control-setup', 977 | confdir: '/var/nowhere/unbound' 978 | } 979 | end 980 | 981 | it { is_expected.to contain_class('unbound::remote') } 982 | 983 | it do 984 | expect(subject).to contain_concat__fragment('unbound-remote').with_content( 985 | %r{^ control-enable: yes\n} 986 | ) 987 | end 988 | 989 | it { is_expected.to contain_exec('unbound-control-setup').with_command('/no/bin/unbound-control-setup -d /var/nowhere/unbound') } 990 | end 991 | 992 | context 'control enablement with interfaces' do 993 | let(:params) do 994 | { 995 | control_enable: true, 996 | interface: [ 997 | '1.2.3.4', 998 | '4.3.2.1' 999 | ], 1000 | restart_cmd: '/bin/false', 1001 | confdir: '/etc/unbound' 1002 | } 1003 | end 1004 | 1005 | it { 1006 | expect(subject).to contain_file('/etc/unbound/interfaces.txt'). 1007 | with_content(%r{^1.2.3.4$}). 1008 | with_content(%r{^4.3.2.1$}). 1009 | that_notifies('Exec[restart unbound]') 1010 | } 1011 | 1012 | it { 1013 | expect(subject).to contain_exec('restart unbound'). 1014 | that_requires('Service[unbound]') 1015 | } 1016 | end 1017 | 1018 | context 'custom interface selection' do 1019 | let(:params) do 1020 | { 1021 | interface: ['::1', '127.0.0.1'] 1022 | } 1023 | end 1024 | 1025 | it do 1026 | expect(subject).to contain_concat__fragment('unbound-header').with_content( 1027 | %r{^ interface: ::1\n interface: 127.0.0.1\n} 1028 | ) 1029 | end 1030 | end 1031 | 1032 | context 'empty pidfile' do 1033 | let(:params) { { pidfile: :undef } } 1034 | 1035 | it do 1036 | expect(subject).to contain_concat__fragment( 1037 | 'unbound-header' 1038 | ).without_content('pidfile:') 1039 | end 1040 | end 1041 | 1042 | context 'roothints' do 1043 | context 'no root hints in config' do 1044 | let(:params) do 1045 | { 1046 | hints_file: 'builtin' 1047 | } 1048 | end 1049 | 1050 | it do 1051 | expect(subject).to contain_concat__fragment( 1052 | 'unbound-header' 1053 | ).without_content(%r{root-hints}) 1054 | end 1055 | 1056 | it { is_expected.not_to contain_systemd__timer('roothints.timer') } 1057 | end 1058 | 1059 | context 'no root hints in config and update_root_hints=unmanaged' do 1060 | let(:params) do 1061 | { 1062 | hints_file: 'builtin', 1063 | update_root_hints: 'unmanaged' 1064 | } 1065 | end 1066 | 1067 | it do 1068 | expect(subject).to contain_concat__fragment( 1069 | 'unbound-header' 1070 | ).without_content(%r{root-hints}) 1071 | end 1072 | 1073 | it { is_expected.not_to contain_systemd__timer('roothints.timer') } 1074 | end 1075 | 1076 | context 'no root hints in config and update_root_hints=absent' do 1077 | let(:params) do 1078 | { 1079 | hints_file: 'builtin', 1080 | update_root_hints: 'absent' 1081 | } 1082 | end 1083 | 1084 | it do 1085 | expect(subject).to contain_concat__fragment( 1086 | 'unbound-header' 1087 | ).without_content(%r{root-hints}) 1088 | end 1089 | 1090 | it { is_expected.to contain_systemd__timer('roothints.timer').with_ensure('absent') } 1091 | end 1092 | 1093 | context 'update_root_hints=absent' do 1094 | let(:params) do 1095 | { 1096 | update_root_hints: 'absent' 1097 | } 1098 | end 1099 | 1100 | it { is_expected.to contain_systemd__timer('roothints.timer').with_ensure('absent') } 1101 | end 1102 | 1103 | context 'hieradata root hints' do 1104 | let(:params) do 1105 | { 1106 | skip_roothints_download: true, 1107 | hints_file_content: File.read('spec/classes/expected/hieradata-root-hint.conf'), 1108 | } 1109 | end 1110 | 1111 | it do 1112 | expect(subject).to contain_file(hints_file).with( 1113 | 'ensure' => 'file', 1114 | 'mode' => '0444', 1115 | 'content' => File.read('spec/classes/expected/hieradata-root-hint.conf') 1116 | ) 1117 | end 1118 | end 1119 | 1120 | context 'with File defaults' do 1121 | let(:pre_condition) { "File { mode => '0644', owner => 'root', group => 'root' }" } 1122 | 1123 | it { is_expected.to compile.with_all_deps } 1124 | end 1125 | end 1126 | 1127 | context 'RPZs config' do 1128 | let(:params) do 1129 | { 1130 | module_config: ['respip'], 1131 | rpzs: { 1132 | 'test1' => { 1133 | 'primary' => ['192.0.1.2', 'primary.example.org'], 1134 | }, 1135 | 'test2' => { 1136 | 'url' => ['https://primary.example.org/zone'], 1137 | 'allow_notify' => ['192.0.1.2', '2001:db8::'], 1138 | 'zonefile' => '/foo/bar', 1139 | 'rpz_action_override' => 'drop', 1140 | 'rpz_log' => true, 1141 | 'rpz_log_name' => 'foobar', 1142 | 'tags' => %w[foo bar], 1143 | }, 1144 | 'test3' => { 1145 | 'url' => ['https://primary.example.org/zone'], 1146 | 'allow_notify' => ['192.0.1.2', '2001:db8::'], 1147 | 'zonefile' => '/foo/bar', 1148 | 'rpz_action_override' => 'cname', 1149 | 'rpz_cname_override' => 'cname.example.org', 1150 | 'rpz_log' => true, 1151 | 'rpz_log_name' => 'foobar', 1152 | 'tags' => %w[foo bar], 1153 | }, 1154 | } 1155 | } 1156 | end 1157 | 1158 | it { is_expected.to compile.with_all_deps } 1159 | 1160 | it do 1161 | is_expected.to contain_concat__fragment('unbound-modules'). 1162 | with_content( 1163 | %r{ 1164 | rpz: 1165 | \s+name:\stest1 1166 | \s+primary:\s"192\.0\.1\.2" 1167 | \s+primary:\s"primary\.example\.org" 1168 | }x 1169 | ). 1170 | with_content( 1171 | %r{ 1172 | rpz: 1173 | \s+name:\stest2 1174 | \s+url:\s"https://primary\.example\.org/zone" 1175 | \s+allow-notify:\s"192\.0\.1\.2" 1176 | \s+allow-notify:\s"2001:db8::" 1177 | \s+zonefile:\s"/foo/bar" 1178 | \s+rpz-action-overrude:\s"drop" 1179 | \s+rpz-log:\syes 1180 | \s+rpz-log-name:\s"foobar" 1181 | \s+tags:\s"foo" 1182 | \s+tags:\s"bar" 1183 | }x 1184 | ). 1185 | with_content( 1186 | %r{ 1187 | rpz: 1188 | \s+name:\stest3 1189 | \s+url:\s"https://primary\.example\.org/zone" 1190 | \s+allow-notify:\s"192\.0\.1\.2" 1191 | \s+allow-notify:\s"2001:db8::" 1192 | \s+zonefile:\s"/foo/bar" 1193 | \s+rpz-action-overrude:\s"cname" 1194 | \s+rpz-cname-override:\s"cname\.example\.org" 1195 | \s+rpz-log:\syes 1196 | \s+rpz-log-name:\s"foobar" 1197 | \s+tags:\s"foo" 1198 | \s+tags:\s"bar" 1199 | }x 1200 | ) 1201 | end 1202 | end 1203 | 1204 | context 'with force_restart' do 1205 | let(:params) { { force_restart: true, control_enable: true } } 1206 | 1207 | it { is_expected.to compile.with_all_deps } 1208 | it { is_expected.to contain_service(service).with_restart(nil) } 1209 | end 1210 | end 1211 | end 1212 | # rubocop:enable RSpec/MultipleMemoizedHelpers 1213 | end 1214 | -------------------------------------------------------------------------------- /manifests/init.pp: -------------------------------------------------------------------------------- 1 | # 2 | # @summary Installs and configures Unbound, the caching DNS resolver from NLnet Labs 3 | # 4 | # @param manage_service ensure puppet manages the service 5 | # @param verbosity see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 6 | # @param statistics_interval see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 7 | # @param statistics_cumulative see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 8 | # @param extended_statistics see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 9 | # @param num_threads see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 10 | # @param port see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 11 | # @param interface see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 12 | # @param interface_automatic see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 13 | # @param interface_automatic_ports see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 14 | # @param outgoing_interface see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 15 | # @param outgoing_range see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 16 | # @param outgoing_port_permit see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 17 | # @param outgoing_port_avoid see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 18 | # @param outgoing_port_permit_first see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 19 | # @param outgoing_num_tcp see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 20 | # @param incoming_num_tcp see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 21 | # @param edns_buffer_size see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 22 | # @param max_udp_size see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 23 | # @param stream_wait_size see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 24 | # @param msg_cache_size see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 25 | # @param msg_cache_slabs see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 26 | # @param num_queries_per_thread see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 27 | # @param jostle_timeout see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 28 | # @param delay_close see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 29 | # @param unknown_server_time_limit see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 30 | # @param so_rcvbuf see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 31 | # @param so_sndbuf see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 32 | # @param so_reuseport see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 33 | # @param ip_transparent see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 34 | # @param ip_freebind see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 35 | # @param rrset_cache_size see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 36 | # @param rrset_cache_slabs see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 37 | # @param cache_max_ttl see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 38 | # @param cache_max_negative_ttl see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 39 | # @param cache_min_ttl see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 40 | # @param infra_host_ttl see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 41 | # @param infra_cache_numhosts see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 42 | # @param infra_cache_slabs see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 43 | # @param infra_cache_min_rtt see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 44 | # @param define_tag see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 45 | # @param do_ip4 see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 46 | # @param do_ip6 see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 47 | # @param prefer_ip6 see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 48 | # @param do_udp see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 49 | # @param do_tcp see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 50 | # @param tcp_mss see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 51 | # @param tls_cert_bundle see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 52 | # @param tls_upstream see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 53 | # @param outgoing_tcp_mss see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 54 | # @param tcp_idle_timeout see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 55 | # @param edns_tcp_keepalive see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 56 | # @param edns_tcp_keepalive_timeout see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 57 | # @param tcp_upstream see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 58 | # @param udp_upstream_without_downstream see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 59 | # @param ssl_upstream see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 60 | # @param ssl_service_key see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 61 | # @param ssl_service_pem see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 62 | # @param ssl_port see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 63 | # @param tls_ciphers see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 64 | # @param tls_ciphersuites see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 65 | # @param use_systemd see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 66 | # @param do_daemonize see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 67 | # @param access_control see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 68 | # @param chroot see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 69 | # @param logfile see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 70 | # @param log_identity see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 71 | # @param log_time_ascii see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 72 | # @param log_queries see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 73 | # @param log_replies see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 74 | # @param log_tag_queryreply see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 75 | # @param log_local_actions see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 76 | # @param log_servfail see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 77 | # @param pidfile see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 78 | # @param hide_identity see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 79 | # @param identity see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 80 | # @param hide_version see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 81 | # @param version see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 82 | # @param hide_trustanchor see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 83 | # @param target_fetch_policy see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 84 | # @param harden_short_bufsize see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 85 | # @param harden_large_queries see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 86 | # @param harden_glue see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 87 | # @param harden_dnssec_stripped see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 88 | # @param harden_below_nxdomain see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 89 | # @param harden_referral_path see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 90 | # @param harden_algo_downgrade see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 91 | # @param use_caps_for_id see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 92 | # @param caps_whitlist see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 93 | # @param qname_minimisation see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 94 | # @param qname_minimisation_strict see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 95 | # @param private_address see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 96 | # @param private_domain see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 97 | # @param unwanted_reply_threshold see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 98 | # @param do_not_query_address see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 99 | # @param do_not_query_localhost see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 100 | # @param prefetch see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 101 | # @param prefetch_key see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 102 | # @param deny_any see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 103 | # @param rrset_roundrobin see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 104 | # @param minimal_responses see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 105 | # @param disable_dnssec_lame_check see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 106 | # @param trust_anchor_file see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 107 | # @param trust_anchor see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 108 | # @param trust_anchor_signaling see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 109 | # @param domain_insecure see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 110 | # @param val_sig_skew_min see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 111 | # @param val_sig_skew_max see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 112 | # @param val_bogus_ttl see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 113 | # @param val_clean_additional see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 114 | # @param val_log_level see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 115 | # @param val_permissive_mode see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 116 | # @param ignore_cd_flag see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 117 | # @param serve_expired see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 118 | # @param serve_expired_ttl see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 119 | # @param serve_expired_ttl_reset see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 120 | # @param serve_expired_reply_ttl see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 121 | # @param serve_expired_client_timeout see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 122 | # @param val_nsec3_keysize_iterations see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 123 | # @param add_holddown see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 124 | # @param del_holddown see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 125 | # @param keep_missing see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 126 | # @param permit_small_holddown see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 127 | # @param key_cache_size see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 128 | # @param key_cache_slabs see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 129 | # @param neg_cache_size see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 130 | # @param unblock_lan_zones see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 131 | # @param insecure_lan_zones see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 132 | # @param local_zone see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 133 | # @param local_data see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 134 | # @param local_data_ptr see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 135 | # @param local_zone_tag see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 136 | # @param local_zone_override see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 137 | # @param ratelimit see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 138 | # @param ratelimit_size see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 139 | # @param ratelimit_slabs see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 140 | # @param ratelimit_factor see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 141 | # @param ratelimit_for_domain see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 142 | # @param ratelimit_below_domain see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 143 | # @param ip_ratelimit see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 144 | # @param ip_ratelimit_size see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 145 | # @param ip_ratelimit_slabs see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 146 | # @param ip_ratelimit_factor see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 147 | # @param fast_server_permil see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 148 | # @param fast_server_num see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 149 | # @param forward see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 150 | # @param stub see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 151 | # @param record see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 152 | # @param access see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 153 | # @param confdir see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 154 | # @param directory see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 155 | # @param conf_d see A directory often included in unbound.conf config 156 | # @param config_file The location of the main config file 157 | # @param control_enable enable nsd-control 158 | # @param control_setup_path the path to nsd-control-setup 159 | # @param control_path see the path to nsd-control 160 | # @param fetch_client client used to fetch files e.g. curl 161 | # @param group the group to use for files 162 | # @param keys_d the directory to store keys 163 | # @param trusted_keys_file the directory for trusted keys 164 | # @param module_config see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 165 | # @param owner the owner to use for files 166 | # @param username see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 167 | # @param package_name The package(s) to install to get unbound 168 | # @param package_ensure the ensure value for the packages 169 | # @param purge_unbound_conf_d if true all unmanaged files in $unbound_conf_d will be purged 170 | # @param root_hints_url the url to download the root hints file 171 | # @param runtime_dir the runtime directory used 172 | # @param auto_trust_anchor_file see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 173 | # @param anchor_fetch_command the command to use to fetch the root anchor 174 | # @param service_name the name of the managed service 175 | # @param service_hasstatus Indicate if the service supports the status parameter 176 | # @param service_ensure the ensure parameter for the managed service 177 | # @param service_enable the enable parameter for the managed service 178 | # @param validate_cmd the validate_cmd to use to check the config 179 | # @param restart_cmd The restart command to use when reload is not enough 180 | # @param force_restart Always force a service reload 181 | # @param custom_server_conf Add some custome config to $configfile 182 | # @param skip_roothints_download don't download the root hints file 183 | # @param python_script see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 184 | # @param dns64_prefix see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 185 | # @param dns64_synthall see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 186 | # @param send_client_subnet see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 187 | # @param client_subnet_zone see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 188 | # @param client_subnet_always_forward see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 189 | # @param max_client_subnet_ipv4 see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 190 | # @param max_client_subnet_ipv6 see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 191 | # @param min_client_subnet_ipv4 see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 192 | # @param min_client_subnet_ipv6 see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 193 | # @param max_ecs_tree_size_ipv4 see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 194 | # @param max_ecs_tree_size_ipv6 see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 195 | # @param ipsecmod_enabled see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 196 | # @param ipsecmod_hook see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 197 | # @param ipsecmod_strict see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 198 | # @param ipsecmod_max_ttl see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 199 | # @param ipsecmod_ignore_bogus see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 200 | # @param ipsecmod_whitelist see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 201 | # @param backend see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 202 | # @param secret_seed see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 203 | # @param redis_server_host see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 204 | # @param redis_server_port see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 205 | # @param redis_timeout see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 206 | # @param unbound_conf_d similar to conf_d, will be merged with conf_d version in future 207 | # @param hints_file the root hints file to use 208 | # @param update_root_hints f we should update the root hints file 209 | # @param hints_file_content the contents of the root hints file 210 | # @param rpzs see https://nlnetlabs.nl/documentation/unbound/unbound.conf/ 211 | # @param unbound_version the unbound_version to use, we can caluclate from the fact but 212 | # specifying reduces the number of puppet runs 213 | class unbound ( 214 | Boolean $manage_service = true, 215 | Integer[0,5] $verbosity = 1, 216 | Optional[Integer] $statistics_interval = undef, 217 | Boolean $statistics_cumulative = false, 218 | Boolean $extended_statistics = false, 219 | Integer[1] $num_threads = 1, 220 | Integer[0, 65535] $port = 53, 221 | Array[String[1]] $interface = [], 222 | Boolean $interface_automatic = false, 223 | Optional[String[1]] $interface_automatic_ports = undef, 224 | Array[String[1]] $outgoing_interface = [], # version 1.5.10 225 | Optional[Integer[1]] $outgoing_range = undef, 226 | Unbound::Range $outgoing_port_permit = '32768-65535', 227 | Unbound::Range $outgoing_port_avoid = '0-32767', 228 | Boolean $outgoing_port_permit_first = true, 229 | Optional[Integer[0]] $outgoing_num_tcp = undef, 230 | Optional[Integer[0]] $incoming_num_tcp = undef, 231 | Integer[0,4096] $edns_buffer_size = 1232, 232 | Optional[Integer[0,65536]] $max_udp_size = undef, 233 | Optional[Unbound::Size] $stream_wait_size = undef, # version 1.9.0 234 | Optional[Unbound::Size] $msg_cache_size = undef, 235 | Optional[Integer] $msg_cache_slabs = undef, 236 | Optional[Integer] $num_queries_per_thread = undef, 237 | Optional[Integer[1]] $jostle_timeout = undef, 238 | Optional[Integer[0]] $delay_close = undef, 239 | Optional[Integer[1]] $unknown_server_time_limit = undef, # version 1.8.2 240 | Optional[Unbound::Size] $so_rcvbuf = undef, 241 | Optional[Unbound::Size] $so_sndbuf = undef, 242 | Boolean $so_reuseport = false, # Version 1.4.22 243 | Boolean $ip_transparent = false, # version 1.5.4 244 | Boolean $ip_freebind = false, # version 1.5.9 245 | Optional[Unbound::Size] $rrset_cache_size = undef, 246 | Optional[Integer] $rrset_cache_slabs = undef, 247 | Optional[Integer] $cache_max_ttl = undef, 248 | Optional[Integer] $cache_max_negative_ttl = undef, 249 | Optional[Integer] $cache_min_ttl = undef, 250 | Optional[Integer] $infra_host_ttl = undef, 251 | Optional[Integer] $infra_cache_numhosts = undef, 252 | Optional[Integer] $infra_cache_slabs = undef, 253 | Optional[Integer] $infra_cache_min_rtt = undef, 254 | Array[String[1]] $define_tag = [], # version 1.5.10 255 | Boolean $do_ip4 = true, 256 | Boolean $do_ip6 = true, 257 | Boolean $prefer_ip6 = false, # version 1.5.10 258 | Boolean $do_udp = true, 259 | Boolean $do_tcp = true, 260 | Optional[Integer[0]] $tcp_mss = undef, # version 1.5.8 261 | Optional[Stdlib::Absolutepath] $tls_cert_bundle = undef, # version 1.7.0 262 | Boolean $tls_upstream = false, # version 1.7.0 263 | Optional[Integer[0]] $outgoing_tcp_mss = undef, # version 1.5.8 264 | Optional[Integer[0]] $tcp_idle_timeout = undef, # version 1.8.0 265 | Boolean $edns_tcp_keepalive = false, # version 1.8.0 266 | Optional[Integer[0]] $edns_tcp_keepalive_timeout = undef, # version 1.8.0 267 | Boolean $tcp_upstream = false, 268 | Boolean $udp_upstream_without_downstream = false, 269 | Boolean $ssl_upstream = false, # version 1.7.0 270 | Optional[Stdlib::Absolutepath] $ssl_service_key = undef, # version 1.7.0 271 | Optional[Stdlib::Absolutepath] $ssl_service_pem = undef, # version 1.7.0 272 | Optional[Integer[0,65535]] $ssl_port = undef, # version 1.7.0 273 | Optional[String[1]] $tls_ciphers = undef, # version 1.9.0 274 | Optional[String[1]] $tls_ciphersuites = undef, # version 1.9.0 275 | Boolean $use_systemd = false, # version 1.6.1 276 | Boolean $do_daemonize = true, 277 | Hash[String[1], Unbound::Access_control] $access_control = {}, # version 1.5.10 278 | Optional[Unbound::Chroot] $chroot = undef, 279 | Optional[Stdlib::Absolutepath] $logfile = undef, 280 | Optional[String[1]] $log_identity = undef, # version 1.6.0 281 | Boolean $log_time_ascii = false, 282 | Boolean $log_queries = false, 283 | Boolean $log_replies = false, # version 1.6.1 284 | Boolean $log_tag_queryreply = false, # version 1.9.0 285 | Boolean $log_local_actions = false, # version 1.8.0 286 | Boolean $log_servfail = false, # version 1.8.0 287 | Stdlib::Absolutepath $pidfile = '/var/run/unbound/unbound.pid', 288 | Boolean $hide_identity = true, 289 | Optional[String[1]] $identity = undef, 290 | Boolean $hide_version = true, 291 | Optional[String[1]] $version = undef, 292 | Boolean $hide_trustanchor = true, # version 1.6.2 293 | Array[Integer] $target_fetch_policy = [], 294 | Boolean $harden_short_bufsize = false, 295 | Boolean $harden_large_queries = false, 296 | Boolean $harden_glue = true, 297 | Boolean $harden_dnssec_stripped = true, 298 | Boolean $harden_below_nxdomain = true, 299 | Boolean $harden_referral_path = false, 300 | Boolean $harden_algo_downgrade = false, # Version 1.5.3 301 | Boolean $use_caps_for_id = false, 302 | Array[String[1]] $caps_whitlist = [], 303 | Boolean $qname_minimisation = false, # version 1.5.7 304 | Boolean $qname_minimisation_strict = false, # version 1.6.0 305 | Array[String[1]] $private_address = [], 306 | Array[String[1]] $private_domain = [], 307 | Integer[0] $unwanted_reply_threshold = 10000000, 308 | Array[String[1]] $do_not_query_address = [], 309 | Boolean $do_not_query_localhost = true, 310 | Boolean $prefetch = false, 311 | Boolean $prefetch_key = false, 312 | Boolean $deny_any = false, # version 1.8.2 313 | Boolean $rrset_roundrobin = false, 314 | Boolean $minimal_responses = false, 315 | Boolean $disable_dnssec_lame_check = false, # version 1.5.9 316 | Optional[Stdlib::Absolutepath] $trust_anchor_file = undef, 317 | Array[String[1]] $trust_anchor = [], 318 | Boolean $trust_anchor_signaling = true, # version 1.6.4 319 | Array[String[1]] $domain_insecure = [], 320 | Optional[Integer[1]] $val_sig_skew_min = undef, 321 | Optional[Integer[1]] $val_sig_skew_max = undef, 322 | Optional[Integer[1]] $val_bogus_ttl = undef, 323 | Boolean $val_clean_additional = true, 324 | Optional[Integer[0,2]] $val_log_level = undef, 325 | Boolean $val_permissive_mode = false, 326 | Boolean $ignore_cd_flag = false, 327 | Boolean $serve_expired = false, # version 1.6.0 328 | Optional[Integer[0]] $serve_expired_ttl = undef, # version 1.8.0 329 | Boolean $serve_expired_ttl_reset = false, # version 1.8.0 330 | Optional[Integer[0]] $serve_expired_reply_ttl = undef, # version 1.8.0 331 | Optional[Integer[0]] $serve_expired_client_timeout = undef, # version 1.8.0 332 | Array[Integer[1]] $val_nsec3_keysize_iterations = [], 333 | Optional[Integer[0]] $add_holddown = undef, 334 | Optional[Integer[0]] $del_holddown = undef, 335 | Optional[Integer[0]] $keep_missing = undef, 336 | Boolean $permit_small_holddown = false, # Version 1.5.5 337 | Optional[Unbound::Size] $key_cache_size = undef, 338 | Optional[Integer] $key_cache_slabs = undef, 339 | Optional[Unbound::Size] $neg_cache_size = undef, 340 | Boolean $unblock_lan_zones = false, 341 | Boolean $insecure_lan_zones = false, # version 1.5.8 342 | Unbound::Local_zone $local_zone = {}, 343 | Array[String[1]] $local_data = [], 344 | Array[String[1]] $local_data_ptr = [], 345 | Hash[String[1], Array[String[1]]] $local_zone_tag = {}, # version 1.5.10 346 | Hash[String[1], Unbound::Local_zone_override] $local_zone_override = {}, # version 1.5.10 347 | Optional[Integer[0]] $ratelimit = undef, 348 | Optional[Unbound::Size] $ratelimit_size = undef, 349 | Optional[Integer[0]] $ratelimit_slabs = undef, 350 | Optional[Integer[0]] $ratelimit_factor = undef, 351 | Hash[String[1], Integer[0]] $ratelimit_for_domain = {}, 352 | Hash[String[1], Integer[0]] $ratelimit_below_domain = {}, 353 | Optional[Integer[0]] $ip_ratelimit = undef, # version 1.6.1 354 | Optional[Unbound::Size] $ip_ratelimit_size = undef, # version 1.6.1 355 | Optional[Integer[0]] $ip_ratelimit_slabs = undef, # version 1.6.1 356 | Optional[Integer[0]] $ip_ratelimit_factor = undef, 357 | Optional[Integer[0,1000]] $fast_server_permil = undef, # version 1.8.2 358 | Optional[Integer[1]] $fast_server_num = undef, # version 1.8.2 359 | Hash $forward = {}, 360 | Hash $stub = {}, 361 | Hash $record = {}, 362 | Array $access = ['::1', '127.0.0.1'], 363 | String[1] $confdir = '/etc/unbound', 364 | Stdlib::Absolutepath $directory = $confdir, 365 | String[1] $conf_d = "${confdir}/conf.d", 366 | String[1] $config_file = "${confdir}/unbound.conf", 367 | Boolean $control_enable = false, 368 | String[1] $control_setup_path = '/usr/sbin/unbound-control-setup', 369 | String[1] $control_path = '/usr/sbin/unbound-control', 370 | String[1] $fetch_client = 'wget -O', 371 | String[1] $group = 'unbound', 372 | String[1] $keys_d = "${confdir}/keys.d", 373 | Stdlib::Absolutepath $trusted_keys_file = "${keys_d}/*.key", 374 | Array[Unbound::Module] $module_config = [], 375 | String[1] $owner = 'unbound', 376 | String[1] $username = $owner, 377 | # OpenBSD sets this to an empty string 378 | Variant[String,Array] $package_name = 'unbound', 379 | String[1] $package_ensure = 'installed', 380 | Boolean $purge_unbound_conf_d = false, 381 | String[1] $root_hints_url = 'https://www.internic.net/domain/named.root', 382 | Stdlib::Absolutepath $runtime_dir = $confdir, 383 | Variant[Stdlib::Absolutepath, Boolean[false]] $auto_trust_anchor_file = "${runtime_dir}/root.key", 384 | String[1] $anchor_fetch_command = "unbound-anchor -a ${auto_trust_anchor_file}", 385 | String[1] $service_name = 'unbound', 386 | Boolean $service_hasstatus = true, 387 | Enum['running', 'stopped'] $service_ensure = 'running', 388 | Boolean $service_enable = true, 389 | String[1] $validate_cmd = '/usr/sbin/unbound-checkconf %', 390 | String[1] $restart_cmd = "/bin/systemctl restart ${service_name}", 391 | Boolean $force_restart = false, 392 | Array[String[1]] $custom_server_conf = [], 393 | Boolean $skip_roothints_download = false, 394 | Optional[Stdlib::Absolutepath] $python_script = undef, 395 | String[1] $dns64_prefix = '64:ff9b::/96', 396 | Boolean $dns64_synthall = false, 397 | Array[String[1]] $send_client_subnet = [], 398 | Array[String[1]] $client_subnet_zone = [], 399 | Boolean $client_subnet_always_forward = false, 400 | Integer[0,128] $max_client_subnet_ipv6 = 56, 401 | Integer[0,32] $max_client_subnet_ipv4 = 24, 402 | Optional[Integer[0,128]] $min_client_subnet_ipv6 = undef, # version 1.8.2 403 | Optional[Integer[0,32]] $min_client_subnet_ipv4 = undef, # version 1.8.2 404 | Optional[Integer[0]] $max_ecs_tree_size_ipv4 = undef, # version 1.8.2 405 | Optional[Integer[0]] $max_ecs_tree_size_ipv6 = undef, # version 1.8.2 406 | Boolean $ipsecmod_enabled = true, 407 | Optional[Stdlib::Absolutepath] $ipsecmod_hook = undef, 408 | Boolean $ipsecmod_strict = false, 409 | Integer[1] $ipsecmod_max_ttl = 3600, 410 | Boolean $ipsecmod_ignore_bogus = false, 411 | Array[String[1]] $ipsecmod_whitelist = [], 412 | Optional[String[1]] $backend = undef, 413 | String[1] $secret_seed = 'default', 414 | String[1] $redis_server_host = '127.0.0.1', 415 | Integer[1,65536] $redis_server_port = 6379, 416 | Integer[1] $redis_timeout = 100, 417 | Stdlib::Absolutepath $unbound_conf_d = "${confdir}/unbound.conf.d", 418 | Unbound::Hints_file $hints_file = "${confdir}/root.hints", 419 | Enum['absent','present','unmanaged'] $update_root_hints = fact('systemd') ? { true => 'present', default => 'unmanaged' }, 420 | Optional[String[1]] $hints_file_content = undef, 421 | Hash[String[1], Unbound::Rpz] $rpzs = {}, 422 | Optional[String[1]] $unbound_version = $facts['unbound_version'], 423 | ) { 424 | $_base_dirs = [$confdir, $conf_d, $keys_d, $runtime_dir] 425 | $_piddir = if $pidfile { dirname($pidfile) } else { undef } 426 | if $_piddir and !($_piddir in ['/run', '/var/run']) { 427 | $dirs = unique($_base_dirs + [$_piddir]) 428 | $_owned_dirs = unique([$runtime_dir, $_piddir]) 429 | } else { 430 | $dirs = unique($_base_dirs) 431 | $_owned_dirs = [$runtime_dir] 432 | } 433 | 434 | if $manage_service { 435 | service { $service_name: 436 | ensure => $service_ensure, 437 | name => $service_name, 438 | enable => $service_enable, 439 | hasstatus => $service_hasstatus, 440 | } 441 | $service_reuse = Service[$service_name] 442 | } else { 443 | $service_reuse = undef 444 | } 445 | 446 | # OpenBSD passes an empty string 447 | unless $package_name.empty { 448 | if $manage_service { 449 | $before_package = [File[$dirs], Concat[$config_file], Service[$service_name]] 450 | } else { 451 | $before_package = [File[$dirs], Concat[$config_file]] 452 | } 453 | package { $package_name: 454 | ensure => $package_ensure, 455 | before => $before_package, 456 | } 457 | } 458 | $dirs.each |$dir| { 459 | $_owner = $dir in $_owned_dirs ? { 460 | true => $owner, 461 | default => undef, 462 | } 463 | file { $dir: 464 | ensure => directory, 465 | owner => $_owner, 466 | } 467 | } 468 | 469 | if $control_enable { 470 | file { "${confdir}/interfaces.txt": 471 | ensure => file, 472 | notify => Exec['restart unbound'], 473 | content => template('unbound/interfaces.txt.erb'), 474 | } 475 | exec { 'restart unbound': 476 | command => $restart_cmd, 477 | refreshonly => true, 478 | require => $service_reuse, 479 | } 480 | if $manage_service { 481 | $service_require = { 'require' => Class['unbound::remote'] } 482 | $service_params = $force_restart ? { 483 | true => $service_require, 484 | false => $service_require + { 'restart' => "${control_path} reload" }, 485 | } 486 | Service[$service_name] { 487 | * => $service_params, 488 | } 489 | } 490 | include unbound::remote 491 | 492 | unless $package_name.empty { 493 | Package[$package_name] -> Class['unbound::remote'] 494 | } 495 | } 496 | 497 | if $auto_trust_anchor_file { 498 | exec { 'download-anchor-file': 499 | command => $anchor_fetch_command, 500 | creates => $auto_trust_anchor_file, 501 | user => $owner, 502 | path => ['/usr/sbin','/usr/local/sbin'], 503 | returns => 1, 504 | before => Concat::Fragment['unbound-header'], 505 | require => File[$runtime_dir], 506 | } 507 | 508 | Exec['download-anchor-file'] -> Concat[$config_file] 509 | } 510 | 511 | # If hint_file is 'builtin', Unbound should use built-in hints instead of file 512 | if $hints_file != 'builtin' { 513 | # Remotely fetch root hints unless hint-files content is set or is explicitlly skipped 514 | unless $hints_file_content or $skip_roothints_download { 515 | exec { 'download-roothints': 516 | command => "${fetch_client} ${hints_file} ${root_hints_url}", 517 | creates => $hints_file, 518 | path => ['/usr/bin','/usr/local/bin'], 519 | before => [Concat::Fragment['unbound-header'], File[$hints_file]], 520 | require => File[$dirs], 521 | } 522 | unless $package_name.empty { 523 | Package[$package_name] -> Exec['download-roothints'] 524 | } 525 | } 526 | 527 | file { $hints_file: 528 | ensure => file, 529 | mode => '0444', 530 | content => $hints_file_content, 531 | } 532 | if $update_root_hints == 'present' { 533 | systemd::timer { 'roothints.timer': 534 | timer_content => file("${module_name}/roothints.timer"), 535 | service_content => epp("${module_name}/roothints.service.epp", { 'hints_file' => $hints_file, 'root_hints_url' => $root_hints_url, 'fetch_client' => $fetch_client }), 536 | active => true, 537 | enable => true, 538 | } 539 | } 540 | } 541 | if $update_root_hints == 'absent' { 542 | systemd::timer { 'roothints.timer': 543 | ensure => 'absent', 544 | } 545 | } 546 | 547 | # purge unmanaged files in configuration directory 548 | file { $unbound_conf_d: 549 | ensure => 'directory', 550 | owner => 'root', 551 | group => '0', 552 | purge => $purge_unbound_conf_d, 553 | recurse => $purge_unbound_conf_d, 554 | } 555 | 556 | concat { $config_file: 557 | validate_cmd => $validate_cmd, 558 | notify => $service_reuse, 559 | } 560 | 561 | concat::fragment { 'unbound-header': 562 | order => '00', 563 | target => $config_file, 564 | content => template('unbound/unbound.conf.erb'), 565 | } 566 | concat::fragment { 'unbound-modules': 567 | order => '09', 568 | target => $config_file, 569 | content => template('unbound/unbound.modules.conf.erb'), 570 | } 571 | 572 | $forward.each |$title, $config| { 573 | unbound::forward { $title: 574 | * => $config, 575 | } 576 | } 577 | 578 | $stub.each |$title, $config| { 579 | unbound::stub { $title: 580 | * => $config, 581 | } 582 | } 583 | 584 | $record.each |$title, $config| { 585 | unbound::record { $title: 586 | * => $config, 587 | } 588 | } 589 | } 590 | --------------------------------------------------------------------------------