20 Commits

Author SHA1 Message Date
57d8f88679 Merge pull request 'feat: Establish version maintenance strategy for BIND9 support (#5)' (#7) from main into origin/main
Reviewed-on: #7
2026-02-07 22:02:27 +00:00
Daniel Akulenok
0271be7752 Merge branch 'main' of ssh://git.valid.dk:2222/daniel/ansible-bind9-role into HEAD
All checks were successful
Test / Lint (push) Successful in 16s
Test / Test (push) Has been skipped
2026-02-06 14:32:10 +01:00
Daniel Akulenok
907735c294 fix: improve readability of bind9 configuration and update variable registration 2026-02-06 13:46:09 +01:00
Daniel Akulenok
cdcc4cbbda fix: standardize YAML formatting and improve readability in bind9 configuration files 2026-02-06 13:38:56 +01:00
Daniel Akulenok
30918dc9f7 Add zone option JSON files for BIND9 grammar
All checks were successful
Test / Lint (push) Successful in 17s
Test / Test (push) Has been skipped
- Created primary.zoneopt.json to define grammar for primary zones with various options including allow-query, allow-transfer, and DNSSEC settings.
- Added redirect.zoneopt.json for redirect zones, specifying options like allow-query and primaries.
- Introduced secondary.zoneopt.json for secondary zones, detailing options such as allow-notify, forwarders, and notify configurations.
- Implemented static-stub.zoneopt.json for static stub zones, including server-addresses and server-names options.
- Added stub.zoneopt.json for stub zones, defining options like check-names and forwarders.
- Created zoneopt.json as a general template for zone options, incorporating common fields across different zone types.
2026-01-31 22:05:58 +01:00
Daniel Akulenok
7388e4eaaf Merge branch 'main' of ssh://git.valid.dk:2222/daniel/ansible-bind9-role 2026-01-31 21:29:19 +01:00
Daniel Akulenok
f6eee76e05 fix: ensure no change detection for bind9 configuration files
All checks were successful
Test / Lint (push) Successful in 12s
Test / Test (push) Has been skipped
2026-01-28 23:37:37 +01:00
0fede04e19 Merge pull request 'feature/forwarders-port-tls-support' (#3) from feature/forwarders-port-tls-support into main
All checks were successful
Test / Lint (push) Successful in 13s
Test / Test (push) Has been skipped
Reviewed-on: #3
2026-01-28 22:29:11 +00:00
Daniel Akulenok
5f4bb3ccda feat: add podman installation step in CI workflow
Some checks failed
Test / Lint (pull_request) Successful in 12s
Test / Test (push) Has been skipped
Test / Test (pull_request) Failing after 21s
Test / Lint (push) Successful in 13s
2026-01-28 23:27:46 +01:00
Daniel Akulenok
4cb9cb3e3f fix: add noqa comments for linting in workflow and role inclusion
Some checks failed
Test / Lint (push) Successful in 13s
Test / Lint (pull_request) Successful in 13s
Test / Test (push) Has been skipped
Test / Test (pull_request) Failing after 37s
2026-01-28 23:25:35 +01:00
Daniel Akulenok
45d9861960 refactor: remove unnecessary tags from backup removal task
Some checks failed
Test / Lint (push) Failing after 12s
Test / Lint (pull_request) Failing after 12s
Test / Test (push) Has been skipped
Test / Test (pull_request) Has been skipped
2026-01-28 23:22:05 +01:00
Daniel Akulenok
28f8ca5c12 fix: resolve ansible-lint errors
Some checks failed
Test / Lint (push) Failing after 7s
Test / Lint (pull_request) Failing after 6s
Test / Test (push) Has been skipped
Test / Test (pull_request) Has been skipped
- Quote octal file mode values (0640, 0750 -> '0640', '0750')
- Add 'Prepare' name to prepare.yml play
- Fix truthy value in .gitea/workflows/test.yaml (on -> 'on')
- Use role name 'bind9' instead of path in converge.yml
- Move tags to top-level for Deploy and Validate Configuration block
- Remove unnecessary comments to clean up code
- Ensure all YAML and Ansible files pass ansible-lint production profile
2026-01-28 23:20:56 +01:00
Daniel Akulenok
17a9918685 fix: resolve yamllint errors
Some checks failed
Test / Lint (push) Failing after 16s
Test / Lint (pull_request) Failing after 12s
Test / Test (push) Has been skipped
Test / Test (pull_request) Has been skipped
- Fix line length in meta/argument_specs.yml (wrap long description)
- Remove extra blank lines in molecule/default/collections.yml
- Fix line lengths in tasks/main.yml (wrap long messages)
- Remove trailing spaces from tasks/main.yml
- Ensure all YAML files pass yamllint with relaxed profile
2026-01-28 23:15:23 +01:00
Daniel Akulenok
ca70afbd51 ci: add simplified Gitea Actions workflow for testing
Some checks failed
Test / Lint (pull_request) Failing after 42s
Test / Test (pull_request) Has been skipped
- Add yamllint for YAML style validation (relaxed profile)
- Add ansible-lint for Ansible best practices (production profile)
- Add Molecule test job that runs only on pull requests
- Lint job runs on all push events to main and feature branches
- Test job depends on lint job passing
- Clean, maintainable pipeline configuration
2026-01-28 23:11:04 +01:00
Daniel Akulenok
68a7b62305 chore: update molecule configuration
- Update prepare.yml with test setup
- Update molecule.yml with test infrastructure configuration
2026-01-28 23:03:41 +01:00
Daniel Akulenok
dae9cb60f5 test: add bind9 forwarding DNS server test case
- Create converge.yml with forwarding DNS configuration
- Configure global forwarders with Google and Cloudflare DNS
- Configure forward-only zone for internal.example with TLS
- Create verify.yml with comprehensive test validation
- Test BIND9 installation, service status, and configuration files
- Verify forwarders and forward zones are properly configured
- Test actual DNS resolution via forwarders
2026-01-28 23:03:30 +01:00
Daniel Akulenok
d075e3ec17 docs: update README.md with port/tls parameter patterns
- Add clarification on different parameter combinations (port/dscp vs port/tls)
- Replace generic 'IP_PORT_DSCP_OPTION' with 'ADDRESS_PORT_TLS_OPTION' example
- Update all configuration examples to show port/tls parameters
- Document usage of forwarders with TLS support
- Improve documentation of flexible configuration formats
2026-01-28 23:03:25 +01:00
Daniel Akulenok
e8f84fce0b docs: update CONFIGURATION_GRAMMAR.md for forwarders port/tls support
- Add tls parameter to forwarders grammar in options section
- Add tls parameter to forwarders grammar in zone section
- Update options and zone examples to demonstrate tls usage
- Rename 'Address with Port/DSCP' section to 'Address with Port/TLS'
- Update all data type examples to show port/tls patterns instead of port/dscp
- Document global and per-address port/tls configuration options
2026-01-28 23:03:19 +01:00
Daniel Akulenok
3d2919721b feat: use parent_address_port_tls macro for forwarders
- Update named.conf.options.j2 to use parent_address_port_tls for forwarders
- Update named.conf.zone.j2 to use parent_address_port_tls for forwarders
- Enables support for per-address and global port/tls parameters
2026-01-28 23:03:07 +01:00
Daniel Akulenok
112ba5f7ca feat: implement list_address_port_tls and parent_address_port_tls macros
- Add list_address_port_tls macro for rendering address lists with port and tls parameters
- Add parent_address_port_tls macro for parent statements with global port/tls
- Follow existing naming pattern with separate list_ and parent_ macros
- Supports forwarders, primaries, and similar blocks with port/tls grammar
2026-01-28 23:02:59 +01:00
26 changed files with 3682 additions and 63 deletions

View File

@@ -0,0 +1,63 @@
---
name: Test
on: # noqa: yaml[truthy]
push:
branches:
- main
- feature/**
pull_request:
branches:
- main
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install tools
run: |
pip install --no-cache-dir yamllint ansible-lint
- name: Run yamllint
run: yamllint -d relaxed .
- name: Run ansible-lint
run: ansible-lint --strict --profile=production
test:
name: Test
runs-on: ubuntu-latest
needs: lint
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install podman
run: |
sudo apt-get update
sudo apt-get install -y podman
- name: Install dependencies
run: |
pip install --no-cache-dir \
ansible \
molecule[podman] \
podman-compose \
pyyaml \
jinja2
- name: Run Molecule tests
run: molecule test

View File

@@ -458,6 +458,7 @@ options:
- <address>
- address: <address>
port: <port>
tls: <tls_name>
# DNSSEC
dnssec_enable: <bool> # DEPRECATED in 9.15+
@@ -540,6 +541,19 @@ options:
# Logging
querylog: <bool>
# DNSTAP - DNS traffic capture
dnstap: # List of message types to capture
- type: <auth|client|forwarder|resolver> # Message type (required)
log: <query|response> # Optional: specific direction
dnstap_output: # Output destination (required if dnstap is set)
output_type: <file|unix> # Output type: file or unix socket (required)
output_file: <path> # File path or socket path (required)
size: <size> # Optional: Max file size before rotation
versions: <integer> # Optional: Number of versions to keep
suffix: <increment|timestamp> # Optional: Rotation suffix type
dnstap_identity: <string> # Optional: Identity string (defaults to hostname)
dnstap_version: <string> # Optional: Version string (defaults to BIND version)
# Zone management
check_names: <master|slave|response> <warn|fail|ignore>
check_dup_records: <warn|fail|ignore>
@@ -593,7 +607,8 @@ options:
forwarders:
- 1.1.1.1
- 8.8.8.8
- address: 8.8.8.8
tls: dot-tls
dnssec_validation: auto
@@ -917,6 +932,7 @@ zones:
- <address>
- address: <address>
port: <port>
tls: <tls_name>
# DNSSEC
dnssec_policy: <policy_name> # DNSSEC policy to use
@@ -1017,7 +1033,8 @@ zones:
forward: only
forwarders:
- 10.0.0.1
- 10.0.0.2
- address: 10.0.0.2
tls: internal-tls
```
---
@@ -1079,9 +1096,9 @@ addresses:
- 10.0.0.0/8
```
### Address with Port/DSCP
### Address with Port/TLS
For options accepting `address [port X] [dscp Y]`:
For options accepting `address [port X] [tls Y]` (e.g., `forwarders`):
```yaml
# Simple list
@@ -1089,27 +1106,28 @@ forwarders:
- 1.1.1.1
- 8.8.8.8
# With source port/dscp
# With global port/tls
forwarders:
port: 5353
dscp: 46
port: 853
tls: dot-tls
addresses:
- 1.1.1.1
- 8.8.8.8
# Per-address port/dscp
# Per-address port/tls
forwarders:
- address: 1.1.1.1
port: 53
- address: 8.8.8.8
port: 5353
dscp: 46
port: 853
tls: cloudflare-tls
# Mixed format
forwarders:
- 1.1.1.1
- address: 8.8.8.8
port: 5353
port: 853
tls: dot-tls
```
### Address with Key/TLS

View File

@@ -126,38 +126,43 @@ Simple options are defined just as that.
```
Some options have several optional parameters. For those, a somewhat flexible
configuration format has been created
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> ]`)
```
IP_PORT_DSCP_OPTION: # Any option that is defined as one of:
# <option> [ port <port> ] [ dscp <dscp> ] { <address> [ port <port> ] [ dscp <dscp> ]; ... }
# <option> [ port <port> ] [ dscp <dscp> ] { <address> [ port <port> ] [ key <key> ] [ 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 source port/dscp, use 'addresses' sub-element
# Example 2: To define global port/tls, use 'addresses' sub-element
[ port: PORT ]
[ dscp: DSCP ]
[ tls: TLS_NAME ]
addresses:
- ADDRESS1
- ADDRESS2
- 127.0.0.1
# Example 3: To define target port/dscp, use 'addresses' as a list of dicts
# Example 3: To define per-address port/tls, use 'addresses' as a list of dicts
addresses:
- address: ADDRESS
[ port: PORT ]
[ dscp: DSCP ]
[ tls: TLS_NAME ]
- address: 127.0.0.1
port: 53
- address: 127.0.0.1
dscp: 42
- address: 127.0.0.1
port: 5353
dscp: 42
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
```

View File

@@ -0,0 +1,19 @@
{
"zone": {
"_id": "<string> [ <class> ]",
"_mapbody": {
"type": {
"_grammar": "forward"
},
"forward": {
"_grammar": "( first | only )"
},
"forwarders": {
"_grammar": "[ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... }"
},
"template": {
"_grammar": "<string>"
}
}
}
}

View File

@@ -0,0 +1,19 @@
{
"zone": {
"_id": "<string> [ <class> ]",
"_mapbody": {
"type": {
"_grammar": "hint"
},
"check-names": {
"_grammar": "( fail | warn | ignore )"
},
"file": {
"_grammar": "<quoted_string>"
},
"template": {
"_grammar": "<string>"
}
}
}
}

View File

@@ -0,0 +1,10 @@
{
"zone": {
"_id": "<string> [ <class> ]",
"_mapbody": {
"in-view": {
"_grammar": "<string>"
}
}
}
}

View File

@@ -0,0 +1,171 @@
{
"zone": {
"_id": "<string> [ <class> ]",
"_mapbody": {
"type": {
"_grammar": "mirror"
},
"allow-notify": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-query": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-query-on": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-transfer": {
"_grammar": "[ port <integer> ] [ transport <string> ] { <address_match_element>; ... }"
},
"allow-update-forwarding": {
"_grammar": "{ <address_match_element>; ... }"
},
"also-notify": {
"_grammar": "[ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... }"
},
"check-names": {
"_grammar": "( fail | warn | ignore )"
},
"database": {
"_grammar": "<string>"
},
"file": {
"_grammar": "<quoted_string>"
},
"ixfr-from-differences": {
"_grammar": "<boolean>"
},
"journal": {
"_grammar": "<quoted_string>"
},
"masterfile-format": {
"_grammar": "( raw | text )"
},
"masterfile-style": {
"_grammar": "( full | relative )"
},
"max-ixfr-ratio": {
"_grammar": "( unlimited | <percentage> )"
},
"max-journal-size": {
"_grammar": "( default | unlimited | <sizeval> )"
},
"max-records": {
"_grammar": "<integer>"
},
"max-records-per-type": {
"_grammar": "<integer>"
},
"max-refresh-time": {
"_grammar": "<integer>"
},
"max-retry-time": {
"_grammar": "<integer>"
},
"max-transfer-idle-in": {
"_grammar": "<integer>"
},
"max-transfer-idle-out": {
"_grammar": "<integer>"
},
"max-transfer-time-in": {
"_grammar": "<integer>"
},
"max-transfer-time-out": {
"_grammar": "<integer>"
},
"max-types-per-name": {
"_grammar": "<integer>"
},
"min-refresh-time": {
"_grammar": "<integer>"
},
"min-retry-time": {
"_grammar": "<integer>"
},
"min-transfer-rate-in": {
"_grammar": "<integer> <integer>"
},
"multi-master": {
"_grammar": "<boolean>"
},
"notify": {
"_grammar": "( explicit | master-only | primary-only | <boolean> )"
},
"notify-cfg": {
"_flags": [
"may occur multiple times"
],
"_id": "<string>",
"_mapbody": {
"notify": {
"_grammar": "<boolean>"
},
"notify-defer": {
"_grammar": "<integer>"
},
"notify-delay": {
"_grammar": "<integer>"
},
"notify-source": {
"_grammar": "( <ipv4_address> | * )"
},
"notify-source-v6": {
"_grammar": "( <ipv6_address> | * )"
}
}
},
"notify-defer": {
"_grammar": "<integer>"
},
"notify-delay": {
"_grammar": "<integer>"
},
"notify-source": {
"_grammar": "( <ipv4_address> | * )"
},
"notify-source-v6": {
"_grammar": "( <ipv6_address> | * )"
},
"plugin": {
"_flags": [
"may occur multiple times"
],
"_grammar": "( query ) <string> [ { <unspecified-text> } ]"
},
"primaries": {
"_grammar": "[ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... }"
},
"provide-zoneversion": {
"_grammar": "<boolean>"
},
"request-expire": {
"_grammar": "<boolean>"
},
"request-ixfr": {
"_grammar": "<boolean>"
},
"request-ixfr-max-diffs": {
"_grammar": "<integer>"
},
"template": {
"_grammar": "<string>"
},
"transfer-source": {
"_grammar": "( <ipv4_address> | * )"
},
"transfer-source-v6": {
"_grammar": "( <ipv6_address> | * )"
},
"try-tcp-refresh": {
"_grammar": "<boolean>"
},
"zero-no-soa-ttl": {
"_grammar": "<boolean>"
},
"zone-statistics": {
"_grammar": "( full | terse | none | <boolean> )"
}
}
}
}

2315
bind9-grammar/options.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,258 @@
{
"zone": {
"_id": "<string> [ <class> ]",
"_mapbody": {
"type": {
"_grammar": "primary"
},
"allow-query": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-query-on": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-transfer": {
"_grammar": "[ port <integer> ] [ transport <string> ] { <address_match_element>; ... }"
},
"allow-update": {
"_grammar": "{ <address_match_element>; ... }"
},
"also-notify": {
"_grammar": "[ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... }"
},
"check-dup-records": {
"_grammar": "( fail | warn | ignore )"
},
"check-integrity": {
"_grammar": "<boolean>"
},
"check-mx": {
"_grammar": "( fail | warn | ignore )"
},
"check-mx-cname": {
"_grammar": "( fail | warn | ignore )"
},
"check-names": {
"_grammar": "( fail | warn | ignore )"
},
"check-sibling": {
"_grammar": "<boolean>"
},
"check-spf": {
"_grammar": "( warn | ignore )"
},
"check-srv-cname": {
"_grammar": "( fail | warn | ignore )"
},
"check-svcb": {
"_grammar": "<boolean>"
},
"check-wildcard": {
"_grammar": "<boolean>"
},
"checkds": {
"_grammar": "( explicit | <boolean> )"
},
"database": {
"_grammar": "<string>"
},
"dlz": {
"_grammar": "<string>"
},
"dnskey-sig-validity": {
"_flags": [
"obsolete"
],
"_grammar": "<integer>"
},
"dnssec-dnskey-kskonly": {
"_flags": [
"obsolete"
],
"_grammar": "<boolean>"
},
"dnssec-loadkeys-interval": {
"_grammar": "<integer>"
},
"dnssec-policy": {
"_grammar": "<string>"
},
"dnssec-secure-to-insecure": {
"_flags": [
"obsolete"
],
"_grammar": "<boolean>"
},
"dnssec-update-mode": {
"_flags": [
"obsolete"
],
"_grammar": "( maintain | no-resign )"
},
"file": {
"_grammar": "<quoted_string>"
},
"forward": {
"_grammar": "( first | only )"
},
"forwarders": {
"_grammar": "[ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... }"
},
"initial-file": {
"_grammar": "<quoted_string>"
},
"inline-signing": {
"_grammar": "<boolean>"
},
"ixfr-from-differences": {
"_grammar": "<boolean>"
},
"journal": {
"_grammar": "<quoted_string>"
},
"key-directory": {
"_grammar": "<quoted_string>"
},
"log-report-channel": {
"_grammar": "<boolean>"
},
"masterfile-format": {
"_grammar": "( raw | text )"
},
"masterfile-style": {
"_grammar": "( full | relative )"
},
"max-ixfr-ratio": {
"_grammar": "( unlimited | <percentage> )"
},
"max-journal-size": {
"_grammar": "( default | unlimited | <sizeval> )"
},
"max-records": {
"_grammar": "<integer>"
},
"max-records-per-type": {
"_grammar": "<integer>"
},
"max-transfer-idle-out": {
"_grammar": "<integer>"
},
"max-transfer-time-out": {
"_grammar": "<integer>"
},
"max-types-per-name": {
"_grammar": "<integer>"
},
"max-zone-ttl": {
"_flags": [
"deprecated"
],
"_grammar": "( unlimited | <duration> )"
},
"notify": {
"_grammar": "( explicit | master-only | primary-only | <boolean> )"
},
"notify-cfg": {
"_flags": [
"may occur multiple times"
],
"_id": "<string>",
"_mapbody": {
"notify": {
"_grammar": "<boolean>"
},
"notify-defer": {
"_grammar": "<integer>"
},
"notify-delay": {
"_grammar": "<integer>"
},
"notify-source": {
"_grammar": "( <ipv4_address> | * )"
},
"notify-source-v6": {
"_grammar": "( <ipv6_address> | * )"
}
}
},
"notify-defer": {
"_grammar": "<integer>"
},
"notify-delay": {
"_grammar": "<integer>"
},
"notify-source": {
"_grammar": "( <ipv4_address> | * )"
},
"notify-source-v6": {
"_grammar": "( <ipv6_address> | * )"
},
"notify-to-soa": {
"_grammar": "<boolean>"
},
"nsec3-test-zone": {
"_flags": [
"test only"
],
"_grammar": "<boolean>"
},
"parental-agents": {
"_grammar": "[ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... }"
},
"parental-source": {
"_grammar": "( <ipv4_address> | * )"
},
"parental-source-v6": {
"_grammar": "( <ipv6_address> | * )"
},
"plugin": {
"_flags": [
"may occur multiple times"
],
"_grammar": "( query ) <string> [ { <unspecified-text> } ]"
},
"provide-zoneversion": {
"_grammar": "<boolean>"
},
"send-report-channel": {
"_grammar": "<string>"
},
"serial-update-method": {
"_grammar": "( date | increment | unixtime )"
},
"sig-signing-nodes": {
"_grammar": "<integer>"
},
"sig-signing-signatures": {
"_grammar": "<integer>"
},
"sig-signing-type": {
"_grammar": "<integer>"
},
"sig-validity-interval": {
"_flags": [
"obsolete"
],
"_grammar": "<integer> [ <integer> ]"
},
"template": {
"_grammar": "<string>"
},
"update-check-ksk": {
"_flags": [
"obsolete"
],
"_grammar": "<boolean>"
},
"update-policy": {
"_grammar": "( local | { ( deny | grant ) <string> ( 6to4-self | external | krb5-self | krb5-selfsub | krb5-subdomain | krb5-subdomain-self-rhs | ms-self | ms-selfsub | ms-subdomain | ms-subdomain-self-rhs | name | self | selfsub | selfwild | subdomain | tcp-self | wildcard | zonesub ) [ <string> ] <rrtypelist>; ... } )"
},
"zero-no-soa-ttl": {
"_grammar": "<boolean>"
},
"zone-statistics": {
"_grammar": "( full | terse | none | <boolean> )"
}
}
}
}

View File

@@ -0,0 +1,58 @@
{
"zone": {
"_id": "<string> [ <class> ]",
"_mapbody": {
"type": {
"_grammar": "redirect"
},
"allow-query": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-query-on": {
"_grammar": "{ <address_match_element>; ... }"
},
"dlz": {
"_grammar": "<string>"
},
"file": {
"_grammar": "<quoted_string>"
},
"masterfile-format": {
"_grammar": "( raw | text )"
},
"masterfile-style": {
"_grammar": "( full | relative )"
},
"max-records": {
"_grammar": "<integer>"
},
"max-records-per-type": {
"_grammar": "<integer>"
},
"max-types-per-name": {
"_grammar": "<integer>"
},
"max-zone-ttl": {
"_flags": [
"deprecated"
],
"_grammar": "( unlimited | <duration> )"
},
"plugin": {
"_flags": [
"may occur multiple times"
],
"_grammar": "( query ) <string> [ { <unspecified-text> } ]"
},
"primaries": {
"_grammar": "[ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... }"
},
"template": {
"_grammar": "<string>"
},
"zone-statistics": {
"_grammar": "( full | terse | none | <boolean> )"
}
}
}
}

View File

@@ -0,0 +1,258 @@
{
"zone": {
"_id": "<string> [ <class> ]",
"_mapbody": {
"type": {
"_grammar": "secondary"
},
"allow-notify": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-query": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-query-on": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-transfer": {
"_grammar": "[ port <integer> ] [ transport <string> ] { <address_match_element>; ... }"
},
"allow-update-forwarding": {
"_grammar": "{ <address_match_element>; ... }"
},
"also-notify": {
"_grammar": "[ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... }"
},
"check-names": {
"_grammar": "( fail | warn | ignore )"
},
"checkds": {
"_grammar": "( explicit | <boolean> )"
},
"database": {
"_grammar": "<string>"
},
"dlz": {
"_grammar": "<string>"
},
"dnskey-sig-validity": {
"_flags": [
"obsolete"
],
"_grammar": "<integer>"
},
"dnssec-dnskey-kskonly": {
"_flags": [
"obsolete"
],
"_grammar": "<boolean>"
},
"dnssec-loadkeys-interval": {
"_grammar": "<integer>"
},
"dnssec-policy": {
"_grammar": "<string>"
},
"dnssec-update-mode": {
"_flags": [
"obsolete"
],
"_grammar": "( maintain | no-resign )"
},
"file": {
"_grammar": "<quoted_string>"
},
"forward": {
"_grammar": "( first | only )"
},
"forwarders": {
"_grammar": "[ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... }"
},
"inline-signing": {
"_grammar": "<boolean>"
},
"ixfr-from-differences": {
"_grammar": "<boolean>"
},
"journal": {
"_grammar": "<quoted_string>"
},
"key-directory": {
"_grammar": "<quoted_string>"
},
"log-report-channel": {
"_grammar": "<boolean>"
},
"masterfile-format": {
"_grammar": "( raw | text )"
},
"masterfile-style": {
"_grammar": "( full | relative )"
},
"max-ixfr-ratio": {
"_grammar": "( unlimited | <percentage> )"
},
"max-journal-size": {
"_grammar": "( default | unlimited | <sizeval> )"
},
"max-records": {
"_grammar": "<integer>"
},
"max-records-per-type": {
"_grammar": "<integer>"
},
"max-refresh-time": {
"_grammar": "<integer>"
},
"max-retry-time": {
"_grammar": "<integer>"
},
"max-transfer-idle-in": {
"_grammar": "<integer>"
},
"max-transfer-idle-out": {
"_grammar": "<integer>"
},
"max-transfer-time-in": {
"_grammar": "<integer>"
},
"max-transfer-time-out": {
"_grammar": "<integer>"
},
"max-types-per-name": {
"_grammar": "<integer>"
},
"min-refresh-time": {
"_grammar": "<integer>"
},
"min-retry-time": {
"_grammar": "<integer>"
},
"min-transfer-rate-in": {
"_grammar": "<integer> <integer>"
},
"multi-master": {
"_grammar": "<boolean>"
},
"notify": {
"_grammar": "( explicit | master-only | primary-only | <boolean> )"
},
"notify-cfg": {
"_flags": [
"may occur multiple times"
],
"_id": "<string>",
"_mapbody": {
"notify": {
"_grammar": "<boolean>"
},
"notify-defer": {
"_grammar": "<integer>"
},
"notify-delay": {
"_grammar": "<integer>"
},
"notify-source": {
"_grammar": "( <ipv4_address> | * )"
},
"notify-source-v6": {
"_grammar": "( <ipv6_address> | * )"
}
}
},
"notify-defer": {
"_grammar": "<integer>"
},
"notify-delay": {
"_grammar": "<integer>"
},
"notify-source": {
"_grammar": "( <ipv4_address> | * )"
},
"notify-source-v6": {
"_grammar": "( <ipv6_address> | * )"
},
"notify-to-soa": {
"_grammar": "<boolean>"
},
"nsec3-test-zone": {
"_flags": [
"test only"
],
"_grammar": "<boolean>"
},
"parental-agents": {
"_grammar": "[ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... }"
},
"parental-source": {
"_grammar": "( <ipv4_address> | * )"
},
"parental-source-v6": {
"_grammar": "( <ipv6_address> | * )"
},
"plugin": {
"_flags": [
"may occur multiple times"
],
"_grammar": "( query ) <string> [ { <unspecified-text> } ]"
},
"primaries": {
"_grammar": "[ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... }"
},
"provide-zoneversion": {
"_grammar": "<boolean>"
},
"request-expire": {
"_grammar": "<boolean>"
},
"request-ixfr": {
"_grammar": "<boolean>"
},
"request-ixfr-max-diffs": {
"_grammar": "<integer>"
},
"send-report-channel": {
"_grammar": "<string>"
},
"sig-signing-nodes": {
"_grammar": "<integer>"
},
"sig-signing-signatures": {
"_grammar": "<integer>"
},
"sig-signing-type": {
"_grammar": "<integer>"
},
"sig-validity-interval": {
"_flags": [
"obsolete"
],
"_grammar": "<integer> [ <integer> ]"
},
"template": {
"_grammar": "<string>"
},
"transfer-source": {
"_grammar": "( <ipv4_address> | * )"
},
"transfer-source-v6": {
"_grammar": "( <ipv6_address> | * )"
},
"try-tcp-refresh": {
"_grammar": "<boolean>"
},
"update-check-ksk": {
"_flags": [
"obsolete"
],
"_grammar": "<boolean>"
},
"zero-no-soa-ttl": {
"_grammar": "<boolean>"
},
"zone-statistics": {
"_grammar": "( full | terse | none | <boolean> )"
}
}
}
}

View File

@@ -0,0 +1,43 @@
{
"zone": {
"_id": "<string> [ <class> ]",
"_mapbody": {
"type": {
"_grammar": "static-stub"
},
"allow-query": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-query-on": {
"_grammar": "{ <address_match_element>; ... }"
},
"forward": {
"_grammar": "( first | only )"
},
"forwarders": {
"_grammar": "[ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... }"
},
"max-records": {
"_grammar": "<integer>"
},
"max-records-per-type": {
"_grammar": "<integer>"
},
"max-types-per-name": {
"_grammar": "<integer>"
},
"server-addresses": {
"_grammar": "{ ( <ipv4_address> | <ipv6_address> ); ... }"
},
"server-names": {
"_grammar": "{ <string>; ... }"
},
"template": {
"_grammar": "<string>"
},
"zone-statistics": {
"_grammar": "( full | terse | none | <boolean> )"
}
}
}
}

View File

@@ -0,0 +1,85 @@
{
"zone": {
"_id": "<string> [ <class> ]",
"_mapbody": {
"type": {
"_grammar": "stub"
},
"allow-query": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-query-on": {
"_grammar": "{ <address_match_element>; ... }"
},
"check-names": {
"_grammar": "( fail | warn | ignore )"
},
"database": {
"_grammar": "<string>"
},
"file": {
"_grammar": "<quoted_string>"
},
"forward": {
"_grammar": "( first | only )"
},
"forwarders": {
"_grammar": "[ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... }"
},
"masterfile-format": {
"_grammar": "( raw | text )"
},
"masterfile-style": {
"_grammar": "( full | relative )"
},
"max-records": {
"_grammar": "<integer>"
},
"max-records-per-type": {
"_grammar": "<integer>"
},
"max-refresh-time": {
"_grammar": "<integer>"
},
"max-retry-time": {
"_grammar": "<integer>"
},
"max-transfer-idle-in": {
"_grammar": "<integer>"
},
"max-transfer-time-in": {
"_grammar": "<integer>"
},
"max-types-per-name": {
"_grammar": "<integer>"
},
"min-refresh-time": {
"_grammar": "<integer>"
},
"min-retry-time": {
"_grammar": "<integer>"
},
"min-transfer-rate-in": {
"_grammar": "<integer> <integer>"
},
"multi-master": {
"_grammar": "<boolean>"
},
"primaries": {
"_grammar": "[ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... }"
},
"template": {
"_grammar": "<string>"
},
"transfer-source": {
"_grammar": "( <ipv4_address> | * )"
},
"transfer-source-v6": {
"_grammar": "( <ipv6_address> | * )"
},
"zone-statistics": {
"_grammar": "( full | terse | none | <boolean> )"
}
}
}
}

View File

@@ -0,0 +1,85 @@
{
"zone": {
"_id": "<string> [ <class> ]",
"_mapbody": {
"type": {
"_grammar": "stub"
},
"allow-query": {
"_grammar": "{ <address_match_element>; ... }"
},
"allow-query-on": {
"_grammar": "{ <address_match_element>; ... }"
},
"check-names": {
"_grammar": "( fail | warn | ignore )"
},
"database": {
"_grammar": "<string>"
},
"file": {
"_grammar": "<quoted_string>"
},
"forward": {
"_grammar": "( first | only )"
},
"forwarders": {
"_grammar": "[ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... }"
},
"masterfile-format": {
"_grammar": "( raw | text )"
},
"masterfile-style": {
"_grammar": "( full | relative )"
},
"max-records": {
"_grammar": "<integer>"
},
"max-records-per-type": {
"_grammar": "<integer>"
},
"max-refresh-time": {
"_grammar": "<integer>"
},
"max-retry-time": {
"_grammar": "<integer>"
},
"max-transfer-idle-in": {
"_grammar": "<integer>"
},
"max-transfer-time-in": {
"_grammar": "<integer>"
},
"max-types-per-name": {
"_grammar": "<integer>"
},
"min-refresh-time": {
"_grammar": "<integer>"
},
"min-retry-time": {
"_grammar": "<integer>"
},
"min-transfer-rate-in": {
"_grammar": "<integer> <integer>"
},
"multi-master": {
"_grammar": "<boolean>"
},
"primaries": {
"_grammar": "[ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... }"
},
"template": {
"_grammar": "<string>"
},
"transfer-source": {
"_grammar": "( <ipv4_address> | * )"
},
"transfer-source-v6": {
"_grammar": "( <ipv6_address> | * )"
},
"zone-statistics": {
"_grammar": "( full | terse | none | <boolean> )"
}
}
}
}

View File

@@ -16,7 +16,6 @@ bind9_config_indent: 4
bind9_group_config: []
bind9_site_config: []
bind9_host_config: []
bind9_default_config:
- name: named.conf
backup: false
@@ -28,10 +27,11 @@ bind9_default_config:
options:
directory: "{{ bind9_working_directory }}"
bind9_config: "{{ [bind9_default_config,
bind9_group_config,
bind9_site_config,
bind9_host_config] |
community.general.lists_mergeby('name',
recursive=true,
list_merge='append_rp') }}"
bind9_config: >-
{{
[bind9_default_config,
bind9_group_config,
bind9_site_config,
bind9_host_config] |
community.general.lists_mergeby('name', recursive=true, list_merge='append_rp')
}}

View File

@@ -16,10 +16,8 @@
- "{{ bind9_cfgdir }}"
- "{{ bind9_working_directory }}"
- "{{ bind9_libdir }}"
dest: "{{
bind9_backup_dir + '/bind9-config-' +
ansible_facts.date_time.iso8601_basic_short + '.tar.gz' }}"
dest: "{{ bind9_backup_dir + '/bind9-config-' + ansible_facts.date_time.iso8601_basic_short + '.tar.gz' }}"
owner: root
group: root
mode: 0640
mode: "0640"
when: bind9_backup_config is defined and bind9_backup_config

View File

@@ -4,4 +4,3 @@ collections:
- name: ansible.posix
- name: community.crypto
- name: community.general

View File

@@ -2,6 +2,114 @@
- name: Converge
hosts: all
tasks:
- name: Create log directory for BIND
ansible.builtin.file:
path: /var/log/named
state: directory
mode: '0755'
owner: bind
group: bind
- name: Include bind9 role
ansible.builtin.include_role:
name: ../../../ansible-bind9-role
name: ../../../ansible-bind9-role # noqa: role-name[path]
vars:
bind9_backup_config: false
bind9_host_config:
- name: named.conf.options
options:
directory: "{{ bind9_working_directory }}"
recursion: true
allow_query:
- any
allow_recursion:
- 10.0.0.0/8
- 192.168.0.0/16
- 172.16.0.0/12
- localhost
- localnets
forwarders:
- address: 91.239.100.100
tls: censurfridns-anycast
- address: 89.233.43.71
tls: censurfridns-unicast
forward: first
dnssec_validation: auto
dnstap:
- type: auth
- type: resolver
log: query
- type: client
log: response
dnstap_output:
output_type: file
output_file: /var/log/named/dnstap.log
size: 20m
versions: 3
suffix: increment
dnstap_identity: dns-server-01
dnstap_version: 9.18
logging:
channels:
- name: default_log
file:
name: /var/log/named/default.log
severity: info
print_time: true
print_severity: true
print_category: true
- name: security_log
file:
name: /var/log/named/security.log
severity: dynamic
print_time: true
print_severity: true
print_category: true
- name: query_log
file:
name: /var/log/named/queries.log
versions: 5
size: 10m
severity: info
print_time: true
- name: dnssec_log
file:
name: /var/log/named/dnssec.log
severity: debug
print_time: true
print_severity: true
- name: rate_limit_log
syslog: daemon
severity: warning
categories:
- name: default
channels:
- default_log
- name: general
channels:
- default_log
- name: security
channels:
- security_log
- name: queries
channels:
- query_log
- name: dnssec
channels:
- dnssec_log
- name: rate-limit
channels:
- rate_limit_log
- name: named.conf.local
tls:
- name: censurfridns-anycast
remote_hostname: anycast.uncensoreddns.org
- name: censurfridns-unicast
remote_hostname: unicast.uncensoreddns.org
zones:
- name: example.internal
type: forward
forward: only
forwarders:
- 10.0.0.53
- 10.0.0.54

View File

@@ -2,13 +2,6 @@
driver:
name: podman
platforms:
- name: debian-bookworm
image: docker.io/jrei/systemd-debian:12
command: /lib/systemd/systemd
privileged: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
- name: debian-trixie
image: docker.io/jrei/systemd-debian:13
command: /lib/systemd/systemd

View File

@@ -1,6 +1,11 @@
---
- hosts: all
- name: Prepare
hosts: all
tasks:
- name: Update apt
ansible.builtin.apt:
update_cache: true
- name: Install bind9-dnsutils package
ansible.builtin.apt:
name: bind9-dnsutils
state: present

View File

@@ -0,0 +1,76 @@
---
- name: Verify
hosts: all
gather_facts: true
tasks:
- name: Check that BIND9 is installed
ansible.builtin.package:
name: bind9
state: present
check_mode: true
register: __bind9_package_check
failed_when: __bind9_package_check is changed
- name: Check that BIND9 service is running
ansible.builtin.service:
name: named
state: started
enabled: true
check_mode: true
register: __bind9_service_check
failed_when: __bind9_service_check is changed
- name: Check that named.conf.options exists
ansible.builtin.stat:
path: /etc/bind/named.conf.options
register: __options_file
failed_when: not __options_file.stat.exists
- name: Check that named.conf.local exists
ansible.builtin.stat:
path: /etc/bind/named.conf.local
register: __local_file
failed_when: not __local_file.stat.exists
- name: Read named.conf.options content
ansible.builtin.slurp:
path: /etc/bind/named.conf.options
register: __options_content
- name: Verify forwarders are configured in options
ansible.builtin.assert:
that:
- "'forwarders' in __options_decoded"
- "'8.8.8.8' in __options_decoded"
- "'forward first' in __options_decoded"
fail_msg: Forwarders not properly configured in named.conf.options
vars:
__options_decoded: "{{ __options_content.content | b64decode }}"
- name: Read named.conf.local content
ansible.builtin.slurp:
path: /etc/bind/named.conf.local
register: __local_content
- name: Verify forward zone is configured
ansible.builtin.assert:
that:
- "'zone \"example.internal\"' in __local_decoded"
- "'type forward' in __local_decoded"
- "'forward only' in __local_decoded"
fail_msg: Forward zone not properly configured in named.conf.local
vars:
__local_decoded: "{{ __local_content.content | b64decode }}"
- name: Test DNS resolution using localhost
ansible.builtin.command:
cmd: dig @localhost google.com +short
register: __dns_query
changed_when: false
failed_when: __dns_query.rc != 0
- name: Verify DNS query returned results
ansible.builtin.assert:
that:
- __dns_query.stdout_lines | length > 0
fail_msg: DNS forwarding is not working

View File

@@ -15,10 +15,16 @@
state: directory
owner: root
group: root
mode: 0750
mode: "0750"
when: bind9_backup_config is defined and bind9_backup_config | bool
- name: Deploy and Validate Configuration
tags:
- bind9
- template
notify:
- Backup bind config
- Restart bind
block:
- name: Create backup of current config
ansible.builtin.copy:
@@ -27,9 +33,9 @@
remote_src: true
owner: root
group: bind
mode: 0640
mode: "0640"
changed_when: false
failed_when: false # It's okay if the file doesn't exist yet
# We do this for every file in the loop
loop: "{{ bind9_config }}"
loop_control:
label: "{{ item.name }}"
@@ -40,11 +46,11 @@
dest: "{{ bind9_cfgdir }}/{{ item.name }}"
owner: root
group: bind
mode: 0640
mode: "0640"
loop: "{{ bind9_config }}"
loop_control:
label: "{{ item.name }}"
register: _template_result
register: bind9_template_result
- name: Validate configuration using named-checkconf
ansible.builtin.command:
@@ -59,7 +65,7 @@
remote_src: true
owner: root
group: bind
mode: 0640
mode: "0640"
loop: "{{ bind9_config }}"
loop_control:
label: "{{ item.name }}"
@@ -67,7 +73,9 @@
- name: Fail due to invalid configuration
ansible.builtin.fail:
msg: "Configuration validation failed. Changes have been reverted. Check the logs for named-checkconf errors."
msg: |
Configuration validation failed. Changes have been reverted.
Check the logs for named-checkconf errors.
always:
- name: Remove backup files
@@ -77,14 +85,7 @@
loop: "{{ bind9_config }}"
loop_control:
label: "{{ item.name }}"
when: bind9_backup_config | bool is false # Keep if backup is forced, otherwise cleanup temporary atomic backup
tags:
- bind9
- template
notify:
- Backup bind config
- Restart bind
when: bind9_backup_config | bool is false
- name: Ensure the named service is started
ansible.builtin.service:

View File

@@ -111,3 +111,35 @@
{{ name }} "{{ value }}";
{% endif %}
{% endmacro %}
{% macro list_address_port_tls(dict, indent=bind9_config_indent) %}
{# This macro is for use for statements with grammar like #}
{# address port 00 tls str; address port 00 tls str; #}
{# it is usually called by a parent macro #}
{% filter indent(indent, true) %}
{% for item in dict %}
{% if item is not mapping %}
{{ item }};
{% else %}
{{ item.address }}
{{- (' port ' + item.port | string) if item.port is defined and item.port -}}
{{- (' tls ' + item.tls | string) if item.tls is defined and item.tls -}};
{% endif %}
{% endfor %}
{% endfilter %}
{% endmacro %}
{% macro parent_address_port_tls(name, dict) %}
{# This macro is for use for statements with grammar like #}
{# statement port 00 tls str { address port 00 tls str; address port 00 tls str; } #}
{# the list inside the statement is handled by list_address_port_tls #}
{% if dict is not mapping and dict is iterable %}
{{ name }} {
{{ list_address_port_tls(dict) }}};
{% else %}
{{ name }}
{{- (' port ' + dict.port | string) if dict.port is defined and dict.port -}}
{{- (' tls ' + dict.tls | string) if dict.tls is defined and dict.tls }} {
{{ list_address_port_tls(dict.addresses) }}};
{% endif %}
{% endmacro %}

View File

@@ -101,7 +101,7 @@ listen-on
{{ functions.simple_item_list(item.options.listen_on.addresses) }}};
{% endfor %}
{% endif %}
{{ functions.parent_address_port_dscp("forwarders", item.options.forwarders) if item.options.forwarders is defined and item.options.forwarders -}}
{{ functions.parent_address_port_tls("forwarders", item.options.forwarders) if item.options.forwarders is defined and item.options.forwarders -}}
{% if item.options.dual_stack_servers is defined and item.options.dual_stack_servers %}
dual-stack-servers
{{ (' port ' + item.options.dual_stack_servers.port | string) if item.options.dual_stack_servers.port is defined and item.options.dual_stack_servers }} {

View File

@@ -47,7 +47,7 @@ server-names {
server-addresses {
{{ functions.simple_item_list(zone.server_addresses) }}};
{% endif %}
{{ functions.parent_address_port_dscp('forwarders', zone.forwarders) if zone.forwarders is defined and zone.forwarders -}}
{{ functions.parent_address_port_tls('forwarders', zone.forwarders) if zone.forwarders is defined and zone.forwarders -}}
{% if zone.allow_transfer is defined and zone.allow_transfer is not string %}
allow-transfer
{{- (' port ' + zone.allow_transfer.port | string) if zone.allow_transfer.port is defined and zone.allow_transfer.port -}}

View File

@@ -2,4 +2,4 @@
- hosts: localhost
remote_user: root
roles:
- bind9
- bind9 # noqa: syntax-check[specific]