No description
  • Jinja 78.9%
  • Python 21.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Isaac 5a37547001
All checks were successful
Lint / Lint (push) Successful in 49s
[work-thsk.34] docs: repository and issue links point at workshop/ (#7)
2026-11-03 09:05:57 +00:00
.gitea/workflows Run lint only in CI 2026-09-19 12:06:14 +02:00
.github/skills/molecule-role-testing Run lint only in CI 2026-09-19 12:06:14 +02:00
bind9-grammar docs: Add BIND9 grammar files for v9.18.44 and v9.20.18 2026-02-07 23:31:04 +01:00
defaults [bind9admin-ki9.3] Rebase transactional restore onto role main 2026-09-29 17:41:17 +02:00
docs [work-thsk.34] docs: repository and issue links point at workshop/ (#7) 2026-11-03 09:05:57 +00:00
handlers fix: standardize YAML formatting and improve readability in bind9 configuration files 2026-02-06 13:38:56 +01:00
meta [work-thsk.34] docs: repository and issue links point at workshop/ (#7) 2026-11-03 09:05:57 +00:00
molecule [work-eap] Reactivate restored named config after contract failure 2026-09-29 19:04:26 +02:00
scripts feat: Add grammar fetch and comparison tooling 2026-02-07 22:52:53 +01:00
tasks [work-eap] Reactivate restored named config after contract failure 2026-09-29 19:04:26 +02:00
templates [bind9admin-ki9.3] Rebase transactional restore onto role main 2026-09-29 17:41:17 +02:00
tests Add swapped-destinations regression test (work-9ss) 2026-09-29 00:39:56 +02:00
vars feat: Initialize BIND9 Ansible role 2022-08-04 12:41:40 +02:00
.ansible-lint Fix Molecule targets for BIND 9.18 and 9.20 2026-09-19 11:47:32 +02:00
.bind9version docs: Add branch version marker for 9.18 LTS support 2026-02-07 22:53:18 +01:00
.gitignore Ignore local Ansible runtime state 2026-08-23 20:36:54 +02:00
.gitlab-ci.yml style: Remove excessive blank lines from GitLab CI configuration 2026-01-27 23:18:56 +01:00
AGENTS.md [work-thsk.34] docs: repository and issue links point at workshop/ (#7) 2026-11-03 09:05:57 +00:00
CHANGELOG.md [work-thsk.34] docs: repository and issue links point at workshop/ (#7) 2026-11-03 09:05:57 +00:00
CONFIGURATION_GRAMMAR.md docs: Add BIND9 version comparison and migration guidance 2026-02-07 23:43:51 +01:00
README.md [work-thsk.34] docs: repository and issue links point at workshop/ (#7) 2026-11-03 09:05:57 +00:00

bind9

A feature-complete ansible role for installing and configuring bind9. The purpose of this role is to fully template out the entire official bind9 configuration file format.

Version Compatibility

BIND9 Version Role Branch Status Supported Platforms
9.20.x+ main ✅ Supported Debian 12-13, Ubuntu 22.04-24.04
9.18.x (LTS) 9.18 ✅ Maintenance Debian 11-13, Ubuntu 20.04-24.04

For detailed version support policy, branching strategy, and migration guidance, see docs/VERSION_SUPPORT.md.

Minimum Requirements:

  • Ansible: 2.13+
  • Python: 3.8+

What the role does:

  • Fully configures named.conf
  • Checks that the config is valid
  • Loads the config into bind

What the role does not do:

  • Manage your zones and records
  • Maintain every aspect of bind (rndc config, etc)
  • Persist or return secret material to callers; the explicit tsig entry point resolves approved references only for the duration of its run

Bugs

Or, as I call them "happy accidents".

  • If you need a variable to be 0 or null, you need to define it as var: '0' or var: 'null', otherwise jinja will assume you want it to be empty/null. Normal integers would be defined as var: 1, letting jinja type it as an integer.
  • If a named configuration option has the name 'key' or 'keys', it will be referenced as 'keyname' or 'keylist' respectively. key/keys are reserved values in most languages.

Role Variables

General configuration

Review the defaults for a full set of configurable parameters. Here are the most interesting ones:

bind9_backup_config: [true, false]: Backup each named.conf.* file or not. Default is 'true'. This setting is useful for testing out configuration changes but can clutter up the destination directory quite a bit if used across many updates.

bind9_debug_config: [true, false]: Print the resulting YAML configuration tree that was sent to the configuration template. Default is 'false'. Useful for comparing with the resulting named.conf files and comparing values.

bind9_config_indent: [integer]: Indentation level for the configuration template. Default is '4'. Set this value to suit your style. Tabs are not supported.

named.conf

bind configuration is set through the various bind9_*_config parameters. These are, in order of precedence:

  1. bind9_default_config
  2. bind9_group_config
  3. bind9_site_config
  4. bind9_host_config

All these configuration parameters are merged in a way where each successing config supercedes the previous one at a config-file level. To illustrate:

bind9_default_config:
  - name: named.conf.options
    options:
      recursion: true

bind9_group_config:
  - name: named.conf.options
    options:
      recursion: false
      notify: primary-only
  - name: named.conf.local
    zone:
      - name: "."
        type: mirror

bind9_site_config:
  - name: named.conf.local
    zone:
      - name: "."
        type: hint
        file: /etc/share/dns/root.hints

The resulting precedence and overwriting of variables will result in the following bind9_config passed to the configuration generator:

bind9_config:
  - name: named.conf.options
    options:
      recursion: false
      notify: primary-only
  - name: named.conf.local
    zone:
      - name: "."
        type: hint
        file: /etc/share/dns/root.hints

The named.conf.options block in bind9_default_config got completely overwritten by the bind9_group_config, and the bind9_site_config completely overwrote named.conf.local, however, named.conf.options was left intact after merging with bind9_site_config.

Configuration Grammar

The bind9 role tries to replicate the official ISC bind9 configuration format as close as possible, only re-implementing them in YAML format. This means that for the most part, section names are the same as in named.conf but kebab-case ('var-name') is replaced with snake_case ('var_name') If you are missing some statements in your resulting config, it is most likely because of this.

The main configuration variable used are a series of bind_*_config variables (See [Role Variables]) that have the following syntax

Every config starts by defining the file name. Each file can contain any amount of top-level statements, as permitted by named.conf

bind9_host_config:
  - name: FILENAME # The filename of your desired config file.
                   # You also need to specify a corresponding `include:` for the file
    SECTION_NAME: # The section name of the bind config you want to define.
                  # Can be 'acl', 'options', 'zone', etc.
                  # See: https://bind9.readthedocs.io/en/v9_18_4/reference.html#configuration-file-grammar
    SECTION_2_NAME: # Every file can have as many sections as needed. Generally, try to keep 
                    # all definitions and references together in a file.

Any option that can be defined multiple times in a named.conf, must be defined as a list

bind9_host_config:
  - name: named.conf.local
    acl:
      - name: ELEMENT_NAME
        addresses: 
          - 127.0.0.1
          - 127.0.0.2
      - name: ELEMENT_2_NAME
        addresses:
          - 127.0.0.3

Simple options are defined just as that.

      SIMPLE_OPTION: string, boolean or integer value

Some options have several optional parameters. For those, a somewhat flexible configuration format has been created. Common patterns include:

  • Address with Port/DSCP: Used by options like primaries, parental_agents (e.g., address [ port <port> ] [ dscp <dscp> ])
  • Address with Port/TLS: Used by options like forwarders (e.g., address [ port <port> ] [ tls <tls> ])
      ADDRESS_PORT_TLS_OPTION: # Example: forwarders option
      # <option> [ port <port> ] [ tls <tls> ] { <address> [ port <port> ] [ tls <tls> ]; ... }
      # has a few optional syntaxes 
      # Example 1: Simple address list
        - ADDRESS1
        - ADDRESS2
      # Example 2: To define global port/tls, use 'addresses' sub-element
        [ port: PORT ]
        [ tls: TLS_NAME ]
        addresses:
          - ADDRESS1
          - ADDRESS2
          - 127.0.0.1
      # Example 3: To define per-address port/tls, use 'addresses' as a list of dicts
        addresses:
          - address: ADDRESS
            [ port: PORT ]
            [ tls: TLS_NAME ]
          - address: 127.0.0.1
            port: 53
          - address: 127.0.0.1
            port: 853
            tls: dot-tls
          - address: 8.8.8.8
            port: 853
            tls: google-tls
      # Example 4: The various formats can be mixed and matched within the main element
        - ADDRESS1
        - address: ADDRESS2
          port: PORT
          tls: TLS_NAME

Dependencies

No dependencies

Example Playbook

Simple sample config of a recursive BIND server that allows your localnetwork to resolve addresses via

- hosts: servers
  roles:
     - bind9
  vars:
    bind9_host_config:
      - name: named.conf.local
        acl:
          - name: mylan
            addresses:
              - 10.0.0.0/8
      - name: named.conf.options
        options:
          forwarders:
            - 1.1.1.1
          allow-query:
            - mylan
          allow-recursion:
            - mylan

Isolated DNSSEC preview

The dnssec_preview task entry point renders one reviewed named.conf.dnssec fragment with the same canonical role template used for deployment. It runs the requested absolute named-checkconf binary and writes a versioned JSON receipt. Both destinations must be absolute paths in a caller-owned isolated directory; this entry point does not install packages, change active BIND configuration, escalate privileges, or control services.

Callers provide one bind9_host_config fragment, the exact bind9_version, and the closed request below:

bind9_dnssec_preview:
  output_path: /run/bind9-preview/named.conf.dnssec
  receipt_path: /run/bind9-preview/receipt.json
  validator_binary: /usr/bin/named-checkconf
  expected_version: "9.20"

The receipt uses format bind9admin.dnssec-role-preview/v1 and records the reported BIND version plus whether validation executed and its return code. Consumers must fail closed if the receipt is absent or malformed, the reported version differs, or validation does not succeed.

TSIG lifecycle entry point

The tsig task entry point is an explicit, fail-closed path for staged TSIG rotation operations. It accepts a versioned secret-reference-only envelope, resolves filesystem or HTTPS Vault material on the controller under no_log, and renders key and scoped authorization configuration inside the role. Direct filesystem references are restricted to the private /var/lib/bind9admin-worker/tsig root; Vault redirects are rejected.

Rotation callers stage both generations, verify authenticated AXFR or prerequisite-only dynamic-update continuity, cut outbound consumers over while servers still accept both keys, verify the configured new path, and only then remove the old key. Each mutating phase snapshots every touched file, validates the complete active configuration, reloads synchronously, and restores, revalidates, and reloads the prior state on failure. The role publishes only a secret-free, versioned operation receipt.

Invoke this entry point with ansible.builtin.include_role and tasks_from: tsig; it is intentionally not run by the default main entry point.

Documentation

Contributing

Contributions are welcome! Please see docs/VERSION_SUPPORT.md for:

  • Branching strategy
  • Testing requirements
  • Backporting policies
  • Release procedures

License

GPL-3.0-or-later

Author Information

Daniel Akulenok daniel@valid.dk Valid.dk