diff --git a/ansible/roles/unbound_resolver/.yamllint b/ansible/roles/unbound_resolver/.yamllint new file mode 100644 index 0000000..3a2255e --- /dev/null +++ b/ansible/roles/unbound_resolver/.yamllint @@ -0,0 +1,13 @@ +extends: default + +rules: + braces: + max-spaces-inside: 1 + level: error + brackets: + max-spaces-inside: 1 + level: error + line-length: disable + # NOTE(retr0h): Templates no longer fail this lint rule. + # Uncomment if running old Molecule templates. + # truthy: disable diff --git a/ansible/roles/unbound_resolver/README.md b/ansible/roles/unbound_resolver/README.md new file mode 100644 index 0000000..2248f11 --- /dev/null +++ b/ansible/roles/unbound_resolver/README.md @@ -0,0 +1,81 @@ +# Unbound + +This role install and configure an Unbound resolver. +It also install a prometheus exporter compiled from [letsencrypt/unbound_exporter](https://github.com/letsencrypt/unbound_exporter) + +## Targets + +- Debian + +## Role variables + +- ``unbound_interfaces``: list of interfaces Unbound has to listen on. If not specified, Unbound will listen on 0.0.0.0. +- ``unbound_authorized_cidrs``: list of authorized CIDRS to query the resolver. As Unbound rejects everything by default, if none is set, the resolver won't answer to anyone. +- ``unbound_threads``: number of threads Unbound runs on. (default: 1) +- ``unbound_cache_size``: size of Unbound cache, in Mb. (default: 100) +- ``unbound_zones``: dictionnary about zones that need to be forwarded to another DNS server. It contains info for every managed zone : + ``name``: name of the zone + ``forward_ip``: list of the servers to forward queries to + ``private``: boolean, has to be specified for dummies zones (ex: .priv). It disables DNSSEC validation for thoses zones. + +Zones that are not explicitely specified in forwards will be forwarded to root servers. + +## Prometheus exporter + +* For the exporter to work properly you need to run the following command on each resolver : +``` +unbound-control-setup +``` +* You also need to ensure that the "extended-statistics: yes" directive is in the conf (it is here). +* The exporter configuration can be change by modifying the systemd service template. + +## Unbound logging + +In order to enable query log, you need to do the following : +* Add the following directives to the config : +``` + logfile: "/var/log/unbound/unbound.log" + log-time-ascii: yes + log-queries: yes + log-replies: yes # will log informations about the reply, slows response time. +``` +* Add the following line in /etc/apparmor.d/usr.sbin.unbound (with the comma) : +``` + /var/log/unbound/unbound.log rw, +``` +* Run the following commands to create both directory and file for logging : +``` +mkdir /var/log/unbound +touch /var/log/unbound/unbound.log +chown -R unbound:unbound /var/log/unbound +apparmor_parser -r /etc/apparmor.d/usr.sbin.unbound +``` +* Restart unbound. + +## Example + +In this example, we specify to forward queries for domain aaa.com to xxx.xxx.xxx.xxx, bbb.com to yyy.yyy.yyy.yyy or xxx.xxx.xxx.xxx as a failover, and requests for a private zone to zzz.zzz.zzz.zzz : +```yml +unbound_interfaces: + - "aaa.aaa.aaa.aaa" + +unbound_authorized_cidrs: + - "aaa.aaa.aaa.0/24" + - "bbb.bbb.bbb.bbb/32" + +unbound_threads: 2 +unbound_cache_size: 1536 + +unbound_zones: + - name: "aaa.com" + forward_ip: + - xxx.xxx.xxx.xxx + - name: "bbb.com" + forward_ip: + - yyy.yyy.yyy.yyy + - xxx.xxx.xxx.xxx + - name: "mysuperprivatezone.priv" + forward_ip: + - zzz.zzz.zzz.zzz + private: true +``` diff --git a/ansible/roles/unbound_resolver/defaults/main.yml b/ansible/roles/unbound_resolver/defaults/main.yml new file mode 100644 index 0000000..b8d5dca --- /dev/null +++ b/ansible/roles/unbound_resolver/defaults/main.yml @@ -0,0 +1,6 @@ +--- +unbound_interfaces: + - "0.0.0.0" +unbound_threads: 1 +unbound_cache_size: 100 +unbound_loglevel: 1 diff --git a/ansible/roles/unbound_resolver/files/logrotate b/ansible/roles/unbound_resolver/files/logrotate new file mode 100644 index 0000000..53324bf --- /dev/null +++ b/ansible/roles/unbound_resolver/files/logrotate @@ -0,0 +1,10 @@ +/var/log/unbound/*.log { + weekly + missingok + rotate 52 + compress + notifempty + postrotate + /usr/sbin/unbound-control log_reopen + endscript +} diff --git a/ansible/roles/unbound_resolver/files/unbound_exporter b/ansible/roles/unbound_resolver/files/unbound_exporter new file mode 100755 index 0000000..029c476 Binary files /dev/null and b/ansible/roles/unbound_resolver/files/unbound_exporter differ diff --git a/ansible/roles/unbound_resolver/handlers/main.yml b/ansible/roles/unbound_resolver/handlers/main.yml new file mode 100644 index 0000000..3116a9d --- /dev/null +++ b/ansible/roles/unbound_resolver/handlers/main.yml @@ -0,0 +1,15 @@ +--- + +- name: Daemon reload + ansible.builtin.systemd_service: + daemon_reload: true + +- name: Restart unbound exporter + ansible.builtin.systemd_service: + name: unbound_exporter + state: restarted + +- name: Reload Unbound + ansible.builtin.systemd_service: + name: unbound + state: reloaded diff --git a/ansible/roles/unbound_resolver/tasks/main.yml b/ansible/roles/unbound_resolver/tasks/main.yml new file mode 100644 index 0000000..bca576d --- /dev/null +++ b/ansible/roles/unbound_resolver/tasks/main.yml @@ -0,0 +1,76 @@ +--- + +- name: Set specific variables for distributions + ansible.builtin.include_vars: "{{ item }}" + with_first_found: + - files: + - '{{ ansible_distribution }}-{{ ansible_distribution_version }}.yml' # CentOS-6.5 + - '{{ ansible_os_family }}-{{ ansible_distribution_version }}.yml' # RedHat-6.5 + - '{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml' # CentOS-6 + - '{{ ansible_os_family }}-{{ ansible_distribution_major_version }}.yml' # RedHat-6 + - '{{ ansible_distribution }}.yml' # CentOS + - '{{ ansible_os_family }}.yml' # RedHat + - 'default.yml' + +- name: Enhance socket buffer size in UDP + ansible.posix.sysctl: + name: "{{ item }}" + value: 4194304 + reload: true + with_items: + - "net.core.rmem_max" + - "net.core.wmem_max" + +- name: Install Unbound + ansible.builtin.apt: + name: "{{ unbound_package }}" + update_cache: true + state: present + when: ansible_os_family == "Debian" + +- name: Setup service configuration + ansible.builtin.template: + src: unbound.conf.j2 + dest: /etc/unbound/unbound.conf.d/custom.conf + owner: unbound + group: unbound + mode: "0755" + notify: Reload Unbound + +- name: Set permission on conf directory + ansible.builtin.file: + path: /etc/unbound + owner: unbound + group: unbound + recurse: true + +- name: Ensure service is enabled at boot and started + ansible.builtin.systemd_service: + name: "unbound" + enabled: true + state: started + +- name: Deploy unbound exporter + ansible.builtin.copy: + src: unbound_exporter + dest: /usr/local/bin/unbound_exporter + mode: "0755" + +- name: Deploy unbound exporter service + ansible.builtin.template: + src: unbound_exporter.service.j2 + dest: /etc/systemd/system/unbound_exporter.service + owner: root + group: root + mode: "0644" + notify: + - Daemon reload + - Restart unbound exporter + +- name: Deploy logrotate config file + ansible.builtin.copy: + src: logrotate + dest: /etc/logrotate.d/unbound + owner: root + group: root + mode: "0644" diff --git a/ansible/roles/unbound_resolver/templates/unbound.conf.j2 b/ansible/roles/unbound_resolver/templates/unbound.conf.j2 new file mode 100644 index 0000000..27018fe --- /dev/null +++ b/ansible/roles/unbound_resolver/templates/unbound.conf.j2 @@ -0,0 +1,58 @@ +## {{ ansible_managed }} + +server: + verbosity: {{unbound_loglevel }} + extended-statistics: yes + + do-udp: yes + do-tcp: yes + do-ip6: no + + num-threads: {{ unbound_threads }} + msg-cache-slabs: {{ unbound_threads }} + rrset-cache-slabs: {{ unbound_threads }} + infra-cache-slabs: {{ unbound_threads }} + key-cache-slabs: {{ unbound_threads }} + + rrset-cache-size: {{ unbound_cache_size }}m + key-cache-size: {{ ((unbound_cache_size/2) | int) }}m + msg-cache-size: {{ ((unbound_cache_size/2) | int) }}m + neg-cache-size: {{ ((unbound_cache_size/4) | int) }}m + + prefetch: yes + cache-min-ttl: 300 + cache-max-ttl: 86400 + + outgoing-range: 8192 + num-queries-per-thread: 4096 + + so-rcvbuf: 4m + so-sndbuf: 4m + so-reuseport: yes + rrset-roundrobin: yes + val-log-level:1 + + +{% for iface in unbound_interfaces %} + interface: {{ iface }} +{% endfor %} + +{% for cidr in unbound_authorized_cidrs %} + access-control: {{ cidr }} allow +{% endfor %} + +{% if unbound_zones is defined %} +{% for zone in unbound_zones %} +{% if zone.private is defined and zone.private %} + domain-insecure: "{{ zone.name }}" +{% endif %} +{% endfor %} + +{% for zone in unbound_zones %} + forward-zone: + name: "{{ zone.name }}" +{% for fwa in zone.forward_ip %} + forward-addr: {{ fwa }} +{% endfor -%} +{% endfor %} +{% endif %} diff --git a/ansible/roles/unbound_resolver/templates/unbound_exporter.service.j2 b/ansible/roles/unbound_resolver/templates/unbound_exporter.service.j2 new file mode 100644 index 0000000..e2dd96c --- /dev/null +++ b/ansible/roles/unbound_resolver/templates/unbound_exporter.service.j2 @@ -0,0 +1,13 @@ +[Unit] +Description=Unbound exporter for prometheus +Documentation=https://github.com/letsencrypt/unbound_exporter +Wants=network-online.target +After=network-online.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/unbound_exporter -unbound.host="unix:///run/unbound.ctl" +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/ansible/roles/unbound_resolver/vars/Debian.yml b/ansible/roles/unbound_resolver/vars/Debian.yml new file mode 100644 index 0000000..f851dcf --- /dev/null +++ b/ansible/roles/unbound_resolver/vars/Debian.yml @@ -0,0 +1,3 @@ +--- + +unbound_package: "unbound"