bertvv.bind
Ansible Role BIND
This Ansible role sets up ISC BIND as a primary DNS server for multiple domains. Its main tasks include:
- Installing BIND
- Configuring the main setup file (for primary, secondary, or forwarding server)
- Creating forward and reverse lookup zone files
This role can handle several forward and reverse zones, including IPv6 support. Although recursive lookups are supported, it's generally not recommended. Consider using a different role for a caching or forwarding DNS server.
If you find this role useful, please give it a star on the Ansible Galaxy page. Thank you!
Check out the change log for updates between versions.
WARNING: If you have been using this role since before v5.0.0, review the change log for crucial breaking changes. Older playbooks may not work if you upgrade to v5.0.0.
Supported Platforms
This role works on multiple platforms, as listed in meta/main.yml. We aim to automate tests for each platform (see .ci.yml), though this isn't always achievable.
Here's some extra commentary about platforms without automated tests:
- Arch Linux and FreeBSD should function, but we currently cannot test on these distributions due to the lack of suitable Docker images.
- CentOS 6 should work, but idempotence tests might fail even after successful BIND installation.
Requirements
You need to install the python-netaddr
package (required for the ipaddr
filter) and dnspython
on the management machine.
Role Variables
Variable | Default | Comments (type) |
---|---|---|
bind_acls |
[] |
A list of ACL definitions, with keys name: and match_list: . See below for an example. |
bind_allow_query |
['localhost'] |
Hosts allowed to query this DNS server. Set to ['any'] to allow all hosts. |
bind_allow_recursion |
['any'] |
As above, but for recursive queries. |
bind_check_names |
[] |
Check host names for RFC compliance and take the defined action (e.g., warn , ignore , fail ). |
bind_dns_keys |
[] |
List of binding keys with keys name: , algorithm: , and secret: . See below for an example. |
bind_dns64 |
false |
If true, enables DNS64 support. |
bind_dns64_clients |
['any'] |
List of clients for the DNS64 function (can be any ACL). |
bind_dnssec_enable |
true |
If true, enables DNSSEC. |
bind_dnssec_validation |
true |
If true, enables DNSSEC validation. |
bind_extra_include_files |
[] |
List of custom config files to include in the main config file. |
bind_forward_only |
false |
If true, configures BIND as a caching name server. |
bind_forwarders |
[] |
List of name servers to forward DNS requests to. |
bind_listen_ipv4 |
['127.0.0.1'] |
IPv4 addresses of network interfaces to listen on. Set to ['any'] to listen on all interfaces. |
bind_listen_ipv4_port |
[53] |
Port number(s) to listen on for IPv4 addresses. |
bind_listen_ipv6 |
['::1'] |
IPv6 addresses of network interfaces to listen on. |
bind_listen_ipv6_port |
[53] |
Port number(s) to listen on for IPv6 addresses. |
bind_log |
data/named.run |
Path to the log file. |
bind_other_logs |
- | List of logging channels to configure, with details for each zone. |
bind_query_log |
- | Mapping with keys file: (e.g., data/query.log ), versions: , and size: to enable query log. |
bind_recursion |
false |
Determines whether to forward requests where the DNS server is not authoritative. |
bind_rrset_order |
random |
Defines order for DNS round robin (either random or cyclic ). |
bind_statistics_channels |
false |
If true, configures BIND with a statistics-channels clause (currently only supports listening on one interface). |
bind_statistics_allow |
['127.0.0.1'] |
Hosts that can access server statistics. |
bind_statistics_host |
127.0.0.1 |
IP address of the interface for the statistics service to listen on. |
bind_statistics_port |
8053 | Network port for the statistics service to listen on. |
bind_zone_dir |
- | Sets a custom absolute path for the server directory (for zone files, etc.) instead of the default. |
bind_key_mapping |
[] | Maps TSIG keys to specific primaries. |
bind_zones |
n/a | List of mappings with zone definitions. See below this table for examples. |
- allow_update |
['none'] |
Hosts allowed to dynamically update the DNS zone. |
- also_notify |
- | Servers receiving notifications when the primary zone file is reloaded. |
- create_forward_zones |
- | Skip creation of forward zones if set to false . |
- create_reverse_zones |
- | Skip creation of reverse zones if set to false . |
- delegate |
[] |
Zone delegation. |
- forwarders |
- | List of forwarders for the forward type zone. |
- hostmaster_email |
hostmaster |
Email address of the system administrator for the zone. |
- hosts |
[] |
Host definitions. |
- ipv6_networks |
[] |
List of IPv6 networks in CIDR notation (e.g., 2001:db8::/48). |
- mail_servers |
[] |
List of mail server mappings (name: and preference: ). |
- name_servers |
[ansible_hostname] |
DNS servers for this domain. |
- name |
example.com |
Domain name. |
- naptr |
[] |
Mappings for NAPTR records (name: , order: , pref: , etc.). |
- networks |
['10.0.2'] |
Networks part of the domain. |
- other_name_servers |
[] |
DNS servers outside of this domain. |
- primaries |
- | Primary DNS servers for this zone. |
- services |
[] |
List of services advertised by SRV records. |
- text |
[] |
Mappings for TXT records (name: and text: ). |
- caa |
[] |
Mappings for CAA records (name: and text: ). |
- type |
- | Optional zone type (e.g., primary , secondary , or forward ). |
bind_zone_file_mode |
0640 | File permissions for the main config file (named.conf). |
bind_zone_minimum_ttl |
1D |
Minimum TTL in the SOA record. |
bind_zone_time_to_expire |
1W |
Expire time in the SOA record. |
bind_zone_time_to_refresh |
1D |
Refresh time in the SOA record. |
bind_zone_time_to_retry |
1H |
Retry time in the SOA record. |
bind_zone_ttl |
1W |
TTL in the SOA record. |
bind_python_version |
- | Python version for Ansible (usually 2 or 3 , defaults to OS standard). |
† A best practice for an authoritative DNS server is to keep recursion disabled. However, in some cases, allowing recursion might be necessary.
Minimal Variables for a Working Zone
To set up a functional authoritative DNS server, define the following variables:
Variable | Primary | Secondary | Forward |
---|---|---|---|
bind_allow_query |
V | V | V |
bind_listen_ipv4 |
V | V | V |
bind_zones |
V | V | V |
- hosts |
V | -- | -- |
- name_servers |
V | -- | -- |
- name |
V | V | -- |
- networks |
V | V | V |
- primaries |
V | V | -- |
- forwarders |
-- | -- | V |
Domain Definitions
bind_zones:
# Example of a primary zone
- name: mydomain.com # Domain name
create_reverse_zones: false # Skip reverse zones creation
primaries:
- 192.0.2.1 # Primary server(s) for this zone
name_servers:
- pub01.mydomain.com.
- pub02.mydomain.com.
hosts:
- name: pub01
ip: 192.0.2.1
ipv6: 2001:db8::1
aliases:
- ns1
- name: pub02
ip: 192.0.2.2
ipv6: 2001:db8::2
aliases:
- ns2
- name: '@' # Enables "http://mydomain.com/"
ip:
- 192.0.2.3 # Multiple IPs for one host
- 192.0.2.4 # Result in DNS round robin
sshfp: # SSH fingerprint
- "3 1 1262006f9a45bb36b1aa14f45f354b694b77d7c3"
- "3 2 e5921564252fe10d2dbafeb243733ed8b1d165b8fa6d5a0e29198e5793f0623b"
ipv6:
- 2001:db8::2
- 2001:db8::3
aliases:
- www
- name: priv01 # Different subnet IP resulting in multiple reverse zones
ip: 10.0.0.1
- name: mydomain.net.
aliases:
- name: sub01
type: DNAME # Example of a DNAME alias record
networks:
- '192.0.2'
- '10'
- '172.16'
delegate:
- zone: foo
dns: 192.0.2.1
services:
- name: _ldap._tcp
weight: 100
port: 88
target: dc001
naptr: # NAPTR record, used for telephony
- name: "sip"
order: 100
pref: 10
flags: "S"
service: "SIP+D2T"
regex: "!^.*$!sip:[email protected]!"
replacement: "_sip._tcp.example.com."
# Minimal example of a secondary zone
- name: acme.com
primaries:
- 172.17.0.2
networks:
- "172.17"
# Minimal example of a forward zone
- name: acme.com
forwarders:
- 172.17.0.2
networks:
- "172.17"
Hosts
Hostnames this DNS server should resolve can be added in bind_zones.hosts
as mappings of name:
, ip:
, aliases:
and sshfp:
. Aliases can be CNAME (default) or DNAME records.
To access http://example.com/
, set the hostname of your web server to '@'
(must be quoted). In BIND syntax, @
refers to the domain name itself.
For multiple IP addresses for a host, add multiple entries for the same name (e.g., priv01
in the example). This will create multiple A/AAAA records for that host, enabling DNS round robin, a simple load balancing method. The order of returned IPs can be configured with the bind_rrset_order
variable.
Networks
As demonstrated, not all hosts may be on the same subnet. This role generates appropriate reverse lookup zones for each subnet. Specify all subnets in bind_zones.networks
or the host won't receive a PTR record for reverse lookups.
Remember to list only the network part! For a Class B IP (e.g., "172.16"), use quotes in the variable file, or the YAML parser will treat it as a float.
Following the examples from https://linuxmonk.ch/wordpress/index.php/2016/managing-dns-zones-with-ansible/ for the gdnsd package, the zone files are idempotent, meaning that updates only occur with actual content changes.
Zone Types and Auto-Detection
The type
parameter (optional) defines if a zone is primary
, secondary
, or forward
. When omitted, the type is automatically set by checking the host IP addresses and primaries
record. If primaries
is absent and forwarders
are provided, the zone type defaults to forward
.
Auto-detection is particularly useful for multi-site DNS setups. It helps to have "shared" bind_zones
definitions in a single group inventory file for all DNS servers (example: group_vars\dns.yml
). This approach allows you to switch server roles by just updating the primaries
record and re-running the playbook. Test the auto-detection with "shared_inventory" molecule scenario by running: molecule test --scenario-name shared_inventory
.
NOTE
- BIND does not support automated multi-master configuration, so the
primaries
list should have only one entry. - When updating
primaries
to switch from primary to secondary server roles, zones will be cleared and recreated from the template as dynamic updates for existing zones aren't supported yet.
Zone types can also be explicitly defined in the host inventory to skip auto-detection:
# Primary Server
bind_zones:
- name: mydomain.com
type: primary
primaries:
- 192.0.2.1
...
# Secondary Server
bind_zones:
- name: mydomain.com
type: secondary
primaries:
- 192.0.2.1
...
# Forwarder Server
bind_zones:
- name: anotherdomain.com
type: forward
forwarders:
- 192.0.3.1
Zone Delegation
To delegate a zone to a DNS server, simply create a NS
record (under delegate):
foo IN NS 192.0.2.1
Service Records
Service (SRV) records can be added with the services
variable, which should be a list of mappings including required keys name:
(service name), target:
(host providing the service), port:
(TCP/UDP port), and optional keys priority:
(default = 0) and weight:
(default = 0).
ACLs
ACLs can be defined like this:
bind_acls:
- name: acl1
match_list:
- 192.0.2.0/24
- 10.0.0.0/8
The names of the ACLs will be used in the global options for allow-transfer
.
Binding Keys
Binding keys can be defined like this:
bind_dns_keys:
- name: primary_key
algorithm: hmac-sha256
secret: "azertyAZERTY123456"
bind_extra_include_files:
- "{{ bind_auth_file }}"
Tip: The extra include file must be set as an Ansible variable, as the file location depends on the OS.
This will be specified in a file, like {{ bind_auth_file }}
(e.g., /etc/bind/auth_transfer.conf
for Debian), which must be added to the list variable bind_extra_include_files
.
Using TSIG for Zone Transfer (XFR) Authorization
To permit transfers between primary and secondary servers based on a TSIG key, set the mapping in the bind_key_mapping
variable:
bind_key_mapping:
primary_ip: TSIG-keyname
Each primary can have only one key (per view).
A check will ensure the key is present in the bind_dns_keys
mapping. This will create a server statement for the a
in bind_auth_file
on a secondary server with the specified key.
Dependencies
No dependencies.
Example Playbooks
Refer to the test playbooks and inventory for detailed examples of most features.
Standard Inventory
- Common variables for all servers defined in all.yml
bind_zone
variable defined on a per-host basis (primary, secondary and forwarder)
❯ tree --dirsfirst molecule/default
molecule/default
├── group_vars
│ └── all.yml
├── host_vars
│ ├── ns1.yml # Primary
│ ├── ns2.yml # Secondary
│ └── ns3.yml # Forwarder
├── converge.yml
...
Shared Inventory
Common variables for primary and secondary servers defined in all.yml.
❯ tree --dirsfirst molecule/shared_inventory
molecule/shared_inventory
├── group_vars
│ └── all.yml
├── converge.yml
...
Testing
This role is tested using Ansible Molecule. Tests are automatically triggered on Github Actions after each commit and PR.
The Molecule configuration will:
- Run Yamllint and Ansible Lint
- Create three Docker containers: one primary (
ns1
), one secondary (ns2
), and one forwarder (ns3
) -default
molecule scenario - Run a syntax check
- Apply the role with a test playbook and check idempotence
- Run acceptance tests with verify playbook
- Create two additional Docker containers, one primary (
ns4
) and one secondary (ns5
), and run theshared_inventory
scenario
This testing process is repeated for all supported Linux distributions.
Local Test Environment
To run acceptance tests locally, install the necessary tools or use this reproducible setup in a VirtualBox VM (configured with Vagrant): https://github.com/bertvv/ansible-testenv.
Manual installation steps:
- Install Docker on your machine.
- As suggested by Molecule, create a Python virtual environment.
- Install the required tools:
python3 -m pip install molecule molecule-docker docker netaddr dnspython yamllint ansible-lint
. - Navigate to the role's root directory and run
molecule test
.
Molecule automatically deletes containers after testing. If you want to inspect them, run molecule converge
followed by molecule login --host HOSTNAME
.
The Docker containers use images created by Jeff Geerling for Ansible testing (look for images named geerlingguy/docker-DISTRO-ansible
). You can use any of his images, but only those mentioned in meta/main.yml are supported.
The default setup creates three CentOS 8 containers (the primary supported platform). Change the distribution with the MOLECULE_DISTRO
variable, for example:
MOLECULE_DISTRO=debian9 molecule test
or
MOLECULE_DISTRO=debian9 molecule converge
You can run acceptance tests on all servers using molecule verify
.
Note: Verification tests require Ansible to communicate directly with the Docker container. This might fail on macOS due to limitations with container IP access (see #2670).
Workaround:
- Run molecule linter:
molecule lint
. - Provision containers:
molecule converge
. - Connect to a container:
molecule login --host ns1
. - Navigate to the role directory:
cd /etc/ansible/roles/bertvv.bind
. - Run the verify playbook:
ansible-playbook -c local -i "`hostname`," -i molecule/default/inventory.ini molecule/default/verify.yml
- Repeat steps 2-4 for
ns2
andns3
.
License
BSD
Contributors
This role was made possible thanks to many contributors. If you have ideas for improvements, feel free to share!
You can post issues, feature requests, or suggestions in the Issues section, and pull requests are welcome. Please create a separate branch for your changes to avoid conflicts post-merge. Don't hesitate to add yourself to the contributor list below with your pull request!
Maintainers:
Contributors include:
- Aido
- Angel Barrera
- B. Verschueren
- Boris Momčilović
- Brad Durrow
- Christopher Hicks
- And many more…
Sets up ISC BIND as an authoritative DNS server for one or more domains (primary and/or secondary).
ansible-galaxy install bertvv.bind