├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .yamllint ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── defaults └── main.yml ├── handlers └── main.yml ├── meta └── main.yml ├── molecule ├── default │ ├── converge.yml │ ├── group_vars │ │ └── all.yml │ ├── host_vars │ │ ├── ns1.yml │ │ ├── ns2.yml │ │ └── ns3.yml │ ├── inventory.ini │ ├── molecule.yml │ └── verify.yml └── shared_inventory │ ├── converge.yml │ ├── group_vars │ └── all.yml │ ├── molecule.yml │ └── verify.yml ├── tasks ├── main.yml └── zones.yml ├── templates ├── auth_transfer.j2 ├── bind_zone.j2 ├── etc_named.conf.j2 ├── reverse_zone.j2 └── reverse_zone_ipv6.j2 └── vars ├── Archlinux.yml ├── Debian.yml ├── FreeBSD.yml └── RedHat.yml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 'on': 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | 11 | test: 12 | name: Molecule 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | distro: 17 | - centos7 18 | - centos8 19 | - debian8 20 | - debian9 21 | - debian10 22 | - ubuntu1604 23 | - ubuntu1804 24 | - ubuntu2004 25 | 26 | steps: 27 | - name: Check out the codebase. 28 | uses: actions/checkout@v2 29 | 30 | - name: Set up Python 3 31 | uses: actions/setup-python@v2 32 | with: 33 | python-version: '3.x' 34 | 35 | - name: Install test dependencies. 36 | run: pip3 install ansible molecule molecule-docker yamllint ansible-lint docker netaddr dnspython 37 | 38 | - name: Run Molecule Primary/Secondary/Forwarder tests 39 | run: molecule test 40 | env: 41 | PY_COLORS: '1' 42 | ANSIBLE_FORCE_COLOR: '1' 43 | MOLECULE_DISTRO: ${{ matrix.distro }} 44 | 45 | - name: Run Molecule Shared Inventory tests 46 | run: molecule test --scenario-name shared_inventory 47 | env: 48 | PY_COLORS: '1' 49 | ANSIBLE_FORCE_COLOR: '1' 50 | MOLECULE_DISTRO: ${{ matrix.distro }} 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore 2 | 3 | # Hidden Vagrant-directory 4 | .vagrant 5 | 6 | # Backup files (e.g. Vim, Gedit, etc.) 7 | *~ 8 | 9 | # Vagrant base boxes (you never know when someone puts one in the repository) 10 | *.box 11 | 12 | # Python artefacts 13 | .ropeproject 14 | *.pyc 15 | 16 | # Ignore test directory 17 | tests/ 18 | vagrant-tests/ 19 | docker-tests/ 20 | 21 | # Ignore editor directories 22 | /.idea 23 | /.vscode 24 | 25 | # Ignore Ansible fact cache 26 | .cache/ 27 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | rules: 4 | braces: 5 | min-spaces-inside: 0 6 | max-spaces-inside: 0 7 | min-spaces-inside-empty: -1 8 | max-spaces-inside-empty: -1 9 | brackets: 10 | min-spaces-inside: 0 11 | max-spaces-inside: 0 12 | min-spaces-inside-empty: -1 13 | max-spaces-inside-empty: -1 14 | colons: 15 | max-spaces-before: 0 16 | max-spaces-after: 1 17 | commas: 18 | max-spaces-before: 0 19 | min-spaces-after: 1 20 | max-spaces-after: 1 21 | comments: 22 | level: warning 23 | require-starting-space: true 24 | min-spaces-from-content: 2 25 | comments-indentation: 26 | level: warning 27 | document-end: disable 28 | document-start: 29 | level: warning 30 | present: true 31 | empty-lines: 32 | max: 2 33 | max-start: 0 34 | max-end: 0 35 | empty-values: 36 | forbid-in-block-mappings: false 37 | forbid-in-flow-mappings: false 38 | hyphens: 39 | max-spaces-after: 1 40 | indentation: 41 | spaces: consistent 42 | indent-sequences: true 43 | check-multi-line-strings: false 44 | key-duplicates: enable 45 | key-ordering: disable 46 | line-length: 47 | max: 1000 48 | level: warning 49 | allow-non-breakable-words: true 50 | allow-non-breakable-inline-mappings: false 51 | new-line-at-end-of-file: enable 52 | new-lines: 53 | type: unix 54 | trailing-spaces: enable 55 | truthy: 56 | allowed-values: ['true', 'false', 'yes', 'no'] 57 | level: warning 58 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | This file contains al notable changes to the bind Ansible role. 4 | 5 | This file adheres to the guidelines of [http://keepachangelog.com/](http://keepachangelog.com/). Versioning follows [Semantic Versioning](http://semver.org/). "GH-X" refers to the X'th issue/pull request on the Github project. 6 | 7 | ## 5.1.0 - 2020-09-17 8 | 9 | This release is long overdue. Apologies to all of you who have been waiting for this! 10 | 11 | ### Added 12 | 13 | - (GH-132) Add support for forward type zones and zone type auto-detection (credit: [Gregory Shulov](https://github.com/GR360RY)) 14 | - (GH-134) Add support to listen on custom port numbers (credit: [flora-five](https://github.com/flora-five)) 15 | - (GH-151) Add switch to choose Python version (credit: [itbane](https://github.com/itbane)) 16 | - (GH-156) Add DNS Certification Authority Authorization (CAA) (credit: [roumano](https://github.com/roumano)) 17 | - (GH-165) Add role variable `bind_allow_transfer` to populate allow-transfer setting instead of adding all defined ACLs. **That means that from now on, you need to specify your ACLs explicitly in allow-transfer if you expect this behavior!** 18 | 19 | ### Modified 20 | 21 | - (GH-131) Give nodes a consistent IP in Molecule tests (credit: [Gregory Shulov](https://github.com/GR360RY)) 22 | - (GH-140) Replace `py36-netaddr` with `py37-netaddr` on FreeBSD (credit: [Shawn Wilsher](https://github.com/sdwilsh)) 23 | - (GH-150) Fix config template for BIND 9.16 re: `dnssec-enable` (credit: [itbane](https://github.com/itbane)) 24 | - (GH-152) Configure TSIG keys for notify/update queries (credit: [itbane](https://github.com/itbane)) 25 | - (GH-155) Fix Jinja block indent in querylog (credit: [Miroslav Hudec](https://github.com/mihudec)) 26 | - (GH-160) Corrected regex, so that IPv6 reverse DNS entries get created correctly (credit: [Zephyr82](https://github.com/Zephyr82)) 27 | 28 | ## 5.0.0 - 2020-10-07 29 | 30 | Quite a bit of breaking changes in this release, so update your playbooks! 31 | 32 | ### Added 33 | 34 | - (GH-122) Support for DNS64 (credit: [Paulius Mazeika](https://github.com/pauliusm)) 35 | - The test playbook now enables the statistics-channels. Enter in a webbrowser to view the server statistics. 36 | - Ubuntu 16.04 is now included in CI tests 37 | 38 | ### Breaking changes 39 | 40 | - The terms `master` and `slave` were replaced by `primary` and `secondary`, respectively, in all playbooks, templates and documentation. This reflects changes in recent versions of BIND. Remark that the actual configuration files still use the "old" names, because most supported distros still have older versions of BIND in their software package repositories. 41 | - `bind_zone_domains` is renamed to `bind_zones` 42 | - `master_server_ip` is renamed to `primaries` and is now a list instead of a string. This makes it possible to specify multiple primary servers. (Inspired by GH-124 by @pavel-z1) 43 | - It is now possible to specify the type of each zone individually. If `bind_zones.hosts` is defined, it becomes a primary zone, if not a secondary. (Inspired by GH-125 by @pavel-z1) 44 | 45 | See the documentation in the [README](README.md) and the [test playbook](molecule/default/converge.yml) for updated examples. 46 | 47 | ### Other changes 48 | 49 | - (GH-130) Primary and secondary server configuration is now unified (a single template instead of separate). CI tests are now executed on Github Actions. Acceptance tests are now performed using a playbook instead of BATS. (credit: [Gregory Shulov](https://github.com/GR360RY)) 50 | - Code cleanup (linter warnings, deprecated comments, etc.) 51 | 52 | ## 4.2.0 - 2020-05-23 53 | 54 | An update that's been long overdue. Several PRs with new features were merged! 55 | 56 | A special thanks to @blofeldthefish for his willingness to help out with maintaining this role and to @RobinsOphalvens for contributing the new testing harness based on Molecule. Thanks to them, further development of this role got out of the deadlock it's been in since the previous version. 57 | 58 | ## Added 59 | 60 | - New supported platforms 61 | - CentOS 8 (GH-107, credit: [Paulius Mazeika](https://github.com/pauliusm)) 62 | - Debian 10 (no changes were needed) 63 | - FreeBSD (GH-100, credit: [psa](https://github.com/psa)) 64 | - Ubuntu 20.04 LTS (no changes were needed) 65 | - (GH-69) Allow TTLs for individual records (credit: [Stuart Knight](https://github.com/blofeldthefish)) 66 | - (GH-79) Added support for the SSHFP record type (credit: [Romuald](https://github.com/rds13)) 67 | - (GH-81) Added support for the DNAME record type (credit: [B. Verschueren](https://github.com/bverschueren)) 68 | - (GH-82) Added support for the NAPTR record type (credit: [Aido](https://github.com/aido)) 69 | - (GH-83) Added support for the [`$GENERATE` directive](http://www.zytrax.com/books/dns/ch8/generate.html) (credit: [Rayford Johnson](https://github.com/rayfordj)) 70 | - (GH-85) New role variable `bind_other_logs` (credit: [Paulo E. Castro](https://github.com/pecastro)) 71 | - (GH-87) New role variable `bind_dns_keys`, a list of binding keys (credit: [Jérôme Avond](https://github.com/jadjay)) 72 | - (GH-88) New role variable `bind_statistics_channels` (credit: [Stuart Knight](https://github.com/blofeldthefish)) 73 | - (GH-105, GH-113) New role variable `bind_query_log`, with more flexibility w.r.t. query logging (credit: [Romuald](https://github.com/rds13) and [Jascha Sticher](https://github.com/itbane)) 74 | - New keys in `bind_zone_domains`: `create_forward_zones` and `create_reverse_zones`. When present and set to false, they will prevent the creation of the forward or reverse zones, respectively. This results in a reverse only or forward only name server for that zone. 75 | 76 | ## Changed 77 | 78 | - Molecule is now used as testing harness (credit: [Robin Ophalvens](https://github.com/RobinOphalvens)). The previous system was written before any standardised testing tools were available. It became too cumbersome to maintain, which had serious impact on the further development of this role. 79 | - (GH-75) Refactored hash gathering to determine if zone files need to be regenerated (credit: [Stuart Knight](https://github.com/blofeldthefish)) 80 | - (GH-89) Add missing `allow-recursion` parameter for bind slaves, allowing them to handle recursion correctly (credit: [Lennart Weller](https://github.com/lhw)) 81 | - (GH-91) Ensure the directory for cached slave zones is created (credit: [Otto Sabart](https://github.com/seberm)) 82 | - (GH-99) Use `bind_group` variable instead of hard-coded value (credit: [Boris Momčilović](https://github.com/kornrunner)) 83 | - (GH-114,115) Fix error with scenario in conjunction with a dhcp shared secret key to provide dynamic dns updates. (credit: [Fabio Rocha](https://github.com/frock81)) 84 | 85 | ## Removed 86 | 87 | - (GH-106) Removed DNSSEC Lookaside Validation, this service has been shut down 88 | 89 | ## 4.1.0 - 2018-10-05 90 | 91 | ## Added 92 | 93 | - (GH-53) Add variable `bind_zone_dir` and `bind_zone_file_mode` for setting the master zone file path and mode, and `bind_extra_include_files` for including arbitrary configuration files into named.conf. (credit: [Brad Durrow](https://github.com/bdurrow)) 94 | - (GH-64) Add variable `bind_query_log` to enable query logging (credit: [Angel Barrera](https://github.com/angelbarrera92)) 95 | 96 | ## Changed 97 | 98 | - (GH-55) Fix issue with non-existing file when grepping domain (credit: [Tom Meinlschmidt](https://github.com/tmeinlschmidt)) 99 | - (GH-57) Fix issue with forwarding in subdomain delegations (credit: [Stuart Knight](https://github.com/blofeldthefish)) 100 | - (GH-66) Fix issue that causes playbook to fail when running in `--check` mode (credit: [Jörg Eichhorn](https://github.com/jeichhorn)) 101 | - (GH-67) Improved documentation with minimal slave configuration (credit: [Christopher Hicks](https://github.com/chicks-net)) 102 | - Add Ubuntu 18.04, Debian 8-9 and Arch Linux to list of supported distros. 103 | 104 | ## 4.0.1 - 2018-05-21 105 | 106 | ### Changed 107 | 108 | - (GH-52) Move all zone specific configuration options to `bind_zones` (credit: [Stuart Knight](https://github.com/blofeldthefish)) 109 | 110 | ## 4.0.0 - 2018-05-19 111 | 112 | ### Added 113 | 114 | - (GH-50) Add support for multiple zones (credit: [Stuart Knight](https://github.com/blofeldthefish)). **This is a breaking change,** as it changes the syntax for specifying zones. 115 | - Allow out-of-zone name server records 116 | 117 | ## 3.9.1 - 2018-04-22 118 | 119 | ## Changed 120 | 121 | - Allow multi-line `ansible_managed` comment (credit: [Fazle Arefin](https://github.com/fazlearefin)) 122 | - Fix the atrocious implementation of (GH-35) 123 | - Updated documentation for specifying hosts with multiple IP addresses 124 | - Create serial as UTC UNIX time (credit: [David J. Haines](https://github.com/dhaines)) 125 | - Fix bugs, linter and deprecation warnings 126 | 127 | ## 3.9.0 - 2017-11-21 128 | 129 | ### Added 130 | 131 | - (GH-35) Role variable `bind_check_names`, which adds support for check-names (e.g. `check-names master ignore;`) 132 | - (GH-36) Role variable `bind_allow_recursion`, which adds support for allow-recursion (credit: [Loic Dachary](https://github.com/dachary)) 133 | - (GH-39) Role variable `bind_zone_delegate`, which adds support for zone delegation / NS records (credit: [Loic Dachary](https://github.com/dachary)) 134 | - (GH-40) Role variables `bind_dnssec_enable` and `bind_dnssec_validation`, which makes DNSSEC validation configurable (credit: [Guillaume Darmont](https://github.com/gdarmont)). 135 | 136 | ### Changed 137 | 138 | - (GH-38) Only append domain to MX if it does not end with a dot (credit: [Loic Dachary](https://github.com/dachary)) 139 | 140 | ## 3.8.0 - 2017-07-12 141 | 142 | This release adds support for multiple TXT entries and fixes some bugs. 143 | 144 | ### Added 145 | 146 | - (GH-31) Support for multiple TXT entries for the same name (credit: [Rafael Bodill](https://github.com/rafi)) 147 | 148 | ### Changed 149 | 150 | - (GH-31) Fixed ipv6 reverse zone hash calculation for complete idempotency (credit: [Stuart Knight](https://github.com/blofeldthefish)) 151 | - (GH-32, GH-33) Fix for bug where CNAMEs and Multi-IP entries weren't working (credit: [Greg Cockburn](https://github.com/gergnz)) 152 | 153 | ## 3.7.1 - 2017-07-03 154 | 155 | ### Changed 156 | 157 | - (GH-29) Zone files are fully idempotent, so are only changed when actual content changes (credit: [@Stuart Knight](https://github.com/blofeldthefish)) 158 | 159 | ## 3.7.0 - 2017-06-01 160 | 161 | ### Added 162 | 163 | - (GH-10) Implement reverse IPv6 lookups 164 | - (GH-28) Add option `bind_forwarders` and `bind_forward_only`, which allows BIND to be set up as a caching name server. 165 | 166 | ## 3.6.1 - 2017-06-01 167 | 168 | ### Changed 169 | 170 | - Fixed a bug with generating the reverse zone names. 171 | 172 | ## 3.6.0 - 2017-06-01 173 | 174 | ### Changed 175 | 176 | - (GH-25) Allow slave log file to be set with variable `bind_log` instead of a hard coded value (credit @kartone). 177 | - The alignment of columns in the reverse zone file are improved 178 | 179 | ### Added 180 | 181 | - (GH-22, 23) Documentation improvements 182 | - (GH-27) Allow dynamic updates (credit: @bverschueren) 183 | 184 | ### Removed 185 | 186 | - The custom filter plugins were removed. The functionality has since been added to Ansible's built-in filter plugins. This does require `python-netaddr` to be installed on the management node. 187 | 188 | ## 3.5.2 - 2016-09-29 189 | 190 | ### Changed 191 | 192 | * The call to `named-checkconf` was fixed. It had the full path to the binary, which is not the same on all distributions. (GH-20, credit @peterjanes) 193 | 194 | ## 3.5.1 - 2016-09-22 195 | 196 | ### Changed 197 | 198 | * The check for master/slave server is improved (GH-19, credit @josetaas) 199 | 200 | ## 3.5.0 - 2016-07-28 201 | 202 | ### Added 203 | 204 | * Introduced role variable `bind_log`, the path to the log file. 205 | * Introduced role variable `bind_zone_also_notify`, a list of servers that will receive a notification when the master zone file is reloaded (GH-18, credit: Joanna Delaporte) 206 | * Reverse zone files now handle the case with only a single host (GH-18, credit: Joanna Delaporte) 207 | 208 | ## 3.4.0 - 2016-05-26 209 | 210 | ### Added 211 | 212 | * (GH-16) Support for service record (SRV) lookups 213 | * Support for text record (TXT) lookups 214 | 215 | ### Changed 216 | 217 | * Fixed Ansible 2.0 deprecation warnings 218 | * Generating a serial is no longer considered a change 219 | * Ensured that all role variables have a default value, e.g. empty list instead of undefined. This simplifies template logic (no `if defined` tests), and is considered [deprecated in playbooks within a *with_* loop](https://docs.ansible.com/ansible/porting_guide_2.0.html#deprecated). 220 | 221 | ## 3.3.1 - 2016-04-08 222 | 223 | ### Removed 224 | 225 | * The `version:` field in `meta/main.yml`. This an unofficial field that is used by a third-party tool for managing role dependencies (librarian-ansible). Custom meta fields are no longer accepted in Ansible 2.0. See [ansible/ansible#13496](https://github.com/ansible/ansible/issues/13496) for more info. Unfortunately, this will break support for librarian-ansible. As a workaround, until this issue is resolved upstream, use version 3.3.0 of this role. 226 | 227 | ## 3.3.0 - 2016-04-08 228 | 229 | ### Added 230 | 231 | * Added role variable `bind_other_name_servers` for adding NS records for DNS servers outside of the domain. (GH-12) 232 | * Re-added `bind_recursion`, as it is needed in some cases. (GH-14) 233 | 234 | ### Removed 235 | 236 | ## 3.2.1 - 2015-12-15 237 | 238 | ### Added 239 | 240 | * The domain name can now also point to an IP address, enabling e.g. "http://example.com/" (GH-11) 241 | 242 | ## 3.2.0 - 2015-12-07 243 | 244 | ### Added 245 | 246 | * Add support for multiple IP addresses per host (GH-9) 247 | * Allow setting `rrset-order` (for DNS round robin) 248 | * Add support for (multiple) IPv6 (AAAA) records (GH-2). For now, only forward lookups are supported. 249 | 250 | ### Changed 251 | 252 | * Test code is put into a separate branch. This means that test code is no longer included when installing the role from Ansible Galaxy. 253 | 254 | ## 3.1.0 - 2015-12-04 255 | 256 | ### Added 257 | 258 | * Add support for zone transfers (GH-8) 259 | * Check whether `bind_zone_master_server_ip` was set (GH-7) 260 | 261 | ### Removed 262 | 263 | * Role variable `bind_recursion` was removed. This role is explicitly only suitable for an authoritative DNS server, and in this case, recursion should be off. 264 | 265 | ## 3.0.0 - 2015-06-14 266 | 267 | ### Added 268 | 269 | * You can now set up a master and slave DNS server. 270 | * The variable `bind_zone_master_server_ip` was added. This is a **required** variable, which makes this release not backwards compatible. 271 | * Automated acceptance tests for the test playbook 272 | 273 | ## 2.0.0 - 2015-06-10 274 | 275 | ### Added 276 | 277 | * Added EL6 to supported platforms. Thanks to @rilindo for verifying this. 278 | 279 | ### Changed 280 | 281 | * Recursion is turned off by default, which fits an authoritative name server. This change is not backwards compatible, as the behaviour of BIND is different from before when you do not set the variable `bind_recursion` explicitly. 282 | 283 | ### Removed 284 | 285 | * Firewall settings. This should not be a concern of this role. Configuring the firewall is functionality offered by other roles (e.g. [bertvv.bind](https://github.com/bertvv/ansible-role-el7)) 286 | 287 | ## 1.0.0 - 2015-04-22 288 | 289 | First release! 290 | 291 | ### Added 292 | 293 | - Functionality for master DNS server 294 | - Multiple reverse lookup zones 295 | 296 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # BSD License 2 | 3 | Copyright (c) 2014, Bert Van Vreckem, (bert.vanvreckem@gmail.com) 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible role BIND 2 | 3 | [![Actions Status](https://github.com/bertvv/ansible-role-bind/workflows/CI/badge.svg)](https://github.com/bertvv/ansible-role-bind/actions) 4 | 5 | An Ansible role for setting up ISC BIND as an **authoritative-only** DNS server for multiple domains. Specifically, the responsibilities of this role are to: 6 | 7 | - install BIND 8 | - set up the main configuration file (primary/secondary/forwarder server) 9 | - set up forward and reverse lookup zone files 10 | 11 | This role supports multiple forward and reverse zones, including for IPv6. Although enabling recursion is supported (albeit *strongly* discouraged), consider using another role if you want to set up a caching or forwarding name server. 12 | 13 | If you like/use this role, please consider giving it a star and rating it on the role's [Ansible Galaxy page](https://galaxy.ansible.com/bertvv/bind). Thanks! 14 | 15 | See the [change log](CHANGELOG.md) for notable changes between versions. 16 | 17 | **WARNING:** If you've been using this role since **before v5.0.0**, please check the change log for important information on breaking changes. Old playbooks will fail if you upgrade to v5.0.0. 18 | 19 | ## Supported platforms 20 | 21 | This role can be used on several platforms, see [meta/main.yml](meta/main.yml) for an updated list. We strive to set up automated tests for each supported platform (see [.ci.yml](.github/workflows/ci.yml)), but this is not always possible. 22 | 23 | A few remarks on supported roles that are not included in automated tests 24 | 25 | - **Arch Linux** and **FreeBSD** should work, but at this time, it's not possible to test the role on these distros, since no suitable Docker images are available. 26 | - **CentOS 6** should work, but idempotence tests fail even if BIND is installed successfully and acceptance tests succeed. 27 | 28 | ## Requirements 29 | 30 | The packages `python-netaddr` (required for the [`ipaddr`](https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters_ipaddr.html) filter) and `dnspython` should be installed on the management node 31 | 32 | ## Role Variables 33 | 34 | | Variable | Default | Comments (type) | 35 | | :-------------------------- | :------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | 36 | | `bind_acls` | `[]` | A list of ACL definitions, which are mappings with keys `name:` and `match_list:`. See below for an example. | 37 | | `bind_allow_query` | `['localhost']` | A list of hosts that are allowed to query this DNS server. Set to ['any'] to allow all hosts | 38 | | `bind_allow_recursion` | `['any']` | Similar to `bind_allow_query`, this option applies to recursive queries. | 39 | | `bind_check_names` | `[]` | Check host names for compliance with RFC 952 and RFC 1123 and take the defined action (e.g. `warn`, `ignore`, `fail`). | 40 | | `bind_dns_keys` | `[]` | A list of binding keys, which are mappings with keys `name:` `algorithm:` and `secret:`. See below for an example. | 41 | | `bind_dns64` | `false` | If `true`, support for [DNS64](https://www.oreilly.com/library/view/dns-and-bind/9781449308025/ch04.html) is enabled | 42 | | `bind_dns64_clients` | `['any']` | A list of clients which the DNS64 function applies to (can be any ACL) | 43 | | `bind_dnssec_enable` | `true` | If `true`, DNSSEC is enabled | 44 | | `bind_dnssec_validation` | `true` | If `true`, DNSSEC validation is enabled | 45 | | `bind_extra_include_files` | `[]` | A list of custom config files to be included from the main config file | 46 | | `bind_forward_only` | `false` | If `true`, BIND is set up as a caching name server | 47 | | `bind_forwarders` | `[]` | A list of name servers to forward DNS requests to. | 48 | | `bind_listen_ipv4` | `['127.0.0.1']` | A list of the IPv4 address of the network interface(s) to listen on. Set to ['any'] to listen on all interfaces. | 49 | | `bind_listen_ipv4_port` | `[53]` | A list of port numbers to listen on for IPv4 addresses. | 50 | | `bind_listen_ipv6` | `['::1']` | A list of the IPv6 address of the network interface(s) to listen on | 51 | | `bind_listen_ipv6_port` | `[53]` | A list of port numbers to listen on for IPv6 addresses. | 52 | | `bind_log` | `data/named.run` | Path to the log file | 53 | | `bind_other_logs` | - | A list of logging channels to configure, with a separate mapping for each zone, with relevant details | 54 | | `bind_query_log` | - | A mapping with keyss `file:` (e.g. `data/query.log`), `versions:`, `size:`. When defined, this will enable the query log | 55 | | `bind_recursion` | `false` | Determines whether requests for which the DNS server is not authoritative should be forwarded†. | 56 | | `bind_rrset_order` | `random` | Defines order for DNS round robin (either `random` or `cyclic`) | 57 | | `bind_statistics_channels` | `false` | If `true`, BIND is configured with a `statistics-channels` clause (currently only supports listening on a single interface) | 58 | | `bind_statistics_allow` | `['127.0.0.1']` | A list of hosts that can access the server statistics | 59 | | `bind_statistics_host` | `127.0.0.1` | IP address of the network interface that the statistics service should listen on | 60 | | `bind_statistics_port` | 8053 | Network port that the statistics service should listen on | 61 | | `bind_zone_dir` | - | When defined, sets a custom absolute path to the server directory (for zone files, etc.) instead of the default. | 62 | | `bind_key_mapping` | [] | `Primary: Keyname` - mapping of TSIG keys to use for a specific primary | 63 | | `bind_zones` | n/a | A list of mappings with zone definitions. See below this table for examples | 64 | | `- allow_update` | `['none']` | A list of hosts that are allowed to dynamically update this DNS zone. | 65 | | `- also_notify` | - | A list of servers that will receive a notification when the primary zone file is reloaded. | 66 | | `- create_forward_zones` | - | When initialized and set to `false`, creation of forward zones will be skipped (resulting in a reverse only zone) | 67 | | `- create_reverse_zones` | - | When initialized and set to `false`, creation of reverse zones will be skipped (resulting in a forward only zone) | 68 | | `- delegate` | `[]` | Zone delegation. | 69 | | `- forwarders` | - | List of forwarders for for the forward type zone | 70 | | `- hostmaster_email` | `hostmaster` | The e-mail address of the system administrator for the zone | 71 | | `- hosts` | `[]` | Host definitions. | 72 | | `- ipv6_networks` | `[]` | A list of the IPv6 networks that are part of the domain, in CIDR notation (e.g. 2001:db8::/48) | 73 | | `- mail_servers` | `[]` | A list of mappings (with keys `name:` and `preference:`) specifying the mail servers for this domain. | 74 | | `- name_servers` | `[ansible_hostname]` | A list of the DNS servers for this domain. | 75 | | `- name` | `example.com` | The domain name | 76 | | `- naptr` | `[]` | A list of mappings with keys `name:`, `order:`, `pref:`, `flags:`, `service:`, `regex:` and `replacement:` specifying NAPTR records. | 77 | | `- networks` | `['10.0.2']` | A list of the networks that are part of the domain | 78 | | `- other_name_servers` | `[]` | A list of the DNS servers outside of this domain. | 79 | | `- primaries` | - | A list of primary DNS servers for this zone. | 80 | | `- services` | `[]` | A list of services to be advertised by SRV records | 81 | | `- text` | `[]` | A list of mappings with keys `name:` and `text:`, specifying TXT records. `text:` can be a list or string. | 82 | | `- caa` | `[]` | A list of mappings with keys `name:` and `text:`, specifying CAA records. `text:` can be a list or string. | 83 | | `- type` | - | Optional zone type. If not specified, autodetection will be used. Possible values include `primary`, `secondary` or `forward` | 84 | | `bind_zone_file_mode` | 0640 | The file permissions for the main config file (named.conf) | 85 | | `bind_zone_minimum_ttl` | `1D` | Minimum TTL field in the SOA record. | 86 | | `bind_zone_time_to_expire` | `1W` | Time to expire field in the SOA record. | 87 | | `bind_zone_time_to_refresh` | `1D` | Time to refresh field in the SOA record. | 88 | | `bind_zone_time_to_retry` | `1H` | Time to retry field in the SOA record. | 89 | | `bind_zone_ttl` | `1W` | Time to Live field in the SOA record. | 90 | | `bind_python_version` | - | The python version that should be used for ansible. Depends on Distro, either `2` or `3`. Defaults to the OS standard | 91 | 92 | † Best practice for an authoritative name server is to leave recursion turned off. However, [for some cases](http://www.zytrax.com/books/dns/ch7/queries.html#allow-query-cache) it may be necessary to have recursion turned on. 93 | 94 | ### Minimal variables for a working zone 95 | 96 | In order to set up an authoritative name server that is available to clients, you should at least define the following variables: 97 | 98 | | Variable | Primary | Secondary | Forward | 99 | | :----------------- | :-----: | :-------: | :-----: | 100 | | `bind_allow_query` | V | V | V | 101 | | `bind_listen_ipv4` | V | V | V | 102 | | `bind_zones` | V | V | V | 103 | | `- hosts` | V | -- | -- | 104 | | `- name_servers` | V | -- | -- | 105 | | `- name` | V | V | -- | 106 | | `- networks` | V | V | V | 107 | | `- primaries` | V | V | -- | 108 | | `- forwarders` | -- | -- | V | 109 | 110 | 111 | ### Domain definitions 112 | 113 | ```Yaml 114 | bind_zones: 115 | # Example of a primary zone (hosts: and name_servers: ares defined) 116 | - name: mydomain.com # Domain name 117 | create_reverse_zones: false # Skip creation of reverse zones 118 | primaries: 119 | - 192.0.2.1 # Primary server(s) for this zone 120 | name_servers: 121 | - pub01.mydomain.com. 122 | - pub02.mydomain.com. 123 | hosts: 124 | - name: pub01 125 | ip: 192.0.2.1 126 | ipv6: 2001:db8::1 127 | aliases: 128 | - ns1 129 | - name: pub02 130 | ip: 192.0.2.2 131 | ipv6: 2001:db8::2 132 | aliases: 133 | - ns2 134 | - name: '@' # Enables "http://mydomain.com/" 135 | ip: 136 | - 192.0.2.3 # Multiple IP addresses for a single host 137 | - 192.0.2.4 # results in DNS round robin 138 | sshfp: # Secure shell fingerprint 139 | - "3 1 1262006f9a45bb36b1aa14f45f354b694b77d7c3" 140 | - "3 2 e5921564252fe10d2dbafeb243733ed8b1d165b8fa6d5a0e29198e5793f0623b" 141 | ipv6: 142 | - 2001:db8::2 143 | - 2001:db8::3 144 | aliases: 145 | - www 146 | - name: priv01 # This IP is in another subnet, will result in 147 | ip: 10.0.0.1 # multiple reverse zones 148 | - name: mydomain.net. 149 | aliases: 150 | - name: sub01 151 | type: DNAME # Example of a DNAME alias record 152 | networks: 153 | - '192.0.2' 154 | - '10' 155 | - '172.16' 156 | delegate: 157 | - zone: foo 158 | dns: 192.0.2.1 159 | services: 160 | - name: _ldap._tcp 161 | weight: 100 162 | port: 88 163 | target: dc001 164 | naptr: # Name Authority Pointer record, used for IP 165 | - name: "sip" # telephony 166 | order: 100 167 | pref: 10 168 | flags: "S" 169 | service: "SIP+D2T" 170 | regex: "!^.*$!sip:customer-service@example.com!" 171 | replacement: "_sip._tcp.example.com." 172 | # Minimal example of a secondary zone 173 | - name: acme.com 174 | primaries: 175 | - 172.17.0.2 176 | networks: 177 | - "172.17" 178 | # Minimal example of a forward zone 179 | - name: acme.com 180 | forwarders: 181 | - 172.17.0.2 182 | networks: 183 | - "172.17" 184 | ``` 185 | 186 | ### Hosts 187 | 188 | Host names that this DNS server should resolve can be specified in `bind_zones.hosts` as a list of mappings with keys `name:`, `ip:`, `aliases:` and `sshfp:`. Aliases can be CNAME (default) or DNAME records. 189 | 190 | To allow to surf to `http://example.com/`, set the host name of your web server to `'@'` (must be quoted!). In BIND syntax, `@` indicates the domain name itself. 191 | 192 | If you want to specify multiple IP addresses for a host, add entries to `bind_zones.hosts` with the same name (e.g. `priv01` in the code snippet). This results in multiple A/AAAA records for that host and allows [DNS round robin](http://www.zytrax.com/books/dns/ch9/rr.html), a simple load balancing technique. The order in which the IP addresses are returned can be configured with role variable `bind_rrset_order`. 193 | 194 | ### Networks 195 | 196 | As you can see, not all hosts are in the same subnet. This role will generate suitable reverse lookup zones for each subnet. All subnets should be specified in `bind_zones.networks`, though, or the host will not get a PTR record for reverse lookup. 197 | 198 | Remark that only the network part should be specified here! When specifying a class B IP address (e.g. "172.16") in a variable file, it must be quoted. Otherwise, the Yaml parser will interpret it as a float. 199 | 200 | Based on the idea and examples detailed at for the gdnsd package, the zone files are fully idempotent, and thus only get updated if "real" content changes. 201 | 202 | ### Zone types and Zone type auto-detection 203 | 204 | Zone `type` is an optional zone parameter that defines if the zone type should be of `primary`, `secondary` or `forward` type. When `type` parameter is omitted, zone type will be autodetected based on the intersection of host IP addresses and `primaries` record when configuring primary or secondary zone. When `primaries` is not defined and `forwarders` is defined, the zone type will be set to `forward`. 205 | 206 | Zone auto-detection functionality is especially useful when deploying multi-site DNS infrastructure. It is convenient to have a "shared" `bind_zones` definitions in a single group inventory file for all dns servers ( ex. `group_vars\dns.yml`). Such an approach allows to switch between primary and secondary server(s) roles by updating `primaries` record only and rerunning the playbook. Zone type auto-detection can be tested with "shared_inventory" molecule scenario by running: `molecule test --scenario-name shared_inventory` 207 | 208 | --- 209 | 210 | **NOTE** 211 | 212 | - bind doesn't support automated [multi-master configuration](https://kb.isc.org/docs/managing-manual-multi-master) and `primaries` list should have a single entry only. 213 | - When `primaries` record is updated to switch primary to secondary server roles, zones will be wiped out and recreated from template as we yet to support dynamic updates for existing zones. 214 | 215 | --- 216 | 217 | Zone types can be also defined explicitly in per host inventory to skip autodetection: 218 | 219 | ```Yaml 220 | # Primary Server 221 | bind_zones: 222 | - name: mydomain.com 223 | type: primary 224 | primaries: 225 | - 192.0.2.1 226 | ... 227 | # Secondary Server 228 | bind_zones: 229 | - name: mydomain.com 230 | type: secondary 231 | primaries: 232 | - 192.0.2.1 233 | ... 234 | # Forwarder Server 235 | bind_zones: 236 | - name: anotherdomain.com 237 | type: forward 238 | forwarders: 239 | - 192.0.3.1 240 | ``` 241 | 242 | ### Zone delegation 243 | 244 | To delegate a zone to a DNS server, it is sufficient to create a `NS` record (under delegate) which is the equivalent of: 245 | 246 | ```text 247 | foo IN NS 192.0.2.1 248 | ``` 249 | 250 | ### Service records 251 | 252 | Service (SRV) records can be added with the services. This should be a list of mappings with mandatory keys `name:` (service name), `target:` (host providing the service), `port:` (TCP/UDP port of the service) and optional keys `priority:` (default = 0) and `weight:` (default = 0). 253 | 254 | ### ACLs 255 | 256 | ACLs can be defined like this: 257 | 258 | ```Yaml 259 | bind_acls: 260 | - name: acl1 261 | match_list: 262 | - 192.0.2.0/24 263 | - 10.0.0.0/8 264 | ``` 265 | 266 | The names of the ACLs will be added to the `allow-transfer` clause in global options. 267 | 268 | ### Binding Keys 269 | 270 | Binding keys can be defined like this: 271 | 272 | ```Yaml 273 | bind_dns_keys: 274 | - name: primary_key 275 | algorithm: hmac-sha256 276 | secret: "azertyAZERTY123456" 277 | bind_extra_include_files: 278 | - "{{ bind_auth_file }}" 279 | ``` 280 | 281 | **tip**: Extra include file must be set as an ansible variable because file is OS dependant 282 | 283 | This will be set in a file *"{{ bind_auth_file }}* (e.g. /etc/bind/auth_transfer.conf for Debian) which have to be added in the list variable **bind_extra_include_files** 284 | 285 | ### Using TSIG for zone transfer (XFR) authorization 286 | 287 | To authorize the transfer of zone between primary & secondary servers based on a TSIG key, set the mapping in the variable `bind_key_mapping`: 288 | 289 | ```Yaml 290 | bind_key_mapping: 291 | primary_ip: TSIG-keyname 292 | ``` 293 | 294 | Each primary can only have one key (per view). 295 | 296 | A check will be performed to ensure the key is actually present in the `bind_dns_keys` mapping. This will add a server statement for the `a` in `bind_auth_file` on a secondary server containing the specified key. 297 | 298 | ## Dependencies 299 | 300 | No dependencies. 301 | 302 | ## Example Playbooks 303 | 304 | See the test playbooks and inventory for an elaborate example that showcases most features. 305 | 306 | ### Standard Inventory 307 | 308 | - Variables common between all servers defined in [all.yml](molecule/default/group_vars/all.yml) 309 | - `bind_zone` variable defined on per host basis ([primary](molecule/default/host_vars/ns1.yml), [secondary](molecule/default/host_vars/ns2.yml) and [forwarder](molecule/default/host_vars/ns3.yml)) 310 | 311 | ```console 312 | ❯ tree --dirsfirst molecule/default 313 | molecule/default 314 | ├── group_vars 315 | │   └── all.yml 316 | ├── host_vars 317 | │   ├── ns1.yml # Primary 318 | │   ├── ns2.yml # Secondary 319 | │   └── ns3.yml # Forwarder 320 | ├── converge.yml 321 | ... 322 | ``` 323 | 324 | ### Shared Inventory 325 | 326 | Variables common between primary and secondary servers defined in [all.yml](molecule/shared_inventory/group_vars/all.yml) 327 | 328 | ```console 329 | ❯ tree --dirsfirst molecule/shared_inventory 330 | molecule/shared_inventory 331 | ├── group_vars 332 | │   └── all.yml 333 | ├── converge.yml 334 | ... 335 | ``` 336 | 337 | ## Testing 338 | 339 | This role is tested using [Ansible Molecule](https://molecule.readthedocs.io/). Tests are launched automatically on [Github Actions](https://github.com/bertvv/ansible-role-bind/actions) after each commit and PR. 340 | 341 | This Molecule configuration will: 342 | 343 | - Run Yamllint and Ansible Lint 344 | - Create three Docker containers, one primary (`ns1`), one secondary (`ns2`) DNS server and forwarder(`ns3`) - `default` molecule scenario 345 | - Run a syntax check 346 | - Apply the role with a [test playbook](molecule/default/converge.yml) and check idempotence 347 | - Run acceptance tests with [verify playbook](molecule/default/verify.yml) 348 | - Create two additional Docker containers, one primary(`ns4`) and one secondary (`ns5`) and run `shared_inventory` scenario 349 | 350 | This process is repeated for all the supported Linux distributions. 351 | 352 | ### Local test environment 353 | 354 | In order to run the acceptance tests on this role locally, you can install the necessary tools on your machine, or use this reproducible setup in a VirtualBox VM (set up with Vagrant): . 355 | 356 | Steps to install the tools manually: 357 | 358 | 1. Docker should be installed on your machine 359 | 2. As recommended by Molecule, create a python virtual environment 360 | 3. Install the software tools `python3 -m pip install molecule molecule-docker docker netaddr dnspython yamllint ansible-lint` 361 | 4. Navigate to the root of the role directory and run `molecule test` 362 | 363 | Molecule automatically deletes the containers after a test. If you would like to check out the containers yourself, run `molecule converge` followed by `molecule login --host HOSTNAME`. 364 | 365 | The Docker containers are based on images created by [Jeff Geerling](https://hub.docker.com/u/geerlingguy), specifically for Ansible testing (look for images named `geerlingguy/docker-DISTRO-ansible`). You can use any of his images, but only the distributions mentioned in [meta/main.yml](meta/main.yml) are supported. 366 | 367 | The default config will start three Centos 8 containers (the primary supported platform at this time). Choose another distro by setting the `MOLECULE_DISTRO` variable with the command, e.g.: 368 | 369 | ``` bash 370 | MOLECULE_DISTRO=debian9 molecule test 371 | ``` 372 | 373 | or 374 | 375 | ``` bash 376 | MOLECULE_DISTRO=debian9 molecule converge 377 | ``` 378 | 379 | You can run the acceptance tests on all servers with `molecule verify`. 380 | 381 | > Verification tests are done using "dig" lookup module by quering dns records and validating responses. This requires direct network communication between Ansible controller node (your machine running Ansible) and the target docker container. 382 | 383 | --- 384 | 385 | **NOTE** 386 | 387 | Molecule verify tests will fail if docker is running on MacOS, as MacOS cannot access container IP directly. This is a known issue. See [#2670](https://github.com/docker/for-mac/issues/2670). 388 | 389 | Workaround: 390 | 391 | 1. Run molecule linter: `molecule lint` 392 | 2. Provision containers: `molecule converge` 393 | 3. Connect to container: `molecule login --host ns1` 394 | 4. Go to role directory: `cd /etc/ansible/roles/bertvv.bind` 395 | 5. Run verify playbook: 396 | 397 | ```console 398 | ansible-playbook -c local -i "`hostname`," -i molecule/default/inventory.ini molecule/default/verify.yml 399 | ``` 400 | 401 | 6. Repeat steps 2-4 for `ns2` and `ns3` 402 | 403 | --- 404 | 405 | ## License 406 | 407 | BSD 408 | 409 | ## Contributors 410 | 411 | This role could only have been realized thanks to the contributions of many. If you have an idea to improve it even further, don't hesitate to pitch in! 412 | 413 | Issues, feature requests, ideas, suggestions, etc. can be posted in the Issues section. 414 | 415 | Pull requests are also very welcome. Please create a topic branch for your proposed changes. If you don't, this will create conflicts in your fork after the merge. Don't hesitate to add yourself to the contributor list below in your PR! 416 | 417 | Maintainers: 418 | 419 | - [Bert Van Vreckem](https://github.com/bertvv/) 420 | - [Gregory Shulov](https://github.com/GR360RY) 421 | - [Stuart Knight](https://github.com/blofeldthefish) 422 | 423 | Contributors: 424 | 425 | - [Aido](https://github.com/aido) 426 | - [Angel Barrera](https://github.com/angelbarrera92) 427 | - [B. Verschueren](https://github.com/bverschueren) 428 | - [Boris Momčilović](https://github.com/kornrunner) 429 | - [Brad Durrow](https://github.com/bdurrow) 430 | - [Christopher Hicks](http://www.chicks.net/) 431 | - [David J. Haines](https://github.com/dhaines) 432 | - [Fabio Rocha](https://github.com/frock81) 433 | - [Fazle Arefin](https://github.com/fazlearefin) 434 | - [flora-five](https://github.com/flora-five) 435 | - [Greg Cockburn](https://github.com/gergnz) 436 | - [Guillaume Darmont](https://github.com/gdarmont) 437 | - [itbane](https://github.com/itbane) 438 | - [jadjay](https://github.com/jadjay) 439 | - [Jascha Sticher](https://github.com/itbane) 440 | - [Joanna Delaporte](https://github.com/jdelaporte) 441 | - [Jörg Eichhorn](https://github.com/jeichhorn) 442 | - [Jose Taas](https://github.com/josetaas) 443 | - [Lennart Weller](https://github.com/lhw) 444 | - [Loic Dachary](http://dachary.org) 445 | - [Mario Ciccarelli](https://github.com/kartone) 446 | - [Miroslav Hudec](https://github.com/mihudec) 447 | - [Otto Sabart](https://github.com/seberm) 448 | - [Paulius Mazeika](https://github.com/pauliusm) 449 | - [Paulo E. Castro](https://github.com/pecastro) 450 | - [Peter Janes](https://github.com/peterjanes) 451 | - [psa](https://github.com/psa) 452 | - [Rafael Bodill](https://github.com/rafi) 453 | - [Rayford Johnson](https://github.com/rayfordj) 454 | - [Robin Ophalvens](https://github.com/RobinOphalvens) 455 | - [Romuald](https://github.com/rds13) 456 | - [roumano](https://github.com/roumano) 457 | - [Shawn Wilsher](https://github.com/sdwilsh) 458 | - [Tom Meinlschmidt](https://github.com/tmeinlschmidt) 459 | - [Jascha Sticher](https://github.com/itbane) 460 | - [Zephyr82](https://github.com/Zephyr82) 461 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # List of zones for which this name server is authoritative 4 | bind_zones: [] 5 | 6 | # List of acls. 7 | bind_acls: [] 8 | 9 | # Key binding for secondary servers 10 | bind_dns_keys: [] 11 | # - name: primary_key 12 | # algorithm: hmac-sha256 13 | # secret: "azertyAZERTY123456" 14 | 15 | # List of IPv4 address of the network interface(s) to listen on. Set to "any" 16 | # to listen on all interfaces 17 | bind_listen_ipv4: 18 | - "127.0.0.1" 19 | 20 | # The list of ports to listen on for IPv4 addresses 21 | bind_listen_ipv4_port: 22 | - 53 23 | 24 | # List of IPv6 address of the network interface(s) to listen on. 25 | bind_listen_ipv6: 26 | - "::1" 27 | 28 | # The list of ports to listen on for IPv6 addresses 29 | bind_listen_ipv6_port: 30 | - 53 31 | 32 | # List of hosts that are allowed to query this DNS server. 33 | bind_allow_query: 34 | - "localhost" 35 | 36 | # A key-value list mapping server-IPs to TSIG keys for signing requests 37 | bind_key_mapping: {} 38 | 39 | # Determines whether recursion should be allowed. Typically, an authoritative 40 | # name server should have recursion turned OFF. 41 | bind_recursion: false 42 | bind_allow_recursion: 43 | - "any" 44 | 45 | # Allows BIND to be set up as a caching name server 46 | bind_forward_only: false 47 | 48 | # List of name servers to forward DNS requests to. 49 | bind_forwarders: [] 50 | 51 | # DNS round robin order (random or cyclic) 52 | bind_rrset_order: "random" 53 | 54 | # statistics channels configuration 55 | bind_statistics_channels: false 56 | bind_statistics_port: 8053 57 | bind_statistics_host: 127.0.0.1 58 | bind_statistics_allow: 59 | - "127.0.0.1" 60 | 61 | # DNSSEC configuration 62 | bind_dnssec_enable: true 63 | bind_dnssec_validation: true 64 | 65 | bind_extra_include_files: [] 66 | 67 | # SOA information 68 | bind_zone_ttl: "1W" 69 | bind_zone_time_to_refresh: "1D" 70 | bind_zone_time_to_retry: "1H" 71 | bind_zone_time_to_expire: "1W" 72 | bind_zone_minimum_ttl: "1D" 73 | 74 | # File mode for primary zone files (needs to be something like 0660 for dynamic updates) 75 | bind_zone_file_mode: "0640" 76 | 77 | # DNS64 support 78 | bind_dns64: false 79 | bind_dns64_clients: 80 | - "any" 81 | 82 | # Set Python version 83 | bind_python_version: "{{ bind_default_python_version }}" 84 | 85 | # Log file 86 | bind_log: "data/named.run" 87 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: reload bind 4 | service: 5 | name: "{{ bind_service }}" 6 | state: reloaded 7 | become: yes 8 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | role_name: bind 4 | namespace: bertvv 5 | author: Bert Van Vreckem 6 | description: > 7 | Sets up ISC BIND as an authoritative DNS server for one or more domains 8 | (primary and/or secondary). 9 | license: BSD 10 | min_ansible_version: 2.7 11 | platforms: 12 | - name: ArchLinux 13 | versions: 14 | - any 15 | - name: Debian 16 | versions: 17 | - jessie 18 | - stretch 19 | - buster 20 | - name: FreeBSD 21 | versions: 22 | - 12.1 23 | - name: EL 24 | versions: 25 | - 6 26 | - 7 27 | - 8 28 | - name: Ubuntu 29 | versions: 30 | - xenial 31 | - bionic 32 | - focal 33 | galaxy_tags: 34 | - dns 35 | - networking 36 | - system 37 | dependencies: [] 38 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Remark: Common variables are defined in `group_vars/all.yml` 3 | 4 | - name: Converge 5 | hosts: all 6 | 7 | roles: 8 | - role: "bertvv.bind" 9 | -------------------------------------------------------------------------------- /molecule/default/group_vars/all.yml: -------------------------------------------------------------------------------- 1 | --- 2 | bind_statistics_channels: true 3 | bind_statistics_allow: 4 | - any 5 | bind_zone_dir: /var/local/named-zones 6 | bind_zone_file_mode: '0660' 7 | bind_allow_query: 8 | - any 9 | bind_listen_ipv4: 10 | - any 11 | bind_listen_ipv6: 12 | - any 13 | bind_acls: 14 | - name: acl1 15 | match_list: 16 | - 10.11.0.0/16 17 | bind_forwarders: 18 | - '8.8.8.8' 19 | - '8.8.4.4' 20 | bind_recursion: true 21 | bind_dns64: true 22 | bind_query_log: 'data/query.log' 23 | bind_check_names: 'master ignore' 24 | bind_zone_minimum_ttl: "2D" 25 | bind_zone_ttl: "2W" 26 | bind_zone_time_to_refresh: "2D" 27 | bind_zone_time_to_retry: "2H" 28 | bind_zone_time_to_expire: "2W" 29 | bind_statistics_host: "{{ ansible_default_ipv4.address }}" 30 | -------------------------------------------------------------------------------- /molecule/default/host_vars/ns1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Primary Configuration 3 | 4 | bind_zones: 5 | # Primary server for domain example.com (use zone type autodetection) 6 | - name: 'example.com' 7 | primaries: 8 | - 10.11.0.1 9 | networks: 10 | - '192.0.2' 11 | ipv6_networks: 12 | - '2001:db9::/48' 13 | name_servers: 14 | - ns1.acme-inc.com. 15 | - ns2.acme-inc.com. 16 | hostmaster_email: admin 17 | hosts: 18 | - name: srv001 19 | ip: 192.0.2.1 20 | ipv6: '2001:db9::1' 21 | aliases: 22 | - www 23 | - name: srv002 24 | ip: 192.0.2.2 25 | ipv6: '2001:db9::2' 26 | - name: mail001 27 | ip: 192.0.2.10 28 | ipv6: '2001:db9::3' 29 | mail_servers: 30 | - name: mail001 31 | preference: 10 32 | # Primary server for domain acme-inc.com (specify zone type explicitly) 33 | - name: 'acme-inc.com' 34 | type: primary 35 | primaries: 36 | - 10.11.0.1 37 | networks: 38 | - '10.11' 39 | ipv6_networks: 40 | - '2001:db8::/48' 41 | name_servers: 42 | - ns1 43 | - ns2 44 | hosts: 45 | - name: ns1 46 | ip: 10.11.0.1 47 | - name: ns2 48 | ip: 10.11.0.2 49 | - name: srv001 50 | ip: 10.11.1.1 51 | ipv6: 2001:db8::1 52 | aliases: 53 | - www 54 | - name: srv002 55 | ip: 10.11.1.2 56 | ipv6: 2001:db8::2 57 | aliases: 58 | - mysql 59 | - name: mail001 60 | ip: 10.11.2.1 61 | ipv6: 2001:db8::d:1 62 | aliases: 63 | - smtp 64 | - mail-in 65 | - name: mail002 66 | ip: 10.11.2.2 67 | ipv6: 2001:db8::d:2 68 | - name: mail003 69 | ip: 10.11.2.3 70 | ipv6: 2001:db8::d:3 71 | aliases: 72 | - imap 73 | - mail-out 74 | - name: srv010 75 | ip: 10.11.0.10 76 | - name: srv011 77 | ip: 10.11.0.11 78 | - name: srv012 79 | ip: 10.11.0.12 80 | mail_servers: 81 | - name: mail001 82 | preference: 10 83 | - name: mail002 84 | preference: 20 85 | services: 86 | - name: _ldap._tcp 87 | weight: 100 88 | port: 88 89 | target: srv010 90 | text: 91 | - name: _kerberos 92 | text: KERBEROS.ACME-INC.COM 93 | - name: '@' 94 | text: 95 | - 'some text' 96 | - 'more text' 97 | -------------------------------------------------------------------------------- /molecule/default/host_vars/ns2.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Secondary Configuration 3 | 4 | bind_zones: 5 | # Secondary server for domain example.com (use zone type autodetection) 6 | - name: 'example.com' 7 | primaries: 8 | - 10.11.0.1 9 | networks: 10 | - '192.0.2' 11 | ipv6_networks: 12 | - '2001:db9::/48' 13 | # Secondary server for domain acme-inc.com (specify zone type explicitly) 14 | - name: 'acme-inc.com' 15 | type: secondary 16 | primaries: 17 | - 10.11.0.1 18 | networks: 19 | - '10.11' 20 | ipv6_networks: 21 | - '2001:db8::/48' 22 | -------------------------------------------------------------------------------- /molecule/default/host_vars/ns3.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Forwarder Configuration 3 | 4 | bind_dnssec_validation: false 5 | bind_zones: 6 | # Forwarder for domain example.com (use zone type autodetection) 7 | - name: 'example.com' 8 | forwarders: 9 | - 10.11.0.1 10 | - 10.11.0.2 11 | networks: 12 | - '192.0.2' 13 | ipv6_networks: 14 | - '2001:db9::/48' 15 | # Forwarder server for domain acme-inc.com (specify zone type explicitly) 16 | - name: 'acme-inc.com' 17 | type: forward 18 | forwarders: 19 | - 10.11.0.1 20 | - 10.11.0.2 21 | networks: 22 | - '10.11' 23 | ipv6_networks: 24 | - '2001:db8::/48' 25 | -------------------------------------------------------------------------------- /molecule/default/inventory.ini: -------------------------------------------------------------------------------- 1 | # Dummy inventory file to make sure group_vars and host_vars are included automatically 2 | localhost ansible_connection=local -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | 5 | driver: 6 | # Specifies the driver that should be used. Podman should also work 7 | name: docker 8 | 9 | # Linting with yamllint and ansible-lint 10 | lint: | 11 | set -e 12 | yamllint . 13 | ansible-lint -x 106 . 14 | 15 | platforms: 16 | # Set name and hostname 17 | - name: ns1 18 | hostname: ns1 19 | # Specify which image should be used. Geerlingguys images are Ansible 20 | # compatible and have Systemd installed 21 | docker_networks: 22 | - name: bind 23 | ipam_config: 24 | - subnet: "10.11.0.0/16" 25 | gateway: "10.11.0.254" 26 | networks: 27 | - name: bind 28 | ipv4_address: "10.11.0.1" 29 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos8}-ansible:latest" 30 | # Command to execute when the container starts 31 | command: ${MOLECULE_DOCKER_COMMAND:-""} 32 | # Volumes to mount within the container. Important to enable systemd 33 | volumes: 34 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 35 | - ${PWD}:/etc/ansible/roles/bertvv.bind:ro 36 | - ${PWD}/library:/root/.ansible/plugins/modules:ro 37 | # Give extended privileges to the container. Necessary for Systemd to 38 | # operate within the container. DO NOT use extended privileges in a 39 | # production environment! 40 | privileged: true 41 | pre_build_image: true 42 | # Allocate pseudo-TTY 43 | tty: true 44 | environment: 45 | container: docker 46 | 47 | - name: ns2 48 | hostname: ns2 49 | networks: 50 | - name: bind 51 | ipv4_address: "10.11.0.2" 52 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos8}-ansible:latest" 53 | command: ${MOLECULE_DOCKER_COMMAND:-""} 54 | volumes: 55 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 56 | - ${PWD}:/etc/ansible/roles/bertvv.bind:ro 57 | - ${PWD}/library:/root/.ansible/plugins/modules:ro 58 | privileged: true 59 | pre_build_image: true 60 | tty: true 61 | environment: 62 | container: docker 63 | 64 | - name: ns3 65 | hostname: ns3 66 | networks: 67 | - name: bind 68 | ipv4_address: "10.11.0.3" 69 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos8}-ansible:latest" 70 | command: ${MOLECULE_DOCKER_COMMAND:-""} 71 | volumes: 72 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 73 | - ${PWD}:/etc/ansible/roles/bertvv.bind:ro 74 | - ${PWD}/library:/root/.ansible/plugins/modules:ro 75 | privileged: true 76 | pre_build_image: true 77 | tty: true 78 | environment: 79 | container: docker 80 | 81 | 82 | provisioner: 83 | name: ansible 84 | config_options: 85 | defaults: 86 | interpreter_python: auto_legacy_silent 87 | callback_whitelist: profile_tasks 88 | ssh_connection: 89 | pipelining: true 90 | 91 | # Runs the verify.yml playbook 92 | verifier: 93 | name: ansible 94 | -------------------------------------------------------------------------------- /molecule/default/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | gather_facts: false 5 | 6 | tasks: 7 | 8 | # Only collect network facts to reduce setup time 9 | - name: Gathering Facts 10 | setup: 11 | gather_subset: 12 | - 'network' 13 | 14 | - name: Set local_dns variable for dig 15 | set_fact: 16 | local_dns: "{{ '@' + ansible_default_ipv4.address }}" 17 | 18 | - name: IPv4 Forward lookups 19 | assert: 20 | that: 21 | - lookup('dig', 'ns1.acme-inc.com', local_dns ) == '10.11.0.1' 22 | - lookup('dig', 'ns2.acme-inc.com', local_dns) == '10.11.0.2' 23 | - lookup('dig', 'srv001.acme-inc.com', local_dns) == '10.11.1.1' 24 | - lookup('dig', 'srv002.acme-inc.com', local_dns) == '10.11.1.2' 25 | - lookup('dig', 'mail001.acme-inc.com', local_dns) == '10.11.2.1' 26 | - lookup('dig', 'mail002.acme-inc.com', local_dns) == '10.11.2.2' 27 | - lookup('dig', 'mail003.acme-inc.com', local_dns) == '10.11.2.3' 28 | - lookup('dig', 'srv010.acme-inc.com', local_dns) == '10.11.0.10' 29 | - lookup('dig', 'srv011.acme-inc.com', local_dns) == '10.11.0.11' 30 | - lookup('dig', 'srv012.acme-inc.com', local_dns) == '10.11.0.12' 31 | - lookup('dig', 'srv001.example.com', local_dns) == '192.0.2.1' 32 | - lookup('dig', 'srv002.example.com', local_dns) == '192.0.2.2' 33 | - lookup('dig', 'mail001.example.com', local_dns) == '192.0.2.10' 34 | 35 | - name: IPv4 Reverse lookups 36 | assert: 37 | that: 38 | - lookup('dig', '10.11.0.1/PTR', local_dns) == 'ns1.acme-inc.com.' 39 | - lookup('dig', '10.11.0.2/PTR', local_dns) == 'ns2.acme-inc.com.' 40 | - lookup('dig', '10.11.1.1/PTR', local_dns) == 'srv001.acme-inc.com.' 41 | - lookup('dig', '10.11.1.2/PTR', local_dns) == 'srv002.acme-inc.com.' 42 | - lookup('dig', '10.11.2.1/PTR', local_dns) == 'mail001.acme-inc.com.' 43 | - lookup('dig', '10.11.2.2/PTR', local_dns) == 'mail002.acme-inc.com.' 44 | - lookup('dig', '10.11.2.3/PTR', local_dns) == 'mail003.acme-inc.com.' 45 | - lookup('dig', '10.11.0.10/PTR', local_dns) == 'srv010.acme-inc.com.' 46 | - lookup('dig', '10.11.0.11/PTR', local_dns) == 'srv011.acme-inc.com.' 47 | - lookup('dig', '10.11.0.12/PTR', local_dns) == 'srv012.acme-inc.com.' 48 | - lookup('dig', '192.0.2.1/PTR', local_dns) == 'srv001.example.com.' 49 | - lookup('dig', '192.0.2.2/PTR', local_dns) == 'srv002.example.com.' 50 | - lookup('dig', '192.0.2.10/PTR', local_dns) == 'mail001.example.com.' 51 | 52 | - name: IPv4 Alias lookups 53 | assert: 54 | that: 55 | - lookup('dig', 'www.acme-inc.com', local_dns) == '10.11.1.1' 56 | - lookup('dig', 'mysql.acme-inc.com', local_dns) == '10.11.1.2' 57 | - lookup('dig', 'smtp.acme-inc.com', local_dns) == '10.11.2.1' 58 | - lookup('dig', 'mail-in.acme-inc.com', local_dns) == '10.11.2.1' 59 | - lookup('dig', 'imap.acme-inc.com', local_dns) == '10.11.2.3' 60 | - lookup('dig', 'mail-out.acme-inc.com', local_dns) == '10.11.2.3' 61 | - lookup('dig', 'www.example.com', local_dns) == '192.0.2.1' 62 | 63 | - name: IPv6 Forward lookups 64 | assert: 65 | that: 66 | - lookup('dig', 'srv001.acme-inc.com/AAAA', local_dns) == '2001:db8::1' 67 | - lookup('dig', 'srv002.acme-inc.com/AAAA', local_dns) == '2001:db8::2' 68 | - lookup('dig', 'mail001.acme-inc.com/AAAA', local_dns) == '2001:db8::d:1' 69 | - lookup('dig', 'mail002.acme-inc.com/AAAA', local_dns) == '2001:db8::d:2' 70 | - lookup('dig', 'mail003.acme-inc.com/AAAA', local_dns) == '2001:db8::d:3' 71 | - lookup('dig', 'srv001.example.com/AAAA', local_dns) == '2001:db9::1' 72 | 73 | - name: IPv6 Reverse lookups 74 | assert: 75 | that: 76 | - lookup('dig', '2001:db8::1/PTR', local_dns) == 'srv001.acme-inc.com.' 77 | - lookup('dig', '2001:db8::2/PTR', local_dns) == 'srv002.acme-inc.com.' 78 | - lookup('dig', '2001:db8::d:1/PTR', local_dns) == 'mail001.acme-inc.com.' 79 | - lookup('dig', '2001:db8::d:2/PTR', local_dns) == 'mail002.acme-inc.com.' 80 | - lookup('dig', '2001:db8::d:3/PTR', local_dns) == 'mail003.acme-inc.com.' 81 | - lookup('dig', '2001:db8::d:3/PTR', local_dns) == 'mail003.acme-inc.com.' 82 | - lookup('dig', '2001:db9::1/PTR', local_dns) == 'srv001.example.com.' 83 | 84 | - name: NS records lookup 85 | assert: 86 | that: 87 | - lookup('dig', 'acme-inc.com/NS', local_dns).split(',') | sort | join(',') == 'ns1.acme-inc.com.,ns2.acme-inc.com.' 88 | - lookup('dig', 'example.com/NS', local_dns).split(',') | sort | join(',') == 'ns1.acme-inc.com.,ns2.acme-inc.com.' 89 | 90 | - name: MX records lookup 91 | assert: 92 | that: 93 | - lookup('dig', 'acme-inc.com/MX', local_dns).split(',') | sort | join(',') == '10 mail001.acme-inc.com.,20 mail002.acme-inc.com.' 94 | - lookup('dig', 'example.com/MX', local_dns).split(',') | sort | join(',') == '10 mail001.example.com.' 95 | 96 | - name: Service records lookup 97 | assert: 98 | that: 99 | - lookup('dig', '_ldap._tcp.acme-inc.com/SRV', local_dns) == '0 100 88 srv010.acme-inc.com.' 100 | 101 | - name: TXT records lookup 102 | assert: 103 | that: 104 | - lookup('dig', '_kerberos.acme-inc.com/TXT', local_dns) == 'KERBEROS.ACME-INC.COM' 105 | - lookup('dig', 'acme-inc.com/TXT', local_dns).split(',') | sort | join(',') == 'more text,some text' 106 | -------------------------------------------------------------------------------- /molecule/shared_inventory/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Remark that common variables are defined in `group_vars/all.yml` 4 | 5 | - name: Primary/Secondary with Shared Inventory 6 | hosts: dns 7 | 8 | roles: 9 | - role: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" 10 | -------------------------------------------------------------------------------- /molecule/shared_inventory/group_vars/all.yml: -------------------------------------------------------------------------------- 1 | --- 2 | bind_statistics_channels: true 3 | bind_statistics_allow: 4 | - any 5 | bind_zone_dir: /var/local/named-zones 6 | bind_zone_file_mode: '0660' 7 | bind_allow_query: 8 | - any 9 | bind_listen_ipv4: 10 | - any 11 | bind_listen_ipv6: 12 | - any 13 | bind_acls: 14 | - name: acl1 15 | match_list: 16 | - 10.11.0/16 17 | bind_forwarders: 18 | - '8.8.8.8' 19 | - '8.8.4.4' 20 | bind_recursion: true 21 | bind_dns64: true 22 | bind_query_log: 'data/query.log' 23 | bind_check_names: 'master ignore' 24 | bind_zone_minimum_ttl: "2D" 25 | bind_zone_ttl: "2W" 26 | bind_zone_time_to_refresh: "2D" 27 | bind_zone_time_to_retry: "2H" 28 | bind_zone_time_to_expire: "2W" 29 | 30 | bind_zones: 31 | - name: 'example.com' 32 | primaries: 33 | - 10.11.0.4 34 | networks: 35 | - '192.0.2' 36 | ipv6_networks: 37 | - '2001:db9::/48' 38 | name_servers: 39 | - ns1.acme-inc.com. 40 | - ns2.acme-inc.com. 41 | hostmaster_email: admin 42 | hosts: 43 | - name: srv001 44 | ip: 192.0.2.1 45 | ipv6: '2001:db9::1' 46 | aliases: 47 | - www 48 | - name: srv002 49 | ip: 192.0.2.2 50 | ipv6: '2001:db9::2' 51 | - name: mail001 52 | ip: 192.0.2.10 53 | ipv6: '2001:db9::3' 54 | mail_servers: 55 | - name: mail001 56 | preference: 10 57 | - name: 'acme-inc.com' 58 | primaries: 59 | - 10.11.0.4 60 | networks: 61 | - '10.11' 62 | ipv6_networks: 63 | - '2001:db8::/48' 64 | name_servers: 65 | - ns1 66 | - ns2 67 | hosts: 68 | - name: ns1 69 | ip: 10.11.0.4 70 | - name: ns2 71 | ip: 10.11.0.5 72 | - name: srv001 73 | ip: 10.11.1.1 74 | ipv6: 2001:db8::1 75 | aliases: 76 | - www 77 | - name: srv002 78 | ip: 10.11.1.2 79 | ipv6: 2001:db8::2 80 | aliases: 81 | - mysql 82 | - name: mail001 83 | ip: 10.11.2.1 84 | ipv6: 2001:db8::d:1 85 | aliases: 86 | - smtp 87 | - mail-in 88 | - name: mail002 89 | ip: 10.11.2.2 90 | ipv6: 2001:db8::d:2 91 | - name: mail003 92 | ip: 10.11.2.3 93 | ipv6: 2001:db8::d:3 94 | aliases: 95 | - imap 96 | - mail-out 97 | - name: srv010 98 | ip: 10.11.0.10 99 | - name: srv011 100 | ip: 10.11.0.11 101 | - name: srv012 102 | ip: 10.11.0.12 103 | mail_servers: 104 | - name: mail001 105 | preference: 10 106 | - name: mail002 107 | preference: 20 108 | services: 109 | - name: _ldap._tcp 110 | weight: 100 111 | port: 88 112 | target: srv010 113 | text: 114 | - name: _kerberos 115 | text: KERBEROS.ACME-INC.COM 116 | - name: '@' 117 | text: 118 | - 'some text' 119 | - 'more text' 120 | -------------------------------------------------------------------------------- /molecule/shared_inventory/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | 5 | driver: 6 | # Specifies the driver that should be used. Podman should also work 7 | name: docker 8 | 9 | lint: | 10 | set -e 11 | yamllint . 12 | ansible-lint -x 106 . 13 | 14 | platforms: 15 | # Set name and hostname 16 | - name: ns4 17 | groups: 18 | - dns 19 | hostname: ns4 20 | docker_networks: 21 | - name: bind 22 | ipam_config: 23 | - subnet: "10.11.0.0/16" 24 | gateway: "10.11.0.254" 25 | networks: 26 | - name: bind 27 | ipv4_address: "10.11.0.4" 28 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos8}-ansible:latest" 29 | command: ${MOLECULE_DOCKER_COMMAND:-""} 30 | volumes: 31 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 32 | - ${PWD}:/etc/ansible/roles/bertvv.bind:ro 33 | - ${PWD}/library:/root/.ansible/plugins/modules:ro 34 | privileged: true 35 | pre_build_image: true 36 | tty: true 37 | environment: 38 | container: docker 39 | 40 | - name: ns5 41 | hostname: ns5 42 | groups: 43 | - dns 44 | networks: 45 | - name: bind 46 | ipv4_address: "10.11.0.5" 47 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos8}-ansible:latest" 48 | command: ${MOLECULE_DOCKER_COMMAND:-""} 49 | volumes: 50 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 51 | - ${PWD}:/etc/ansible/roles/bertvv.bind:ro 52 | - ${PWD}/library:/root/.ansible/plugins/modules:ro 53 | privileged: true 54 | pre_build_image: true 55 | tty: true 56 | environment: 57 | container: docker 58 | 59 | provisioner: 60 | name: ansible 61 | config_options: 62 | defaults: 63 | interpreter_python: auto_legacy_silent 64 | callback_whitelist: profile_tasks 65 | ssh_connection: 66 | pipelining: true 67 | 68 | # Runs the verify.yml playbook 69 | verifier: 70 | name: ansible 71 | -------------------------------------------------------------------------------- /molecule/shared_inventory/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | gather_facts: false 5 | 6 | tasks: 7 | 8 | # Only collect network facts to reduce setup time 9 | - name: Gathering Facts 10 | setup: 11 | gather_subset: 12 | - 'network' 13 | 14 | - name: Set local_dns variable for dig 15 | set_fact: 16 | local_dns: "{{ '@' + ansible_default_ipv4.address }}" 17 | 18 | - name: IPv4 Forward lookups 19 | assert: 20 | that: 21 | - lookup('dig', 'ns1.acme-inc.com', local_dns ) == '10.11.0.4' 22 | - lookup('dig', 'ns2.acme-inc.com', local_dns) == '10.11.0.5' 23 | - lookup('dig', 'srv001.acme-inc.com', local_dns) == '10.11.1.1' 24 | - lookup('dig', 'srv002.acme-inc.com', local_dns) == '10.11.1.2' 25 | - lookup('dig', 'mail001.acme-inc.com', local_dns) == '10.11.2.1' 26 | - lookup('dig', 'mail002.acme-inc.com', local_dns) == '10.11.2.2' 27 | - lookup('dig', 'mail003.acme-inc.com', local_dns) == '10.11.2.3' 28 | - lookup('dig', 'srv010.acme-inc.com', local_dns) == '10.11.0.10' 29 | - lookup('dig', 'srv011.acme-inc.com', local_dns) == '10.11.0.11' 30 | - lookup('dig', 'srv012.acme-inc.com', local_dns) == '10.11.0.12' 31 | - lookup('dig', 'srv001.example.com', local_dns) == '192.0.2.1' 32 | - lookup('dig', 'srv002.example.com', local_dns) == '192.0.2.2' 33 | - lookup('dig', 'mail001.example.com', local_dns) == '192.0.2.10' 34 | 35 | - name: IPv4 Reverse lookups 36 | assert: 37 | that: 38 | - lookup('dig', '10.11.0.4/PTR', local_dns) == 'ns1.acme-inc.com.' 39 | - lookup('dig', '10.11.0.5/PTR', local_dns) == 'ns2.acme-inc.com.' 40 | - lookup('dig', '10.11.1.1/PTR', local_dns) == 'srv001.acme-inc.com.' 41 | - lookup('dig', '10.11.1.2/PTR', local_dns) == 'srv002.acme-inc.com.' 42 | - lookup('dig', '10.11.2.1/PTR', local_dns) == 'mail001.acme-inc.com.' 43 | - lookup('dig', '10.11.2.2/PTR', local_dns) == 'mail002.acme-inc.com.' 44 | - lookup('dig', '10.11.2.3/PTR', local_dns) == 'mail003.acme-inc.com.' 45 | - lookup('dig', '10.11.0.10/PTR', local_dns) == 'srv010.acme-inc.com.' 46 | - lookup('dig', '10.11.0.11/PTR', local_dns) == 'srv011.acme-inc.com.' 47 | - lookup('dig', '10.11.0.12/PTR', local_dns) == 'srv012.acme-inc.com.' 48 | - lookup('dig', '192.0.2.1/PTR', local_dns) == 'srv001.example.com.' 49 | - lookup('dig', '192.0.2.2/PTR', local_dns) == 'srv002.example.com.' 50 | - lookup('dig', '192.0.2.10/PTR', local_dns) == 'mail001.example.com.' 51 | 52 | - name: IPv4 Alias lookups 53 | assert: 54 | that: 55 | - lookup('dig', 'www.acme-inc.com', local_dns) == '10.11.1.1' 56 | - lookup('dig', 'mysql.acme-inc.com', local_dns) == '10.11.1.2' 57 | - lookup('dig', 'smtp.acme-inc.com', local_dns) == '10.11.2.1' 58 | - lookup('dig', 'mail-in.acme-inc.com', local_dns) == '10.11.2.1' 59 | - lookup('dig', 'imap.acme-inc.com', local_dns) == '10.11.2.3' 60 | - lookup('dig', 'mail-out.acme-inc.com', local_dns) == '10.11.2.3' 61 | - lookup('dig', 'www.example.com', local_dns) == '192.0.2.1' 62 | 63 | - name: IPv6 Forward lookups 64 | assert: 65 | that: 66 | - lookup('dig', 'srv001.acme-inc.com/AAAA', local_dns) == '2001:db8::1' 67 | - lookup('dig', 'srv002.acme-inc.com/AAAA', local_dns) == '2001:db8::2' 68 | - lookup('dig', 'mail001.acme-inc.com/AAAA', local_dns) == '2001:db8::d:1' 69 | - lookup('dig', 'mail002.acme-inc.com/AAAA', local_dns) == '2001:db8::d:2' 70 | - lookup('dig', 'mail003.acme-inc.com/AAAA', local_dns) == '2001:db8::d:3' 71 | - lookup('dig', 'srv001.example.com/AAAA', local_dns) == '2001:db9::1' 72 | 73 | - name: IPv6 Reverse lookups 74 | assert: 75 | that: 76 | - lookup('dig', '2001:db8::1/PTR', local_dns) == 'srv001.acme-inc.com.' 77 | - lookup('dig', '2001:db8::2/PTR', local_dns) == 'srv002.acme-inc.com.' 78 | - lookup('dig', '2001:db8::d:1/PTR', local_dns) == 'mail001.acme-inc.com.' 79 | - lookup('dig', '2001:db8::d:2/PTR', local_dns) == 'mail002.acme-inc.com.' 80 | - lookup('dig', '2001:db8::d:3/PTR', local_dns) == 'mail003.acme-inc.com.' 81 | - lookup('dig', '2001:db8::d:3/PTR', local_dns) == 'mail003.acme-inc.com.' 82 | - lookup('dig', '2001:db9::1/PTR', local_dns) == 'srv001.example.com.' 83 | 84 | - name: NS records lookup 85 | assert: 86 | that: 87 | - lookup('dig', 'acme-inc.com/NS', local_dns).split(',') | sort | join(',') == 'ns1.acme-inc.com.,ns2.acme-inc.com.' 88 | - lookup('dig', 'example.com/NS', local_dns).split(',') | sort | join(',') == 'ns1.acme-inc.com.,ns2.acme-inc.com.' 89 | 90 | - name: MX records lookup 91 | assert: 92 | that: 93 | - lookup('dig', 'acme-inc.com/MX', local_dns).split(',') | sort | join(',') == '10 mail001.acme-inc.com.,20 mail002.acme-inc.com.' 94 | - lookup('dig', 'example.com/MX', local_dns).split(',') | sort | join(',') == '10 mail001.example.com.' 95 | 96 | - name: Service records lookup 97 | assert: 98 | that: 99 | - lookup('dig', '_ldap._tcp.acme-inc.com/SRV', local_dns) == '0 100 88 srv010.acme-inc.com.' 100 | 101 | - name: TXT records lookup 102 | assert: 103 | that: 104 | - lookup('dig', '_kerberos.acme-inc.com/TXT', local_dns) == 'KERBEROS.ACME-INC.COM' 105 | - lookup('dig', 'acme-inc.com/TXT', local_dns).split(',') | sort | join(',') == 'more text,some text' 106 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Initialise distribution-specific variables 4 | - name: Source specific variables 5 | include_vars: "{{ item }}" 6 | with_first_found: 7 | - "{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml" 8 | - "{{ ansible_distribution }}.yml" 9 | - "{{ ansible_os_family }}-{{ ansible_distribution_major_version }}.yml" 10 | - "{{ ansible_os_family }}.yml" 11 | - "{{ ansible_distribution_file_variety }}-{{ ansible_distribution_major_version }}.yml" 12 | - "{{ ansible_distribution_file_variety }}.yml" 13 | tags: bind 14 | 15 | - name: Check `primaries` or `forwarders` was set for each zone 16 | assert: 17 | that: 18 | - item.primaries is defined or item.forwarders is defined 19 | quiet: yes 20 | loop: "{{ bind_zones }}" 21 | loop_control: 22 | label: "{{ item.name }}" 23 | tags: bind 24 | 25 | # Fix molecule and ci failures 26 | - name: Update package cache for Debian based distros 27 | apt: 28 | update_cache: yes 29 | become: yes 30 | changed_when: false 31 | when: ansible_os_family == 'Debian' 32 | tags: bind 33 | 34 | - name: Assert that all XFR keys exist in the key list 35 | assert: 36 | that: bind_dns_keys | selectattr("name","equalto",bind_key_mapping[item]) | list | count > 0 37 | loop: "{{ bind_key_mapping.keys() | list }}" 38 | when: bind_key_mapping | list | count > 0 39 | 40 | - name: Install BIND 41 | package: 42 | pkg: "{{ item }}" 43 | state: present 44 | become: yes 45 | with_items: 46 | - "{{ bind_packages }}" 47 | tags: bind 48 | 49 | - name: Ensure runtime directories referenced in config exist 50 | file: 51 | path: "{{ item }}" 52 | state: directory 53 | owner: "{{ bind_owner }}" 54 | group: "{{ bind_group }}" 55 | mode: 0770 56 | become: yes 57 | with_items: 58 | - "{{ bind_dir }}/dynamic" 59 | - "{{ bind_dir }}/data" 60 | - "{{ bind_zone_dir }}" 61 | tags: bind 62 | 63 | - name: Ensure Directory for Cached Secondary Zones exists 64 | file: 65 | path: "{{ bind_secondary_dir }}" 66 | state: directory 67 | owner: "{{ bind_owner }}" 68 | group: "{{ bind_group }}" 69 | mode: 0770 70 | setype: named_cache_t 71 | become: yes 72 | tags: bind 73 | 74 | - name: Create serial, based on UTC UNIX time 75 | command: date -u +%s 76 | register: timestamp 77 | changed_when: false 78 | run_once: true 79 | check_mode: false 80 | tags: bind 81 | 82 | # file to set keys for XFR authentication 83 | - name: Create extra config for authenticated XFR request 84 | template: 85 | src: auth_transfer.j2 86 | dest: "{{ bind_conf_dir }}/{{ auth_file }}" 87 | mode: 0640 88 | owner: root 89 | group: "{{ bind_group }}" 90 | become: yes 91 | when: bind_dns_keys is defined and bind_dns_keys|length > 0 92 | notify: reload bind 93 | tags: bind 94 | 95 | - name: Configure zones 96 | include_tasks: zones.yml 97 | tags: bind 98 | 99 | - name: Main BIND config file 100 | template: 101 | src: etc_named.conf.j2 102 | dest: "{{ bind_config }}" 103 | owner: "{{ bind_owner }}" 104 | group: "{{ bind_group }}" 105 | mode: 0640 106 | setype: named_conf_t 107 | validate: 'named-checkconf %s' 108 | become: yes 109 | notify: reload bind 110 | tags: bind 111 | 112 | - name: Start BIND service 113 | service: 114 | name: "{{ bind_service }}" 115 | state: started 116 | enabled: true 117 | become: yes 118 | tags: bind 119 | -------------------------------------------------------------------------------- /tasks/zones.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set list of all host IP addresses 3 | set_fact: 4 | host_all_addresses: "{{ ansible_all_ipv4_addresses | union(ansible_all_ipv6_addresses) }}" 5 | tags: bind 6 | 7 | - name: Read forward zone hashes 8 | shell: 'grep -s "^; Hash:" {{ bind_zone_dir }}/{{ item.name }} || true' 9 | changed_when: false 10 | check_mode: false 11 | register: forward_hashes_temp 12 | with_items: 13 | - "{{ bind_zones }}" 14 | run_once: true 15 | loop_control: 16 | label: "{{ item.name }}" 17 | tags: bind 18 | 19 | - name: Create dict of forward hashes 20 | set_fact: 21 | forward_hashes: "{{ forward_hashes|default([]) + [ {'hash': item.stdout|default(), 'name': item.item.name} ] }}" 22 | with_items: 23 | - "{{ forward_hashes_temp.results }}" 24 | run_once: true 25 | loop_control: 26 | label: "{{ item.item.name }}" 27 | tags: bind 28 | 29 | - name: Read reverse ipv4 zone hashes 30 | shell: "grep -s \"^; Hash:\" {{ bind_zone_dir }}/{{ ('.'.join(item.1.replace(item.1+'.','').split('.')[::-1])) }}.in-addr.arpa || true" 31 | changed_when: false 32 | check_mode: false 33 | register: reverse_hashes_temp 34 | with_subelements: 35 | - "{{ bind_zones }}" 36 | - networks 37 | - flags: 38 | skip_missing: true 39 | run_once: true 40 | loop_control: 41 | label: "{{ item.1 }}" 42 | tags: bind 43 | 44 | - name: Create dict of reverse hashes 45 | set_fact: 46 | reverse_hashes: "{{ reverse_hashes|default([]) + [ {'hash': item.0.stdout|default(), 'network': item.1} ] }}" 47 | with_subelements: 48 | - "{{ reverse_hashes_temp.results }}" 49 | - item 50 | run_once: true 51 | loop_control: 52 | label: "{{ item.1.name |default(item.0.cmd.split(' ')[4]) }}" 53 | tags: bind 54 | 55 | - name: Read reverse ipv6 zone hashes 56 | shell: "grep -s \"^; Hash:\" {{ bind_zone_dir }}/{{ (item.1 | ipaddr('revdns'))[-(9+(item.1|regex_replace('^.*/','')|int)//2):-1] }} || true" 57 | changed_when: false 58 | check_mode: false 59 | register: reverse_hashes_ipv6_temp 60 | with_subelements: 61 | - "{{ bind_zones }}" 62 | - ipv6_networks 63 | - flags: 64 | skip_missing: true 65 | run_once: true 66 | loop_control: 67 | label: "{{ item.1 }}" 68 | tags: bind 69 | 70 | - name: Create dict of reverse ipv6 hashes 71 | set_fact: 72 | reverse_hashes_ipv6: "{{ reverse_hashes_ipv6|default([]) + [ {'hash': item.0.stdout|default(), 'network': item.1} ] }}" 73 | with_subelements: 74 | - "{{ reverse_hashes_ipv6_temp.results }}" 75 | - item 76 | run_once: true 77 | loop_control: 78 | label: "{{ item.1.name |default(item.0.cmd.split(' ')[4]) }}" 79 | tags: bind 80 | 81 | - name: Create forward lookup zone file 82 | template: 83 | src: bind_zone.j2 84 | dest: "{{ bind_zone_dir }}/{{ item.name }}" 85 | owner: "{{ bind_owner }}" 86 | group: "{{ bind_group }}" 87 | mode: "{{ bind_zone_file_mode }}" 88 | setype: named_zone_t 89 | validate: 'named-checkzone -d {{ item.name }} %s' 90 | become: yes 91 | with_items: 92 | - "{{ bind_zones }}" 93 | loop_control: 94 | label: "{{ item.name }}" 95 | when: > 96 | (item.create_forward_zones is not defined or item.create_forward_zones) and 97 | ((item.type is defined and item.type == 'primary') or 98 | (item.type is not defined and item.primaries is defined and 99 | (host_all_addresses | intersect(item.primaries) | length > 0))) 100 | notify: reload bind 101 | tags: bind 102 | 103 | - name: Create reverse lookup zone file 104 | template: 105 | src: reverse_zone.j2 106 | dest: "{{ bind_zone_dir }}/{{ ('.'.join(item.1.replace(item.1+'.','').split('.')[::-1])) }}.in-addr.arpa" 107 | owner: "{{ bind_owner }}" 108 | group: "{{ bind_group }}" 109 | mode: "{{ bind_zone_file_mode }}" 110 | setype: named_zone_t 111 | validate: "named-checkzone {{ ('.'.join(item.1.replace(item.1+'.','').split('.')[::-1])) }}.in-addr.arpa %s" 112 | become: yes 113 | with_subelements: 114 | - "{{ bind_zones }}" 115 | - networks 116 | - flags: 117 | skip_missing: true 118 | loop_control: 119 | label: "{{ item.1 }}" 120 | when: > 121 | (item.create_reverse_zones is not defined or item.create_reverse_zones) and 122 | ((item[0].type is defined and item[0].type == 'primary') or 123 | (item[0].type is not defined and item[0].primaries is defined and 124 | (host_all_addresses | intersect(item[0].primaries) | length > 0))) 125 | notify: reload bind 126 | tags: bind 127 | 128 | - name: Create reverse IPv6 lookup zone file 129 | template: 130 | src: reverse_zone_ipv6.j2 131 | dest: "{{ bind_zone_dir }}/{{ (item.1 | ipaddr('revdns'))[-(9+(item.1|regex_replace('^.*/','')|int)//2):-1] }}" 132 | owner: "{{ bind_owner }}" 133 | group: "{{ bind_group }}" 134 | mode: "{{ bind_zone_file_mode }}" 135 | setype: named_zone_t 136 | validate: "named-checkzone {{ (item.1 | ipaddr('revdns'))[-(9+(item.1|regex_replace('^.*/','')|int)//2):] }} %s" 137 | become: yes 138 | with_subelements: 139 | - "{{ bind_zones }}" 140 | - ipv6_networks 141 | - flags: 142 | skip_missing: true 143 | loop_control: 144 | label: "{{ item.1 }}" 145 | when: > 146 | (item.create_reverse_zones is not defined or item.create_reverse_zones) and 147 | ((item[0].type is defined and item[0].type == 'primary') or 148 | (item[0].type is not defined and item[0].primaries is defined and 149 | (host_all_addresses | intersect(item[0].primaries) | length > 0))) 150 | notify: reload bind 151 | tags: bind 152 | -------------------------------------------------------------------------------- /templates/auth_transfer.j2: -------------------------------------------------------------------------------- 1 | // {{ ansible_managed }} 2 | {% if bind_key_mapping | length > 0 %} 3 | {% for primary in bind_key_mapping.keys() %} 4 | server {{ primary }} { 5 | keys { {{ bind_key_mapping[primary] }}; }; 6 | }; 7 | {% endfor %} 8 | 9 | {% endif %} 10 | server {{ ansible_default_ipv4.address }} { 11 | keys { {% for mykey in bind_dns_keys %} {{ mykey.name }}; {% endfor %} }; 12 | }; 13 | 14 | {% for mykey in bind_dns_keys %} 15 | key {{ mykey.name }} { 16 | algorithm {{ mykey.algorithm }}; 17 | secret "{{ mykey.secret }}"; 18 | }; 19 | {% endfor %} 20 | -------------------------------------------------------------------------------- /templates/bind_zone.j2: -------------------------------------------------------------------------------- 1 | {# 2 | # First create a dict holding the entire zone information and create a hash 3 | # from it, that it can be compared with subsequent task executions. In this 4 | # way the serial will only be updated if there are some content changes. 5 | #} 6 | {% set _zone_data = {} %} 7 | {% set _ = _zone_data.update({'ttl': bind_zone_ttl}) %} 8 | {% set _ = _zone_data.update({'domain': item.name}) %} 9 | {% set _ = _zone_data.update({'mname': item.name_servers|default([])}) %} 10 | {% set _ = _zone_data.update({'aname': item.other_name_servers|default([])}) %} 11 | {% set _ = _zone_data.update({'mail': item.mail_servers|default([])}) %} 12 | {% if item.hostmaster_email is defined %} 13 | {% set _ = _zone_data.update({'rname': (( item.hostmaster_email )) + ('' if (item.hostmaster_email is search('\.')) else ('.' + _zone_data['domain']))}) %} 14 | {% else %} 15 | {% set _ = _zone_data.update({'rname': 'hostmaster.' + _zone_data['domain']}) %} 16 | {% endif %} 17 | {% set _ = _zone_data.update({'refresh': bind_zone_time_to_refresh}) %} 18 | {% set _ = _zone_data.update({'retry': bind_zone_time_to_retry}) %} 19 | {% set _ = _zone_data.update({'expire': bind_zone_time_to_expire}) %} 20 | {% set _ = _zone_data.update({'minimum': bind_zone_minimum_ttl}) %} 21 | {% set _ = _zone_data.update({'hosts': item.hosts|default([])}) %} 22 | {% set _ = _zone_data.update({'delegate': item.delegate|default([])}) %} 23 | {% set _ = _zone_data.update({'services': item.services|default([])}) %} 24 | {% set _ = _zone_data.update({'text': item.text|default([])}) %} 25 | {% set _ = _zone_data.update({'caa': item.caa|default([])}) %} 26 | {% set _ = _zone_data.update({'naptr': item.naptr|default([])}) %} 27 | {# 28 | # Compare the zone file hash with the current zone data hash and set serial 29 | # accordingly 30 | #} 31 | {% set _zone = {'hash': _zone_data | string | hash('md5')} %} 32 | {% for _result in forward_hashes if _result.name == item.name %} 33 | {% set _hash_serial = _result.hash.split(' ')[2:] %} 34 | {% if _hash_serial and _hash_serial[0] == _zone['hash'] %} 35 | {% set _ = _zone.update({'serial': _hash_serial[1]}) %} 36 | {% else %} 37 | {% set _ = _zone.update({'serial': timestamp.stdout}) %} 38 | {% endif %} 39 | {% endfor %} 40 | {# 41 | # Eventually output the zone data 42 | #} 43 | ; Hash: {{ _zone['hash'] }} {{ _zone['serial'] }} 44 | ; Zone file for {{ _zone_data['domain'] }} 45 | {{ ansible_managed | comment(decoration='; ') }} 46 | 47 | $ORIGIN {{ _zone_data['domain'] }}. 48 | $TTL {{ _zone_data['ttl'] }} 49 | 50 | {% if _zone_data['mname']|length > 0 %} 51 | @ IN SOA {{ _zone_data['mname']|first }}{% if not _zone_data['mname']|first|regex_search('\.$') %}.{{ _zone_data['domain'] }}.{% endif %} {{ _zone_data['rname'] }}. ( 52 | {% else %} 53 | @ IN SOA {{ ansible_hostname }}.{{ _zone_data['domain'] }}. {{ _zone_data['rname'] }}. ( 54 | {% endif %} 55 | {{ _zone['serial'] }} 56 | {{ _zone_data['refresh'] }} 57 | {{ _zone_data['retry'] }} 58 | {{ _zone_data['expire'] }} 59 | {{ _zone_data['minimum'] }} ) 60 | 61 | {% if _zone_data['mname']|length > 0 %} 62 | {% for ns in _zone_data['mname'] %} 63 | IN NS {{ ns }}{% if not ns|regex_search('\.$') %}.{{ _zone_data['domain'] }}.{% endif %} 64 | 65 | {% endfor %} 66 | {% else %} 67 | IN NS {{ ansible_hostname }}.{{ _zone_data['domain'] }}. 68 | {% endif %} 69 | {% for ns in _zone_data['aname'] %} 70 | IN NS {{ ns }}. 71 | {% endfor %} 72 | 73 | {% for mail in _zone_data['mail'] %} 74 | {% if loop.first %}@{% else %} {% endif %} IN MX {{ mail.preference}} {{ mail.name }}{% if not mail.name.endswith('.') %}.{{ _zone_data['domain'] }}.{% endif %} 75 | {% endfor %} 76 | 77 | {% if _zone_data['delegate']|length > 0 %} 78 | {% for host in _zone_data['delegate'] %} 79 | {{ host.zone.ljust(20) }} IN NS {{ host.dns }} 80 | {% endfor %} 81 | {% endif %} 82 | 83 | {% if _zone_data['hosts']|length > 0 %} 84 | {% for host in _zone_data['hosts'] %} 85 | {% if host.ip is defined %} 86 | {% if host.ip is string %} 87 | {% if "$GENERATE" not in host.name.upper() %} 88 | {{ host.name.ljust(20) }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN A {{ host.ip }} 89 | {% endif %} 90 | {% if "$GENERATE" in host.name.upper() %} 91 | {{ host.name.ljust(20) }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN A {{ host.ip }} 92 | {% endif %} 93 | {% else %} 94 | {% for ip in host.ip %} 95 | {{ host.name.ljust(20) }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN A {{ ip }} 96 | {% endfor %} 97 | {% endif %} 98 | {% endif %} 99 | {% if host.ipv6 is defined %} 100 | {% if host.ipv6 is string %} 101 | {{ host.name.ljust(20) }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN AAAA {{ host.ipv6 }} 102 | {% else %} 103 | {% for ip6 in host.ipv6 %} 104 | {{ host.name.ljust(20) }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN AAAA {{ ip6 }} 105 | {% endfor %} 106 | {% endif %} 107 | {% endif %} 108 | {% if host.aliases is defined %} 109 | {% for alias in host.aliases %} 110 | {% if "$GENERATE" not in host.name.upper() %} 111 | {{ (alias.name|default(alias)).ljust(20) }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN {{ alias.type|default('cname')|upper}} {{ host.name }} 112 | {% endif %} 113 | {% if "$GENERATE" in host.name.upper() %} 114 | {{ alias.ljust(20) }} IN CNAME {{ host.name.rsplit(None, 1)[1] }} 115 | {% endif %} 116 | {% endfor %} 117 | {% endif %} 118 | {% if host.sshfp is defined %} 119 | {% for sshfp in host.sshfp %} 120 | {{ host.name.ljust(20) }} IN SSHFP {{ sshfp}} 121 | {% endfor %} 122 | {% endif %} 123 | {% endfor %} 124 | {% else %} 125 | {{ ansible_hostname.ljust(26) }} IN A {{ ansible_default_ipv4.address }} 126 | {% endif %} 127 | {% for service in _zone_data['services'] %} 128 | {{ service.name.ljust(20) }}{{ (service.ttl|string).rjust(6) if service.ttl is defined else ''.ljust(6) }} IN SRV {{ service.priority|default('0') }} {{ service.weight|default('0') }} {{ service.port }} {{ service.target }} 129 | {% endfor %} 130 | {% for text in _zone_data['text'] %} 131 | {% if text.text is string %} 132 | {{ text.name.ljust(20) }} IN TXT "{{ text.text }}" 133 | {% else %} 134 | {% for entry in text.text %} 135 | {{ text.name.ljust(20) }} IN TXT "{{ entry }}" 136 | {% endfor %} 137 | {% endif %} 138 | {% endfor %} 139 | {% for caa in _zone_data['caa'] %} 140 | {% if caa.text is string %} 141 | {{ caa.name.ljust(20) }} IN CAA {{ caa.text }} 142 | {% else %} 143 | {% for entry in caa.text %} 144 | {{ caa.name.ljust(20) }} IN CAA {{ entry }} 145 | {% endfor %} 146 | {% endif %} 147 | {% endfor %} 148 | {% for naptr in _zone_data['naptr'] %} 149 | {{ naptr.name.ljust(20) }} IN NAPTR {{ naptr.order|default('100') }} {{ naptr.pref|default('10') }} "{{ naptr.flags }}" "{{ naptr.service }}" "{{ naptr.regex }}" {{ naptr.replacement }} 150 | {% endfor %} 151 | -------------------------------------------------------------------------------- /templates/etc_named.conf.j2: -------------------------------------------------------------------------------- 1 | // 2 | // named.conf 3 | // 4 | {{ ansible_managed | comment('c') }} 5 | // 6 | {% for acl in bind_acls %} 7 | acl "{{ acl.name }}" { 8 | {% for match in acl.match_list %} 9 | {{ match }}; 10 | {% endfor %} 11 | }; 12 | 13 | {% endfor %} 14 | options { 15 | {% for port in bind_listen_ipv4_port %} 16 | listen-on port {{ port }} { {{ bind_listen_ipv4|join('; ') }}; }; 17 | {% endfor %} 18 | {% for port in bind_listen_ipv6_port %} 19 | listen-on-v6 port {{ port }} { {{ bind_listen_ipv6|join('; ') }}; }; 20 | {% endfor %} 21 | directory "{{ bind_dir }}"; 22 | dump-file "{{ bind_dir }}/data/cache_dump.db"; 23 | statistics-file "{{ bind_dir }}/data/named_stats.txt"; 24 | memstatistics-file "{{ bind_dir }}/data/named_mem_stats.txt"; 25 | allow-query { {{ bind_allow_query|join('; ') }}; }; 26 | {% if bind_acls|length != 0 %} 27 | allow-transfer { {% for acl in bind_acls %}"{{ acl.name }}"; {% endfor %}}; 28 | {% endif %} 29 | {% if bind_check_names is defined %} 30 | check-names {{ bind_check_names }}; 31 | {% endif %} 32 | 33 | recursion {% if bind_recursion %}yes{% else %}no{% endif %}; 34 | {% if bind_recursion %}allow-recursion { {{ bind_allow_recursion|join('; ') }}; }; 35 | {% endif %} 36 | {% if bind_forwarders|length > 0 %}forwarders { {{ bind_forwarders|join('; ') }}; };{% endif %} 37 | {% if bind_forward_only %}forward only;{% endif %} 38 | 39 | rrset-order { order {{ bind_rrset_order }}; }; 40 | 41 | {% if bind_dnssec_enable %} dnssec-enable {{ bind_dnssec_enable }};{% endif %} 42 | dnssec-validation {{ bind_dnssec_validation }}; 43 | 44 | /* Path to ISC DLV key */ 45 | bindkeys-file "{{ bind_bindkeys_file }}"; 46 | 47 | managed-keys-directory "{{ bind_dir }}/dynamic"; 48 | 49 | pid-file "{{ bind_pid_file }}"; 50 | session-keyfile "{{ bind_session_keyfile }}"; 51 | {% if bind_query_log is defined %} 52 | 53 | querylog yes; 54 | {% endif %} 55 | {% if bind_dns64 %} 56 | dns64 64:ff9b::/96 { 57 | clients { {{ bind_dns64_clients|join('; ') }}; }; 58 | }; 59 | {% endif %} 60 | }; 61 | 62 | {% if bind_statistics_channels %} 63 | statistics-channels { 64 | inet {{ bind_statistics_host }} port {{ bind_statistics_port }} allow { {{ bind_statistics_allow|join('; ') }}; }; 65 | }; 66 | {% endif %} 67 | 68 | logging { 69 | channel default_debug { 70 | file "{{ bind_log }}"; 71 | severity dynamic; 72 | print-time yes; 73 | }; 74 | {% if bind_query_log is defined %} 75 | channel querylog { 76 | {% if bind_query_log.file is defined %} 77 | file "{{ bind_query_log.file }}" versions {{ bind_query_log.versions }} size {{ bind_query_log.size }}; 78 | {% else %} 79 | file "{{ bind_query_log }}" versions 600 size 20m; 80 | {% endif %} 81 | severity dynamic; 82 | print-time yes; 83 | }; 84 | category queries { querylog; }; 85 | {% endif %} 86 | {% if bind_other_logs is defined %} 87 | 88 | {% for log in bind_other_logs %} 89 | channel {{ log.name }} { 90 | file "{{ log.file }}" versions {{ log.versions }} size {{ log.size }}; 91 | severity dynamic; 92 | print-time yes; 93 | }; 94 | category "{{ log.name }}" { "{{ log.name }}"; }; 95 | {% endfor %} 96 | {% endif %} 97 | }; 98 | 99 | {% for file in bind_default_zone_files %} 100 | include "{{ file }}"; 101 | {% endfor %} 102 | {% for file in bind_extra_include_files %} 103 | include "{{ file }}"; 104 | {% endfor %} 105 | {% if bind_zones is defined %} 106 | {% for bind_zone in bind_zones %} 107 | 108 | {% if bind_zone.create_forward_zones is not defined or bind_zone.create_forward_zones %} 109 | {# Start: set zone type #} 110 | {% set _all_addresses = ansible_all_ipv4_addresses | union(ansible_all_ipv6_addresses) %} 111 | {% if bind_zone.type is defined and bind_zone.type == 'primary' %} 112 | {% set _type = 'primary' %} 113 | {% elif bind_zone.type is defined and bind_zone.type == 'secondary' %} 114 | {% set _type = 'secondary' %} 115 | {% elif bind_zone.type is defined and bind_zone.type == 'forward' %} 116 | {% set _type = 'forward' %} 117 | {% elif bind_zone.type is not defined and bind_zone.primaries is defined and (_all_addresses|intersect(bind_zone.primaries)|length > 0) %} 118 | {% set _type = 'primary' %} 119 | {% elif bind_zone.type is not defined and bind_zone.primaries is defined and not (_all_addresses|intersect(bind_zone.primaries)|length > 0) %} 120 | {% set _type = 'secondary' %} 121 | {% elif bind_zone.type is not defined and bind_zone.forwarders is defined %} 122 | {% set _type = 'forward' %} 123 | {% endif %} 124 | {# End: set zone type #} 125 | zone "{{ bind_zone.name }}" IN { 126 | {% if _type == 'primary' %} 127 | type master; 128 | file "{{ bind_zone_dir }}/{{ bind_zone.name }}"; 129 | notify yes; 130 | {% if bind_zone.also_notify is defined %} 131 | also-notify { {{ bind_zone.also_notify|join('; ') }}; }; 132 | {% endif %} 133 | {% if bind_zone.allow_update is defined %} 134 | allow-update { {{ bind_zone.allow_update|join('; ') }}; }; 135 | {% else %} 136 | allow-update { none; }; 137 | {% endif %} 138 | {% elif _type == 'secondary' %} 139 | type slave; 140 | masters { {{ bind_zone.primaries|join('; ') }}; }; 141 | file "{{ bind_secondary_dir }}/{{ bind_zone.name }}"; 142 | {% elif _type == 'forward' %} 143 | type forward; 144 | forward only; 145 | forwarders { {{ bind_zone.forwarders|join('; ') }}; }; 146 | {% endif %} 147 | }; 148 | {% endif %} 149 | {% if bind_zone.networks is defined %} 150 | {% if bind_zone.create_reverse_zones is not defined or bind_zone.create_reverse_zones %} 151 | {% for network in bind_zone.networks %} 152 | 153 | zone "{{ ('.'.join(network.replace(network+'.','').split('.')[::-1])) }}.in-addr.arpa" IN { 154 | {% if _type == 'primary' %} 155 | type master; 156 | file "{{ bind_zone_dir }}/{{ ('.'.join(network.replace(network+'.','').split('.')[::-1])) }}.in-addr.arpa"; 157 | notify yes; 158 | {% if bind_zone.also_notify is defined %} 159 | also-notify { {{ bind_zone.also_notify|join('; ') }}; }; 160 | {% endif %} 161 | {% if bind_zone.allow_update is defined %} 162 | allow-update { {{ bind_zone.allow_update|join('; ') }}; }; 163 | {% else %} 164 | allow-update { none; }; 165 | {% endif %} 166 | {% elif _type == 'secondary' %} 167 | type slave; 168 | masters { {{ bind_zone.primaries|join('; ') }}; }; 169 | file "{{ bind_secondary_dir }}/{{ ('.'.join(network.replace(network+'.','').split('.')[::-1])) }}.in-addr.arpa"; 170 | {% elif _type == 'forward' %} 171 | type forward; 172 | forward only; 173 | forwarders { {{ bind_zone.forwarders|join('; ') }}; }; 174 | {% endif %} 175 | }; 176 | {% endfor %} 177 | {% endif %} 178 | {% endif %} 179 | {% if bind_zone.ipv6_networks is defined %} 180 | {% if bind_zone.create_reverse_zones is not defined or bind_zone.create_reverse_zones %} 181 | {% for network in bind_zone.ipv6_networks %} 182 | 183 | zone "{{ (network | ipaddr('revdns'))[-(9+(network|regex_replace('^.*/','')|int)//2):] }}" IN { 184 | {% if _type == 'primary' %} 185 | type master; 186 | file "{{ bind_zone_dir }}/{{ (network | ipaddr('revdns'))[-(9+(network|regex_replace('^.*/','')|int)//2):-1] }}"; 187 | notify yes; 188 | {% if bind_zone.also_notify is defined %} 189 | also-notify { {{ bind_zone.also_notify|join('; ') }}; }; 190 | {% endif %} 191 | {% if bind_zone.allow_update is defined %} 192 | allow-update { {{ bind_zone.allow_update|join('; ') }}; }; 193 | {% else %} 194 | allow-update { none; }; 195 | {% endif %} 196 | {% elif _type == 'secondary' %} 197 | type slave; 198 | masters { {{ bind_zone.primaries|join('; ') }}; }; 199 | file "{{ bind_secondary_dir }}/{{ (network | ipaddr('revdns'))[-(9+(network|regex_replace('^.*/','')|int)//2):-1] }}"; 200 | {% elif _type == 'forward' %} 201 | type forward; 202 | forward only; 203 | forwarders { {{ bind_zone.forwarders|join('; ') }}; }; 204 | {% endif %} 205 | }; 206 | {% endfor %} 207 | {% endif %} 208 | {% endif %} 209 | {% endfor %} 210 | {% endif %} 211 | -------------------------------------------------------------------------------- /templates/reverse_zone.j2: -------------------------------------------------------------------------------- 1 | {# 2 | # First create a dict holding the entire zone information and create a hash 3 | # from it, that it can be compared with subsequent task executions. In this 4 | # way the serial will only be updated if there are some content changes. 5 | #} 6 | {% set _zone_data = {} %} 7 | {% set _ = _zone_data.update({'ttl': bind_zone_ttl}) %} 8 | {% set _ = _zone_data.update({'domain': item.0.name}) %} 9 | {% set _ = _zone_data.update({'mname': item.0.name_servers|default([])}) %} 10 | {% set _ = _zone_data.update({'aname': item.0.other_name_servers|default([])}) %} 11 | {% if item.0.hostmaster_email is defined %} 12 | {% set _ = _zone_data.update({'rname': (( item.0.hostmaster_email )) + ('' if (item.0.hostmaster_email is search('\.')) else ('.' + _zone_data['domain']))}) %} 13 | {% else %} 14 | {% set _ = _zone_data.update({'rname': 'hostmaster.' + _zone_data['domain']}) %} 15 | {% endif %} 16 | {% set _ = _zone_data.update({'refresh': bind_zone_time_to_refresh}) %} 17 | {% set _ = _zone_data.update({'retry': bind_zone_time_to_retry}) %} 18 | {% set _ = _zone_data.update({'expire': bind_zone_time_to_expire}) %} 19 | {% set _ = _zone_data.update({'minimum': bind_zone_minimum_ttl}) %} 20 | {% set _ = _zone_data.update({'hosts': item.0.hosts|default([]) | selectattr('ip', 'defined') | selectattr('ip', 'string') | selectattr('ip', 'search', '^'+item.1) | list}) %} 21 | {% set _ = _zone_data.update({'revip': ('.'.join(item.1.replace(item.1+'.','').split('.')[::-1]))}) %} 22 | {# 23 | # Compare the zone file hash with the current zone data hash and set serial 24 | # accordingly 25 | #} 26 | {% set _zone = {'hash': _zone_data | string | hash('md5')} %} 27 | {% for _result in reverse_hashes if _result.network == item.1 %} 28 | {% set _hash_serial = _result.hash.split(' ')[2:] %} 29 | {% if _hash_serial and _hash_serial[0] == _zone['hash'] %} 30 | {% set _ = _zone.update({'serial': _hash_serial[1]}) %} 31 | {% else %} 32 | {% set _ = _zone.update({'serial': timestamp.stdout}) %} 33 | {% endif %} 34 | {% endfor %} 35 | {# 36 | # Eventually output the zone data 37 | #} 38 | ; Hash: {{ _zone['hash'] }} {{ _zone['serial'] }} 39 | ; Reverse zone file for {{ _zone_data['domain'] }} 40 | {{ ansible_managed | comment(decoration='; ') }} 41 | 42 | $TTL {{ _zone_data['ttl'] }} 43 | $ORIGIN {{ ('.'.join(item.1.replace(item.1+'.','').split('.')[::-1])) }}.in-addr.arpa. 44 | 45 | {% if _zone_data['mname']|length > 0 %} 46 | @ IN SOA {{ _zone_data['mname']|first }}{% if not _zone_data['mname']|first|regex_search('\.$') %}.{{ _zone_data['domain'] }}.{% endif %} {{ _zone_data['rname'] }}. ( 47 | {% else %} 48 | @ IN SOA {{ ansible_hostname }}.{{ _zone_data['domain'] }}. {{ _zone_data['rname'] }}. ( 49 | {% endif %} 50 | {{ _zone['serial'] }} 51 | {{ _zone_data['refresh'] }} 52 | {{ _zone_data['retry'] }} 53 | {{ _zone_data['expire'] }} 54 | {{ _zone_data['minimum'] }} ) 55 | 56 | {% if _zone_data['mname']|length > 0 %} 57 | {% for ns in _zone_data['mname'] %} 58 | IN NS {{ ns }}{% if not ns|regex_search('\.$') %}.{{ _zone_data['domain'] }}.{% endif %} 59 | 60 | {% endfor %} 61 | {% else %} 62 | IN NS {{ ansible_hostname }}.{{ _zone_data['domain'] }}. 63 | {% endif %} 64 | {% for ns in _zone_data['aname'] %} 65 | IN NS {{ ns }}. 66 | {% endfor %} 67 | 68 | {% if _zone_data['hosts']|length > 0 %} 69 | {% for host in _zone_data['hosts'] %} 70 | {% if host.ip is defined %} 71 | {% if host.ip == item.1 %} 72 | @ IN PTR {{ host.name }}.{{ _zone_data['domain'] }}. 73 | {% else %} 74 | {% if host.ip is string and host.ip.startswith(item.1) %} 75 | {% if host.name == '@' %} 76 | {{ ('.'.join(host.ip.replace(item.1+'.','').split('.')[::-1])).ljust(16) }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN PTR {{ _zone_data['domain'] }}. 77 | {% else %} 78 | {% if "$GENERATE" not in host.name.upper() %} 79 | {{ ('.'.join(host.ip.replace(item.1+'.','').split('.')[::-1])).ljust(16) }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN PTR {{ host.name }}.{{ _zone_data['domain'] }}. 80 | {% endif %} 81 | {% if "$GENERATE" in host.name.upper() %} 82 | {{ host.name.rsplit(None, 1)[0] }} {{ ('.'.join(host.ip.replace(item.1+'.','').split('.')[::-1])).ljust(16) }} IN PTR {{ host.name.rsplit(None, 1)[1] }}.{{ _zone_data['domain'] }}. 83 | {% endif %} 84 | {% endif %} 85 | {% else %} 86 | {% for ip in host.ip %} 87 | {% if ip.startswith(item.1) %} 88 | {{ ('.'.join(ip.replace(item.1+'.','').split('.')[::-1])).ljust(16) }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN PTR {{ _zone_data['domain'] }}. 89 | {% if host.name == '@' %} 90 | {% else %} 91 | {{ ('.'.join(ip.replace(item.1+'.','').split('.')[::-1])).ljust(16) }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN PTR {{ host.name }}.{{ _zone_data['domain'] }}. 92 | {% endif %} 93 | {% endif %} 94 | {% endfor %} 95 | {% endif %} 96 | {% endif %} 97 | {% endif %} 98 | {% endfor %} 99 | {% else %} 100 | {{ ('.'.join(ansible_default_ipv4.address.replace(item.1+'.','').split('.')[::-1])).ljust(16) }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN PTR {{ ansible_hostname }}.{{ _zone_data['domain'] }}. 101 | {% endif %} 102 | -------------------------------------------------------------------------------- /templates/reverse_zone_ipv6.j2: -------------------------------------------------------------------------------- 1 | {# 2 | # First create a dict holding the entire zone information and create a hash 3 | # from it, that it can be compared with subsequent task executions. In this 4 | # way the serial will only be updated if there are some content changes. 5 | #} 6 | {% set _zone_data = {} %} 7 | {% set _ = _zone_data.update({'ttl': bind_zone_ttl}) %} 8 | {% set _ = _zone_data.update({'domain': item.0.name}) %} 9 | {% set _ = _zone_data.update({'mname': item.0.name_servers|default([])}) %} 10 | {% set _ = _zone_data.update({'aname': item.0.other_name_servers|default([])}) %} 11 | {% if item.0.hostmaster_email is defined %} 12 | {% set _ = _zone_data.update({'rname': (( item.0.hostmaster_email )) + ('' if (item.0.hostmaster_email is search('\.')) else ('.' + _zone_data['domain']))}) %} 13 | {% else %} 14 | {% set _ = _zone_data.update({'rname': 'hostmaster.' + _zone_data['domain']}) %} 15 | {% endif %} 16 | {% set _ = _zone_data.update({'refresh': bind_zone_time_to_refresh}) %} 17 | {% set _ = _zone_data.update({'retry': bind_zone_time_to_retry}) %} 18 | {% set _ = _zone_data.update({'expire': bind_zone_time_to_expire}) %} 19 | {% set _ = _zone_data.update({'minimum': bind_zone_minimum_ttl}) %} 20 | {% set _ = _zone_data.update({'hosts': item.0.hosts|default([]) | selectattr('ipv6','defined') | selectattr('ipv6','string') | selectattr('ipv6', 'search', '^'+item.1|regex_replace(':\/.*$','')) | list }) %} 21 | {% set _ = _zone_data.update({'revip': (item.1 | ipaddr('revdns'))[-(9+(item.1|regex_replace('^.*/','')|int)//2):] }) %} 22 | {# 23 | # Compare the zone file hash with the current zone data hash and set serial 24 | # accordingly 25 | #} 26 | {% set _zone = {'hash': _zone_data | string | hash('md5')} %} 27 | {% for _result in reverse_hashes_ipv6 if _result.network == item.1 %} 28 | {% set _hash_serial = _result.hash.split(' ')[2:] %} 29 | {% if _hash_serial and _hash_serial[0] == _zone['hash'] %} 30 | {% set _ = _zone.update({'serial': _hash_serial[1]}) %} 31 | {% else %} 32 | {% set _ = _zone.update({'serial': timestamp.stdout}) %} 33 | {% endif %} 34 | {% endfor %} 35 | {# 36 | # Eventually output the zone data 37 | #} 38 | ; Hash: {{ _zone['hash'] }} {{ _zone['serial'] }} 39 | ; Reverse zone file for {{ _zone_data['domain'] }} 40 | {{ ansible_managed | comment(decoration='; ') }} 41 | 42 | $TTL {{ _zone_data['ttl'] }} 43 | $ORIGIN {{ (item.1 | ipaddr('revdns'))[-(9+(item.1|regex_replace('^.*/','')|int)//2):] }} 44 | 45 | {% if _zone_data['mname']|length > 0 %} 46 | @ IN SOA {{ _zone_data['mname']|first }}{% if not _zone_data['mname']|first|regex_search('\.$') %}.{{ _zone_data['domain'] }}.{% endif %} {{ _zone_data['rname'] }}. ( 47 | {% else %} 48 | @ IN SOA {{ ansible_hostname }}.{{ _zone_data['domain'] }}. {{ _zone_data['rname'] }}. ( 49 | {% endif %} 50 | {{ _zone['serial'] }} 51 | {{ _zone_data['refresh'] }} 52 | {{ _zone_data['retry'] }} 53 | {{ _zone_data['expire'] }} 54 | {{ _zone_data['minimum'] }} ) 55 | 56 | {% if _zone_data['mname']|length > 0 %} 57 | {% for ns in _zone_data['mname'] %} 58 | IN NS {{ ns }}{% if not ns|regex_search('\.$') %}.{{ _zone_data['domain'] }}.{% endif %} 59 | 60 | {% endfor %} 61 | {% else %} 62 | IN NS {{ ansible_hostname }}.{{ _zone_data['domain'] }}. 63 | {% endif %} 64 | {% for ns in _zone_data['aname'] %} 65 | IN NS {{ ns }}. 66 | {% endfor %} 67 | 68 | {% if _zone_data['hosts']|length > 0 %} 69 | {% for host in _zone_data['hosts'] %} 70 | {% if host.ipv6 is defined %} 71 | {% if host.ipv6 == item.1 %} 72 | @ IN PTR {{ host.name }}.{{ _zone_data['domain'] }}. 73 | {% else %} 74 | {% if host.ipv6 is string and host.ipv6.startswith(item.1|regex_replace(':\/.*$','')) %} 75 | {% if host.name == '@' %} 76 | {{ host.ipv6 | ipaddr('revdns') }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN PTR {{ _zone_data['domain'] }}. 77 | {% else %} 78 | {{ host.ipv6 | ipaddr('revdns') }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN PTR {{ host.name }}.{{ _zone_data['domain'] }}. 79 | {% endif %} 80 | {% else %} 81 | {% for ip in host.ipv6 %} 82 | {% if ip.startswith(item.1|regex_replace(':\/.*$','')) %} 83 | {{ ip | ipaddr('revdns') }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN PTR {{ _zone_data['domain'] }}. 84 | {% if host.name == '@' %} 85 | {% else %} 86 | {{ ip | ipaddr('revdns') }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN PTR {{ host.name }}.{{ _zone_data['domain'] }}. 87 | {% endif %} 88 | {% endif %} 89 | {% endfor %} 90 | {% endif %} 91 | {% endif %} 92 | {% endif %} 93 | {% endfor %} 94 | {% else %} 95 | {{ ansible_default_ipv6.address | ipaddr('revdns') }}{{ (host.ttl|string).rjust(6) if host.ttl is defined else ''.ljust(6) }} IN PTR {{ ansible_hostname }}.{{ _zone_data['domain'] }}. 96 | {% endif %} 97 | -------------------------------------------------------------------------------- /vars/Archlinux.yml: -------------------------------------------------------------------------------- 1 | # Distro specific variables for Arch Linux 2 | --- 3 | 4 | bind_packages: 5 | - python-netaddr 6 | - python-dns 7 | - bind 8 | - bind-tools 9 | 10 | bind_service: named 11 | 12 | # Main config file 13 | bind_config: /etc/named.conf 14 | 15 | # Zone files included in the installation 16 | bind_default_zone_files: [] 17 | 18 | # Directory with run-time stuff 19 | bind_dir: /var/named 20 | bind_conf_dir: "{{ bind_dir }}" 21 | auth_file: "auth_transfer.conf" 22 | bind_auth_file: "{{ bind_conf_dir }}/{{ auth_file }}" 23 | 24 | bind_owner: root 25 | bind_group: named 26 | 27 | bind_bindkeys_file: "/etc/named.iscdlv.key" 28 | bind_pid_file: "/run/named/named.pid" 29 | bind_session_keyfile: "/run/named/session.key" 30 | 31 | # Custom location for zone files 32 | bind_zone_dir: "{{ bind_dir }}" 33 | bind_secondary_dir: "{{ bind_dir }}/secondary" 34 | -------------------------------------------------------------------------------- /vars/Debian.yml: -------------------------------------------------------------------------------- 1 | # Distro specific variables for Debian 2 | --- 3 | bind_default_python_version: '3' 4 | bind_packages: 5 | - "{{ ( bind_python_version == '3' ) | ternary( 'python3-netaddr', 'python-netaddr' ) }}" 6 | - "{{ ( bind_python_version == '3' ) | ternary( 'python3-dnspython', 'python-dnspython' ) }}" 7 | - bind9 8 | - bind9utils 9 | 10 | bind_service: bind9 11 | 12 | # Main config file 13 | bind_config: /etc/bind/named.conf 14 | 15 | # Localhost zone 16 | bind_default_zone_files: 17 | - /etc/bind/named.conf.default-zones 18 | 19 | # Directory with run-time stuff 20 | bind_dir: /var/cache/bind 21 | bind_conf_dir: "/etc/bind" 22 | auth_file: "auth_transfer.conf" 23 | bind_auth_file: "{{ bind_conf_dir }}/{{ auth_file }}" 24 | 25 | bind_owner: root 26 | bind_group: bind 27 | 28 | bind_bindkeys_file: "/etc/named.iscdlv.key" 29 | bind_pid_file: "/run/named/named.pid" 30 | bind_session_keyfile: "/run/named/session.key" 31 | 32 | # Custom location for zone files 33 | bind_zone_dir: "{{ bind_dir }}" 34 | bind_secondary_dir: "{{ bind_dir }}/secondary" 35 | -------------------------------------------------------------------------------- /vars/FreeBSD.yml: -------------------------------------------------------------------------------- 1 | # Distro specific variables for FreeBSD 2 | --- 3 | 4 | bind_packages: 5 | - py37-netaddr 6 | - py37-dnspython 7 | - bind911 8 | 9 | bind_service: named 10 | 11 | # Main config file 12 | bind_config: /usr/local/etc/namedb/named.conf 13 | 14 | # Localhost zone 15 | bind_default_zone_files: 16 | - /usr/local/etc/namedb/named.conf.default-zones 17 | 18 | # Directory with run-time stuff 19 | bind_dir: /var/cache/named 20 | bind_conf_dir: "/usr/local/etc/namedb/" 21 | auth_file: "auth_transfer.conf" 22 | bind_auth_file: "{{ bind_conf_dir }}/{{ auth_file }}" 23 | 24 | bind_owner: bind 25 | bind_group: bind 26 | 27 | bind_bindkeys_file: "/usr/local/etc/namedb/bind.keys" 28 | bind_pid_file: "/var/run/named/named.pid" 29 | bind_session_keyfile: "/var/run/named/session.key" 30 | 31 | # Custom location for zone files 32 | bind_zone_dir: "{{ bind_dir }}" 33 | bind_secondary_dir: "{{ bind_dir }}/secondary" 34 | -------------------------------------------------------------------------------- /vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | # Distro specific variables for RedHat 2 | --- 3 | bind_default_python_version: "{{ ( ansible_distribution_major_version == '8' ) | ternary( '3', '2' ) }}" 4 | bind_packages: 5 | - "{{ ( bind_python_version == '3' ) | ternary( 'python3-netaddr', 'python-netaddr' ) }}" 6 | - "{{ ( bind_python_version == '3' ) | ternary( 'python3-dns', 'python-dns' ) }}" 7 | - bind 8 | - bind-utils 9 | 10 | bind_service: named 11 | 12 | # Main config file 13 | bind_config: /etc/named.conf 14 | 15 | # Zone files included in the installation 16 | bind_default_zone_files: 17 | - /etc/named.root.key 18 | - /etc/named.rfc1912.zones 19 | 20 | # Directory with run-time stuff 21 | bind_dir: /var/named 22 | bind_conf_dir: "/etc/named" 23 | auth_file: "auth_transfer.conf" 24 | bind_auth_file: "{{ bind_conf_dir }}/{{ auth_file }}" 25 | 26 | bind_owner: root 27 | bind_group: named 28 | 29 | bind_bindkeys_file: "/etc/named.iscdlv.key" 30 | bind_pid_file: "/run/named/named.pid" 31 | bind_session_keyfile: "/run/named/session.key" 32 | 33 | # Custom location for zone files 34 | bind_zone_dir: "{{ bind_dir }}" 35 | bind_secondary_dir: "{{ bind_dir }}/secondary" 36 | --------------------------------------------------------------------------------