bertvv.bind

Ansible Role BIND

Actions Status

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

❯ 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 the shared_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:

  1. Install Docker on your machine.
  2. As suggested by Molecule, create a Python virtual environment.
  3. Install the required tools: python3 -m pip install molecule molecule-docker docker netaddr dnspython yamllint ansible-lint.
  4. 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:

  1. Run molecule linter: molecule lint.
  2. Provision containers: molecule converge.
  3. Connect to a container: molecule login --host ns1.
  4. Navigate to the role directory: cd /etc/ansible/roles/bertvv.bind.
  5. Run the verify playbook:
ansible-playbook -c local -i "`hostname`," -i molecule/default/inventory.ini molecule/default/verify.yml
  1. Repeat steps 2-4 for ns2 and ns3.

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:

Informazioni sul progetto

Sets up ISC BIND as an authoritative DNS server for one or more domains (primary and/or secondary).

Installa
ansible-galaxy install bertvv.bind
Licenza
other
Download
406.9k
Proprietario
Hi! My contribs are often related to my job (teaching Linux), but are mostly done in my free time. I can't always respond quickly to PRs and Issues. Sorry!