From f84f5e22ca8b78d0c83c0b44377b967cb40f031b Mon Sep 17 00:00:00 2001 From: Nebula Date: Sat, 27 Jan 2024 15:59:00 -0500 Subject: [PATCH] init: add ansible-dns --- .github/workflows/ci.yml | 32 ++++++++ .gitignore | 1 + .pre-commit-config.yaml | 10 +++ .vscode/extensions.json | 8 ++ .vscode/settings.json | 4 + .yamllint | 6 ++ LICENSE | 21 +++++ Makefile | 20 +++++ README.md | 41 ++++++++++ ansible.cfg | 10 +++ group_vars/all.yml | 95 +++++++++++++++++++++++ inventories/sample/hosts | 12 +++ playbook.yml | 35 +++++++++ requirements-dev.txt | 1 + requirements.yml | 7 ++ roles/blocky/defaults/main.yml | 46 +++++++++++ roles/blocky/handlers/main.yml | 5 ++ roles/blocky/tasks/main.yml | 99 ++++++++++++++++++++++++ roles/blocky/templates/blocky.service.j2 | 18 +++++ roles/blocky/templates/config.yml.j2 | 34 ++++++++ 20 files changed, 505 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 .yamllint create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 ansible.cfg create mode 100644 group_vars/all.yml create mode 100644 inventories/sample/hosts create mode 100644 playbook.yml create mode 100644 requirements-dev.txt create mode 100644 requirements.yml create mode 100644 roles/blocky/defaults/main.yml create mode 100644 roles/blocky/handlers/main.yml create mode 100644 roles/blocky/tasks/main.yml create mode 100644 roles/blocky/templates/blocky.service.j2 create mode 100644 roles/blocky/templates/config.yml.j2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0412efd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +--- +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + + workflow_dispatch: + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install test dependencies + run: pip3 install ansible yamllint==1.30.0 ansible-lint==6.14.6 + + - name: Ansible-lint + run: ansible-lint playbook.yml + + - name: Lint yaml files + run: yamllint . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8b42eb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.retry diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a3f6355 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +--- +repos: + # - repo: https://github.com/ansible-community/ansible-lint.git + # rev: v6.14.6 + # hooks: + # - id: ansible-lint + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.30.0 + hooks: + - id: yamllint diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..993d74a --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "redhat.ansible", + "redhat.vscode-yaml", + "tamasfe.even-better-toml", + "ybaumes.highlight-trailing-white-spaces" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..402f4a8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "yaml.schemaStore.enable": false, + "ansible.python.interpreterPath": "/bin/python" +} diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..f14ed57 --- /dev/null +++ b/.yamllint @@ -0,0 +1,6 @@ +--- +extends: default +rules: + line-length: + max: 180 + level: warning diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..54fe3c5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Nebula + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1ad6f41 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +.PHONY: help run install checks + +help: + @echo Ansible-DNS Development Help + @echo ------------------------------------------------------------------------- + @echo run : Run the playbook + @echo install : Install requirements + @echo checks : Run syntax and pre-commit checks + @echo ------------------------------------------------------------------------- + +run: + ansible-playbook -i inventories/sample/hosts playbook.yml -b + +install: + ansible-galaxy install -r requirements.yml + +checks: + ansible-playbook playbook.yml --syntax-check + pre-commit run --all-files + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4393965 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Ansible-DNS + +Deploy a fast recursive DNS server with Ansible. + +## Requirements + +- Linux machine with a modern Python version +- Another Linux machine with a fresh Debian/Ubuntu installation + +## Installation + +1. Install Ansible and jmespath on the host: `pip install ansible jmespath` +2. Clone this repo: `git clone https://github.com/itsnebulalol/ansible-dns && cd ansible-dns` +3. Prepare your host file: `cp -rfp inventories/sample inventories/my-inventory` +4. Edit your hosts file (`inventories/my-inventory/hosts`) +5. Modify `group_vars/all.yml` to your needs +6. Install dependencies: `ansible-galaxy install -r requirements.yml` +7. Run the playbook: `ansible-playbook -i inventories/my-inventory/hosts playbook.yml -b -K` + +## Uninstallation + +Coming soon + +## License + +This repository is licensed under the MIT License. A copy is included when cloning the repository, and can also be found [here](https://github.com/itsnebulalol/ansible-dns/blob/main/LICENSE). + +## I need help + +Please open an [issue](https://github.com/itsnebulalol/ansible-dns/issues) if you think there is a bug. + +## Support + +If you would like to support my work, you can [sponsor me with GitHub Sponsors](https://github.com/sponsors/itsnebulalol)! + +## Credits + +- [ansible-nas](https://github.com/davestephens/ansible-nas) +- [ansible-role-blocky-dns](https://github.com/ngine-io/ansible-role-blocky-dns) +- [ansible-role-unbound](https://github.com/publicarray/ansible-role-unbound) ([fork](https://github.com/itsnebulalol/ansible-role-unbound)) +- [unbound-docker](https://github.com/MatthewVance/unbound-docker) diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..923ca05 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,10 @@ +[defaults] +retry_files_enabled = False + +# fact caching +gathering = smart +fact_caching = jsonfile +fact_caching_connection = /tmp/facts_cache + +# two hours timeout +fact_caching_timeout = 7200 diff --git a/group_vars/all.yml b/group_vars/all.yml new file mode 100644 index 0000000..1cea5d1 --- /dev/null +++ b/group_vars/all.yml @@ -0,0 +1,95 @@ +sysctl_config: + net.ipv4.ip_forward: 1 + net.ipv4.conf.all.forwarding: 1 + net.ipv6.conf.all.forwarding: 1 + net.core.rmem_max: 4194304 + net.core.wmem_max: 4194304 + +unbound_compile: true +unbound_compile_version: 1.19.0 +unbound_compile_sha256: a97532468854c61c2de48ca4170de854fd3bc95c8043bb0cfb0fe26605966624 + +unbound_optimise: true +unbound_optimise_memory: 80 +unbound: + server: + cache_max_ttl: 86400 + cache_min_ttl: 300 + root_hints: "root.hints" + do_ip4: yes + do_ip6: yes + do_tcp: yes + do_udp: yes + edns_buffer_size: 1232 + interface: 0.0.0.0@5053 + port: 5053 + prefer_ip6: no + rrset_roundrobin: yes + username: "unbound" + directory: "/usr/local/etc/unbound" + chroot: "/usr/local/etc/unbound" + + log_local_actions: no + log_queries: no + log_replies: no + log_servfail: no + logfile: unbound.log + verbosity: 1 + + infra_cache_slabs: 4 + incoming_num_tcp: 10 + key_cache_slabs: 4 + msg_cache_size: 142768128 + msg_cache_slabs: 4 + num_queries_per_thread: 4096 + num_threads: 4 + outgoing_range: 8192 + rrset_cache_size: 285536256 + rrset_cache_slabs: 4 + minimal_responses: yes + prefetch: yes + prefetch_key: yes + serve_expired: yes + so-rcvbuf: 4m + so_sndbuf: 4m + so_reuseport: yes + + aggressive_nsec: yes + delay_close: 10000 + do_daemonize: no + do_not_query_localhost: no + neg_cache_size: 4M + qname_minimisation: yes + + auto_trust_anchor_file: root.key + deny_any: yes + harden_algo_downgrade: yes + harden_below_nxdomain: yes + harden_dnssec_stripped: yes + harden_glue: yes + harden_large_queries: yes + harden_referral_path: no + harden_short_bufsize: yes + hide_http_user_agent: no + hide_identity: yes + hide_version: yes + http_user_agent: "DNS" + identity: "DNS" + + ratelimit: 1000 + tls_cert_bundle: /etc/ssl/certs/ca-certificates.crt + unwanted_reply_threshold: 10000 + use_caps_for_id: yes + val_clean_additional: yes + + private_address: + - 127.0.0.0/8 + - 10.0.0.0/8 # private networks (RFC 1918) + - 172.16.0.0/12 + - 192.168.0.0/16 + - 169.254.0.0/16 # link_local network (RFC 3927) + - fd00::/8 + - fe80::/10 + + remote_control: + control_enable: false diff --git a/inventories/sample/hosts b/inventories/sample/hosts new file mode 100644 index 0000000..0205f12 --- /dev/null +++ b/inventories/sample/hosts @@ -0,0 +1,12 @@ +# To connect using a non-root user (and elevate to root with sudo later), +# replace `ansible_ssh_user=root` with something like this: `ansible_ssh_user=username become=true become_user=root` +# +# If you're running this Ansible playbook on the same server as the one you're installing to, +# consider adding an additional `ansible_connection=local` argument to the host line below. +# +# Ansible may fail to discover which Python interpreter to use on the host for some distros (like Ubuntu 20.04). +# You may sometimes need to explicitly add the argument `ansible_python_interpreter=/usr/bin/python3` +# to the host line below. + +[all] +server1 ansible_host=192.168.1.10 diff --git a/playbook.yml b/playbook.yml new file mode 100644 index 0000000..d4bc877 --- /dev/null +++ b/playbook.yml @@ -0,0 +1,35 @@ +--- +- name: Ansible-DNS + hosts: all + become: true + + pre_tasks: + - name: Set sysctl config + ansible.posix.sysctl: + name: '{{ item.key }}' + value: '{{ item.value }}' + sysctl_set: true + state: present + reload: true + ignoreerrors: true + with_dict: '{{ sysctl_config }}' + + # - name: Check if port 53 is in use + # ansible.builtin.wait_for: + # host: localhost + # port: 53 + # timeout: 1 + # register: port_53_status + # ignore_errors: true + + # - name: Disable systemd-resolved + # ansible.builtin.systemd: + # name: systemd-resolved + # state: stopped + # enabled: false + # masked: true + # when: port_53_status.elapsed < 1 + + roles: + - unbound + - blocky diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..416634f --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +pre-commit diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..0a54f88 --- /dev/null +++ b/requirements.yml @@ -0,0 +1,7 @@ +collections: + - community.general + +roles: + - name: unbound + src: https://github.com/itsnebulalol/ansible-role-unbound + version: master diff --git a/roles/blocky/defaults/main.yml b/roles/blocky/defaults/main.yml new file mode 100644 index 0000000..e655d57 --- /dev/null +++ b/roles/blocky/defaults/main.yml @@ -0,0 +1,46 @@ +--- +blocky_version: v0.23 +blocky_base: "blocky-{{ blocky_version }}" +blocky_arch: "{{ ansible_machine }}" +blocky_system: "{{ ansible_system }}" +blocky_base_url: https://github.com/0xERR0R/blocky/releases/download +blocky_download_url: "{{ blocky_base_url }}/{{ blocky_version }}/blocky_{{ blocky_version }}_{{ blocky_system }}_{{ blocky_arch }}.tar.gz" +blocky_checksum_url: "{{ blocky_base_url }}/{{ blocky_version }}/blocky_checksums.txt" +blocky_download_path: /tmp +blocky_install_path: /opt/blocky + +blocky_blocking_ttl: 1h + +blocky_blocking_client_groups_block: + default: + - ads + - malware + +blocky_blocking_blacklists: + ads: + - https://github.com/T145/black-mirror/releases/download/latest/BLOCK_DOMAIN.txt + - https://big.oisd.nl + - https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.txt + - https://gist.github.com/itsnebulalol/21205b44c081be2b2417829d4396408b/raw/blocklist.txt + malware: + - https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/tif.txt + +blocky_blocking_whitelists: + ads: + - https://github.com/T145/black-mirror/releases/download/latest/ALLOW_DOMAIN.txt + - https://raw.githubusercontent.com/hagezi/dns-blocklists/main/whitelist.txt + - https://raw.githubusercontent.com/hagezi/dns-blocklists/main/whitelist-referral.txt + - https://gist.github.com/itsnebulalol/21205b44c081be2b2417829d4396408b/raw/allowlist.txt + +blocky_ports_dns: 53 +blocky_ports_http: 4000 + +blocky_log_level: info + +blocky_upstreams: + default: + - 127.0.0.1:5053 + +blocky_custom_dns: {} + +blocky_custom_domain: {} diff --git a/roles/blocky/handlers/main.yml b/roles/blocky/handlers/main.yml new file mode 100644 index 0000000..b5ebc5a --- /dev/null +++ b/roles/blocky/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Restart blocky + ansible.builtin.systemd: + name: blocky + state: restarted diff --git a/roles/blocky/tasks/main.yml b/roles/blocky/tasks/main.yml new file mode 100644 index 0000000..e80c0df --- /dev/null +++ b/roles/blocky/tasks/main.yml @@ -0,0 +1,99 @@ +--- +- name: Create group + ansible.builtin.group: + name: blocky + +- name: Create user + ansible.builtin.user: + name: blocky + group: blocky + create_home: false + system: true + +- name: Ensure install and conf dirs exist + ansible.builtin.file: + path: "{{ item }}" + mode: "0755" + group: blocky + owner: blocky + state: directory + with_items: + - "{{ blocky_install_path }}/{{ blocky_base }}" + +- name: Check if blocky has been installed already + ansible.builtin.stat: + path: "{{ blocky_install_path }}/{{ blocky_base }}/blocky" + register: install_status + +- name: Download blocky archive + ansible.builtin.get_url: + url: "{{ blocky_download_url }}" + dest: "{{ blocky_download_path }}/{{ blocky_base }}.tgz" + group: root + owner: root + mode: "0644" + checksum: "sha256:{{ blocky_checksum_url }}" + force: false + when: not install_status.stat.exists + register: download_status + +- name: Expand blocky archive + ansible.builtin.unarchive: + src: "{{ blocky_download_path }}/{{ blocky_base }}.tgz" + dest: "{{ blocky_install_path }}/{{ blocky_base }}/" + group: root + owner: root + mode: "0755" + copy: false + when: download_status.changed or not install_status.stat.exists + +- name: Ensure owner and mode of blocky binary + ansible.builtin.file: + path: "{{ blocky_install_path }}/{{ blocky_base }}/blocky" + group: blocky + owner: blocky + mode: "0755" + notify: Restart blocky + +- name: Create link to blocky + ansible.builtin.file: + src: "{{ blocky_install_path }}/{{ blocky_base }}/blocky" + dest: "{{ blocky_install_path }}/blocky" + group: blocky + owner: blocky + mode: "0755" + state: link + notify: Restart blocky + +- name: Configure blocky + ansible.builtin.template: + src: config.yml.j2 + dest: "{{ blocky_install_path }}/config.yaml" + owner: root + group: root + mode: "0644" + notify: Restart blocky + +- name: Configure blocky service + ansible.builtin.template: + src: blocky.service.j2 + dest: /etc/systemd/system/blocky.service + owner: root + group: root + mode: "0644" + notify: Restart blocky + +- name: Ensure blocky service is enabled and running + ansible.builtin.systemd: + name: blocky + state: started + daemon_reload: true + enabled: true + +- name: Flush handlers + ansible.builtin.meta: flush_handlers + +- name: Ensure blocky is ready + ansible.builtin.wait_for: + port: "{{ blocky_ports_dns }}" + delay: 2 diff --git a/roles/blocky/templates/blocky.service.j2 b/roles/blocky/templates/blocky.service.j2 new file mode 100644 index 0000000..d99beb0 --- /dev/null +++ b/roles/blocky/templates/blocky.service.j2 @@ -0,0 +1,18 @@ +# {{ ansible_managed }} +[Unit] +Description=Blocky DNS +After=network.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +User=blocky +Group=blocky +Restart=on-failure +RestartSec=10 +WorkingDirectory={{ blocky_install_path }} +ExecStart={{ blocky_install_path }}/blocky --config {{ blocky_install_path }}/config.yaml +AmbientCapabilities=CAP_NET_BIND_SERVICE + +[Install] +WantedBy=multi-user.target diff --git a/roles/blocky/templates/config.yml.j2 b/roles/blocky/templates/config.yml.j2 new file mode 100644 index 0000000..7003f8d --- /dev/null +++ b/roles/blocky/templates/config.yml.j2 @@ -0,0 +1,34 @@ +# {{ ansible_managed }} +upstream: + {{ blocky_upstreams | to_nice_yaml(indent=2) | trim | indent(2) }} + +blocking: + blockTTL: {{ blocky_blocking_ttl | to_nice_yaml(indent=2) | trim | indent(2) }} + blackLists: + {{ blocky_blocking_blacklists | to_nice_yaml(indent=2) | trim | indent(4) }} + clientGroupsBlock: + {{ blocky_blocking_client_groups_block | to_nice_yaml(indent=2) | trim | indent(4) }} + whiteLists: + {{ blocky_blocking_whitelists | to_nice_yaml(indent=2) | trim | indent(4) }} + +ports: + dns: {{ blocky_ports_dns | to_nice_yaml(indent=2) | trim | indent(2) }} + http: {{ blocky_ports_http | to_nice_yaml(indent=2) | trim | indent(2) }} + +logLevel: {{ blocky_log_level | to_yaml }} + +customDNS: + customTTL: 60m + filterUnmappedTypes: true + rewrite: + mapping: + {{ blocky_custom_dns | to_nice_yaml(indent=2) | trim | indent(4) }} + +conditional: + fallbackUpstream: false + mapping: + {{ blocky_custom_domain | to_nice_yaml(indent=2) | trim | indent(4) }} + +prometheus: + enable: true + path: /metrics