Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bindcar"
version = "0.5.1"
version = "0.6.0"
edition = "2021"
rust-version = "1.75"
authors = ["Erick Bourgeois <firestoned@firestoned.io>"]
Expand Down
19 changes: 12 additions & 7 deletions docs/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String>` 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<String, String>` 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`)
Expand All @@ -23,21 +25,24 @@ 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
- Fixed double semicolon bug in serialization of raw directives

### 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
Expand Down
231 changes: 212 additions & 19 deletions docs/src/developer-guide/rndc-parser.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ Parse `rndc showzone <zone>` 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)?;
Expand All @@ -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<String>,

// Primary/Secondary options
pub primaries: Option<Vec<PrimarySpec>>,
pub also_notify: Option<Vec<IpAddr>>,
pub notify: Option<NotifyMode>,

// Access Control options
pub allow_query: Option<Vec<IpAddr>>,
pub allow_transfer: Option<Vec<IpAddr>>,
pub allow_update: Option<Vec<IpAddr>>,
// Core fields
pub zone_name: String,
pub class: DnsClass,
pub zone_type: ZoneType,
pub file: Option<String>,

// Primary/Secondary options
pub primaries: Option<Vec<PrimarySpec>>,
pub also_notify: Option<Vec<IpAddr>>,
pub notify: Option<NotifyMode>,

// Access Control options
pub allow_query: Option<Vec<IpAddr>>,
pub allow_transfer: Option<Vec<IpAddr>>,
pub allow_update: Option<Vec<IpAddr>>,
pub allow_update_raw: Option<String>, // Raw directive for key-based updates
pub allow_update_forwarding: Option<Vec<IpAddr>>,
pub allow_notify: Option<Vec<IpAddr>>,
Expand Down Expand Up @@ -161,6 +161,79 @@ pub struct PrimarySpec {
}
```

### ForwarderSpec

Forwarder specification for forward zones:

```rust
pub struct ForwarderSpec {
pub address: IpAddr,
pub port: Option<u16>,
pub tls_config: Option<String>,
}
```

### 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
Expand Down Expand Up @@ -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:
Expand Down
Loading