Add catalog-zone-gen tool
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.
This commit is contained in:
166
README.md
Normal file
166
README.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# 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.yaml` next 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.
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
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` and `coo`) — error
|
||||
Reference in New Issue
Block a user