- Jinja 78.9%
- Python 21.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .gitea/workflows | ||
| .github/skills/molecule-role-testing | ||
| bind9-grammar | ||
| defaults | ||
| docs | ||
| handlers | ||
| meta | ||
| molecule | ||
| scripts | ||
| tasks | ||
| templates | ||
| tests | ||
| vars | ||
| .ansible-lint | ||
| .bind9version | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| CONFIGURATION_GRAMMAR.md | ||
| README.md | ||
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
tsigentry 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'orvar: 'null', otherwise jinja will assume you want it to be empty/null. Normal integers would be defined asvar: 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:
- bind9_default_config
- bind9_group_config
- bind9_site_config
- 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
- Configuration Grammar Reference: Comprehensive guide to all BIND9 configuration options
- Version Support Policy: Supported versions, branching strategy, and release management
- Changelog: Version history and upgrade notes
Contributing
Contributions are welcome! Please see docs/VERSION_SUPPORT.md for:
- Branching strategy
- Testing requirements
- Backporting policies
- Release procedures
Links
- Repository: https://git.valid.dk/workshop/ansible-bind9-role
- Issues: https://git.valid.dk/workshop/ansible-bind9-role/issues
- BIND9 Documentation: https://bind9.readthedocs.io/
License
GPL-3.0-or-later
Author Information
Daniel Akulenok daniel@valid.dk Valid.dk