bindantic

Pydantic-based BIND9 configuration management library


Keywords
bind, bind9, dns, pydantic, configuration, named, conf, automation, named-conf, python, sysadmin
License
MIT
Install
pip install bindantic==2.0.0

Documentation

bindantic

PyPI version Python versions License CI Coverage Docs PyPI Downloads

bindantic is a library for managing BIND9 DNS server configuration via Pydantic models.

Instead of manually editing named.conf, you describe the configuration in Python, and the library generates correct BIND9 syntax and (optionally) places files into the required directories.

Table of contents

Features

  • Full support for all named.conf blocks - acl, controls, dnssec-policy, http, key, key-store, logging, options, remote-servers, server, statistics-channels, tls, trust-anchors, view, zone.
  • All common resource record types - A, AAAA, CAA, CERT, CNAME, DNAME, DNSKEY, DS, HINFO, LOC, MX, NAPTR, NSEC, NS, PTR, RP, RRSIG, SOA, SPF, SRV, SSHFP, TLSA, TXT.
  • Built-in validation - pass strings, numbers, IP addresses, durations, and bindantic formats them correctly for BIND.
  • Validated against a real BIND9, not just unit tests on strings - CI generates configuration for every block and resource record type and runs it through a real named-checkconf, so the output isn't just "what we believe BIND9 syntax looks like."
  • Syntax generation in one line - model.model_bind_syntax() for any block or the whole named.conf, zone.model_bind_syntax_zone_file() for a ready-to-use zone file.
  • Generate files without writing, or write straight to disk - config.generate_files() returns a list of generated files; config.write_files("./my_config") creates named.conf, zones, keys, and DNSSEC policies, organised into subdirectories.
  • Python 3.10+, static typing (py.typed), 97%+ test coverage.
  • No extra dependencies - only Pydantic.

Installation

pip install bindantic

Warning

named-checkconf version: bindantic generates syntax according to the latest stable BIND 9.20.x release. The named-checkconf utility from your distro's bind9-utils/bind-utils package may be several minor versions older than that and reject directives it doesn't recognize yet (this project's own CI hit exactly that with an outdated apt package - see CHANGELOG.md). Always check generated configuration with the same named-checkconf version as your production server, if possible.

Quick start

Example of a minimal configuration:

from bindantic import (
    ARecord,
    NamedConfig,
    NSRecord,
    OptionsBlock,
    SOARecord,
    ZoneBlock,
    ZoneTypeEnum,
)

config = NamedConfig(
    options_block=OptionsBlock(
        directory="/etc/bind",
        recursion=True,
        allow_recursion=["localhost", "localnets"],
        listen_on=["any"],
        listen_on_v6=["any"],
    ),
    zone_blocks=[
        ZoneBlock(
            comment="optional comment",
            name="example.com",
            zone_type=ZoneTypeEnum.PRIMARY,
            file="zones/example.com.zone",
            resource_records=[
                SOARecord(
                    mname="ns1.example.com",
                    rname="admin.example.com",
                    serial=2026010101,
                    refresh=10800,
                    retry=3600,
                    expire=604800,
                    minimum=3600,
                    origin="example.com",
                    ttl=3600,
                ),
                NSRecord(nsdname="ns1.example.com", comment="optional comment"),
                ARecord(name="@", address="192.168.1.1"),
            ],
        )
    ],
)
Output of config.model_bind_syntax()
options {
    allow-recursion {
        localhost;
        localnets;
    };
    directory "/etc/bind";
    listen-on {
        any;
    };
    listen-on-v6 {
        any;
    };
    recursion yes;
};

# optional comment
zone example.com. {
    type primary;
    file "zones/example.com.zone";
};
Output of config.zone_blocks[0].model_bind_syntax_zone_file()
$TTL 3600
$ORIGIN example.com.
@                                                IN   SOA ns1.example.com. admin.example.com. (
                                                                 2026010101 ; Serial number (YYYYMMDDNN)
                                                                 10800      ; Refresh time
                                                                 3600       ; Retry time
                                                                 604800     ; Expire time
                                                                 3600       ; Minimum TTL
                                                      )
@                                                IN   NS         ns1.example.com. ; optional comment
@                                                IN   A          192.168.1.1
Output of config.generate_files()
[
    GeneratedFile(
        path=PosixPath("/etc/bind/zones/example.com.zone"),
        content="<CONTENT>",
        type="zone",
    ),
    GeneratedFile(
        path=PosixPath("/etc/bind/named.conf"),
        content="<CONTENT>",
        type="config",
    ),
]
Output of config.write_files(base_dir="./my_config")
my_config/
├── named.conf
└── zones/
    └── example.com.zone

named.conf (directory is rewritten to the base_dir you actually pass in):

# Automatically generated by bindantic - please adjust!

options {
    allow-recursion {
        localhost;
        localnets;
    };
    directory "my_config";
    listen-on {
        any;
    };
    listen-on-v6 {
        any;
    };
    recursion yes;
};

# optional comment
zone example.com. {
    type primary;
    file "zones/example.com.zone";
};

zones/example.com.zone is identical to the model_bind_syntax_zone_file() output above.

More examples

Focused, runnable scripts for common real-world setups - see also Examples in the documentation:

Documentation

Full documentation, including an exhaustive per-field API reference generated from the models themselves, is at DVSAWR.github.io/bindantic.

Contributing

Contributions are welcome - see CONTRIBUTING.md for the development setup and workflow. Please review the Code of Conduct before participating, and see SECURITY.md to report a security issue privately instead of opening a public one.

Versioning

bindantic follows Semantic Versioning.

  • Public API - everything importable from the top-level bindantic package (models, enums, field type aliases) is covered by semver guarantees.
  • Internal - any module prefixed with _ (e.g. bindantic._base_model, bindantic._base_types_validation) is an implementation detail and may change without notice.
  • Major - removing/renaming a public model or field, or a change that makes previously valid input invalid, or a change to the generated BIND syntax output.
  • Minor - new models, new optional fields, support for new BIND directives.
  • Patch - bug fixes that don't change the public API surface.

bindantic targets the latest stable BIND 9.20.x release; tracking a new BIND directive is treated as a minor bump unless it conflicts with existing behavior.

See CHANGELOG.md for the release history.

License

MIT - see LICENSE.