From 99aa850797f71fc0f8be834700d532e9a4bf12f0 Mon Sep 17 00:00:00 2001 From: Erick Bourgeois Date: Tue, 30 Dec 2025 10:25:12 -0500 Subject: [PATCH] Finalize the parser for all of bind9 conf Signed-off-by: Erick Bourgeois --- Cargo.lock | 2 +- Cargo.toml | 2 +- docs/src/changelog.md | 19 +- docs/src/developer-guide/rndc-parser.md | 231 ++++++++++++++++++++++-- 4 files changed, 226 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4c7380..1cd773b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,7 +130,7 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bindcar" -version = "0.5.1" +version = "0.6.0" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index c5246d3..5cb081a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bindcar" -version = "0.5.1" +version = "0.6.0" edition = "2021" rust-version = "1.75" authors = ["Erick Bourgeois "] diff --git a/docs/src/changelog.md b/docs/src/changelog.md index b1bb880..10d379c 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -9,12 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Enhanced Zone Configuration Support** - Full BIND9 zone option preservation - - Added 30+ new structured fields to `ZoneConfig` (notify, forwarders, DNSSEC, transfer control, etc.) - - Added 6 new enum types: `ForwarderSpec`, `NotifyMode`, `ForwardMode`, `AutoDnssecMode`, `CheckNamesMode`, `MasterfileFormat` - - Added `raw_options: HashMap` catch-all for unknown BIND9 options - - Catch-all parser automatically preserves any unrecognized BIND9 zone options + - Added 30+ new structured fields to `ZoneConfig` (organized by category: access control, transfer control, DNSSEC, forwarding, zone maintenance, etc.) + - Added 6 new enum types: `NotifyMode`, `ForwardMode`, `AutoDnssecMode`, `CheckNamesMode`, `MasterfileFormat`, `ForwarderSpec` + - Added `raw_options: HashMap` catch-all for unrecognized BIND9 zone options + - Catch-all parser automatically preserves any unknown BIND9 zone options - Full round-trip preservation: parse → modify → serialize with zero data loss - Support for TSIG key references in `allow-update` via `allow_update_raw` field + - 43 comprehensive tests for new types and serialization in `rndc_types_tests.rs` + - 11 tests for unknown option preservation in `rndc_parser_tests.rs` - Comprehensive RNDC output parser using nom combinators - Support for parsing `rndc showzone` output into structured ZoneConfig - CIDR notation handling in IP address lists (e.g., `10.0.0.1/32`) @@ -23,14 +25,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Zone modification via PATCH /api/v1/zones/{name} for `also-notify`, `allow-transfer`, and `allow-update` ### Changed -- **ZoneConfig Structure** - Enhanced with 30+ optional fields organized by category -- **Parser** - Now preserves all unknown options in `raw_options` HashMap -- **Serializer** - Extended to serialize all new fields and raw options +- **ZoneConfig Structure** - Enhanced with 30+ optional fields organized by category (all backward compatible) +- **Parser** - Now preserves all unknown options in `raw_options` HashMap via catch-all parser +- **Serializer** - Extended `to_rndc_block()` to serialize all new fields and raw options - Zone modification now uses `rndc showzone` instead of `rndc zonestatus` for full configuration retrieval - RNDC errors now return 500 Internal Server Error (was 502 Bad Gateway) - Raw RNDC error messages returned to clients (no wrapper text) ### Fixed +- PATCH operations now preserve key-based `allow-update` directives through `allow_update_raw` field +- Fixed double semicolon bug in serialization of raw directives (now strips trailing semicolons before joining) - `rndc modzone` now sends complete zone definition including type (was causing "zone type not specified" errors) - Parser handles real-world BIND9 output with CIDR notation and TSIG keys - PATCH operations now preserve key-based `allow-update` directives when modifying other fields @@ -38,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Documentation - Added comprehensive RNDC parser documentation in developer guide +- Enhanced RNDC parser docs with new ZoneConfig fields and unknown option preservation - Added parser architecture diagrams and usage examples - Documented CIDR stripping rationale and key-based ACL handling - Created roadmap for BIND9 full zone configuration support diff --git a/docs/src/developer-guide/rndc-parser.md b/docs/src/developer-guide/rndc-parser.md index 778353d..e600cad 100644 --- a/docs/src/developer-guide/rndc-parser.md +++ b/docs/src/developer-guide/rndc-parser.md @@ -41,10 +41,10 @@ Parse `rndc showzone ` output: use bindcar::rndc_parser::parse_showzone; let output = r#"zone "example.com" { - type primary; - file "/var/cache/bind/example.com.zone"; - allow-transfer { 10.0.0.1/32; 10.0.0.2/32; }; - also-notify { 10.0.0.1; 10.0.0.2; }; +type primary; +file "/var/cache/bind/example.com.zone"; +allow-transfer { 10.0.0.1/32; 10.0.0.2/32; }; +also-notify { 10.0.0.1; 10.0.0.2; }; };"#; let config = parse_showzone(output)?; @@ -61,21 +61,21 @@ Primary data structure representing a BIND9 zone configuration: ```rust pub struct ZoneConfig { - // Core fields - pub zone_name: String, - pub class: DnsClass, - pub zone_type: ZoneType, - pub file: Option, - - // Primary/Secondary options - pub primaries: Option>, - pub also_notify: Option>, - pub notify: Option, - - // Access Control options - pub allow_query: Option>, - pub allow_transfer: Option>, - pub allow_update: Option>, +// Core fields +pub zone_name: String, +pub class: DnsClass, +pub zone_type: ZoneType, +pub file: Option, + +// Primary/Secondary options +pub primaries: Option>, +pub also_notify: Option>, +pub notify: Option, + +// Access Control options +pub allow_query: Option>, +pub allow_transfer: Option>, +pub allow_update: Option>, pub allow_update_raw: Option, // Raw directive for key-based updates pub allow_update_forwarding: Option>, pub allow_notify: Option>, @@ -161,6 +161,79 @@ pub struct PrimarySpec { } ``` +### ForwarderSpec + +Forwarder specification for forward zones: + +```rust +pub struct ForwarderSpec { + pub address: IpAddr, + pub port: Option, + pub tls_config: Option, +} +``` + +### NotifyMode + +NOTIFY mode for zone transfer notifications: + +```rust +pub enum NotifyMode { + Yes, // Send NOTIFY to all NS records and also-notify list + No, // Do not send NOTIFY + Explicit, // Send NOTIFY only to also-notify list + MasterOnly, // Send NOTIFY only from primary servers (legacy term) + PrimaryOnly, // Send NOTIFY only from primary servers (modern term) +} +``` + +### ForwardMode + +Forwarding mode for forward zones: + +```rust +pub enum ForwardMode { + Only, // Forward queries and do not attempt direct resolution + First, // Forward queries, fall back to direct resolution if no answer +} +``` + +### AutoDnssecMode + +Automatic DNSSEC signing mode: + +```rust +pub enum AutoDnssecMode { + Off, // DNSSEC signing disabled + Maintain, // Maintain existing signatures + Create, // Create new signatures automatically +} +``` + +### CheckNamesMode + +Check-names policy for zone data validation: + +```rust +pub enum CheckNamesMode { + Fail, // Reject zones with invalid names + Warn, // Accept but log warnings + Ignore, // Accept without warnings +} +``` + +### MasterfileFormat + +Zone file format: + +```rust +pub enum MasterfileFormat { + Text, // Standard text format + Raw, // Binary format (faster loading) + Map, // Memory-mapped format +} +``` + ## Parser Features ### CIDR Notation Handling @@ -251,6 +324,126 @@ assert_eq!(config.primaries, Some(vec![ ])); ``` +## Enhanced Features (v0.6.0+) + +### Unknown Option Preservation + +The parser includes a catch-all mechanism that preserves all unknown BIND9 zone options: + +```rust +let input = r#"zone "example.com" { + type primary; + file "/var/cache/bind/example.com.zone"; + zone-statistics full; + max-zone-ttl 86400; + custom-option "custom-value"; +};"#; + +let config = parse_showzone(input)?; + +// Unknown options preserved in raw_options HashMap +assert_eq!(config.raw_options.get("zone-statistics"), Some(&"full".to_string())); +assert_eq!(config.raw_options.get("max-zone-ttl"), Some(&"86400".to_string())); +assert_eq!(config.raw_options.get("custom-option"), Some(&"\"custom-value\"".to_string())); + +// Serialization preserves all options +let serialized = config.to_rndc_block(); +assert!(serialized.contains("zone-statistics full")); +assert!(serialized.contains("max-zone-ttl 86400")); +assert!(serialized.contains("custom-option \"custom-value\"")); +``` + +**Benefits:** +- **Future-Proof**: New BIND9 options automatically supported without code changes +- **No Data Loss**: Complete round-trip preservation of all configuration +- **Graceful Degradation**: Unrecognized options preserved verbatim +- **BIND9 Compatibility**: Works across all BIND9 versions + +### Block-Style Option Preservation + +The parser handles complex block-style options: + +```rust +let input = r#"zone "example.com" { + type primary; + update-policy { grant example.com. zonesub any; }; + acl-list { 10.0.0.0/8; 192.168.0.0/16; }; +};"#; + +let config = parse_showzone(input)?; + +// Block-style options preserved with full syntax +assert!(config.raw_options.contains_key("update-policy")); +let update_policy = config.raw_options.get("update-policy").unwrap(); +assert!(update_policy.contains("grant")); +assert!(update_policy.contains("zonesub")); +``` + +### Comprehensive Zone Configuration + +Parse zones with 30+ structured fields plus catch-all: + +```rust +let input = r#"zone "internal.local" { + type primary; + file "/var/cache/bind/internal.local.zone"; + + // Access control + allow-transfer { 10.244.1.18/32; 10.244.1.21/32; }; + allow-update { key "bindy-operator"; }; + also-notify { 10.244.1.18; 10.244.1.21; }; + notify yes; + + // DNSSEC + auto-dnssec maintain; + inline-signing yes; + + // Transfer control + max-transfer-time-in 3600; + + // Zone maintenance + max-zone-ttl 86400; + zone-statistics full; + + // Custom options + custom-bind-option "value"; +};"#; + +let config = parse_showzone(input)?; + +// Structured fields +assert_eq!(config.zone_type, ZoneType::Primary); +assert_eq!(config.notify, Some(NotifyMode::Yes)); +assert_eq!(config.auto_dnssec, Some(AutoDnssecMode::Maintain)); +assert_eq!(config.inline_signing, Some(true)); +assert_eq!(config.max_transfer_time_in, Some(3600)); +assert_eq!(config.max_zone_ttl, Some(86400)); + +// Raw directive preserved +assert!(config.allow_update_raw.is_some()); +assert!(config.allow_update_raw.unwrap().contains("key")); + +// Unknown options preserved +assert_eq!(config.raw_options.get("zone-statistics"), Some(&"full".to_string())); +assert_eq!(config.raw_options.get("custom-bind-option"), Some(&"\"value\"".to_string())); +``` + +**Supported Structured Fields (30+)**: + +**Access Control**: `allow-query`, `allow-transfer`, `allow-update`, `allow-update-forwarding`, `allow-notify` + +**Transfer Control**: `max-transfer-time-in`, `max-transfer-time-out`, `max-transfer-idle-in`, `max-transfer-idle-out`, `transfer-source`, `transfer-source-v6`, `alt-transfer-source`, `alt-transfer-source-v6` + +**DNSSEC**: `auto-dnssec`, `dnssec-dnskey-kskonly`, `dnssec-loadkeys-interval`, `dnssec-update-mode`, `inline-signing` + +**Forwarding**: `forwarders`, `forward` + +**Zone Maintenance**: `max-journal-size`, `max-records`, `max-zone-ttl`, `serial-update-method`, `zone-statistics` + +**Refresh/Retry**: `max-refresh-time`, `min-refresh-time`, `max-retry-time`, `min-retry-time` + +**Miscellaneous**: `check-names`, `masterfile-format`, `masterfile-style`, `notify`, `update-policy`, `sig-validity-interval`, `sig-signing-signatures` + ## Round-Trip Serialization Parse, modify, and serialize zone configurations: