2026-03-28 17:04:36 -07:00
2026-02-28 16:13:58 -08:00
2026-02-28 16:13:02 -08:00
2026-03-28 17:04:36 -07:00
2026-03-28 17:04:36 -07:00

catalog-zone-gen

Generate RFC 9432 DNS catalog zone files from a declarative input file.

Given a list of zones with their catalog assignments and optional properties, this tool produces one BIND-format zone file per catalog. Output is deterministic: re-running with unchanged input produces unchanged output (no serial bump, no file write).

Installation

go install catalog-zone-gen@latest

Or build from source:

go build -o catalog-zone-gen .

Usage

catalog-zone-gen [--config path] [--output-dir path] [--bind-conf path] <input-file>

Flags:

  • --config — path to YAML config file (default: catz.yaml next to the input file)
  • --output-dir — directory for output zone files (default: same directory as the input file)
  • --bind-conf — path to write a BIND domains.conf file (optional; see BIND Config Output)

Configuration File

The config file (default catz.yaml) defines catalog zone names and SOA parameters.

catalogs:
  catalog1:
    zone: catalog1.example.com.
  catalog2:
    zone: catalog2.example.com.

soa:
  mname: ns1.example.com.
  rname: hostmaster.example.com.

Fields

Field Required Description
catalogs yes Map of catalog names to their zone FQDNs. Names are used as references in the input file.
catalogs.<name>.zone yes The FQDN of the catalog zone (trailing dot optional, will be normalized).
soa.mname yes Primary nameserver for the SOA record.
soa.rname yes Responsible person email (in DNS format: hostmaster.example.com.).

SOA timing values are hardcoded: refresh=900, retry=600, expire=2147483646, minimum=0. The NS record is hardcoded to invalid. per RFC 9432.

Input File Format

Whitespace and comma delimited. Lines starting with # are comments. Blank lines are ignored.

<zone-name>  <catalog>[, <catalog>...]  [, group=<value>]  [, coo=<fqdn>]  [, file=<path>]  [, dnssec]

Fields

Position Format Description
First token FQDN Zone name (trailing dot optional, normalized internally).
Bare names identifier Catalog assignments — must match a key in the config catalogs map. At least one required.
group=<value> key=value RFC 9432 group property. Tells consumers to apply shared configuration to grouped zones.
coo=<fqdn> key=value RFC 9432 change-of-ownership property. Points to the old catalog zone during migration.
file=<path> key=value Zone data file path for BIND config output. Zones without file= are included in catalog zones but skipped in --bind-conf output.
dnssec bare flag Adds dnssec-policy standard; inline-signing yes; to the BIND config for this zone.

A zone can appear in multiple catalogs (for distributing to different server groups).

Example

# Production zones
zone.example.org    catalog1, catalog2, file=data/zones/example.org
zone.example.com    catalog2, coo=old-catalog.example.com., file=data/zones/example.com
test.example.net    catalog1, group=internal, file=data/zones/example.net, dnssec
app.example.org     catalog1, group=external, coo=migrated.example.com., file=data/zones/app.example.org

Whitespace and comma placement is flexible. These are all equivalent:

zone.example.org  catalog1,catalog2
zone.example.org  catalog1 , catalog2
zone.example.org  catalog1, catalog2

Output

One BIND-format zone file per catalog, written to the output directory.

Filename: <catalog-zone-fqdn>.zone (e.g., catalog1.example.com.zone)

Example output:

catalog1.example.com.	0	IN	SOA	ns1.example.com. hostmaster.example.com. 2026030201 900 600 2147483646 0
catalog1.example.com.	0	IN	NS	invalid.
version.catalog1.example.com.	0	IN	TXT	"2"
grfen8g.zones.catalog1.example.com.	0	IN	PTR	app.example.org.
group.grfen8g.zones.catalog1.example.com.	0	IN	TXT	"external"
coo.grfen8g.zones.catalog1.example.com.	0	IN	PTR	migrated.example.com.
2qvgcfg.zones.catalog1.example.com.	0	IN	PTR	test.example.net.
group.2qvgcfg.zones.catalog1.example.com.	0	IN	TXT	"internal"
1860l9o.zones.catalog1.example.com.	0	IN	PTR	zone.example.org.

Record order

  1. SOA record
  2. NS record (invalid.)
  3. Version TXT record ("2")
  4. Member zones sorted alphabetically by zone name, each with:
    • PTR record (member zone)
    • Group TXT record (if group= set)
    • COO PTR record (if coo= set)

All records use TTL 0 and class IN. Fully-qualified owner names; no $ORIGIN or $TTL directives.

Member zone hashing

Member zone labels are generated by FNV-1a 32-bit hashing the normalized zone name, then encoding as lowercase base32hex without padding. This produces compact labels like grfen8g.

Changing tools will likely produce different hash labels, which is intentional per RFC 9432 Section 5.4 — it triggers a reconfig event on consumers.

SOA serial

Format: YYYYMMDDNN where NN is a sequence number (01-99).

  • New zone files start at YYYYMMDD01.
  • On subsequent runs, the tool generates output with the existing serial and compares bytes. If unchanged, no write occurs.
  • If content differs, the serial is incremented (same date bumps NN; new date resets to YYYYMMDD01).
  • If NN reaches 99 on the same date: the tool errors.

Validation

The tool validates input and reports errors with file location:

error: zones.txt:3: unknown catalog "bogus"
error: zones.txt:5: zone example.com. already assigned to catalog "catalog1" at line 2

Checked conditions:

  • Unknown catalog name (not in config) — error
  • Same zone assigned to the same catalog more than once — error
  • Hash collision (two zone names produce the same hash within a catalog) — error
  • Missing required config fields — error
  • Unknown properties (anything other than group, coo, file) — error
  • Empty file= value — error
  • dnssec=<anything> (dnssec is a bare flag, not a key=value) — error
  • When --bind-conf is used: zones without file= are silently skipped (included in catalog output only)

BIND Config Output

When --bind-conf <path> is specified, a BIND domains.conf file is written in addition to the catalog zone files. This file defines all zones as type master with their file paths from the file= input property.

Example output:

# THIS FILE IS GENERATED BY catalog-zone-gen
#=============================================
#
zone "askask.com" {
        type master;
        file  "data/ask/askask.com";
};
zone "bitcard.org" {
        type master;
        file  "data/misc/bitcard.org"; dnssec-policy standard; inline-signing yes;
};
  • Zones are sorted alphabetically by name
  • 8-space indentation
  • DNSSEC zones (marked with dnssec in the input) get dnssec-policy standard; inline-signing yes; on the same line as file
  • Zones without a file= property are skipped (they appear in catalog zones only)
Description
DNS catalog zones helper
Readme 101 KiB
Languages
Go 100%