Generate RFC 9432 DNS catalog zone files from a declarative input file. Parses zone-to-catalog assignments with optional group and coo properties, produces deterministic BIND-format output with automatic SOA serial management and change detection.
5.2 KiB
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] <input-file>
Flags:
--config— path to YAML config file (default:catz.yamlnext to the input file)--output-dir— directory for output zone files (default: same directory as the input file)
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>]
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. |
A zone can appear in multiple catalogs (for distributing to different server groups).
Example
# Production zones
zone.example.org catalog1, catalog2
zone.example.com catalog2, coo=old-catalog.example.com.
test.example.net catalog1, group=internal
app.example.org catalog1, group=external, coo=migrated.example.com.
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
- SOA record
- NS record (
invalid.) - Version TXT record (
"2") - 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
groupandcoo) — error