180 lines
5.2 KiB
YAML
180 lines
5.2 KiB
YAML
---
|
|
# see: https://calomel.org/unbound_dns.html
|
|
# see: https://wiki.archlinux.org/title/Unbound
|
|
- name: Install unbound
|
|
ansible.builtin.package:
|
|
name: unbound
|
|
state: present
|
|
|
|
# Note: on archlinux this is already shipped within unbound
|
|
- name: Install unbound-anchor on debian/ubuntu
|
|
ansible.builtin.package:
|
|
name: unbound-anchor
|
|
state: present
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: Ensure unbound configuration is owned by unbound
|
|
ansible.builtin.shell: |
|
|
find "{{ unbound_config_base_path }}" -type d -exec chmod 755 {} \;
|
|
find "{{ unbound_config_base_path }}" -type f -exec chmod 644 {} \;
|
|
chown -R unbound:unbound "{{ unbound_config_base_path }}"
|
|
args:
|
|
executable: /bin/bash
|
|
|
|
- name: Ensure apparmor profile for unbound exists
|
|
ansible.builtin.copy:
|
|
dest: /etc/apparmor.d/usr.sbin.unbound
|
|
content: |
|
|
#include <tunables/global>
|
|
|
|
/usr/sbin/unbound {
|
|
#include <abstractions/base>
|
|
#include <abstractions/nameservice>
|
|
|
|
capability net_bind_service,
|
|
capability setgid,
|
|
capability setuid,
|
|
capability sys_chroot,
|
|
capability sys_resource,
|
|
|
|
/etc/unbound/** r,
|
|
/etc/unbound/root.key* rw,
|
|
/var/lib/unbound/** rwk,
|
|
/run/unbound.pid rw,
|
|
/usr/sbin/unbound mr,
|
|
|
|
# Allow reading system certificates
|
|
/etc/ssl/certs/** r,
|
|
/usr/share/ca-certificates/** r,
|
|
|
|
# Allow network access
|
|
network inet dgram,
|
|
network inet6 dgram,
|
|
network inet stream,
|
|
network inet6 stream,
|
|
}
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
when: ansible_facts.apparmor.status == "enabled"
|
|
notify:
|
|
- Reload AppArmor profile
|
|
|
|
- name: Check if root.hints exists
|
|
ansible.builtin.stat:
|
|
path: "{{ unbound_root_hints_path }}"
|
|
register: root_hints
|
|
|
|
- name: Update root.hints (if older than 6 months or missing)
|
|
when: >
|
|
(not root_hints.stat.exists) or
|
|
(ansible_facts['date_time']['epoch'] | int - root_hints.stat.mtime > 15552000)
|
|
|
|
block:
|
|
- name: Download latest root hints from internic
|
|
ansible.builtin.get_url:
|
|
url: https://www.internic.net/domain/named.root
|
|
dest: "{{ unbound_root_hints_path }}"
|
|
owner: unbound
|
|
group: unbound
|
|
mode: "0644"
|
|
- name: Check if unbound ad_servers configuration exists
|
|
ansible.builtin.stat:
|
|
path: "{{ unbound_ad_servers_config_path }}"
|
|
register: ad_servers
|
|
|
|
- name: Update the ad_servers list if older than 2 weeks or missing
|
|
when: >
|
|
(not ad_servers.stat.exists) or
|
|
(ansible_facts['date_time']['epoch'] | int - ad_servers.stat.mtime > 1209600)
|
|
|
|
block:
|
|
- name: Download stevenblack's hosts file
|
|
ansible.builtin.get_url:
|
|
url: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
|
dest: /tmp/hosts.txt
|
|
mode: "0644"
|
|
|
|
- name: Convert hosts file to unbound format
|
|
ansible.builtin.shell: |
|
|
set -o pipefail
|
|
grep '^0\.0\.0\.0' /tmp/hosts.txt | awk '{print "local-zone: \""$2"\" always_nxdomain"}' > "{{ unbound_ad_servers_config_path }}" &&
|
|
chown unbound:unbound "{{ unbound_ad_servers_config_path }}"
|
|
args:
|
|
executable: /bin/bash
|
|
|
|
- name: Clean up temporary file
|
|
ansible.builtin.file:
|
|
path: /tmp/hosts.txt
|
|
state: absent
|
|
- name: Initialize dnssec trust anchor if missing
|
|
ansible.builtin.command: unbound-anchor -a {{ unbound_anchor_root_key }}
|
|
args:
|
|
creates: "{{ unbound_anchor_root_key }}"
|
|
|
|
- name: Ensure root.key has correct ownership and permissions
|
|
ansible.builtin.file:
|
|
path: "{{ unbound_anchor_root_key }}"
|
|
owner: unbound
|
|
group: unbound
|
|
mode: "0640"
|
|
state: file
|
|
|
|
- name: Create /etc/default/unbound for DAEMON_OPTS
|
|
ansible.builtin.copy:
|
|
dest: /etc/default/unbound
|
|
content: |
|
|
# Unbound daemon options
|
|
DAEMON_OPTS=""
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
|
|
- name: Configure sysctl for unbound socket buffers
|
|
ansible.posix.sysctl:
|
|
name: net.core.rmem_max
|
|
value: "1048576"
|
|
state: present
|
|
sysctl_set: true
|
|
reload: true
|
|
|
|
- name: Install unbound config
|
|
ansible.builtin.template:
|
|
src: "{{ item.src }}"
|
|
dest: "{{ item.dest }}"
|
|
owner: unbound
|
|
group: unbound
|
|
loop:
|
|
- { src: unbound.conf.j2, dest: "{{ unbound_config_path }}" }
|
|
- { src: custom-lan.conf.j2, dest: "{{ unbound_custom_lan_config_path }}" }
|
|
- { src: custom-vpn.conf.j2, dest: "{{ unbound_custom_vpn_config_path }}" }
|
|
notify:
|
|
- Check Unbound config syntax
|
|
- Reload systemd and restart unbound
|
|
|
|
- name: Remove WireGuard dependency from unbound (using ip-freebind instead)
|
|
ansible.builtin.file:
|
|
path: /etc/systemd/system/unbound.service.d
|
|
state: absent
|
|
notify: Reload systemd and restart unbound
|
|
|
|
- name: Enables unbound service
|
|
ansible.builtin.service:
|
|
name: unbound
|
|
enabled: true
|
|
state: started
|
|
|
|
- name: Firewall ufw rules for unbound
|
|
community.general.ufw:
|
|
rule: allow
|
|
port: "{{ unbound_port }}"
|
|
proto: any
|
|
src: "{{ item.src }}"
|
|
comment: "{{ item.comment }}"
|
|
direction: in
|
|
loop: "{{ unbound_firewall_allowed_sources | default([]) }}"
|
|
retries: 5
|
|
delay: 2
|
|
register: ufw_result
|
|
until: ufw_result is succeeded
|