Repository navigation
fix: address code review findings for RFC 9432 Catalog Zones #290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
poyrazK
wants to merge
43
commits into
main
Choose a base branch
from
release/catalog-zones
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
e9db2e3
feat: add database schema and domain types for Catalog Zones (RFC 9432)
poyrazK 380dd3a
feat: add CATZ/CZTR packet handling for read/write
poyrazK 6abf213
feat: add catalog repository interface and PostgreSQL implementation
poyrazK 0f4855d
feat: add catalog service methods and REST API endpoints
poyrazK c73a92a
feat: add slave catalog polling mechanism
poyrazK d6b0f39
test: add mock implementations for catalog zone interfaces
poyrazK 5b19259
fix: address code review findings - security, robustness, and complet…
poyrazK 23c4307
fix: add tenant authorization to AddCatalogEntry and RemoveCatalogEnt…
poyrazK 0631906
fix: syncZoneFromCatalog now rolls back zone on AXFR failure and retu…
poyrazK 9de9106
feat: wire catalog zone poller into server startup with TTL cleanup
poyrazK 8e5c785
fix: replace O(n) ListZones scan with targeted GetZoneByCatalogName q…
poyrazK 4f50f4e
fix(repository): restore full schema — catalog additions were not a w…
poyrazK 7d4f751
test: add behavioral tests for RFC 9432 Catalog Zones
poyrazK ba9c7ee
fix: address PR #290 code review findings
poyrazK 210bc4d
docs: add RFC 9432 Catalog Zones to README, features, and ADR 0012
poyrazK 82d4c27
test: add coverage for catalog zone gaps
poyrazK 186e52b
fix: resolve lint issues in catalog zone code (errcheck, errorlint, r…
poyrazK 72eff0d
fix: use valid UUIDs in catalog zone integration tests
poyrazK 23a7829
fix: use valid UUIDs for catalog zone IDs in integration tests
poyrazK ad4cf69
fix: use valid UUID in GetCatalogZone test for non-existent ID
poyrazK da2b36c
fix: add unique constraint for catalog zone PTR records in schema
poyrazK cace6ea
fix: use substring for prefix match in RemoveZoneFromCatalog to avoid…
poyrazK 0070400
fix: handle NULL catalog_zone_name in GetZoneByCatalogName scan
poyrazK 5e095d1
fix: include catalog_zone_name in CreateZone and CreateZoneWithRecord…
poyrazK b0a3d2d
fix: update RemoveZoneFromCatalog unit test to match new substring query
poyrazK 67e598a
fix: update unit test mocks for CreateZone 10-column INSERT
poyrazK e7f29b6
fix: add GetRecordsByNames unit test to improve coverage
poyrazK 0b2117f
fix: add test for GetCurrentTimeUint32 to eliminate 0% coverage package
poyrazK 0c468d8
fix: add coverage tests for DNSSEC config parsing
poyrazK f67f514
test: add coverage tests for parseDNSSECConfig, MockDNSService catalo…
poyrazK 90141a7
test: add UpdateRecord content-type and invalid body coverage tests
poyrazK ba5a936
test: add TestNewWithNilLogger for Handler.New nil logger coverage
poyrazK aaeed00
test: add UpdateRecord coverage tests for low TTL and error path
poyrazK 9da2bd8
test: add MockRepo UpdateRecord and ListRecordsForZoneStreaming cover…
poyrazK 416ee40
test: add GetDNSKEYs and UpdateRecord coverage to MockRepo and MockDN…
poyrazK da5bc2e
test: add AddCatalogEntry error path coverage tests
poyrazK 4fb4bf4
test: add goCommand SetStdout/Run coverage tests
poyrazK ced757d
test: add runSeed default URL test coverage
poyrazK 6949633
test: add run error path coverage tests for apikey
poyrazK bbd3e1b
test: add GetRecordsByNames empty names edge case test
poyrazK 8a00205
refactor: add injectable interfaces for TCP connections and tickers t…
poyrazK 0267422
test: add tests for catalog zone polling functions
poyrazK 6d01641
test: add PollCatalogZone and SyncZonesFromCatalog tests
poyrazK File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| # ADR 0012: RFC 9432 Catalog Zones | ||
|
|
||
| ## Status | ||
| Accepted | ||
|
|
||
| ## Date | ||
| 2026-06-10 | ||
|
|
||
| ## Context | ||
|
|
||
| RFC 9432 defines Catalog Zones (CATZ type 53) and Catalog Zone Transfer (CZTR type 54) for publishing zone metadata in DNS. This enables slave DNS servers to automatically discover, provision, and synchronize zones from a master server without manual configuration. | ||
|
|
||
| cloudDNS needed to support the slave-side catalog zone workflow: | ||
| 1. A catalog zone exists on the master server containing zone metadata (name, ID, group) as PTR records | ||
| 2. Slave servers poll the catalog zone periodically (or on demand) | ||
| 3. Zone entries are fetched via AXFR from the catalog zone | ||
| 4. Each zone is then fetched via AXFR from the master and provisioned locally | ||
|
|
||
| Key design decisions were required: | ||
| - How to model catalog zones in the existing PostgreSQL schema | ||
| - How to store zone inventory entries (PTR records with custom content format) | ||
| - How to handle the polling loop lifecycle | ||
| - How to avoid O(n) scans when checking zone existence | ||
|
|
||
| ## Decision | ||
|
|
||
| ### Schema | ||
|
|
||
| A new `catalog_zones` table stores catalog zone metadata: | ||
|
|
||
| ```sql | ||
| CREATE TABLE catalog_zones ( | ||
| id UUID PRIMARY KEY, | ||
| tenant_id TEXT NOT NULL, | ||
| zone_name TEXT NOT NULL, | ||
| version TEXT NOT NULL DEFAULT '1', | ||
| serial UINT NOT NULL DEFAULT 1, | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP | ||
| ); | ||
| ``` | ||
|
|
||
| Zone entries are stored as PTR records in the existing `dns_records` table, within the catalog zone's own zone. The content format is `zone_name:zone_id[:group_id]` (e.g., `foo.example.com.:uuid-123:group1`). | ||
|
|
||
| The `dns_zones` table gains two new columns: | ||
| - `catalog_id` — FK to `catalog_zones(id)` linking a zone to its source catalog | ||
| - `catalog_zone_name` — the zone's name within the catalog (used for targeted lookups) | ||
|
|
||
| ### API | ||
|
|
||
| Seven REST endpoints for catalog zone management: | ||
|
|
||
| | Method | Path | Description | | ||
| |--------|------|-------------| | ||
| | POST | `/catalog-zones` | Create a catalog zone | | ||
| | GET | `/catalog-zones` | List all catalog zones for tenant | | ||
| | GET | `/catalog-zones/{id}` | Get a specific catalog zone | | ||
| | DELETE | `/catalog-zones/{id}` | Delete a catalog zone | | ||
| | POST | `/catalog-zones/{id}/entries` | Add a zone to the catalog | | ||
| | DELETE | `/catalog-zones/{id}/entries/{zone_name}` | Remove a zone from the catalog | | ||
| | GET | `/catalog-zones/{id}/entries` | List all entries in a catalog zone | | ||
|
|
||
| ### Storage | ||
|
|
||
| Catalog entries are stored as PTR records with reversed zone names (e.g., `foo.example.com.` → `com.example.foo.`). This enables lexicographic range queries for efficient deletion of exact matches without LIKE. | ||
|
|
||
| Removing an entry uses a prefix range query: | ||
| ```sql | ||
| DELETE FROM dns_records | ||
| WHERE zone_id = $1 AND type = 'PTR' | ||
| AND content >= $2 AND content < $3 -- $2 = "zoneName:", $3 = "zoneName:\xff" | ||
| ``` | ||
|
|
||
| This correctly distinguishes `foo.example.com.` from `foobar.example.com.`. | ||
|
|
||
| ### Polling | ||
|
|
||
| The server starts a background poller when `CATALOG_POLLING_ENABLED=true`. The poller: | ||
| 1. Queries CZTR (RFC 9432 Catalog Zone Transfer) to verify master support | ||
| 2. Queries SOA to detect serial changes (skip if unchanged) | ||
| 3. Performs AXFR on the catalog zone to fetch all PTR records | ||
| 4. For each entry, calls `syncZoneFromCatalog` which: | ||
| - Checks if zone already exists via `GetZoneByCatalogName` (O(1), not `ListZones` (O(n)) | ||
| - Creates the zone record with `catalog_zone_name` set | ||
| - Performs AXFR on the zone to fetch all records | ||
| - Rolls back zone creation if AXFR fails | ||
|
|
||
| ### Tenant Isolation | ||
|
|
||
| All catalog operations are tenant-scoped. The `tenant_id` is propagated through the service layer to the repository with `WHERE tenant_id = $2` filters on all queries. The poller uses `CATALOG_TENANT_ID` (required env var) or falls back to `NodeID` with a warning log. | ||
|
|
||
| ## Consequences | ||
|
|
||
| ### Positive | ||
| - Slave servers can auto-provision zones from a master without manual configuration | ||
| - Zone metadata (name, ID, group) is published in DNS using standard record types | ||
| - Serial-based change detection avoids unnecessary re-syncs | ||
| - TTL cleanup goroutine prevents unbounded memory growth in the poller state | ||
| - Targeted `GetZoneByCatalogName` query replaces O(n) `ListZones` scan | ||
|
|
||
| ### Negative | ||
| - New `catalog_zones` table and two new columns on `dns_zones` require schema migration | ||
| - Catalog zone itself must be provisioned as a regular zone before entries can be added | ||
| - `ON CONFLICT DO NOTHING` silently ignores duplicate entries — callers can't detect this | ||
|
|
||
| ### Neutral | ||
| - The REST API for catalog management is separate from the DNS protocol | ||
| - The poller runs as a background goroutine; if it fails, it logs and continues (next interval) | ||
| - `PollCatalogZone` and `SyncZonesFromCatalog` service methods are stubs — actual AXFR fetch is done by the server poller in `client.go` | ||
|
|
||
| ## Alternatives Considered | ||
|
|
||
| ### Alternative 1: Store entries in a separate table | ||
| **Why rejected:** Using PTR records in the existing `dns_records` table keeps catalog entries in one place and leverages existing query patterns. A separate table would add complexity without significant benefit. | ||
|
|
||
| ### Alternative 2: Use CNAME records for catalog entries | ||
| **Why rejected:** RFC 9432 specifies PTR records with the content format `zone_name:zone_id[:group_id]`. CNAME doesn't support this content format and doesn't enable the lexicographic range deletion pattern. | ||
|
|
||
| ### Alternative 3: Include zone data in the catalog zone itself via AXFR | ||
| **Why rejected:** AXFR on a catalog zone only transfers the catalog metadata (PTR records). Individual zone data is fetched via separate AXFR requests. This is the design in RFC 9432 and keeps the catalog zone lightweight. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid process-wide env mutation without restoration in subtests.
Line 475, Line 488, Line 506, and Line 526 unset
TRUST_ANCHOR_*variables viaos.Unsetenvbut never restore prior values. That can leak state across tests and cause order-dependent flakes. Prefer capturing prior values and restoring int.Cleanup(or isolate via helper that does this once per subtest).Also applies to: 488-492, 506-510, 526-530
🤖 Prompt for AI Agents