From ae87d0b6803c854de25aae38b109cd68f9ad9fd0 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:38:54 +0200 Subject: [PATCH 001/113] Upstream TSIG support. - New `cascade tsig add` client command to request that the daemon add a TSIG key to the Cascade TSIG store. - New cascaded POST /tsig/ API to add a TSIG key to the Cascade TSIG store. - Extension of the `cascade zone add` command `--source` argument with an optional `!` syntax to define the TSIG key that Cascade should use when sending an XFR request to the upstream. - Pass the key defined by the source to the zone loader instead of None (the zone loader is already capable of using the key, it just wasn't being told which key to use) --- crates/api/src/lib.rs | 64 +++++++++++++++- crates/cli/src/commands/mod.rs | 10 ++- crates/cli/src/commands/tsig.rs | 129 ++++++++++++++++++++++++++++++++ src/center.rs | 77 ++++++++++++++----- src/tsig/mod.rs | 4 + src/units/http_server.rs | 34 +++++++++ 6 files changed, 292 insertions(+), 26 deletions(-) create mode 100644 crates/cli/src/commands/tsig.rs diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index d599693b9..05e38de5c 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -187,6 +187,35 @@ pub struct KmipKeyImport { pub flags: String, } +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct TsigAdd { + pub name: String, + pub alg: TsigAlgorithm, + pub secret: String, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct TsigAddResult; + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub enum TsigAddError { + InvalidKeyName, + AlreadyExists, + InvalidAlgorithmName, + InvalidBase64Secret, +} + +impl Display for TsigAddError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TsigAddError::InvalidKeyName => write!(f, "invalid TSIG key name"), + TsigAddError::AlreadyExists => write!(f, "TSIG key already exists"), + TsigAddError::InvalidAlgorithmName => write!(f, "invalid TSIG algorithm name"), + TsigAddError::InvalidBase64Secret => write!(f, "invalid TSIG base64 encoded secret"), + } + } +} + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct ZoneAdd { pub name: ZoneName, @@ -206,6 +235,8 @@ pub enum ZoneAddError { AlreadyExists, NoSuchPolicy, PolicyMidDeletion, + InvalidTsigKeyName(String), + NoSuchTsigKey, Other(String), } @@ -215,6 +246,8 @@ impl fmt::Display for ZoneAddError { Self::AlreadyExists => "a zone of this name already exists", Self::NoSuchPolicy => "no policy with that name exists", Self::PolicyMidDeletion => "the specified policy is being deleted", + Self::InvalidTsigKeyName(reason) => reason, + Self::NoSuchTsigKey => "no TSIG key with that name exists", Self::Other(reason) => reason, }) } @@ -291,17 +324,22 @@ impl Display for ZoneSource { } impl From<&str> for ZoneSource { - fn from(s: &str) -> Self { + fn from(mut s: &str) -> Self { + let tsig_key = s.split_once('!').map(|(new_s, k)| { + s = new_s; + k.to_string() + }); + if let Ok(addr) = s.parse::() { ZoneSource::Server { addr, - tsig_key: None, + tsig_key, xfr_status: Default::default(), } } else if let Ok(addr) = s.parse::() { ZoneSource::Server { addr: SocketAddr::new(addr, DEFAULT_AXFR_PORT), - tsig_key: None, + tsig_key, xfr_status: Default::default(), } } else { @@ -478,6 +516,26 @@ impl Display for KeyType { } } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub enum TsigAlgorithm { + Sha1, + Sha256, + Sha384, + Sha512, +} + +impl Display for TsigAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TsigAlgorithm::Sha1 => "hmac-sha1", + TsigAlgorithm::Sha256 => "hmac-sha256", + TsigAlgorithm::Sha384 => "hmac-sha384", + TsigAlgorithm::Sha512 => "hmac-sha512", + } + .fmt(f) + } +} + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct ZoneHistory { pub history: Vec, diff --git a/crates/cli/src/commands/mod.rs b/crates/cli/src/commands/mod.rs index 5e19da16d..1a31590bd 100644 --- a/crates/cli/src/commands/mod.rs +++ b/crates/cli/src/commands/mod.rs @@ -6,6 +6,7 @@ pub mod keyset; pub mod policy; pub mod status; pub mod template; +pub mod tsig; pub mod zone; use crate::client::CascadeApiClient; @@ -37,10 +38,10 @@ pub enum Command { /// Execute manual key roll or key removal commands #[command(name = "keyset")] KeySet(self::keyset::KeySet), - // - // /// Manage keys - // #[command(name = "key")] - // Key(self::key::Key), + + /// Manage TSIG keys + #[command(name = "tsig")] + Tsig(self::tsig::Tsig), // - Command: add/remove/modify a zone // - Command: add/remove/modify a key for a zone // - Command: add/remove/modify a key @@ -74,6 +75,7 @@ impl Command { Self::Policy(policy) => policy.execute(client).await, Self::KeySet(keyset) => keyset.execute(client).await, Self::Hsm(hsm) => hsm.execute(client).await, + Self::Tsig(tsig) => tsig.execute(client).await, Self::Template(template) => template.execute(client).await, } } diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs new file mode 100644 index 000000000..d399d975c --- /dev/null +++ b/crates/cli/src/commands/tsig.rs @@ -0,0 +1,129 @@ +use std::str::FromStr; + +use cascade_api::{TsigAddError, TsigAddResult}; + +use crate::client::CascadeApiClient; +use crate::println; + +#[derive(Clone, Debug, clap::ValueEnum)] +pub enum TsigAlgorithm { + Sha1, + Sha256, + Sha384, + Sha512, +} + +impl FromStr for TsigAlgorithm { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "hmac-sha1" => Ok(TsigAlgorithm::Sha1), + "hmac-sha256" => Ok(TsigAlgorithm::Sha256), + "hmac-sha384" => Ok(TsigAlgorithm::Sha384), + "hmac-sha512" => Ok(TsigAlgorithm::Sha512), + other => Err(format!("'{other}' is not a recognized TSIG algorithm")), + } + } +} + +impl From for crate::api::TsigAlgorithm { + fn from(alg: TsigAlgorithm) -> Self { + match alg { + TsigAlgorithm::Sha1 => cascade_api::TsigAlgorithm::Sha1, + TsigAlgorithm::Sha256 => cascade_api::TsigAlgorithm::Sha256, + TsigAlgorithm::Sha384 => cascade_api::TsigAlgorithm::Sha384, + TsigAlgorithm::Sha512 => cascade_api::TsigAlgorithm::Sha512, + } + } +} + +#[derive(Clone, Debug, clap::Args)] +pub struct Tsig { + #[command(subcommand)] + command: TsigCommand, +} + +#[derive(Clone, Debug, clap::Subcommand)] +pub enum TsigCommand { + #[command(name = "add")] + Add { + /// The name of the TSIG key to add. + /// + /// Can also be in the form [algorithm]:keyname:secret. + name: String, + + /// The TSIG algorithm to use. + /// + /// Can be omitted if provided as part of the name. + /// Required if [SECRET] is provided. + #[arg(requires = "secret")] + alg: Option, + + /// Base64 encoded secret key bytes. + /// + /// Can be omitted if provided as part of the name. + /// Required if [ALG] is provided. + #[arg(requires = "alg")] + secret: Option, + }, +} + +impl Tsig { + pub async fn execute(self, client: CascadeApiClient) -> Result<(), String> { + match self.command { + TsigCommand::Add { name, alg, secret } => { + let (name, alg, secret) = match (alg, secret) { + (None, None) => { + let parts: Vec<&str> = name.split(':').collect(); + match parts.as_slice() { + [alg_part, name_part, secret_part] => { + let alg = TsigAlgorithm::from_str(alg_part)?; + let name = name_part.to_string(); + let secret = secret_part.to_string(); + (name, alg, secret) + } + + [name_part, secret_part] => { + let alg = TsigAlgorithm::Sha256; + let name = name_part.to_string(); + let secret = secret_part.to_string(); + (name, alg, secret) + } + + _ => { + return Err( + "Invalid TSIG key format, should be: [algorithm]:keyname:secret" + .to_string(), + ); + } + } + } + + (Some(alg), Some(secret)) => (name, alg, secret), + + _ => unreachable!("Excluded via Clap 'requires' rules"), + }; + + let res: Result = client + .post_json_with( + "tsig/add", + &crate::api::TsigAdd { + name: name.clone(), + alg: alg.into(), + secret, + }, + ) + .await?; + + match res { + Ok(TsigAddResult) => { + println!("Added TSIG key '{name}'"); + Ok(()) + } + Err(err) => Err(format!("Failed to add TSIG key '{name}': {err}")), + } + } + } + } +} diff --git a/src/center.rs b/src/center.rs index a86efc007..f5071a54e 100644 --- a/src/center.rs +++ b/src/center.rs @@ -1,6 +1,7 @@ //! Cascade's central command. use std::collections::HashMap; +use std::str::FromStr; use std::{ fmt, io, sync::{Arc, Mutex}, @@ -9,6 +10,7 @@ use std::{ use arc_swap::ArcSwap; use bytes::Bytes; +use cascade_api::{TsigAddError, TsigAddResult}; use domain::base::iana::Class; use domain::rdata::dnssec::Timestamp; use domain::zonetree::StoredName; @@ -20,6 +22,7 @@ use crate::config::RuntimeConfig; use crate::loader::Loader; use crate::loader::zone::LoaderZoneHandle; use crate::manager::record_zone_event; +use crate::tsig::ImportError; use crate::units::key_manager::KeyManager; use crate::units::zone_server::ZoneServer; use crate::units::zone_signer::ZoneSigner; @@ -90,7 +93,7 @@ pub async fn add_zone( ) -> Result<(), ZoneAddError> { let zone = Arc::new(Zone::new(name.clone())); - { + let source = { let mut state = center.state.lock().unwrap(); // We check whether the state contains this zone, because @@ -122,7 +125,38 @@ pub async fn add_zone( if !state.zones.insert(zone_by_name.clone()) { return Err(ZoneAddError::AlreadyExists); } - } + + match source { + cascade_api::ZoneSource::None => crate::loader::Source::None, + cascade_api::ZoneSource::Zonefile { path } => crate::loader::Source::Zonefile { path }, + cascade_api::ZoneSource::Server { + addr, + tsig_key, + xfr_status: _, + } => { + let tsig_key = if let Some(key_name) = tsig_key { + // Verify that the key name is syntactically valid. + let key_name = Name::from_str(&key_name) + .map_err(|err| ZoneAddError::InvalidTsigKeyName(err.to_string()))?; + + // Lookup the key in the TSIG key store. + let key = state + .tsig_store + .get(&key_name) + .ok_or(ZoneAddError::NoSuchTsigKey)? + .inner + .clone(); + + // Use the found key. + Some(key) + } else { + None + }; + + crate::loader::Source::Server { addr, tsig_key } + } + } + }; // Send out a registration command so that prerequisites for zone setup // (such as invoking dnst keyset create, ..., init) can be done _before_ @@ -145,23 +179,6 @@ pub async fn add_zone( { let mut state = zone.state.lock().unwrap(); - let source = match source { - cascade_api::ZoneSource::None => crate::loader::Source::None, - cascade_api::ZoneSource::Zonefile { path } => crate::loader::Source::Zonefile { path }, - cascade_api::ZoneSource::Server { - addr, - tsig_key, - xfr_status: _, - } => { - // TODO: TSIG. - let _ = tsig_key; - crate::loader::Source::Server { - addr, - tsig_key: None, - } - } - }; - // Set the source of the zone, and begin loading it. LoaderZoneHandle { zone: &zone, @@ -254,6 +271,20 @@ pub fn get_zone(center: &Arc
, name: &StoredName) -> Option> { state.zones.get(name).map(|zone| zone.0.clone()) } +pub async fn add_tsig_key( + center: &Arc
, + name: Name>, + alg: domain::tsig::Algorithm, + secret: &[u8], +) -> Result { + crate::tsig::import_key(center, name.clone(), alg, secret, false) + .map_err(|ImportError::AlreadyExists| TsigAddError::AlreadyExists)?; + + info!("Added TSIG key '{name}'"); + + Ok(TsigAddResult) +} + //----------- State ------------------------------------------------------------ /// Global state for Cascade. @@ -362,6 +393,10 @@ pub enum ZoneAddError { NoSuchPolicy, /// The specified policy is being deleted. PolicyMidDeletion, + /// The specified TSIG key name is invalid. + InvalidTsigKeyName(String), + /// No TSIG key with that name exists. + NoSuchTsigKey, /// Some other error occurred. Other(String), } @@ -374,6 +409,8 @@ impl fmt::Display for ZoneAddError { Self::AlreadyExists => "a zone of this name already exists", Self::NoSuchPolicy => "no policy with that name exists", Self::PolicyMidDeletion => "the specified policy is being deleted", + Self::InvalidTsigKeyName(reason) => reason, + Self::NoSuchTsigKey => "no TSIG key with that name exists", Self::Other(reason) => reason, }) } @@ -385,6 +422,8 @@ impl From for api::ZoneAddError { ZoneAddError::AlreadyExists => Self::AlreadyExists, ZoneAddError::NoSuchPolicy => Self::NoSuchPolicy, ZoneAddError::PolicyMidDeletion => Self::PolicyMidDeletion, + ZoneAddError::InvalidTsigKeyName(reason) => Self::InvalidTsigKeyName(reason), + ZoneAddError::NoSuchTsigKey => Self::NoSuchTsigKey, ZoneAddError::Other(reason) => Self::Other(reason), } } diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index a2b959b31..4260a6200 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -81,6 +81,10 @@ impl TsigStore { }); self.enqueued_save = Some(task); } + + pub fn get(&self, key_name: &tsig::KeyName) -> Option<&TsigKey> { + self.map.get(key_name) + } } //----------- Actions ---------------------------------------------------------- diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 0e82b0dab..3ab69b408 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -1,4 +1,5 @@ use std::future::IntoFuture; +use std::str::FromStr; use std::sync::Arc; use std::sync::atomic::Ordering::Relaxed; use std::time::Duration; @@ -21,6 +22,8 @@ use domain::base::Ttl; use domain::base::iana::Class; use domain::dnssec::sign::keys::keyset::KeyType; use domain::rdata::Soa; +use domain::tsig::Algorithm; +use domain::utils::base64; use domain::zonetree::ReadableZone; use domain::zonetree::error::OutOfZone; use domain_kmip::ConnectionSettings; @@ -102,6 +105,10 @@ impl HttpServer { .route("/status", get(Self::status)) .route("/status/keys", get(Self::status_keys)) .route("/debug/change-logging", post(Self::change_logging)) + // .route("/tsig/", get(Self::tsig_keys_list)) + .route("/tsig/add", post(Self::tsig_key_add)) + // .route("/tsig/{name}/remove", post(Self::tsig_key_remove)) + // .route("/tsig/{name}/status", get(Self::tsig_key_status)) .route("/zone/", get(Self::zones_list)) .route("/zone/add", post(Self::zone_add)) // TODO: .route("/zone/{name}/", get(Self::zone_get)) @@ -1095,6 +1102,33 @@ impl HttpServer { Json(KeyStatusResult { expirations, zones }) } + + async fn tsig_key_add( + State(state): State>, + Json(tsig_add): Json, + ) -> Json> { + let Ok(secret) = base64::decode::>(&tsig_add.secret) else { + return Json(Err(TsigAddError::InvalidBase64Secret)); + }; + + let res = match Name::>::from_str(&tsig_add.name) { + Ok(tsig_key_name) => { + let alg = match tsig_add.alg { + TsigAlgorithm::Sha1 => Algorithm::Sha1, + TsigAlgorithm::Sha256 => Algorithm::Sha256, + TsigAlgorithm::Sha384 => Algorithm::Sha384, + TsigAlgorithm::Sha512 => Algorithm::Sha512, + }; + center::add_tsig_key(&state.center, tsig_key_name, alg, &secret).await + } + Err(_err) => return Json(Err(TsigAddError::InvalidKeyName)), + }; + + match res { + Ok(TsigAddResult) => Json(Ok(TsigAddResult)), + Err(err) => Json(Err(err)), + } + } } //------------ HttpServer Handler for /kmip ---------------------------------- From 25fdfe95dc6780e2735ecdac1e55fca9bb4cf1ca Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:53:52 +0200 Subject: [PATCH 002/113] Fix RustDoc violations. --- crates/cli/src/commands/tsig.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs index d399d975c..e940e28cf 100644 --- a/crates/cli/src/commands/tsig.rs +++ b/crates/cli/src/commands/tsig.rs @@ -50,20 +50,20 @@ pub enum TsigCommand { Add { /// The name of the TSIG key to add. /// - /// Can also be in the form [algorithm]:keyname:secret. + /// Can also be in the form `[algorithm]:keyname:secret`. name: String, /// The TSIG algorithm to use. /// /// Can be omitted if provided as part of the name. - /// Required if [SECRET] is provided. + /// Required if `[SECRET]` is provided. #[arg(requires = "secret")] alg: Option, /// Base64 encoded secret key bytes. /// /// Can be omitted if provided as part of the name. - /// Required if [ALG] is provided. + /// Required if `[ALG]` is provided. #[arg(requires = "alg")] secret: Option, }, From 8a11e9dd2b6ea923e1416cb127a696713d6d3574 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 3 Apr 2026 23:17:57 +0200 Subject: [PATCH 003/113] (De)serialize to Base64. As Base64 is typically how tooling present/accept TSIG key secret data to/from users. --- src/tsig/file/v1.rs | 47 +++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/tsig/file/v1.rs b/src/tsig/file/v1.rs index a6e6615e0..cf7f72f4a 100644 --- a/src/tsig/file/v1.rs +++ b/src/tsig/file/v1.rs @@ -86,9 +86,32 @@ pub struct KeySpec { pub alg: AlgSpec, /// The private key material. + #[serde(with = "tsig_base64")] pub data: Box<[u8]>, } +mod tsig_base64 { + use domain::utils::base64; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(data: &[u8], serializer: S) -> Result + where + S: Serializer, + { + base64::encode_string(data).serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let data = base64::decode::>(&s) + .map_err(serde::de::Error::custom)?; + Ok(data.into()) + } +} + //--- Conversion impl KeySpec { @@ -133,16 +156,16 @@ impl KeySpec { #[serde(rename_all = "kebab-case")] pub enum AlgSpec { /// SHA-1. - Sha1, + HmacSha1, /// SHA-256. - Sha256, + HmacSha256, /// SHA-384, - Sha384, + HmacSha384, /// SHA-512. - Sha512, + HmacSha512, } //--- Conversion @@ -151,20 +174,20 @@ impl AlgSpec { /// Parse from this specification. pub fn parse(self) -> tsig::Algorithm { match self { - AlgSpec::Sha1 => tsig::Algorithm::Sha1, - AlgSpec::Sha256 => tsig::Algorithm::Sha256, - AlgSpec::Sha384 => tsig::Algorithm::Sha384, - AlgSpec::Sha512 => tsig::Algorithm::Sha512, + AlgSpec::HmacSha1 => tsig::Algorithm::Sha1, + AlgSpec::HmacSha256 => tsig::Algorithm::Sha256, + AlgSpec::HmacSha384 => tsig::Algorithm::Sha384, + AlgSpec::HmacSha512 => tsig::Algorithm::Sha512, } } /// Build into this specification. pub fn build(alg: tsig::Algorithm) -> Self { match alg { - tsig::Algorithm::Sha1 => AlgSpec::Sha1, - tsig::Algorithm::Sha256 => AlgSpec::Sha256, - tsig::Algorithm::Sha384 => AlgSpec::Sha384, - tsig::Algorithm::Sha512 => AlgSpec::Sha512, + tsig::Algorithm::Sha1 => AlgSpec::HmacSha1, + tsig::Algorithm::Sha256 => AlgSpec::HmacSha256, + tsig::Algorithm::Sha384 => AlgSpec::HmacSha384, + tsig::Algorithm::Sha512 => AlgSpec::HmacSha512, } } } From 52c88cf3c7768450afc6bb13f0bb35ef10864312 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 3 Apr 2026 23:18:14 +0200 Subject: [PATCH 004/113] cargo fmt. --- src/tsig/file/v1.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tsig/file/v1.rs b/src/tsig/file/v1.rs index cf7f72f4a..dd875fb07 100644 --- a/src/tsig/file/v1.rs +++ b/src/tsig/file/v1.rs @@ -106,8 +106,7 @@ mod tsig_base64 { D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; - let data = base64::decode::>(&s) - .map_err(serde::de::Error::custom)?; + let data = base64::decode::>(&s).map_err(serde::de::Error::custom)?; Ok(data.into()) } } From 79b330822878e07bf4de82ca225dc8c2b3eba8cb Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:40:48 +0200 Subject: [PATCH 005/113] (de)serialize TSIG algorithm names exactly as defined in the IANA registry. --- src/tsig/file/v1.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/tsig/file/v1.rs b/src/tsig/file/v1.rs index dd875fb07..26cc0c9fd 100644 --- a/src/tsig/file/v1.rs +++ b/src/tsig/file/v1.rs @@ -151,19 +151,26 @@ impl KeySpec { //----------- AlgSpec ---------------------------------------------------------- /// A TSIG key algorithm specification. +/// +/// A subset of the [IANA TSIG algorithm name registry]. +/// +/// [IANA TSIG algorithm name registry]: https://www.iana.org/assignments/tsig-algorithm-names/tsig-algorithm-names.xhtml #[derive(Copy, Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] pub enum AlgSpec { - /// SHA-1. + /// hmac-sha1. + #[serde(rename = "hmac-sha1")] HmacSha1, - /// SHA-256. + /// hmac-sha256. + #[serde(rename = "hmac-sha256")] HmacSha256, - /// SHA-384, + /// hmac-sha384, + #[serde(rename = "hmac-sha384")] HmacSha384, - /// SHA-512. + /// hmac-sha512. + #[serde(rename = "hmac-sha512")] HmacSha512, } From 4b07b118fcd4ba52fe8ee26193edc8b41902a999 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:33:42 +0200 Subject: [PATCH 006/113] Use ^ instead of ! and start adding man page content. --- crates/api/src/lib.rs | 9 ++++++++- doc/manual/source/man/cascade-zone.rst | 14 +++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 05e38de5c..41702244b 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -323,9 +323,16 @@ impl Display for ZoneSource { } } +/// Support parsing of ``-source`` command line arguments. +/// +/// Supported forms: +/// - `[:][^]` +/// - `` impl From<&str> for ZoneSource { fn from(mut s: &str) -> Self { - let tsig_key = s.split_once('!').map(|(new_s, k)| { + // Split out any provided TSIG key from the rest of the + // source argument. + let tsig_key = s.split_once('^').map(|(new_s, k)| { s = new_s; k.to_string() }); diff --git a/doc/manual/source/man/cascade-zone.rst b/doc/manual/source/man/cascade-zone.rst index 72cf80795..21c739230 100644 --- a/doc/manual/source/man/cascade-zone.rst +++ b/doc/manual/source/man/cascade-zone.rst @@ -84,11 +84,23 @@ Commands Options for :subcmd:`zone add` ------------------------------ -.. option:: --source +.. option:: --source ][^ +.. option:: --source The zone source can be an IP address (with or without port, defaults to port 53) or a file path. + When providing an IP address (with or without port) you may also optionally + suffix it with ``^`` to indicate that the specified RFC 8945 + TSIG key should be used to sign the zone transfer request. + + Note: When providing a file path to a zone file to load, if :subcmd:`zone + add` is executed on a different host than where the ``cascaded`` daemon is + running the path must be valid on the **daemon** host. + + Note: In order to use a TSIG key you MUST also supply the key to Cascade + via :subcmd:`tsig add`. + .. option:: --policy Policy to use for this zone. From 4d56ffbe89698ba6ee789a562e627d2fe38eff98 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Tue, 7 Apr 2026 23:50:49 +0200 Subject: [PATCH 007/113] Add a cascade tsig man page. --- Cargo.toml | 2 + doc/manual/build/man/cascade-debug.1 | 4 +- doc/manual/build/man/cascade-health.1 | 2 +- doc/manual/build/man/cascade-hsm.1 | 2 +- doc/manual/build/man/cascade-keyset.1 | 4 +- doc/manual/build/man/cascade-policy.1 | 2 +- doc/manual/build/man/cascade-status.1 | 2 +- doc/manual/build/man/cascade-template.1 | 2 +- doc/manual/build/man/cascade-zone.1 | 29 ++++++++- doc/manual/build/man/cascade.1 | 5 +- doc/manual/build/man/cascaded-config.toml.5 | 43 +++++-------- doc/manual/build/man/cascaded-policy.toml.5 | 2 +- doc/manual/build/man/cascaded.1 | 2 +- doc/manual/source/conf.py | 1 + doc/manual/source/index.rst | 1 + doc/manual/source/man/cascade-tsig.rst | 69 +++++++++++++++++++++ doc/manual/source/man/cascade.rst | 7 +++ 17 files changed, 137 insertions(+), 42 deletions(-) create mode 100644 doc/manual/source/man/cascade-tsig.rst diff --git a/Cargo.toml b/Cargo.toml index 12e0a0465..9bb61a20c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -248,6 +248,7 @@ assets = [ ["doc/manual/build/man/cascade-config.1", "usr/share/man/man1/cascade-config.1", "644"], ["doc/manual/build/man/cascade-health.1", "usr/share/man/man1/cascade-health.1", "644"], ["doc/manual/build/man/cascade-hsm.1", "usr/share/man/man1/cascade-hsm.1", "644"], +["doc/manual/build/man/cascade-tsig.1", "usr/share/man/man1/cascade-tsig.1", "644"], ["doc/manual/build/man/cascade-keyset.1", "usr/share/man/man1/cascade-keyset.1", "644"], ["doc/manual/build/man/cascade-policy.1", "usr/share/man/man1/cascade-policy.1", "644"], ["doc/manual/build/man/cascade-status.1", "usr/share/man/man1/cascade-status.1", "644"], @@ -302,6 +303,7 @@ assets = [ { source = "doc/manual/build/man/cascade-config.1", dest = "/usr/share/man/man1/cascade-config.1", mode = "644", doc = true }, { source = "doc/manual/build/man/cascade-health.1", dest = "/usr/share/man/man1/cascade-health.1", mode = "644", doc = true }, { source = "doc/manual/build/man/cascade-hsm.1", dest = "/usr/share/man/man1/cascade-hsm.1", mode = "644", doc = true }, +{ source = "doc/manual/build/man/cascade-tsig.1", dest = "/usr/share/man/man1/cascade-tsig.1", mode = "644", doc = true }, { source = "doc/manual/build/man/cascade-keyset.1", dest = "/usr/share/man/man1/cascade-keyset.1", mode = "644", doc = true }, { source = "doc/manual/build/man/cascade-policy.1", dest = "/usr/share/man/man1/cascade-policy.1", mode = "644", doc = true }, { source = "doc/manual/build/man/cascade-status.1", dest = "/usr/share/man/man1/cascade-status.1", mode = "644", doc = true }, diff --git a/doc/manual/build/man/cascade-debug.1 b/doc/manual/build/man/cascade-debug.1 index 5ce339d55..dbbc6b7ff 100644 --- a/doc/manual/build/man/cascade-debug.1 +++ b/doc/manual/build/man/cascade-debug.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-DEBUG" "1" "Nov 21, 2025" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-DEBUG" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-debug \- Debug / troubleshoot Cascade .SH SYNOPSIS @@ -89,6 +89,6 @@ Cascade online documentation .SH AUTHOR NLnet Labs .SH COPYRIGHT -2025–2025, NLnet Labs +2025–2026, NLnet Labs .\" Generated by docutils manpage writer. . diff --git a/doc/manual/build/man/cascade-health.1 b/doc/manual/build/man/cascade-health.1 index 39f3def64..29bd227a2 100644 --- a/doc/manual/build/man/cascade-health.1 +++ b/doc/manual/build/man/cascade-health.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HEALTH" "1" "Feb 25, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HEALTH" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-health \- Check the health of Cascade .sp diff --git a/doc/manual/build/man/cascade-hsm.1 b/doc/manual/build/man/cascade-hsm.1 index 147f0ff77..cf5f3bffd 100644 --- a/doc/manual/build/man/cascade-hsm.1 +++ b/doc/manual/build/man/cascade-hsm.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HSM" "1" "Feb 27, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HSM" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-hsm \- Manage HSMs .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-keyset.1 b/doc/manual/build/man/cascade-keyset.1 index e38ab362d..30b191c8f 100644 --- a/doc/manual/build/man/cascade-keyset.1 +++ b/doc/manual/build/man/cascade-keyset.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-KEYSET" "1" "Mar 23, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-KEYSET" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-keyset \- Execute manual key roll or key removal commands .SH SYNOPSIS @@ -153,7 +153,7 @@ dnskey | dnst key2ds \-n /dev/stdin\fP\&. .SH SEE ALSO .INDENT 0.0 .TP -.B \fI\%https://cascade.docs.nlnetlabs.nl\fP +.B \X'tty: link https://cascade.docs.nlnetlabs.nl'\fI\%https://cascade.docs.nlnetlabs.nl\fP\X'tty: link' Cascade online documentation .TP \fBcascade\fP(1) diff --git a/doc/manual/build/man/cascade-policy.1 b/doc/manual/build/man/cascade-policy.1 index 035c2436f..f503e4df0 100644 --- a/doc/manual/build/man/cascade-policy.1 +++ b/doc/manual/build/man/cascade-policy.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-POLICY" "1" "Feb 25, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-POLICY" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-policy \- Manage policies .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-status.1 b/doc/manual/build/man/cascade-status.1 index aaedfd62a..ee27023b4 100644 --- a/doc/manual/build/man/cascade-status.1 +++ b/doc/manual/build/man/cascade-status.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-STATUS" "1" "Feb 25, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-STATUS" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-status \- Show the status of Cascade .sp diff --git a/doc/manual/build/man/cascade-template.1 b/doc/manual/build/man/cascade-template.1 index dc5377cfb..6542e234c 100644 --- a/doc/manual/build/man/cascade-template.1 +++ b/doc/manual/build/man/cascade-template.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-TEMPLATE" "1" "Feb 25, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-TEMPLATE" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-template \- Print example config or policy files .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-zone.1 b/doc/manual/build/man/cascade-zone.1 index 8600066dc..3aac4c8ac 100644 --- a/doc/manual/build/man/cascade-zone.1 +++ b/doc/manual/build/man/cascade-zone.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-ZONE" "1" "Mar 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-ZONE" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-zone \- Manage zones .SH SYNOPSIS @@ -116,9 +116,34 @@ Get the history of a single zone. .SH OPTIONS FOR ZONE ADD .INDENT 0.0 .TP -.B \-\-source +.B \-\-source ][^ +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-source The zone source can be an IP address (with or without port, defaults to port 53) or a file path. +.sp +When providing an IP address (with or without port) you may also optionally +suffix it with \fB^\fP to indicate that the specified RFC 8945 +TSIG key should be used to sign the zone transfer request. +.sp +\fBNOTE:\fP +.INDENT 7.0 +.INDENT 3.5 +When providing the path to a zone file to load, if \fBzone +add\fP is executed on a different host than where the \fBcascaded\fP +daemon is running the path must be valid on the \fBdaemon\fP host. +.UNINDENT +.UNINDENT +.sp +\fBNOTE:\fP +.INDENT 7.0 +.INDENT 3.5 +Note: In order to use a TSIG key you MUST also supply the key to +Cascade via \fBtsig add\fP\&. +.UNINDENT +.UNINDENT .UNINDENT .INDENT 0.0 .TP diff --git a/doc/manual/build/man/cascade.1 b/doc/manual/build/man/cascade.1 index 1e3c7b87e..23a83d761 100644 --- a/doc/manual/build/man/cascade.1 +++ b/doc/manual/build/man/cascade.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE" "1" "Feb 25, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade \- Cascade CLI .SH SYNOPSIS @@ -73,6 +73,9 @@ Manage policies. \fBcascade\-keyset\fP(1) Execute manual key roll or key removal commands. .TP +\fBcascade\-tsig\fP(1) +Manage TSIG keys. +.TP \fBcascade\-hsm\fP(1) Manage HSMs. .TP diff --git a/doc/manual/build/man/cascaded-config.toml.5 b/doc/manual/build/man/cascaded-config.toml.5 index c567ec8e5..b3e9adedb 100644 --- a/doc/manual/build/man/cascaded-config.toml.5 +++ b/doc/manual/build/man/cascaded-config.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-CONFIG.TOML" "5" "Feb 25, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-CONFIG.TOML" "5" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-config.toml \- Cascade configuration file .sp @@ -68,19 +68,18 @@ identity = \(dqcascade:cascade\(dq servers = [\(dq127.0.0.1:4539\(dq, \(dq[::1]:4539\(dq] [loader] -notify\-listeners = [\(dq127.0.0.1:4540\(dq, \(dq[::1]:4540\(dq] [loader.review] -servers = [\(dq127.0.0.1:4541\(dq, \(dq[::1]:4541\(dq] +servers = [\(dq127.0.0.1:4540\(dq, \(dq[::1]:4540\(dq] [signer] [signer.review] -servers = [\(dq127.0.0.1:4542\(dq, \(dq[::1]:4542\(dq] +servers = [\(dq127.0.0.1:4541\(dq, \(dq[::1]:4541\(dq] [key\-manager] [server] -servers = [\(dq127.0.0.1:4543\(dq, \(dq[::1]:4543\(dq] +servers = [\(dq127.0.0.1:4542\(dq, \(dq[::1]:4542\(dq] .EE .UNINDENT .UNINDENT @@ -274,7 +273,9 @@ optional). If this option is set to \fBtrue\fP, the server changes its working directory to the root directory and as such influences where files are looked for. Use absolute path names in configuration -to avoid ambiguities. +to avoid ambiguities. Additionally, it will redirect stdout and stderr to +\fB/dev/null\fP and you need to choose \fBsyslog\fP or \fBfile\fP as the +\fI\%log\-target\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -324,35 +325,21 @@ dropping privileges, if that is enabled). .UNINDENT .SS How zones are loaded. .sp -The \fB[loader]\fP section. -.INDENT 0.0 -.TP -.B notify\-listeners = [\(dq127.0.0.1:4540\(dq, \(dq[::1]:4540\(dq] -Where to listen for zone change notifications. -.sp -A DNS server will be bound to these addresses. If a DNS NOTIFY message for -a known zone is received there, the zone will be reloaded appropriately. -.sp -Unless explicitly specified (e.g. \fBudp://localhost:4540\fP), each address will -be served over UDP and TCP. An empty array will disable listening entirely. -.sp -These sockets may be bound by systemd and passed into Cascade. If systemd -does not provide them, Cascade will bind them itself (and will do so before -dropping privileges, if that is enabled). -.UNINDENT +The \fB[loader]\fP section. (This only includes the \fB[loader.review]\fP section +below, for now). .SS How loaded zones are reviewed. .sp The \fB[loader.review]\fP section. .INDENT 0.0 .TP -.B servers = [\(dq127.0.0.1:4541\(dq, \(dq[::1]:4541\(dq] +.B servers = [\(dq127.0.0.1:4540\(dq, \(dq[::1]:4540\(dq] Where to serve loaded zones for review. .sp A DNS server will be bound to these addresses, and will serve the contents of all loaded zones. This can be used to verify the consistency of these zones. .sp -Unless explicitly specified (e.g. \fBudp://localhost:4541\fP), each address will +Unless explicitly specified (e.g. \fBudp://localhost:4540\fP), each address will be served over UDP and TCP. An empty array will disable serving entirely. .sp These sockets may be bound by systemd and passed into Cascade. If systemd @@ -368,14 +355,14 @@ below, for now). The \fB[signer.review]\fP section. .INDENT 0.0 .TP -.B servers = [\(dq127.0.0.1:4542\(dq, \(dq[::1]:4542\(dq] +.B servers = [\(dq127.0.0.1:4541\(dq, \(dq[::1]:4541\(dq] Where to serve signed zones for review. .sp A DNS server will be bound to these addresses, and will serve the contents of all signed (but not necessarily published) zones. This can be used to check the correctness of the signer. .sp -Unless explicitly specified (e.g. \fBudp://localhost:4542\fP), each address will +Unless explicitly specified (e.g. \fBudp://localhost:4541\fP), each address will be served over UDP and TCP. An empty array will disable serving entirely. .sp These sockets may be bound by systemd and passed into Cascade. If systemd @@ -390,13 +377,13 @@ The \fB[key\-manager]\fP section. (Currently without options) The \fB[server]\fP section. .INDENT 0.0 .TP -.B servers = [\(dq127.0.0.1:4543\(dq, \(dq[::1]:4543\(dq] +.B servers = [\(dq127.0.0.1:4542\(dq, \(dq[::1]:4542\(dq] Where to serve published zones. .sp A DNS server will be bound to these addresses, and will serve the contents of all published zones. This is the final output from Cascade. .sp -Unless explicitly specified (e.g. \fBudp://localhost:4543\fP), each address will +Unless explicitly specified (e.g. \fBudp://localhost:4542\fP), each address will be served over UDP and TCP. At least one address must be specified. .sp These sockets may be bound by systemd and passed into Cascade. If systemd diff --git a/doc/manual/build/man/cascaded-policy.toml.5 b/doc/manual/build/man/cascaded-policy.toml.5 index e92c90006..9b618b234 100644 --- a/doc/manual/build/man/cascaded-policy.toml.5 +++ b/doc/manual/build/man/cascaded-policy.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-POLICY.TOML" "5" "Feb 25, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-POLICY.TOML" "5" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-policy.toml \- Cascade policy file format .sp diff --git a/doc/manual/build/man/cascaded.1 b/doc/manual/build/man/cascaded.1 index d06176ee5..c8f65ff01 100644 --- a/doc/manual/build/man/cascaded.1 +++ b/doc/manual/build/man/cascaded.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED" "1" "Feb 25, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded \- DNSSEC signer .SH SYNOPSIS diff --git a/doc/manual/source/conf.py b/doc/manual/source/conf.py index d0f2c767d..9f935d2e1 100644 --- a/doc/manual/source/conf.py +++ b/doc/manual/source/conf.py @@ -250,6 +250,7 @@ ('man/cascade', 'cascade', 'Cascade CLI', author, 1), ('man/cascade-debug', 'cascade-debug', 'Debug / troubleshoot Cascade', author, 1), ('man/cascade-health', 'cascade-health', 'Check the health of Cascade', author, 1), + ('man/cascade-tsig', 'cascade-tsig', 'Manage TSIG keys', author, 1), ('man/cascade-hsm', 'cascade-hsm', 'Manage HSMs', author, 1), ('man/cascade-keyset', 'cascade-keyset', 'Execute manual key roll or key removal commands', author, 1), ('man/cascade-policy', 'cascade-policy', 'Manage policies', author, 1), diff --git a/doc/manual/source/index.rst b/doc/manual/source/index.rst index 503d22c82..dcfacf34e 100644 --- a/doc/manual/source/index.rst +++ b/doc/manual/source/index.rst @@ -161,6 +161,7 @@ Examples of things we're interested in: man/cascade-policy man/cascade-status man/cascade-template + man/cascade-tsig man/cascade-zone cascade-hsm-bridge Daemon cascade-hsm-bridge Configuration File Format diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst new file mode 100644 index 000000000..41fef1966 --- /dev/null +++ b/doc/manual/source/man/cascade-tsig.rst @@ -0,0 +1,69 @@ +cascade tsig +============ + +.. versionadded:: 0.1.0-beta1 + +Synopsis +-------- + +:program:`cascade` ``[GLOBAL OPTIONS]`` tsig ```` + +:program:`cascade` ``[GLOBAL OPTIONS]`` tsig :subcmd:`add` ``[OPTIONS]`` + +Description +----------- + +Manage RFC 8945 TSIG keys for authenticating zone transfers. + +Global Options +-------------- + +See :doc:`cascade` for information about global options supported by every CLI +command. + +Commands +-------- + +.. subcmd:: add + + Register a new TSIG key. + +Options for :subcmd:`tsig add` +------------------------------ + +.. option:: --name +.. option:: --name []:: + + The name of the TSIG key to add. + + Alternatively this argument also supports dig syntax for specifying all of + the TSIG properties at once in colon separated form. The colon separated + syntax cannot be used in combination with the ``--alg`` and ``--secret`` + options. + +.. option:: --alg + + The TSIG algorithm of the specified TSIG key. Can be one of: hmac-sha1, + hmac-sha256, hmac-sha384 or hmac-sha512. + +.. option:: --secret + + A base64 encoded string defining the actual TSIG key material bytes. + +See Also +-------- + +https://cascade.docs.nlnetlabs.nl + Cascade online documentation + +**cascade**\ (1) + :doc:`cascade` + +**cascaded**\ (1) + :doc:`cascaded` + +**cascaded-config.toml**\ (5) + :doc:`cascaded-config.toml` + +**cascaded-policy.toml**\ (5) + :doc:`cascaded-policy.toml` diff --git a/doc/manual/source/man/cascade.rst b/doc/manual/source/man/cascade.rst index aa939ab49..b2923e8ac 100644 --- a/doc/manual/source/man/cascade.rst +++ b/doc/manual/source/man/cascade.rst @@ -55,6 +55,10 @@ Commands Execute manual key roll or key removal commands. + :doc:`cascade-tsig `\ (1) + + Manage TSIG keys. + :doc:`cascade-hsm `\ (1) Manage HSMs. @@ -81,6 +85,9 @@ Commands **cascade-keyset**\ (1) Execute manual key roll or key removal commands. + **cascade-tsig**\ (1) + Manage TSIG keys. + **cascade-hsm**\ (1) Manage HSMs. From 53f62051e413021785bad84029f3dd4898207e3e Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 8 Apr 2026 00:02:01 +0200 Subject: [PATCH 008/113] More TSIG related man page updates. --- doc/manual/source/man/cascade-tsig.rst | 2 +- doc/manual/source/man/cascade-zone.rst | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 41fef1966..d0ceecb7a 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -39,7 +39,7 @@ Options for :subcmd:`tsig add` Alternatively this argument also supports dig syntax for specifying all of the TSIG properties at once in colon separated form. The colon separated syntax cannot be used in combination with the ``--alg`` and ``--secret`` - options. + options. If ```` is not specified it defaults to SHA256. .. option:: --alg diff --git a/doc/manual/source/man/cascade-zone.rst b/doc/manual/source/man/cascade-zone.rst index 21c739230..8b02ef6f2 100644 --- a/doc/manual/source/man/cascade-zone.rst +++ b/doc/manual/source/man/cascade-zone.rst @@ -85,7 +85,7 @@ Options for :subcmd:`zone add` ------------------------------ .. option:: --source ][^ -.. option:: --source +.. option:: --source The zone source can be an IP address (with or without port, defaults to port 53) or a file path. @@ -94,12 +94,12 @@ Options for :subcmd:`zone add` suffix it with ``^`` to indicate that the specified RFC 8945 TSIG key should be used to sign the zone transfer request. - Note: When providing a file path to a zone file to load, if :subcmd:`zone - add` is executed on a different host than where the ``cascaded`` daemon is - running the path must be valid on the **daemon** host. + .. note:: When providing the path to a zone file to load, if :subcmd:`zone + add` is executed on a different host than where the ``cascaded`` + daemon is running the path must be valid on the **daemon** host. - Note: In order to use a TSIG key you MUST also supply the key to Cascade - via :subcmd:`tsig add`. + .. note:: Note: In order to use a TSIG key you MUST also supply the key to + Cascade via :subcmd:`tsig add`. .. option:: --policy From 9b50f3c5c340493dac854146baddd8cd52e87d88 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 8 Apr 2026 00:30:41 +0200 Subject: [PATCH 009/113] Try adding an NSD integration page to showcase TSIG support. --- doc/manual/source/index.rst | 12 +++++++-- doc/manual/source/nsd.rst | 54 +++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 doc/manual/source/nsd.rst diff --git a/doc/manual/source/index.rst b/doc/manual/source/index.rst index dcfacf34e..be59ef875 100644 --- a/doc/manual/source/index.rst +++ b/doc/manual/source/index.rst @@ -112,8 +112,16 @@ Examples of things we're interested in: .. toctree:: :maxdepth: 2 :hidden: - :caption: Integrations - :name: toc-integrations + :caption: Nameserver Integrations + :name: toc-nameserver-integrations + + nsd + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: HSM Integrations + :name: toc-hsm-integrations softhsm thales diff --git a/doc/manual/source/nsd.rst b/doc/manual/source/nsd.rst new file mode 100644 index 000000000..fae773c6d --- /dev/null +++ b/doc/manual/source/nsd.rst @@ -0,0 +1,54 @@ +Integrating with NSD +==================== + +.. epigraph:: + + Name Server Daemon (NSD) by NLnet Labs is an authoritative DNS name server. + + -- https://nsd.docs.nlnetlabs.nl/ + +Using NSD as a primary to Cascade +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To use NSD as an upstream name server of Cascade is done by adding a zone to +NSD that refers to Cascade as a secondary name server to which NSD will +provide XFR (zone transfers), optionally authenticated using a TSIG key. + +For example the following NSD configuration file fragment adds an example.com +zone to NSD that is to be served as input to Cascade. + +.. code-block:: + + zone: + name: example.com + zonefile: "zonefile.name" + notify: 127.0.0.1@4542 NOKEY + provide-xfr: 127.0.0.1 NOKEY + store-ixfr: yes + create-ixfr: yes + +A TSIG key can be used to authenticate the NOTIFY and XFR communications. For +example\: + +.. code-block:: + + key: + name: "sec1_key" + algorithm: hmac-md5 + secret: "6KM6qiKfwfEpamEq72HQdA==" + + zone: + name: example.com + zonefile: "zonefile.name" + notify: 127.0.0.1@4542 sec1_key + provide-xfr: 127.0.0.1 sec1_key + store-ixfr: yes + create-ixfr: yes + +See https://nsd.docs.nlnetlabs.nl/en/latest/running/using-tsig.html for more +information. + +Using NSD as a secondary to Cascade +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +TODO From 5845baf525fc3fdced14ec69cb8707a18aa25874 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 8 Apr 2026 00:38:20 +0200 Subject: [PATCH 010/113] Expand the NSD example. --- doc/manual/source/nsd.rst | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/doc/manual/source/nsd.rst b/doc/manual/source/nsd.rst index fae773c6d..b2ac1a7a0 100644 --- a/doc/manual/source/nsd.rst +++ b/doc/manual/source/nsd.rst @@ -10,20 +10,24 @@ Integrating with NSD Using NSD as a primary to Cascade ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To use NSD as an upstream name server of Cascade is done by adding a zone to -NSD that refers to Cascade as a secondary name server to which NSD will -provide XFR (zone transfers), optionally authenticated using a TSIG key. +To use NSD as an upstream name server of Cascade you must add a zone to +NSD that refers to Cascade as a secondary name server so that NSD will send +a NOTIFY message to Cascade when the zone changes and will allow Cascade to +make an XFR request to receive the update zone. Optionally NSD and Cascade +can be configured with the same TSIG key to authenticate the NOTIFY and XFR +messages. For example the following NSD configuration file fragment adds an example.com -zone to NSD that is to be served as input to Cascade. +zone to NSD that is to be served as input to a Cascade daemon running on host +192.168.0.2 listening on the default port 4542: .. code-block:: zone: name: example.com zonefile: "zonefile.name" - notify: 127.0.0.1@4542 NOKEY - provide-xfr: 127.0.0.1 NOKEY + notify: 192.168.0.2@4542 NOKEY + provide-xfr: 192.168.0.2 NOKEY store-ixfr: yes create-ixfr: yes @@ -34,20 +38,34 @@ example\: key: name: "sec1_key" - algorithm: hmac-md5 - secret: "6KM6qiKfwfEpamEq72HQdA==" + algorithm: hmac-sha256 + secret: "...==" zone: name: example.com zonefile: "zonefile.name" - notify: 127.0.0.1@4542 sec1_key - provide-xfr: 127.0.0.1 sec1_key + notify: 192.168.0.2@4542 sec1_key + provide-xfr: 192.168.0.2 sec1_key store-ixfr: yes create-ixfr: yes See https://nsd.docs.nlnetlabs.nl/en/latest/running/using-tsig.html for more information. +Adding the TSIG key to Cascade is done using the ``cascade tsig add`` CLI +command, e.g. like so: + +.. code-block:: bash + + $ cascade tsig add --name sec1_key --alg hmac-sha256 --secret "...==" + +And then instructing Cascade to use the TSIG key when adding the zone, assuming +that the NSD daemon is running on host 192.168.0.1 on port 53: + +.. code-block:: bash + + $ cascade zone add --source 192.168.0.1^sec1_key --policy default example.com + Using NSD as a secondary to Cascade ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 7002eb312e48cca314a7aeebf206400799a2cc7a Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 8 Apr 2026 08:09:56 +0200 Subject: [PATCH 011/113] Tweak the NSD example. --- doc/manual/source/nsd.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/manual/source/nsd.rst b/doc/manual/source/nsd.rst index b2ac1a7a0..b8dd1a2a5 100644 --- a/doc/manual/source/nsd.rst +++ b/doc/manual/source/nsd.rst @@ -52,6 +52,9 @@ example\: See https://nsd.docs.nlnetlabs.nl/en/latest/running/using-tsig.html for more information. +.. tip:: Remember to reload the NSD configuration or restart NSD so that + changes to the configuration take effect. + Adding the TSIG key to Cascade is done using the ``cascade tsig add`` CLI command, e.g. like so: @@ -59,8 +62,11 @@ command, e.g. like so: $ cascade tsig add --name sec1_key --alg hmac-sha256 --secret "...==" -And then instructing Cascade to use the TSIG key when adding the zone, assuming -that the NSD daemon is running on host 192.168.0.1 on port 53: +To use the new TAIG key it must be specified when adding a zone to +Cascade. Assuming that NSD is running on host 192.168.0.1 on port 53 +the following command instructs Cascade to add the ``example.com`` +zone sourced from the NSD server using the ``sec1_key`` TSIG key to +authenticate with NSD: .. code-block:: bash From 0750e4e293e4af6816ac3e6f1ed7f78faa5e2f3b Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 8 Apr 2026 13:35:20 +0200 Subject: [PATCH 012/113] Advise how to create a TSIG key. --- doc/manual/build/man/cascade-debug.1 | 2 +- doc/manual/build/man/cascade-health.1 | 2 +- doc/manual/build/man/cascade-hsm.1 | 2 +- doc/manual/build/man/cascade-keyset.1 | 2 +- doc/manual/build/man/cascade-policy.1 | 2 +- doc/manual/build/man/cascade-status.1 | 2 +- doc/manual/build/man/cascade-template.1 | 2 +- doc/manual/build/man/cascade-zone.1 | 2 +- doc/manual/build/man/cascade.1 | 2 +- doc/manual/build/man/cascaded-config.toml.5 | 2 +- doc/manual/build/man/cascaded-policy.toml.5 | 2 +- doc/manual/build/man/cascaded.1 | 2 +- doc/manual/source/man/cascade-tsig.rst | 5 +++++ doc/manual/source/man/cascade-zone.rst | 2 +- 14 files changed, 18 insertions(+), 13 deletions(-) diff --git a/doc/manual/build/man/cascade-debug.1 b/doc/manual/build/man/cascade-debug.1 index dbbc6b7ff..437895a64 100644 --- a/doc/manual/build/man/cascade-debug.1 +++ b/doc/manual/build/man/cascade-debug.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-DEBUG" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-DEBUG" "1" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-debug \- Debug / troubleshoot Cascade .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-health.1 b/doc/manual/build/man/cascade-health.1 index 29bd227a2..476a02689 100644 --- a/doc/manual/build/man/cascade-health.1 +++ b/doc/manual/build/man/cascade-health.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HEALTH" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HEALTH" "1" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-health \- Check the health of Cascade .sp diff --git a/doc/manual/build/man/cascade-hsm.1 b/doc/manual/build/man/cascade-hsm.1 index cf5f3bffd..26ce5a308 100644 --- a/doc/manual/build/man/cascade-hsm.1 +++ b/doc/manual/build/man/cascade-hsm.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HSM" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HSM" "1" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-hsm \- Manage HSMs .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-keyset.1 b/doc/manual/build/man/cascade-keyset.1 index 30b191c8f..41992ce00 100644 --- a/doc/manual/build/man/cascade-keyset.1 +++ b/doc/manual/build/man/cascade-keyset.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-KEYSET" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-KEYSET" "1" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-keyset \- Execute manual key roll or key removal commands .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-policy.1 b/doc/manual/build/man/cascade-policy.1 index f503e4df0..a804fae28 100644 --- a/doc/manual/build/man/cascade-policy.1 +++ b/doc/manual/build/man/cascade-policy.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-POLICY" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-POLICY" "1" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-policy \- Manage policies .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-status.1 b/doc/manual/build/man/cascade-status.1 index ee27023b4..7de23b9fb 100644 --- a/doc/manual/build/man/cascade-status.1 +++ b/doc/manual/build/man/cascade-status.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-STATUS" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-STATUS" "1" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-status \- Show the status of Cascade .sp diff --git a/doc/manual/build/man/cascade-template.1 b/doc/manual/build/man/cascade-template.1 index 6542e234c..8eae7eb85 100644 --- a/doc/manual/build/man/cascade-template.1 +++ b/doc/manual/build/man/cascade-template.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-TEMPLATE" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-TEMPLATE" "1" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-template \- Print example config or policy files .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-zone.1 b/doc/manual/build/man/cascade-zone.1 index 3aac4c8ac..f95e199e9 100644 --- a/doc/manual/build/man/cascade-zone.1 +++ b/doc/manual/build/man/cascade-zone.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-ZONE" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-ZONE" "1" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-zone \- Manage zones .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade.1 b/doc/manual/build/man/cascade.1 index 23a83d761..34cb8eded 100644 --- a/doc/manual/build/man/cascade.1 +++ b/doc/manual/build/man/cascade.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE" "1" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade \- Cascade CLI .SH SYNOPSIS diff --git a/doc/manual/build/man/cascaded-config.toml.5 b/doc/manual/build/man/cascaded-config.toml.5 index b3e9adedb..7aed0229c 100644 --- a/doc/manual/build/man/cascaded-config.toml.5 +++ b/doc/manual/build/man/cascaded-config.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-CONFIG.TOML" "5" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-CONFIG.TOML" "5" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-config.toml \- Cascade configuration file .sp diff --git a/doc/manual/build/man/cascaded-policy.toml.5 b/doc/manual/build/man/cascaded-policy.toml.5 index 9b618b234..3beacae9b 100644 --- a/doc/manual/build/man/cascaded-policy.toml.5 +++ b/doc/manual/build/man/cascaded-policy.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-POLICY.TOML" "5" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-POLICY.TOML" "5" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-policy.toml \- Cascade policy file format .sp diff --git a/doc/manual/build/man/cascaded.1 b/doc/manual/build/man/cascaded.1 index c8f65ff01..a6a57cfb0 100644 --- a/doc/manual/build/man/cascaded.1 +++ b/doc/manual/build/man/cascaded.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED" "1" "Apr 07, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED" "1" "Apr 08, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded \- DNSSEC signer .SH SYNOPSIS diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index d0ceecb7a..02964d18b 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -15,6 +15,11 @@ Description Manage RFC 8945 TSIG keys for authenticating zone transfers. +.. tip:: Cascade isn't currently able to generate TSIG keys itself. + One way to generate a TSIG key is to use the `tsig-keygen + `_ tool from the ISC BIND project. + Global Options -------------- diff --git a/doc/manual/source/man/cascade-zone.rst b/doc/manual/source/man/cascade-zone.rst index 8b02ef6f2..6aceb1103 100644 --- a/doc/manual/source/man/cascade-zone.rst +++ b/doc/manual/source/man/cascade-zone.rst @@ -207,7 +207,7 @@ Options for :subcmd:`zone reject` The serial number of the zone to reject. Options for :subcmd:`zone override` ---------------------------------- +----------------------------------- .. option:: <--unsigned|--signed> From 7143a5497ff600b4853dd3f594923e3f334d182b Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 8 Apr 2026 13:51:03 +0200 Subject: [PATCH 013/113] Start creating a system test for upstream TSIG. Begin with a test that should fail: fetching a zone without using the required TSIG key. --- .../scripts/manage-test-environment.sh | 36 +++++++++++++- integration-tests/system-tests.yml | 11 +++++ .../tests/upstream-tsig/action.yml | 48 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 integration-tests/tests/upstream-tsig/action.yml diff --git a/integration-tests/scripts/manage-test-environment.sh b/integration-tests/scripts/manage-test-environment.sh index 2b811cd19..0d1d833ee 100755 --- a/integration-tests/scripts/manage-test-environment.sh +++ b/integration-tests/scripts/manage-test-environment.sh @@ -325,18 +325,34 @@ remote-control: control-enable: yes control-interface: "${base_dir}/nsd-primary/nsd.sock" +key: + name: "tsig-key" + algorithm: hmac-sha256 + secret: "COzoVsYQmXeXiyq1Quhp0bbVnMyxjPxsaGSoIWR98i0=" + pattern: name: primary zonefile: "%s.primary-zone" - allow-notify: 127.0.0.1 NOKEY provide-xfr: 127.0.0.1 NOKEY notify: 127.0.0.1@${_cascade_port} NOKEY store-ixfr: yes create-ixfr: yes +pattern: + name: primary-tsig + zonefile: "%s.primary-zone" + provide-xfr: 127.0.0.1 tsig-key + notify: 127.0.0.1@${_cascade_port} tsig-key + store-ixfr: yes + create-ixfr: yes + zone: name: example.test include-pattern: primary + +zone: + name: example-tsig.test + include-pattern: primary-tsig EOF tee "${base_dir}/nsd-primary/zones/example.test.primary-zone" <<'EOF' >&2 @@ -358,6 +374,24 @@ mail MX 10 example.test. text TXT "Hello World!" EOF +tee "${base_dir}/nsd-primary/zones/example-tsig.test.primary-zone" <<'EOF' >&2 +$TTL 5 ; use a very short TTL for sped up keyset rolls +example-tsig.test. IN SOA ns1.example-tsig.test. mail.example-tsig.test. ( + 1 ; serial + 60 ; refresh (60 seconds) + 60 ; retry (60 seconds) + 3600 ; expire (1 hour) + 5 ; minimum (5 seconds) + ) +@ NS example-tsig.test. +@ NS ns1.example-tsig.test. +@ A 127.0.0.1 +ns1 A 127.0.0.1 + +www A 169.254.1.1 +mail MX 10 example-tsig.test. +text TXT "Hello TSIG World!" +EOF } diff --git a/integration-tests/system-tests.yml b/integration-tests/system-tests.yml index 2abc18a7b..a334676e7 100644 --- a/integration-tests/system-tests.yml +++ b/integration-tests/system-tests.yml @@ -243,3 +243,14 @@ jobs: - uses: ./integration-tests/tests/remove-zone with: log-level: ${{ inputs.log-level }} + + # Added for https://github.com/NLnetLabs/cascade/pull/564 + upstream-tsig: + name: Use TSIG with an upstream nameserver. + runs-on: ubuntu-latest + strategy: + matrix: + rust: [stable] + steps: + - uses: actions/checkout@v4 + - uses: ./integration-tests/tests/upstream-tsig diff --git a/integration-tests/tests/upstream-tsig/action.yml b/integration-tests/tests/upstream-tsig/action.yml new file mode 100644 index 000000000..1d4044657 --- /dev/null +++ b/integration-tests/tests/upstream-tsig/action.yml @@ -0,0 +1,48 @@ +# Making reusable composite actions documented at +# https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action#creating-a-composite-action-within-the-same-repository +name: 'Use TSIG with an upstream nameserver.' +description: 'Use TSIG with an upstream nameserver.' +defaults: + # see: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#defaultsrunshell + run: + shell: bash --noprofile --norc -eo pipefail -x {0} +inputs: + log-level: + description: The level of logging that Cascade should output. + required: false + default: debug + type: choice + options: + - error + - warning + - info + - debug + - trace +runs: + using: "composite" + steps: + - uses: ./.github/actions/prepare-systest-env + - uses: ./.github/actions/setup-and-start-cascade + with: + log-level: ${{ inputs.log-level }} + + - name: Add zone without the required TSIG key. + run: | + cascade zone add --policy default --source 127.0.0.1:1055 example-tsig.test + + - name: Check zone status + run: | + timeout=10 # seconds + start=$(date +%s) + until cascade zone status example-tsig.test | grep -q "Published zone available"; do + if (($(date +%s) > (start + timeout))); then + exit 0 + fi + sleep 1 + done + echo "::error:: zone status unexpectedly reports TSIG required zone was retrieved and published without using the TSIG key" + exit 1 + + - name: Print log files on any failure in this job + uses: ./.github/actions/print-logfiles + if: failure() From c0724eebef01dd08d695c464187d110cfca1efc4 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:15:58 +0200 Subject: [PATCH 014/113] FIX: allow hmac- when specifying the algorithm name to `tsig key add`. --- crates/cli/src/commands/tsig.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs index e940e28cf..749157b50 100644 --- a/crates/cli/src/commands/tsig.rs +++ b/crates/cli/src/commands/tsig.rs @@ -7,10 +7,10 @@ use crate::println; #[derive(Clone, Debug, clap::ValueEnum)] pub enum TsigAlgorithm { - Sha1, - Sha256, - Sha384, - Sha512, + HmacSha1, + HmacSha256, + HmacSha384, + HmacSha512, } impl FromStr for TsigAlgorithm { @@ -18,10 +18,10 @@ impl FromStr for TsigAlgorithm { fn from_str(s: &str) -> Result { match s { - "hmac-sha1" => Ok(TsigAlgorithm::Sha1), - "hmac-sha256" => Ok(TsigAlgorithm::Sha256), - "hmac-sha384" => Ok(TsigAlgorithm::Sha384), - "hmac-sha512" => Ok(TsigAlgorithm::Sha512), + "hmac-sha1" => Ok(TsigAlgorithm::HmacSha1), + "hmac-sha256" => Ok(TsigAlgorithm::HmacSha256), + "hmac-sha384" => Ok(TsigAlgorithm::HmacSha384), + "hmac-sha512" => Ok(TsigAlgorithm::HmacSha512), other => Err(format!("'{other}' is not a recognized TSIG algorithm")), } } @@ -30,10 +30,10 @@ impl FromStr for TsigAlgorithm { impl From for crate::api::TsigAlgorithm { fn from(alg: TsigAlgorithm) -> Self { match alg { - TsigAlgorithm::Sha1 => cascade_api::TsigAlgorithm::Sha1, - TsigAlgorithm::Sha256 => cascade_api::TsigAlgorithm::Sha256, - TsigAlgorithm::Sha384 => cascade_api::TsigAlgorithm::Sha384, - TsigAlgorithm::Sha512 => cascade_api::TsigAlgorithm::Sha512, + TsigAlgorithm::HmacSha1 => cascade_api::TsigAlgorithm::Sha1, + TsigAlgorithm::HmacSha256 => cascade_api::TsigAlgorithm::Sha256, + TsigAlgorithm::HmacSha384 => cascade_api::TsigAlgorithm::Sha384, + TsigAlgorithm::HmacSha512 => cascade_api::TsigAlgorithm::Sha512, } } } @@ -85,7 +85,7 @@ impl Tsig { } [name_part, secret_part] => { - let alg = TsigAlgorithm::Sha256; + let alg = TsigAlgorithm::HmacSha256; let name = name_part.to_string(); let secret = secret_part.to_string(); (name, alg, secret) From 678d55e54c5831dfc51f6b04c695f28f823992a3 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:16:15 +0200 Subject: [PATCH 015/113] FIX: Wrong `tsig add` subcommand documentation. --- doc/manual/source/man/cascade-tsig.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 02964d18b..3e4dc174f 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -8,7 +8,7 @@ Synopsis :program:`cascade` ``[GLOBAL OPTIONS]`` tsig ```` -:program:`cascade` ``[GLOBAL OPTIONS]`` tsig :subcmd:`add` ``[OPTIONS]`` +:program:`cascade` ``[GLOBAL OPTIONS]`` tsig :subcmd:`add` ```` ```` ```` Description ----------- @@ -33,11 +33,10 @@ Commands Register a new TSIG key. -Options for :subcmd:`tsig add` +Arguments for :subcmd:`tsig add` ------------------------------ -.. option:: --name -.. option:: --name []:: +.. option:: The name of the TSIG key to add. @@ -46,12 +45,12 @@ Options for :subcmd:`tsig add` syntax cannot be used in combination with the ``--alg`` and ``--secret`` options. If ```` is not specified it defaults to SHA256. -.. option:: --alg +.. option:: The TSIG algorithm of the specified TSIG key. Can be one of: hmac-sha1, hmac-sha256, hmac-sha384 or hmac-sha512. -.. option:: --secret +.. option:: A base64 encoded string defining the actual TSIG key material bytes. From 0c0d38c8844cf057dc15184eaf17a3abff333231 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:16:31 +0200 Subject: [PATCH 016/113] Make the upstream TSIG system test try using TSIG. --- .../tests/upstream-tsig/action.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/integration-tests/tests/upstream-tsig/action.yml b/integration-tests/tests/upstream-tsig/action.yml index 1d4044657..a60f67a34 100644 --- a/integration-tests/tests/upstream-tsig/action.yml +++ b/integration-tests/tests/upstream-tsig/action.yml @@ -43,6 +43,25 @@ runs: echo "::error:: zone status unexpectedly reports TSIG required zone was retrieved and published without using the TSIG key" exit 1 + - name: Remove and re-add the zone with the required TSIG key. + run: | + cascade zone remove example-tsig.test + cascade tsig add tsig-key hmac-sha256 "COzoVsYQmXeXiyq1Quhp0bbVnMyxjPxsaGSoIWR98i0=" + cascade zone add --policy default --source 127.0.0.1:1055^tsig-key example-tsig.test + + - name: Check zone status + run: | + timeout=10 # seconds + start=$(date +%s) + until cascade zone status example-tsig.test | grep -q "Published zone available"; do + if (($(date +%s) > (start + timeout))); then + cascade zone status example-tsig.test + echo "::error:: timeout: zone status did not report published zone available" + exit 1 + fi + sleep 1 + done + - name: Print log files on any failure in this job uses: ./.github/actions/print-logfiles if: failure() From b44b805673b63e8a996ba145485a4a0b82399739 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:21:20 +0200 Subject: [PATCH 017/113] Fix merge error. --- doc/manual/source/man/cascade-zone.rst | 33 ++++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/doc/manual/source/man/cascade-zone.rst b/doc/manual/source/man/cascade-zone.rst index 6e9360c4b..19bf284a6 100644 --- a/doc/manual/source/man/cascade-zone.rst +++ b/doc/manual/source/man/cascade-zone.rst @@ -42,12 +42,15 @@ Commands .. subcmd:: add - Register a new zone. + Register a new zone. The zone will be loaded, signed and published. .. subcmd:: remove Remove a zone. + .. note:: Once removed downstream servers will no longer be able to fetech + the zone! + .. subcmd:: list List registered zones. @@ -83,15 +86,25 @@ Commands Options for :subcmd:`zone add` ------------------------------ -.. option:: --source ][^ -.. option:: --source - - The zone source can be an IP address (with or without port, defaults to port - 53) or a file path. - - When providing an IP address (with or without port) you may also optionally - suffix it with ``^`` to indicate that the specified RFC 8945 - TSIG key should be used to sign the zone transfer request. +.. option:: --source + + The zone source can be the IP address of an upstream nameserver (with + or without port, defaults to port 53) or the path to a zone file locally + available to the ``cascaded`` daemon.` + + When specifying an upstream nameserver you may also optionally suffix it + with ``^`` to indicate that the specified RFC 8945 TSIG key + should be used to sign any SOA, AXFR and IXFR queries that will be sent to + the upstream source. + + Zones sourced from an upstream nameserver will be automatically updated if + a new version is detected. This can happen if the upstream nameserver sends + an RFC 1996 NOTIFY message to Cascade, or if an IXFR or SOA query (if the + upstream responds with NOTIMP to an IXFR request) sent by Cascade (due to a + SOA timer expiring) discovers that a newer SOA SERIAL is available, or due + to an operator issuing a `zone reload` command. For zones that have already + been retrieved at least once via AXFR, subsequent refreshes will attempt to + use IXFR and fallback to AXFR if IXFR is not available. .. note:: When providing the path to a zone file to load, if :subcmd:`zone add` is executed on a different host than where the ``cascaded`` From 059cd0ab7572cb44f30476f73bfc6561057f3925 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:22:09 +0200 Subject: [PATCH 018/113] Fix sphinx-build WARNING: Title underline too short. --- doc/manual/source/man/cascade-tsig.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 3e4dc174f..06ae88109 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -34,7 +34,7 @@ Commands Register a new TSIG key. Arguments for :subcmd:`tsig add` ------------------------------- +-------------------------------- .. option:: From 5c38ef95d40090cd3a51213950c3a19cbf305bee Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:27:06 +0200 Subject: [PATCH 019/113] Add `tsig list` CLI subcommand. --- crates/api/Cargo.toml | 2 +- crates/api/src/lib.rs | 36 +++++++++++++++++++++- crates/cli/src/commands/tsig.rs | 48 +++++++++++++++++++++++++++-- src/units/http_server.rs | 54 ++++++++++++++++++++++++--------- 4 files changed, 121 insertions(+), 19 deletions(-) diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index 70f7cb4d5..a7d799bd6 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -32,7 +32,7 @@ features = ["serde1"] [dependencies.domain] workspace = true # TODO: Enable and use 'new::base'? -features = ["bytes", "serde"] +features = ["bytes", "serde", "tsig"] # The API uses 'serde' for transforming high-level types to and from the # underlying wire format. diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 7123ca579..f3615de0c 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fmt::{self, Display}; use std::net::{IpAddr, SocketAddr}; use std::time::{Duration, SystemTime}; @@ -187,9 +188,14 @@ pub struct KmipKeyImport { pub flags: String, } +//----------- TsigKeyName ----------------------------------------------------- + +/// The name of a TSIG key. +pub type TsigKeyName = domain::tsig::KeyName; + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct TsigAdd { - pub name: String, + pub name: TsigKeyName, pub alg: TsigAlgorithm, pub secret: String, } @@ -216,6 +222,34 @@ impl Display for TsigAddError { } } +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct TsigRemoveResult; + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub enum TsigRemoveError { + NotFound, + InUse, +} + +impl fmt::Display for TsigRemoveError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + TsigRemoveError::NotFound => "no such TSIG key was found", + TsigRemoveError::InUse => "the TSIG key cannot be removed as it is in use", + }) + } +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct TsigListResult { + pub tsig_keys: HashMap, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct TsigListResultItem { + pub zones: Vec, +} + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct ZoneAdd { pub name: ZoneName, diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs index 749157b50..314ffeb25 100644 --- a/crates/cli/src/commands/tsig.rs +++ b/crates/cli/src/commands/tsig.rs @@ -1,6 +1,8 @@ use std::str::FromStr; -use cascade_api::{TsigAddError, TsigAddResult}; +use cascade_api::{ + TsigAddError, TsigAddResult, TsigKeyName, TsigListResult, TsigRemoveError, TsigRemoveResult, +}; use crate::client::CascadeApiClient; use crate::println; @@ -45,6 +47,7 @@ pub struct Tsig { } #[derive(Clone, Debug, clap::Subcommand)] +#[allow(clippy::large_enum_variant)] pub enum TsigCommand { #[command(name = "add")] Add { @@ -67,6 +70,12 @@ pub enum TsigCommand { #[arg(requires = "alg")] secret: Option, }, + + #[command(name = "remove")] + Remove { name: TsigKeyName }, + + #[command(name = "list")] + List, } impl Tsig { @@ -105,11 +114,14 @@ impl Tsig { _ => unreachable!("Excluded via Clap 'requires' rules"), }; + let tsig_key_name = TsigKeyName::from_str(&name) + .map_err(|err| format!("Invalid TSIG key name: {err}"))?; + let res: Result = client .post_json_with( "tsig/add", &crate::api::TsigAdd { - name: name.clone(), + name: tsig_key_name, alg: alg.into(), secret, }, @@ -124,6 +136,38 @@ impl Tsig { Err(err) => Err(format!("Failed to add TSIG key '{name}': {err}")), } } + TsigCommand::Remove { name } => { + let res: Result = + client.post_json(&format!("tsig/{name}/remove")).await?; + + match res { + Ok(TsigRemoveResult) => { + println!("Removed TSIG key {name}"); + Ok(()) + } + Err(e) => Err(format!("Failed to remove TSIG key: {e}")), + } + } + TsigCommand::List => { + let response: TsigListResult = client.get_json("tsig/").await?; + + for (tsig_key_name, key_info) in response.tsig_keys { + let zones = key_info + .zones + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "); + + print!("{tsig_key_name}: used by zones: "); + if !zones.is_empty() { + println!("{zones}"); + } else { + println!("none"); + } + } + Ok(()) + } } } } diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 4f8bd82a9..161ffed0e 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -1,5 +1,5 @@ +use std::collections::HashMap; use std::future::IntoFuture; -use std::str::FromStr; use std::sync::Arc; use std::sync::atomic::Ordering::Relaxed; use std::time::Duration; @@ -105,9 +105,9 @@ impl HttpServer { .route("/status", get(Self::status)) .route("/status/keys", get(Self::status_keys)) .route("/debug/change-logging", post(Self::change_logging)) - // .route("/tsig/", get(Self::tsig_keys_list)) + .route("/tsig/", get(Self::tsig_key_list)) .route("/tsig/add", post(Self::tsig_key_add)) - // .route("/tsig/{name}/remove", post(Self::tsig_key_remove)) + .route("/tsig/{name}/remove", post(Self::tsig_key_remove)) // .route("/tsig/{name}/status", get(Self::tsig_key_status)) .route("/zone/", get(Self::zones_list)) .route("/zone/add", post(Self::zone_add)) @@ -1105,24 +1105,48 @@ impl HttpServer { return Json(Err(TsigAddError::InvalidBase64Secret)); }; - let res = match Name::>::from_str(&tsig_add.name) { - Ok(tsig_key_name) => { - let alg = match tsig_add.alg { - TsigAlgorithm::Sha1 => Algorithm::Sha1, - TsigAlgorithm::Sha256 => Algorithm::Sha256, - TsigAlgorithm::Sha384 => Algorithm::Sha384, - TsigAlgorithm::Sha512 => Algorithm::Sha512, - }; - center::add_tsig_key(&state.center, tsig_key_name, alg, &secret).await - } - Err(_err) => return Json(Err(TsigAddError::InvalidKeyName)), + let alg = match tsig_add.alg { + TsigAlgorithm::Sha1 => Algorithm::Sha1, + TsigAlgorithm::Sha256 => Algorithm::Sha256, + TsigAlgorithm::Sha384 => Algorithm::Sha384, + TsigAlgorithm::Sha512 => Algorithm::Sha512, }; - match res { + match center::add_tsig_key(&state.center, tsig_add.name, alg, &secret).await { Ok(TsigAddResult) => Json(Ok(TsigAddResult)), Err(err) => Json(Err(err)), } } + + async fn tsig_key_remove( + State(state): State>, + Path(name): Path>, + ) -> Json> { + todo!() + } + + async fn tsig_key_list(State(http_state): State>) -> Json { + let state = http_state.center.state.lock().unwrap(); + let mut tsig_keys = HashMap::new(); + for tsig_key_name in state.tsig_store.map.keys() { + let zones = state + .zones + .iter() + .filter_map(|zone| { + let zone_state = zone.0.state.lock().unwrap(); + match &zone_state.loader.source { + loader::Source::Server { + tsig_key: Some(tsig_key), + .. + } if tsig_key.name() == tsig_key_name => Some(zone.0.name.clone()), + _ => None, + } + }) + .collect::>(); + tsig_keys.insert(tsig_key_name.clone(), TsigListResultItem { zones }); + } + Json(TsigListResult { tsig_keys }) + } } //------------ HttpServer Handler for /kmip ---------------------------------- From 80b6b3d6fee8a5b54c498ec79b9dcf5b8698ed02 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:30:27 +0200 Subject: [PATCH 020/113] Document the tsig list subcommand. --- doc/manual/build/man/cascade-tsig.1 | 7 +++++++ doc/manual/source/man/cascade-tsig.rst | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/doc/manual/build/man/cascade-tsig.1 b/doc/manual/build/man/cascade-tsig.1 index d7a91a060..17001c96d 100644 --- a/doc/manual/build/man/cascade-tsig.1 +++ b/doc/manual/build/man/cascade-tsig.1 @@ -38,6 +38,8 @@ Added in version 0.1.0\-beta1. \fBcascade\fP \fB[GLOBAL OPTIONS]\fP tsig \fB\fP .sp \fBcascade\fP \fB[GLOBAL OPTIONS]\fP tsig \fI\%add\fP \fB\fP \fB\fP \fB\fP +.sp +\fBcascade\fP \fB[GLOBAL OPTIONS]\fP tsig \fI\%list\fP .SH DESCRIPTION .sp Manage RFC 8945 TSIG keys for authenticating zone transfers. @@ -59,6 +61,11 @@ command. .B add Register a new TSIG key. .UNINDENT +.INDENT 0.0 +.TP +.B list +List registered TSIG keys and the zones that use them. +.UNINDENT .SH ARGUMENTS FOR TSIG ADD .INDENT 0.0 .TP diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 06ae88109..3519556cc 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -10,6 +10,8 @@ Synopsis :program:`cascade` ``[GLOBAL OPTIONS]`` tsig :subcmd:`add` ```` ```` ```` +:program:`cascade` ``[GLOBAL OPTIONS]`` tsig :subcmd:`list` + Description ----------- @@ -33,6 +35,10 @@ Commands Register a new TSIG key. +.. subcmd:: list + + List registered TSIG keys and the zones that use them. + Arguments for :subcmd:`tsig add` -------------------------------- From 5c5ef7ee8bc6bbc269b03ed6430d211851bb67a3 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:31:01 +0200 Subject: [PATCH 021/113] Commit updated generated man pages. --- doc/manual/build/man/cascade-zone.1 | 37 ++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/doc/manual/build/man/cascade-zone.1 b/doc/manual/build/man/cascade-zone.1 index f40222fd9..f21f5aa0c 100644 --- a/doc/manual/build/man/cascade-zone.1 +++ b/doc/manual/build/man/cascade-zone.1 @@ -64,12 +64,20 @@ command. .INDENT 0.0 .TP .B add -Register a new zone. +Register a new zone. The zone will be loaded, signed and published. .UNINDENT .INDENT 0.0 .TP .B remove Remove a zone. +.sp +\fBNOTE:\fP +.INDENT 7.0 +.INDENT 3.5 +Once removed downstream servers will no longer be able to fetech +the zone! +.UNINDENT +.UNINDENT .UNINDENT .INDENT 0.0 .TP @@ -114,17 +122,24 @@ Get the history of a single zone. .SH OPTIONS FOR ZONE ADD .INDENT 0.0 .TP -.B \-\-source ][^ -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-source -The zone source can be an IP address (with or without port, defaults to port -53) or a file path. +.B \-\-source +The zone source can be the IP address of an upstream nameserver (with +or without port, defaults to port 53) or the path to a zone file locally +available to the \fBcascaded\fP daemon.\(ga +.sp +When specifying an upstream nameserver you may also optionally suffix it +with \fB^\fP to indicate that the specified RFC 8945 TSIG key +should be used to sign any SOA, AXFR and IXFR queries that will be sent to +the upstream source. .sp -When providing an IP address (with or without port) you may also optionally -suffix it with \fB^\fP to indicate that the specified RFC 8945 -TSIG key should be used to sign the zone transfer request. +Zones sourced from an upstream nameserver will be automatically updated if +a new version is detected. This can happen if the upstream nameserver sends +an RFC 1996 NOTIFY message to Cascade, or if an IXFR or SOA query (if the +upstream responds with NOTIMP to an IXFR request) sent by Cascade (due to a +SOA timer expiring) discovers that a newer SOA SERIAL is available, or due +to an operator issuing a \fIzone reload\fP command. For zones that have already +been retrieved at least once via AXFR, subsequent refreshes will attempt to +use IXFR and fallback to AXFR if IXFR is not available. .sp \fBNOTE:\fP .INDENT 7.0 From 1908500729c150b9d0d2771e55fb856afe5a7a2b Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:46:58 +0200 Subject: [PATCH 022/113] Remove accidentally commited sphinx-build outputs. --- doc/manual/build/doctrees/architecture.doctree | Bin 15652 -> 0 bytes .../build/doctrees/before-you-start.doctree | Bin 18567 -> 0 bytes doc/manual/build/doctrees/building.doctree | Bin 26743 -> 0 bytes doc/manual/build/doctrees/environment.pickle | Bin 227026 -> 0 bytes doc/manual/build/doctrees/faq.doctree | Bin 20886 -> 0 bytes doc/manual/build/doctrees/glossary.doctree | Bin 116311 -> 0 bytes doc/manual/build/doctrees/hsms.doctree | Bin 40050 -> 0 bytes doc/manual/build/doctrees/index.doctree | Bin 26448 -> 0 bytes doc/manual/build/doctrees/installation.doctree | Bin 34971 -> 0 bytes doc/manual/build/doctrees/intro.doctree | Bin 29817 -> 0 bytes .../build/doctrees/key-management.doctree | Bin 60097 -> 0 bytes doc/manual/build/doctrees/limitations.doctree | Bin 18851 -> 0 bytes .../build/doctrees/man/cascade-debug.doctree | Bin 14744 -> 0 bytes .../build/doctrees/man/cascade-health.doctree | Bin 9255 -> 0 bytes .../build/doctrees/man/cascade-hsm.doctree | Bin 41243 -> 0 bytes .../build/doctrees/man/cascade-keyset.doctree | Bin 29628 -> 0 bytes .../build/doctrees/man/cascade-policy.doctree | Bin 13594 -> 0 bytes .../build/doctrees/man/cascade-status.doctree | Bin 9253 -> 0 bytes .../doctrees/man/cascade-template.doctree | Bin 10669 -> 0 bytes .../build/doctrees/man/cascade-tsig.doctree | Bin 18287 -> 0 bytes .../build/doctrees/man/cascade-zone.doctree | Bin 61204 -> 0 bytes doc/manual/build/doctrees/man/cascade.doctree | Bin 25350 -> 0 bytes .../doctrees/man/cascaded-config.toml.doctree | Bin 62568 -> 0 bytes .../doctrees/man/cascaded-policy.toml.doctree | Bin 104769 -> 0 bytes doc/manual/build/doctrees/man/cascaded.doctree | Bin 24881 -> 0 bytes doc/manual/build/doctrees/nethsm.doctree | Bin 49900 -> 0 bytes doc/manual/build/doctrees/nsd.doctree | Bin 9600 -> 0 bytes doc/manual/build/doctrees/quick-start.doctree | Bin 33044 -> 0 bytes doc/manual/build/doctrees/review-hooks.doctree | Bin 22569 -> 0 bytes .../build/doctrees/smartcard-hsm.doctree | Bin 25679 -> 0 bytes doc/manual/build/doctrees/softhsm.doctree | Bin 18007 -> 0 bytes doc/manual/build/doctrees/thales.doctree | Bin 49865 -> 0 bytes 32 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 doc/manual/build/doctrees/architecture.doctree delete mode 100644 doc/manual/build/doctrees/before-you-start.doctree delete mode 100644 doc/manual/build/doctrees/building.doctree delete mode 100644 doc/manual/build/doctrees/environment.pickle delete mode 100644 doc/manual/build/doctrees/faq.doctree delete mode 100644 doc/manual/build/doctrees/glossary.doctree delete mode 100644 doc/manual/build/doctrees/hsms.doctree delete mode 100644 doc/manual/build/doctrees/index.doctree delete mode 100644 doc/manual/build/doctrees/installation.doctree delete mode 100644 doc/manual/build/doctrees/intro.doctree delete mode 100644 doc/manual/build/doctrees/key-management.doctree delete mode 100644 doc/manual/build/doctrees/limitations.doctree delete mode 100644 doc/manual/build/doctrees/man/cascade-debug.doctree delete mode 100644 doc/manual/build/doctrees/man/cascade-health.doctree delete mode 100644 doc/manual/build/doctrees/man/cascade-hsm.doctree delete mode 100644 doc/manual/build/doctrees/man/cascade-keyset.doctree delete mode 100644 doc/manual/build/doctrees/man/cascade-policy.doctree delete mode 100644 doc/manual/build/doctrees/man/cascade-status.doctree delete mode 100644 doc/manual/build/doctrees/man/cascade-template.doctree delete mode 100644 doc/manual/build/doctrees/man/cascade-tsig.doctree delete mode 100644 doc/manual/build/doctrees/man/cascade-zone.doctree delete mode 100644 doc/manual/build/doctrees/man/cascade.doctree delete mode 100644 doc/manual/build/doctrees/man/cascaded-config.toml.doctree delete mode 100644 doc/manual/build/doctrees/man/cascaded-policy.toml.doctree delete mode 100644 doc/manual/build/doctrees/man/cascaded.doctree delete mode 100644 doc/manual/build/doctrees/nethsm.doctree delete mode 100644 doc/manual/build/doctrees/nsd.doctree delete mode 100644 doc/manual/build/doctrees/quick-start.doctree delete mode 100644 doc/manual/build/doctrees/review-hooks.doctree delete mode 100644 doc/manual/build/doctrees/smartcard-hsm.doctree delete mode 100644 doc/manual/build/doctrees/softhsm.doctree delete mode 100644 doc/manual/build/doctrees/thales.doctree diff --git a/doc/manual/build/doctrees/architecture.doctree b/doc/manual/build/doctrees/architecture.doctree deleted file mode 100644 index 5c416205accd84a107a847048451c434db92dd84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15652 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 diff --git a/doc/manual/build/doctrees/building.doctree b/doc/manual/build/doctrees/building.doctree deleted file mode 100644 index 84fd25e26825600145262246e6d70a5af56338c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26743 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'xawW6I^a$M`K%+l=4R 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 z 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 diff --git a/doc/manual/build/doctrees/glossary.doctree b/doc/manual/build/doctrees/glossary.doctree deleted file mode 100644 index 9d8145f10975d27a528e11aed72c6cc9879a7b0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 116311 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 X-DVZS&tD;#S*VhtNMx!gCsF)A#&$zd diff --git a/doc/manual/build/doctrees/index.doctree b/doc/manual/build/doctrees/index.doctree deleted file mode 100644 index 1b4d31ef6e74437c52b6a503d6c78aece06a3556..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26448 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 UTcW6leeLLFMTbN~$xgNOKM3v(wg3PC diff --git a/doc/manual/build/doctrees/intro.doctree b/doc/manual/build/doctrees/intro.doctree deleted file mode 100644 index 2ebe2e76f3c92b240401dc8e03191b06b18990ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29817 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

}(N1B%mS97j(|rQ_^4U zL--$Kj0|1d>ju~6;_2oNnbFJq^n}a&t!bB8`xL$?SmejtBBS&=yVF1wD7`v00itD% ST)ssAfl4`mzAULli~k?|Y(TF7 diff --git a/doc/manual/build/doctrees/limitations.doctree b/doc/manual/build/doctrees/limitations.doctree deleted file mode 100644 index ddcca1fea07e289ec9af5547c0f1cd08d9a9bb5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18851 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 BN00yj diff --git a/doc/manual/build/doctrees/man/cascade-debug.doctree b/doc/manual/build/doctrees/man/cascade-debug.doctree deleted file mode 100644 index 385aabec1bfe1597a9dcdb0df20083593269d69d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14744 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 Uxa`r(GN_j$pJu5iEvB{q3#je-;s5{u diff --git a/doc/manual/build/doctrees/man/cascade-hsm.doctree b/doc/manual/build/doctrees/man/cascade-hsm.doctree deleted file mode 100644 index 4e3fdda2c1585ea79e1bc71766a0d532c2d18779..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41243 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 VwFvD!Vik7YF-qqHn%tRN{Vim*v;Y7A diff --git a/doc/manual/build/doctrees/man/cascade-status.doctree b/doc/manual/build/doctrees/man/cascade-status.doctree deleted file mode 100644 index bc9b4971305f400a4beb477c8a56772582fbdeff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9253 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 XFL4N$UHVi8HFD(BJoRKXwDx}i@}mpg diff --git a/doc/manual/build/doctrees/man/cascade-template.doctree b/doc/manual/build/doctrees/man/cascade-template.doctree deleted file mode 100644 index 166b8b47cf08790311c7aba4ab0696e43d13eefa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10669 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 C+tjN7 diff --git a/doc/manual/build/doctrees/man/cascade.doctree b/doc/manual/build/doctrees/man/cascade.doctree deleted file mode 100644 index 8509678583cf3e3cd72882bc149e907889ffb395..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25350 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 b^_0`B);l%g(FWB*a0dAQ$W@9RkA?pQU%!Zn diff --git a/doc/manual/build/doctrees/man/cascaded-config.toml.doctree b/doc/manual/build/doctrees/man/cascaded-config.toml.doctree deleted file mode 100644 index 2d0b71ed10424a26419a65b74189ae0a7ff14f3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62568 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 diff --git a/doc/manual/build/doctrees/man/cascaded.doctree b/doc/manual/build/doctrees/man/cascaded.doctree deleted file mode 100644 index a26de8adea95fccba7dae2b7918fb461f8d1da49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24881 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 MZahm$TCx^OJ>4}^z1z2R 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 diff --git a/doc/manual/build/doctrees/smartcard-hsm.doctree b/doc/manual/build/doctrees/smartcard-hsm.doctree deleted file mode 100644 index dc9c3ca03dfa7c7f1171868e6fd351870ec277f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25679 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 I={0iy3oo>9T>t<8 diff --git a/doc/manual/build/doctrees/softhsm.doctree b/doc/manual/build/doctrees/softhsm.doctree deleted file mode 100644 index 12312cd9067cd0bb52b3e0f866d42b277840d1ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18007 zcmeHPU2GiJb(Sns5|3w|3%QjLQ?j-Hv%6fzc0rL;tV*T~QBGyU&hqZe-JKz4 zXV&vWY7-f0iUu)(L7Fz(hXMuCAGAe)07mmxw0SKG6n$tO3iPQ!3iJ&1TMLE?$5dBo_o%B&$(xQedc#>T|XlK1@oG%`krCB1XPj+dRl|&vT5n?_nlH`7uLtL1l14HM#4e5P#2Dychm;QuF)imo`^-9MPdVesIF$ zU3xUs=k@3GxnQ=Huth%>9FMv2#iGVNUexWLDBd@EwpDZ;wWxAe<(i0p=e=IPT15L| zk6S)Bi(za0>Ug@_D>$w<44yU(OAK#5<+q6emhFu&?Kki0f*F?UIlknz%WR8zy5X`u z?+OMMu#W98Zm~DpzUBF>q7+IB({0y7p_aqsv8HIjJ+>NB$8_#8ZaRW%1J)M8lKpej z6dLngS{$r6TU)<|A=Dj=Qizvl7A)sj!M>hd*G~|M3&AIYm{3F^KMG`1|11PjzW@RM z9DaToKbP?Hc?g@ns8{qW`iuHYH=jo3fc%FJBbwAG!~d;X~C+%t|I zITGzK26=&q7`?tJXoq7DLX7MV<4ko1eb1JQYN*WhY)BMWRpGiu+a$qpY`?2Bo_piw z>gJ_#8N%WTr^8iIU~kzzQ_-*QLPkC2*-YH;o76*GnYOv@dl0!@5-gs3jXT;NN$sXk zeaG+y>~&l7O>}m{V|x%x+cCO^1!){GP3#KO?n5j|U?Gi;M>Ko7$ldM>Yi(n5bA6Td z9eW#k=qkhJr+HfT9xA&Yq@SC%CE^N&Sltl0F1u!<21b)zZEoI@+sN?TF@|7x(pa|T z7IL|Kp6Q;~cbALBy}i9cyp3(ATa;VmvR(}pr#^G<))VeOR_=#7XyFN8AG1^s6J53O zu)1m%y6RU$)e~FYQ4|n-=Fn{dJ2wk^D`36>EMl+$@USIez``NT6JoW)hz*XTduW%T zzC}8=4SK&kO}D-i>DDvh94o-@`5gI-|uEAf68WLAxrJRe^K#YgZ#09~*8!+Zj zZ?QIt!Oy~^C&_ewqb{M@#D3) z(o~&?Pp`|`|VV4f^!jf!54}5u+%)p0LjCc+45lq7$oA9SSo30!t}wg`sWW4l8*Vq@s$crL=ynF#?aDVg3mw5@;GK^7E;kx_%O2-p2Jp1?ed?( zE+0xXvxLxG0y8@VCH8daDf-qiERC>7x^WXo)Qcd@ikkqRun4dPjZGV80*!!^4P+8R z^M2nnR0%Au=D0P8AQz1TX_O}}AQ=#XcI|;2HilGT&#dPI;kO0DN&}E-IkY6=#(OWY z>o^|7Fi;)?3~SdmcL9N9Q-WpG5TMA($>)c2$MG%9$_1HR%kKff#c+{mCup`++~rQu zG}`eoXNzfu1B7Nfg95uHyhWF}0%(yAF{B9nKAltuu>s!^DH<0zP%THI`d5kSdtMP9iXElP0Y^v;y(t0E~Bb^YfNnj&5 ze^5|kYjt*9gwuZi`vA#fD(WZDK2myu#-qx8V#L7k)SOIGb@A!(`B>#aY=1l1FMkb7 z4*iigSYEJ2_raI3DO-IG$IC0M@XkvQGG8wieRpKj(guzZ0S-DBA1tz>@DdM4#-tQg zX?Y5s-7|q5RNdYKOt^!Cq4b4hYA&;Jz7p0LxSr^#o*5~CgU#Yq`TsJVczL&nPT^jJinp(#UIq=km_-8tKR{|fBvlRl zVOv|#CfM2vjT8)5=;@q;m=cWvm=$P$SdPua*rzSHXrtvTgxc$~6 zkbAT+PEWcC4%3xaG3Qt&M%3k>LwQ9WQfQ>UNg^Cc!vp3CF`@vA@`)kb80EzJZ&c%6 zBIqqo7f#;LBE36acJbgo9oM&$hl_dpe~|KI8^1SY>8B~d;^=ZFKtK4U%>+=cM$4K7 zmJs+uY9!=+eF}M`WnwF=jFOTX;>t&wIgKo4L|r6n4t$@K$4hZ8}bS63c6p~#@ zQmz-L6O?L;?@G2jKJE2qQ`ig{iIpu4EtTzy56_8l!XGOJ-~Nk9#7{gFZ70N^Opa)Y zsD(UzI1T)TQL#$*$$zDr0XmYw10kJH2s4sjBcG=;D#hj!S%dcp0j@pU(Lxv0L6Jjw zqfo;Ca&ASV)AY^PZ@d9m41bTq7AMq4%|a>9&A!e7RY(%!gLmivlYz@o9~gw+HEl0B zWy>!&)>!F&xv4C*c}HWr24_Ij4tEChJAA4bsdV832z>Pl`m`>T?>QU z6>#UgP2s9+WIVP4Q)(qF{Ld*Yj4|=Qqh1CRuYtx?xo;*w6~T0meT9gEZ-=Z>h!7>k z@Yo2;rF}`-dv(OZh7o2NM5LC67y5Fbr++4WC)5AQLrgz5*4H5|F&nZl@LmI*Tw=W) zIITtzA2Gs}_S$2wPIy!l_c;Yv3##2?FTc#zZ(q-4iV6(>s`XNde&5)jPsTQHugKpB zFOF1=HQi`wnJv{7bkL~aqoS0X^t)Or9T-OTiH~@gjm@yca&z$BP5c*E4e4PFokMF@ zpf_nA2ha65%xC-oMai5f!9U_Jl)@SVF=|FkCE*i?_hGp z!lv4)LFXUZ+Yv4ek()q#oDTPz)Ke0<(b6Xy{Abg7!j#WUVM-Q`-x*~<3Nx$}W=Q20 z;*8MiK0&HS_B9Yk=pcxcKW;oEn?6Dqvd>I$ti*rb!zN67VG7fx8P+#P8JS8_u&N7n zCq%L&DRSm?yGY(Da$zTgc2(MpzJurogbtUIG|iy<6x>8W1o5_>41(nLbVJoqqMAM~ zqe;0lG~84%I)-p?D@0!VVALf+vK=v?I`Ag#QFI2{QcEQ8lSNdr2=yt8kd@rGM;V?- z?!6OFlWmFtiDWmzCZ6wR#SlPQ6CO9nDTOmIJ#|FiMsS60ipfTc8Sz|dxYuou|@#1krtF69-8J|E|tn9Vns<9<~n&i-X?RtH! zcD=r`ReO583RNZldj zJ6cjaeLJPGR%(=$k4s~9wOLzH)>o@5<@HMa+G@SAzFMkQ>&*(NymqZpuHaX7ZRw+F ztWTk_P>4^BPgKGamGDF*JW&ZbQV9vzSepVirXjv}M*v91m2!c!BpfMpbjXwL&y%}i zg8M{PgkK{)V@SWoL%B@I9dVe;1UKmV(sK+vuoMUl-6Jpu@s*f`;8b?NblrP$GNYdDGh_6`44TVOnfBmMXGiSIroq4k zT<4N+DU-fUD$%}%A%QGkyWuC&`_wboXu4X&1yj#}oGj-mkYR%+Ewz1i04T2rev zYHC~HTfJNnWv$*(D{>GmB5LK%%Ilk;s#Gs6RX99dORA`9r7AFUgI&H2hk6Ff+E$w<~uw<6_QxxqWU zyrij}l4v)%P-{z?sJ6A5Qd_Eta;+ljRYizuwW2Coql(83p>*06(MFJ?sZ=X%QBhP) z5xlB&s!F9<>U7Y$Q)ytrowC~De5q2eSHzM)fTDustyGtq^-j5|p%=`E#N@HBh=lz4 zorx;~8LU(V?*Q5J&XQ1gRqND2ah+>~?AuCHRW%^{M_rLf)7*QqNuO*|=nM)sKiQ<_ z1hx6Ux=9m*{M{+`e;S1S>Zp-UotC#YY|l{1wGHa~Knc$;z2c^gh-buJ6 zk;4XT7CO;T0~fL*Z+UzQ4<2u)2@^h?!i22T@LL}>;iuOvEg>}gk__;}k1@cvM+Z0( zeA@NfE^gQ&OT@NXn!pWt>DtX+SHrP{J#cVWJ@O+xAU2vvyjWglUtL7xen+NZEH1OV zjxXMi;oviqo+VWO-A7Oz%wpw`ihY2~NF?oZA+Xc}%JWLCPmN3}#ecyC zBRpjwNG#&>O&+3>mtv9$6vq{%EJf!VDN}L9LoQO{6prF=?|9ywRZ+>j`F zG|1KpgT>LHvewvO$1+D2WEg*RE^3n5tiKpM>mjGW#r-#0R(X0sHmJ<1p7D@1X%255 z3372Aq2a4ff_OBT$2|?o$!a6_Np2xhy&qHIjt8R84_uW<-P@C9Gy9NYFlDn#)9FgRKq26G_^ zEsVQ^3c2X3@0j>@+OyRbWqs3AkTL_Boxw0T-$6H&ft`8yL2#PV+%3P?x=-PX;RnGy z@?ea93t?W$rFGHaXTuT^X4kqcI_tLL%yWzvHHcEt!In^$v`oryMecWy)4A!zUuS}I z;=TbcO6PrAI-`lU-^FnA$e^V-2x=f0*F(uHmO}-SQ+J3y!vfD*$gZC>)4L<5ip zMI14-SRnM{!8}K7pp_N{PZ?45MGaUg8PFKsJQ|#%{RYXgx)4Ou=o#Md^|On?d|&j_ zYzUgnDJnsVn`qJ2NHZ@{>=yb1SU#&D^P7VOswoLT%~pIvmh$IBKmYlX0)Ear9d)dG zDoMM4AmnV?O?g*z~2(Kf@pcf_RUeCn#o%MJdyGZl&;mwo51;}Ktm2A!=oud_| z@a+d@-2sx?@3)d9J$kJIrK$y|+#b@3!sK`tjCw|Xm$~w8OM<}gjSv@99q%?!ku4eS zH*i_95UqFoK2iedbs1qcNvH<)Aw39^BZQggh4Z8+U=zox89kgIF%}OuBfaloX9$t$ zwS|TqJU~E87sA6B3o2^uv_uc#lokS|NCl%;S9}nhqn=3{yTa^*I&D8VCam4zhTjfO zM#zNJ;#p*=$!x0@ZohT~v=ij!+_<48ZNB}FFp1mJlm_QVDui#wcppkMYffc-mj>%3 z6)4Y^UbKO_C#W#|`||dGP}l!TCinaL_qMO#M$PsM^zkqB@z3<}7S;F?ef*d{{s|v` zFiU-SgFfbrS0>0__Vw@Y%MSKs<9*q1UpCtJ^$S!@jx(o!Pyd=6ME@`xf$n&RBhWP; zIfC@s_`&I{BuU~=s|Mte7>o&pC8RLodLa|DC1E|V7LwO%+h!u1vg)&Ag-Pw%ksfE` zQDvj^BmG17J09)ih4H?kCosWKXR3{tevsatQ*C4+gs%v}Vys5x-vJ}PF}#DViMJ%_S)u#LQv=9^jg!4B`-B5QhMegvDdFXQp?%2Q$;d zK7d_{iYzON0jcH0)>u;6QA*`hTq?)0;}q?x9FiS5u1YzMlvL#?CzfT$E|(prl2nw6 zEICs0{r~It`t?lDOwaBt0ZO2a(N0gl>%ag1{qKK2w)tni^yPK@U$nF4R>Qz)_@$Ox zv;0o9l^!&$R?zuWXYWTkAL{Ihwt41N-wnO0)rr>OjjHW5YM#~Vywb7PMO%EU8aQrC zy>D-}C+sb+boSuK&4Cj%EbXW0{*$f1TJ+4oX)PL8oxnCO+GfM@jiU`WtQpT6mii@?C@8HIjFU zdeyrUIC9DMkI))UMFm9;wa%&a@sg&Z+j?kFCnKvb@Tt@N_Fj9R{qU(x=xx&8V{ZhI zibCuGGJg&l1_Pzo%(vaE2K@_M!wXx+92NoYY|hQcj9#?;X0hTqwMA>**h735xNf6x zREPoMNT1qP-Bt_yM}M9>b@ajmv$J^AY(p+q8kR8!0Wmj^p8WZ}G`>>SfC- zplM)2LU;!8tk#he_(sF=1FL1UT`w>eJhzEv#G7~-;=pXVn2^`gQNp$lEyN+b>6*iAh;wG-q8!nWXv{1dkUw~mKX&fGmWT8?SkS12LE}C#!EXxz|W&Z@Lx2b3o zVK?dg6Wz<&6K&!VqK$q~gVMrh5meE!x)*sJi~QI?DMO4zO`{3HsU4_vdeQ_Hb?i?^ zTktJdZbQ9q8Re7_8}UV}nuk|u}wSB1F* z@kHWn*$s`V*)lBCcN)tEbVebOQ!KiiD^9RXurZ|)d=9fq>^OsFM^;|w)2DzyB54ZZ z0RcSWf;U<3PxK0oEiqE0h}Z}eCC$Fn%{<*a^3QxFp){icL@ua6DqI zWP*Gb-zs6E6ae@&p90vfDF8UHq}L#wbUpqGc{I8nSnw>XWE^S15OV^@Y{0IvAi_QR z6Vl7PVmb|ztTwos1+uR!a{&gB;RO6MJTj-2c91k1^DP*S+_9~4QA%ReZ->%@l>4Hh z%z-kt{w{t!ZF*&hS?-o4?HPvC_y8wy9Zx(Ty6>{@sqetd2Z;~RZ+#N)#8%85DGJ#H zMU!$r{{j}WGTHsMRdp7eDsT*Y+g)JAp7IweRs-4;WMaE*cyJBqQr~HUV7vl3=w@r# zXd?3BwWJb71Vj>xan-hno{V!xPeK`cwf%;NxP%dc4#}LaQpltbyWabW{EVxXVVhSh zqXo6hDxMUyA69LOtbkVGFa&mp+0aXF3$v+Ma2J?-1Gj)s3Hopll_7c&%334V@4vNA zMc-FzRy_7p4sMsu^Si)fPvqdS#}gjAJH=xg1E;M?;7h-U7hkkNmT<61IuACt4HI;2 z_{;Enn@Y~^H=GiLHkh~IfV^YtlbmJgI>QTB;WB0Vf{Ea0Ln)AnZU$@xUv(M{qh(pO zLhJ{y@jfpGtOgCks(~GQUDo+|^5{9PMm%T*hL6Y~+=9R}3(0f?iiFZ+FdX$9TS#5D zw6wh;D?(@*nvP#86iOwGQL~Db20{aKb4GyP3kAb4Ub-Lxa<9;r7lk2y49-k7aJ}WH zWSgY;OY3KPE_E876@Pd`II;&0u-5C0nH;Fl~l`?LuEb!@xcjTVot9Z&+~DU@3d?7E_`>qMKtr zQ#XDdio$+r2!)ZuE{(SmDB}xwf0%>xctRQb6Uwj)k}Tw&?=JZuy5~rheQ4thnc=C^XJ2K%!0-=rC03nwP|gV06ER?fd8uWiA>_NoCpu`NQnWLm1=CHYTe0ybH!#sYix4S4#sW!lEN;`lI$EI4TJ^e7~%Ej{K834z#x zBYRKo>Up>X(1s0pir}}z_o|8GcG&9 ze)9dV+7A3|LS+}OK@$`o*l)DM3N}>`q+d>=My0~pmStdrjwpz_5n&_5;4uIK1S-08 z#WA^aE&3D(9K>V`y?fU2?y(k6pcx`Yz&;H&Tp^_4jRXi9AZpd^?%x7fjM5v(?z-E=s`|45(NWwo%=Kr~(})84pdze<&b92@$j3^%o@{T!Wp z&Rs{|QRsn%HH*5u^f>3*jQaU^4 z0i;?Td%p^M8#a)O+2szVeH>+3ycekWZVv4IU%V6RY-;i?`pTIY_3z+MWllWvLM;E{ z$c7P^6$42J(BNscsH7bb2XsJ$_JjYCRd*>NK8BtYjPDr%#?Js?@4)-RI7EXnHR%WA zA3Tn&WB|;dv?NX-V*thx=3WB+fx+)f=nQ*JE&bpKOFxEnltx=RHR-qXD|_5Fg|*Da zUgM&JAUpB=^m(XAI3g!bp1Xj3W9UjpWZO*}6?ds*oPgs)6ehyvmeD{S1?>P0X@~%R zfFZ0}FGRx|G7X8S8#KDY|BsA-{}VvwiP7*+P5R;g#8Fsg0_#ltFs+#OTh`Tn@`W=l zEklz>z4$ynmTo0}#?+gFcVz^4p9Jt;9Sz>pq#wL@CxDqg0~kvv;xU5G;IA+U+*U6@ z-;=MMgjn9!6-Ztm0VJoezAK}FgqrjN$^Q3I&_kEF*x@5+NP&#)K>Vk-huG6B|c2!11HZRJ0C|V!0HqUK(J>TdxK%S_~ehCeg1s;=yNBJpSf5*bL8~# zr%SIpZ58AhS9c2Ze>4K{~i-qZFcUe@Jk^x0>SO3nEwt^4wNOM4UspxbQMwhQRBQu7!cJQk(%3U1vX*C^WTvdn3a@H(ki!2bo)z)_{Y2&1m!J$J z(`aDt0gkB9q!fyT-mJy81e9tVmyv3ZO$#U|5YO&7Wr>29l!lKe1@iiMe*)W7B1}oN z!^$i^%@m(YXex{2rkaxicOwhIeL|3xKH$FeFqA>O#lyRIe7tHgq+?d%*d#HRY0Ssl z)zJNM+8E$@MC4WWgdJl~WSr^U1#zMy=LwFIft`7QMVR?wHB3xM?9}&q*b9({S%^CP zka~1!r!kms_f)g!Y{wQ04mk#p%sup3Sa)1SDqe?JH}pSek$$U=u;7OA`XbaH98nE3g-wktAi1Tkeq^JmPG~5V&E18X`X) zo8+F=u&$V}hlK7cw2;9Hfz+o;_P36T*;}e7#)HV_n5^Igl@;{K{UpT#B_a$xa^jAW1#(QJnckIp?1 z?LG!aZUsS2vgrJ?X$5h!*}jQE_zDqm7x~L)ApZwbDx=RbG@htXcbjuI%fl%7CIIef ztr_=_knVBi75|i(U^Er-+8Xjt=2W^l=s|jm6&`67;H+5XJb|JsLLbam8e65_=UpN3 zJ~oR)sDzj;OQ(3Wbj=tOpvcE-J{adE23>Amqb7pa`VSFJ2%iqQHAT*?`7JVu#IZ#( z=6)lG8vA0pi3nNr9}*DIPdZ-4C~|W)wJB$x;HV`7n6mtRR0p7H9@qpza2Th_n)oTM z5>|XHUR2hqmMtphdojMI{p2J_=q+`!FfUFRDo1(oHQ#{ zZLH&$x{v8@f?7FmwHv5^V9d{-IDhuE5msDJr8%py@2Vy$;*z-f+wQq_!7TP&hXobWDfs%M9} z)lgQB52Qj17K>au!nZLlD*SNmT=qSYzdZ(#H5a7 z!Fn}7M$J1Q&gPnT4@!6vYKs`_W$J?dD;D`oC!tP%7{k?rO%*dh@K`t2T;ES?#Ow z2zZ5snVtvp-@m^YI9~r$^yb@f-HGJ~bXQUGe8(PV*j%oE?jh9g~F33_6Z1UVCM$K)o_R<1G1G;1dTg zy0lXt@KKQL;SqFuLhw-rU+ze$e#x5KlsS;J?&RlA8i={`QkN|;HX2W+B^EW_BR;>7 zwbbk*K5D6dEp;(YU)yEPI7`{#0wDRs+Q%W*t}(I(JI$IMPQjvB5=L5h{?bu=>0=-D z8b^w}dMu-2aj01&Z6aljC=+MA29fGN%Nk1mm^OvdzjfI`=^uNHpqK-1fip1+(-0dG zoQf^}MHWwMr@HDt2Li7DZTk2eKCajQ4nCtzWczpOpU2-lB~SmB;)H?Brss~48DU}~ z8Re%geHP)&i|{+CKt7fHK~zB$?ygukF@}nI;^@_2{|PT)pRgc(N>mSQ`Bk`gbgC6c zU+JPSICwZ*MK_8W`^5cS@a^ah0N=K7s1N0tkkxNC_6dJ7Rs6fx5wcKqn>`^ylEGdK zzXX5f0VE;?M%czhZ0uZ!@8nCPD1lML8Bv}b`5g}!+Wg>4#mDALvS4`kxpt#?lA6h* z?y9!o!P3D4^uuXfe7Im%gV4jW=)Vi*f^`NL4B^k`oQh|9%lw!>)M4!8QR=TfR3vfs zKgpVd+u}|r%n?g@9@XU`uq5J57IK701U zapU9}JKJv`j7mh#O>zRG>rlM39nq(I}kYiej>F@_E62@yf{Are$$u=Em zc(#)6Cf?3q+wPgBqmsM2xLVq7lNv82J^W?XyfpErg~rvqwkbi8^b^Bu9qO4h+Xy5% zzFyGqzJU@&&hXv=Vka4C`^50@cmeM~q^cROpG(?{nGO`q%Dwv>I{8nGj(WnC*?*L1}6*I@~y9MTa9GPE7nF|Mk3SljxkW0tyYvt`|1{EyA;;jAG6BT zdvJ*47~RzSypj(C<%w?Ukw{5Qz5dMxY;M>87dGrpP4?f}{wUOk3Z_Iy&QRtUrHk=L zeD0n#3kAH{>>6l2$_-)Y(HvbRlJ`oUZ}}K1&@!NW%;A#)X=K?^NvNZ76)E~Y5Rb|n zx_3Yi3HmOt0s8#>=%eAoA$RLhbsHP+Y1M81QJzh}_ra$E*nq>3pvHCwg-Cs>K080J z{1x~_xHXyUqk~hZNC&+o@-t)&eaqV)A2R13#a+BHU-6l0^uZ~d6eks!8&WHW3V}S& zi6_RvN01u8rCAL4Pe>Z!Oh&kIHKPJgDf9|l7CO!t>DsL7+Vj)r#4}O3E94epFFUNc zSTJ4Y#sz_DE)HbV{$A+gy7Qz%?{a$Eo7Y;j!sAU&dnn%GGG~nx(d>M|% z4x>*U&Ch$T8=w%Gaok}q(-Q@6)ih#Ta5-s6`?OWJy-d4U0TN?ePt1r1K!-TCgW+iv zPth`jcdQx^p5u*Ey7d6kP+v8>LrK{@c*pP&#cPCd)sRctU=(c5;WB1bM_1pR`r7w2 zVJR8%uD;7mB*;%Px2N3`1eP`SlBWMQ^(<-q=`2~1?V8X)Eb3=R=GSCAF)ST zS&8-^RAq~R^>M~odOPqm{KboqBRYkwNCb~Z{*FiZ#-e@Il6NIySFoY7aWCBhu(s>3DLP=P{bTzQDcrvUm$@qgO zh&W`*B9rr+)Sf0iyk`Z|-JR3OOGa48= zG|TP-Sn_}^1#YuJ zZq9=+Y_%C6N7FN&GHCSp&uOH?)Bok|li5-CTfY4E$6&lBoW$>1OCka0nVZKOld`iU zM7EJi!?Ona^v%fw-GCY>9#o+0pF=jh%qvT6lSE(bqT08bZ@PwGHPc8H_C2>QrFVWT zivvbP?TVK~}ZQ`d8kP#&cMX3>@oc|JAaf%s3BXBt@2MSm>`f-=b%* zE0jlp&|RDEx1;GLTbz`;F=wb`i~%FPc;x(rExL4%I-%=Qi@vjn>rwGHt};e~ ze+|hFP5}Ehxv615modx`^R!yjBdm+rgO9VKo#nj!`Q8+W&)lTssg&bZIl)BO|17ip zc(vlsXNi(*tw_VOR>Tc(Z#CC;17V<)QX21fAfi6avrcKeOaF=!1IEDY!#Vv%u=&t2 z$HU`&hFLL#_vvpqcgG6<7Fx$5y8EEv2B1~hJO3n0NDa4lNU`_H{``uP30Dj?!3fjk8JCN$yTt$X1j##=VaFg7uO=ImZ#MgQ6=bK&lPB6ZH0}&HG zo@~eyk$6BGq5)k6BTq;2b_L1=p}Lc{WqrBnv=6o~`_sv(A16Kczgzt2Emsp+D9wxm^uVB4;a+1J7e+8h6zu!xV zzobo6j5itKub@n5jc$|ROXlc%uVI>$lF=Ko8Ll5oz>a3iTe#KdvkX3I$5vNS}YD3Q;a3Y$NLdWei4p)0t$OpJ^D#udk> zSUr@P$df>5!^asCE?PrLME+&m$p|Fkjl#ZZfuCMHi^CFer_)rkhA+An5JrS*5^weGHB(0A` zNO*Ok_DHb^Yu@7`9NMuj^`k2Jk^ih$%6p%kd*l%Qzx2rA$3E~14u+jLD@}yCxe(Py z{JA-*v0D~l*E!=w4<%&~Km{UBoyJAPaX6Ol7c_R4X2_bFfw;p(J%uARay0d~cJ~T; z<3MS)bf9Q9aMlQZ3w13$DskG5RaI{V6e@K_XY_FB5AG5j%ez2dscxzaeB*y*^qKC; z-7U0;J$308LL}O5G(r@w{pTUP)@QqFK=|grrZ`dynfw%GQwx<$CXeDSxRUf;QXf+n z(_NgIfZr6eiFXU?JLlG*x&tK!iV}P{S}EcMS8;DNNiyadlz5^s(m@DGm(fi)GlihP z7=j?(qxnWZ(0~3#aae@{RVq~g8HCwAtmYdh0Ky?=}0I$rg@ep_yDorX`m zo8jj8xB#P%zE z16+Zz%C*V_LIAT^cr^){t8c*s5?sHF@uKCW?-T~T zoLsj}{{Y^PZTc~J~*8{`H=liEnBNK5?`n%H}z?N54s zP`26*HvLi5|KU>`Qj1Ce5k1A`QAX&8ZWoP(qxuz(AR+OOOfgDU(Bea)BWdU9nGCS% zWTYlG)ndP&^d(*EC^{N&V>-eY+LZ67<29&6LCUX494sfKu0PeLrN1oLJsy-_&K`QB z7CG54n52CivZ`7gdtY={;F&GI!DqV4Tq*-c!#W@8?1?r9E>2N)POXay@*S&&uOZRz z`e-NYau0b2<+!gi(RTR+7c5(yGtt&q)BBz1b}$g=u;G`W>iCoqiZj1pHbN^oQ6EhR z?bkVD?>rN2TNEh|u7{qsQCSU~>1?*~Ws63S5*}rPP`8VpFtHQ% zlSggeLUw<7sYwKNn<(*F)>|@_RD&`{51C|lm1~Yq+c8*3Jj*STRls)Ju^2jS%Y27U zcf_xi`EvRWgylQvFAM^wk_EWcqTXsi`J`uF1**i?Q|qJKiQhmutwj)G!)ZD}=k#s6 zqn&N5T?Ut6QxtJGTFiEOLMJYnHKKRQ3uExuFSVBg+ijIxZxI|dMQ`#1z(nN$)h{3+ zfR3$rUVP}lBY@{lZ=u@NJv~Xfe~;`Q%xe z$H8LIYGXmjwCmAEt97MwCagqTg){;Cf)brp&7nNKGRnd)T98gy-0j_JpQL&HN5Q9c zR?N{IsTU}iLT{v%Gr#ZCU<>L6vV)u&zq^GViof^N>(uokDexEU3-)*HZ`Ti_8gczm z`mhj!tXJscbM*CR>EqAo<4@`19-R2E-%TH<=;K-Xc!NHc>EpdH$LmkhM}jPRHfN+&gOSe@K7v+;3NNe@4ySr@#I^eSDQZconp*&#PcFEsS>P zG|ph%QFXv6WUT?z;t7c|`JO!EskTdK!xa#U_~N2-5l zCAx25eKghGSNsc%0|?rxiwgxvqfAzD#SaJ)Yk;cVax{MaDI&qY2Lzs4?|-aw9>N1v z>>!3fdOl=54ZH`}(eCt@k(1bzk2OP8+=rtDE}lFNB~2-jh^o;c2?QE2Q{xJm6nwm! zYg?O*;_RF;J$qo<*b_fMajU)3j9pzP%HI7h7B>9OpL^b;_kgZ^EEHEJ(m8hei~L2F9|kcO z;3e)pHCA+I>9lYtv3a@X<0u5&Q^<#ayYb#21Te>%Q8yauccI-7B>XDa95osS0)8}d zo7HJ{c7-(C-H8I(qd7r#a0tj&1gtKKMPZk50`8CwxI-%hTz4l5aOZLY?(|v!PG*KB s>2Fqahq70t++;aEsUEYbFzK|ajj#qY(Oo1fp->}}I<`K=>9^AV2X17h<^TWy From 0426bc691350c04068dbb01500e9783573142900 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:48:05 +0200 Subject: [PATCH 023/113] Remove accidentally commited sphinx-build outputs. --- .../_sphinx_javascript_frameworks_compat.js | 123 ------------------ doc/manual/build/man/_static/jquery.js | 2 - 2 files changed, 125 deletions(-) delete mode 100644 doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js delete mode 100644 doc/manual/build/man/_static/jquery.js diff --git a/doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js b/doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js deleted file mode 100644 index 81415803e..000000000 --- a/doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js +++ /dev/null @@ -1,123 +0,0 @@ -/* Compatability shim for jQuery and underscores.js. - * - * Copyright Sphinx contributors - * Released under the two clause BSD licence - */ - -/** - * small helper function to urldecode strings - * - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL - */ -jQuery.urldecode = function(x) { - if (!x) { - return x - } - return decodeURIComponent(x.replace(/\+/g, ' ')); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} diff --git a/doc/manual/build/man/_static/jquery.js b/doc/manual/build/man/_static/jquery.js deleted file mode 100644 index c4c6022f2..000000000 --- a/doc/manual/build/man/_static/jquery.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="

",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 Date: Fri, 10 Apr 2026 22:50:38 +0200 Subject: [PATCH 024/113] Add TODO notes. --- src/units/http_server.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 161ffed0e..d0024aeb5 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -1122,6 +1122,26 @@ impl HttpServer { State(state): State>, Path(name): Path>, ) -> Json> { + // TODO: Don't remove a TSIG key which is currently in use. + // + // Currently if a zone was added with `--source + // ip[:port]^` that would cause the TSIG key to be used + // by the loader when refreshing the zone. + // + // In future policies may refer to TSIG keys in a couple of places: + // + // 1. In server outbound settings for signing NOTIFY, SOA and XFR messages + // to downstream nameservers. + // 2. In key manager settings for instructing dnst keyset which nameserver + // to query to sanity check the signed zone contents, with a TSIG key if + // one is needed to authenticate to the specified nameserver in order to + // do XFR. + // + // So we need to check all of these places to see if a key is in use. + // + // Alternatively we would need to update the TSIG key store to track + // if (and where?) a key is being used and check with the TSIG key + // store. todo!() } From 542d70e5b6e16ecb9bed28b368eb042e1cdb8014 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 13 Apr 2026 16:36:35 +0200 Subject: [PATCH 025/113] Implement `cascade tsig remove`. --- doc/manual/build/man/cascade-debug.1 | 2 +- doc/manual/build/man/cascade-health.1 | 2 +- doc/manual/build/man/cascade-hsm.1 | 2 +- doc/manual/build/man/cascade-keyset.1 | 2 +- doc/manual/build/man/cascade-policy.1 | 2 +- doc/manual/build/man/cascade-status.1 | 2 +- doc/manual/build/man/cascade-template.1 | 2 +- doc/manual/build/man/cascade-tsig.1 | 22 ++++++++++++++++++++- doc/manual/build/man/cascade-zone.1 | 2 +- doc/manual/build/man/cascade.1 | 2 +- doc/manual/build/man/cascaded-config.toml.5 | 2 +- doc/manual/build/man/cascaded-policy.toml.5 | 2 +- doc/manual/build/man/cascaded.1 | 2 +- doc/manual/source/man/cascade-tsig.rst | 10 ++++++++++ src/units/http_server.rs | 21 +++++++++++++++++--- 15 files changed, 61 insertions(+), 16 deletions(-) diff --git a/doc/manual/build/man/cascade-debug.1 b/doc/manual/build/man/cascade-debug.1 index 5848ae130..cd8db02e2 100644 --- a/doc/manual/build/man/cascade-debug.1 +++ b/doc/manual/build/man/cascade-debug.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-DEBUG" "1" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-DEBUG" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-debug \- Debug / troubleshoot Cascade .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-health.1 b/doc/manual/build/man/cascade-health.1 index 133e4cd03..73c81b3dc 100644 --- a/doc/manual/build/man/cascade-health.1 +++ b/doc/manual/build/man/cascade-health.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HEALTH" "1" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HEALTH" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-health \- Check the health of Cascade .sp diff --git a/doc/manual/build/man/cascade-hsm.1 b/doc/manual/build/man/cascade-hsm.1 index 0b0deaf70..3ff0f301d 100644 --- a/doc/manual/build/man/cascade-hsm.1 +++ b/doc/manual/build/man/cascade-hsm.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HSM" "1" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HSM" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-hsm \- Manage HSMs .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-keyset.1 b/doc/manual/build/man/cascade-keyset.1 index 207ad3ec3..c638cbe83 100644 --- a/doc/manual/build/man/cascade-keyset.1 +++ b/doc/manual/build/man/cascade-keyset.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-KEYSET" "1" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-KEYSET" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-keyset \- Execute manual key roll or key removal commands .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-policy.1 b/doc/manual/build/man/cascade-policy.1 index 39a2dc766..88eaface5 100644 --- a/doc/manual/build/man/cascade-policy.1 +++ b/doc/manual/build/man/cascade-policy.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-POLICY" "1" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-POLICY" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-policy \- Manage policies .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-status.1 b/doc/manual/build/man/cascade-status.1 index f25e7c890..ffeb461d9 100644 --- a/doc/manual/build/man/cascade-status.1 +++ b/doc/manual/build/man/cascade-status.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-STATUS" "1" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-STATUS" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-status \- Show the status of Cascade .sp diff --git a/doc/manual/build/man/cascade-template.1 b/doc/manual/build/man/cascade-template.1 index 4afae1f31..5f649d618 100644 --- a/doc/manual/build/man/cascade-template.1 +++ b/doc/manual/build/man/cascade-template.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-TEMPLATE" "1" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-TEMPLATE" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-template \- Print example config or policy files .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-tsig.1 b/doc/manual/build/man/cascade-tsig.1 index 17001c96d..3c38630d2 100644 --- a/doc/manual/build/man/cascade-tsig.1 +++ b/doc/manual/build/man/cascade-tsig.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-TSIG" "1" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-TSIG" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-tsig \- Manage TSIG keys .sp @@ -40,6 +40,8 @@ Added in version 0.1.0\-beta1. \fBcascade\fP \fB[GLOBAL OPTIONS]\fP tsig \fI\%add\fP \fB\fP \fB\fP \fB\fP .sp \fBcascade\fP \fB[GLOBAL OPTIONS]\fP tsig \fI\%list\fP +.sp +\fBcascade\fP \fB[GLOBAL OPTIONS]\fP tsig \fI\%remove\fP \fB\fP .SH DESCRIPTION .sp Manage RFC 8945 TSIG keys for authenticating zone transfers. @@ -66,6 +68,24 @@ Register a new TSIG key. .B list List registered TSIG keys and the zones that use them. .UNINDENT +.INDENT 0.0 +.TP +.B add +Remove a registered TSIG key. +.sp +\fBNOTE:\fP +.INDENT 7.0 +.INDENT 3.5 +Returns an error if the key does not exist in the TSIG key store +or if any zone exists that is configured to authenticate with an +.INDENT 0.0 +.INDENT 3.5 +upstream source using the specified TSIG key. +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT .SH ARGUMENTS FOR TSIG ADD .INDENT 0.0 .TP diff --git a/doc/manual/build/man/cascade-zone.1 b/doc/manual/build/man/cascade-zone.1 index f21f5aa0c..d1e2caa47 100644 --- a/doc/manual/build/man/cascade-zone.1 +++ b/doc/manual/build/man/cascade-zone.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-ZONE" "1" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-ZONE" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-zone \- Manage zones .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade.1 b/doc/manual/build/man/cascade.1 index 0555a5ffa..ba905959c 100644 --- a/doc/manual/build/man/cascade.1 +++ b/doc/manual/build/man/cascade.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE" "1" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade \- Cascade CLI .SH SYNOPSIS diff --git a/doc/manual/build/man/cascaded-config.toml.5 b/doc/manual/build/man/cascaded-config.toml.5 index 029c34183..c18e61275 100644 --- a/doc/manual/build/man/cascaded-config.toml.5 +++ b/doc/manual/build/man/cascaded-config.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-CONFIG.TOML" "5" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-CONFIG.TOML" "5" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-config.toml \- Cascade configuration file .sp diff --git a/doc/manual/build/man/cascaded-policy.toml.5 b/doc/manual/build/man/cascaded-policy.toml.5 index b56777c67..989fee49d 100644 --- a/doc/manual/build/man/cascaded-policy.toml.5 +++ b/doc/manual/build/man/cascaded-policy.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-POLICY.TOML" "5" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-POLICY.TOML" "5" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-policy.toml \- Cascade policy file format .sp diff --git a/doc/manual/build/man/cascaded.1 b/doc/manual/build/man/cascaded.1 index ed91e2133..b6fdaa3be 100644 --- a/doc/manual/build/man/cascaded.1 +++ b/doc/manual/build/man/cascaded.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED" "1" "Apr 10, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded \- DNSSEC signer .SH SYNOPSIS diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 3519556cc..f446d23f8 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -12,6 +12,8 @@ Synopsis :program:`cascade` ``[GLOBAL OPTIONS]`` tsig :subcmd:`list` +:program:`cascade` ``[GLOBAL OPTIONS]`` tsig :subcmd:`remove` ```` + Description ----------- @@ -39,6 +41,14 @@ Commands List registered TSIG keys and the zones that use them. +.. subcmd:: remove + + Remove a registered TSIG key. + + .. note:: Returns an error if the key does not exist in the TSIG key store + or if any zone exists that is configured to authenticate with an + upstream source using the specified TSIG key. + Arguments for :subcmd:`tsig add` -------------------------------- diff --git a/src/units/http_server.rs b/src/units/http_server.rs index d0024aeb5..367a520f6 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -1119,8 +1119,8 @@ impl HttpServer { } async fn tsig_key_remove( - State(state): State>, - Path(name): Path>, + State(http_server_state): State>, + Path(tsig_key_name): Path, ) -> Json> { // TODO: Don't remove a TSIG key which is currently in use. // @@ -1142,7 +1142,22 @@ impl HttpServer { // Alternatively we would need to update the TSIG key store to track // if (and where?) a key is being used and check with the TSIG key // store. - todo!() + let mut state = http_server_state.center.state.lock().unwrap(); + + if !state.tsig_store.map.contains_key(&tsig_key_name) { + return Json(Err(TsigRemoveError::NotFound)); + } + + if state.zones.iter().any(|z| { + let zone_state = z.0.state.lock().unwrap(); + matches!(zone_state.loader.source, crate::loader::Source::Server { tsig_key: Some(ref key), .. } if tsig_key_name == key.name()) + }) { + return Json(Err(TsigRemoveError::InUse)); + } + + let _ = state.tsig_store.map.remove(&tsig_key_name); + state.tsig_store.mark_dirty(&http_server_state.center); + Json(Ok(TsigRemoveResult)) } async fn tsig_key_list(State(http_state): State>) -> Json { From 67becab358c7500789063cb1b282ef4d2e3986f1 Mon Sep 17 00:00:00 2001 From: Philip-NLnetLabs Date: Wed, 15 Apr 2026 08:36:17 +0200 Subject: [PATCH 026/113] Introduce public-nameservers and send it to keyset. Handle TSIG related (#580) Co-authored-by: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> --- crates/api/src/lib.rs | 7 ++-- src/main.rs | 11 ++++-- src/policy/file/v1.rs | 10 ++++++ src/policy/mod.rs | 32 ++++++++++++++++- src/state/v1.rs | 5 +++ src/tsig/mod.rs | 55 ++++++++++++++++++++++++++++- src/units/http_server.rs | 74 +++++++++++++++------------------------- src/units/key_manager.rs | 25 +++++++++++--- src/zone/state/v1.rs | 5 +++ 9 files changed, 166 insertions(+), 58 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index f3615de0c..015640817 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -749,12 +749,15 @@ pub struct KeyMsg { #[derive(Deserialize, Serialize, Debug, Clone)] pub enum PolicyReloadError { Io(Utf8PathBuf, String), + Check(String), } impl Display for PolicyReloadError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let PolicyReloadError::Io(p, e) = self; - format!("{p}: {e}").fmt(f) + match self { + PolicyReloadError::Io(p, e) => format!("{p}: {e}").fmt(f), + PolicyReloadError::Check(e) => e.to_string().fmt(f), + } } } diff --git a/src/main.rs b/src/main.rs index ec0823f5e..6fcb13831 100644 --- a/src/main.rs +++ b/src/main.rs @@ -109,9 +109,14 @@ fn main() -> ExitCode { // Load all policies. let mut updates = Vec::new(); - let res = policy::reload_all(&mut state.policies, &config, |name, _| { - updates.push(name.clone()); - }); + let res = policy::reload_all( + &mut state.policies, + &config, + &state.tsig_store, + |name, _| { + updates.push(name.clone()); + }, + ); if let Err(err) = res { error!("Cascade couldn't load all policies: {err}"); diff --git a/src/policy/file/v1.rs b/src/policy/file/v1.rs index 0a803c4ce..1e9c03653 100644 --- a/src/policy/file/v1.rs +++ b/src/policy/file/v1.rs @@ -144,6 +144,13 @@ pub struct KeyManagerSpec { /// How keys are generated. pub generation: KeyManagerGenerationSpec, + + /// The upstream nameservers to use when checking for RRSIG propagation + /// during a key roll. The value is a list of strings. Each string has the following + /// syntax: `:[^].` + /// The port is mandatory. The TSIG key name is optional and the name + /// of the key is preceded by a caret character (`^`). + pub publication_nameservers: Vec, } //--- Conversion @@ -239,6 +246,7 @@ impl KeyManagerSpec { default_ttl: self.records.ttl.as_ttl(), ds_algorithm: self.ds_algorithm, auto_remove: self.auto_remove, + publication_nameservers: self.publication_nameservers, } } @@ -270,6 +278,7 @@ impl KeyManagerSpec { ds_algorithm: policy.ds_algorithm.clone(), auto_remove: policy.auto_remove, + publication_nameservers: policy.publication_nameservers.clone(), records: KeyManagerRecordsSpec { ttl: TimeSpan::from_ttl(policy.default_ttl), @@ -318,6 +327,7 @@ impl Default for KeyManagerSpec { algorithm: Default::default(), ds_algorithm: DsAlgorithm::Sha256, auto_remove: true, + publication_nameservers: Default::default(), records: Default::default(), generation: Default::default(), } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index b42560404..0f98ec8b8 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -2,6 +2,7 @@ use std::fmt::{Display, Formatter}; use std::net::SocketAddr; +use std::str::FromStr; use std::{fs, io, sync::Arc}; use bytes::Bytes; @@ -11,6 +12,7 @@ use domain::base::Ttl; use serde::{Deserialize, Serialize}; use tracing::{debug, error, info, warn}; +use crate::tsig::TsigStore; use crate::{api::PolicyReloadError, config::Config}; pub mod file; @@ -51,9 +53,10 @@ pub enum PolicyChange { pub fn reload_all( policies: &mut foldhash::HashMap, Policy>, config: &Config, + tsig_store: &TsigStore, mut on_change: impl FnMut(&Box, PolicyChange), ) -> Result<(), PolicyReloadError> { - let new_versions = load_all(policies, config)?; + let new_versions = load_all(policies, config, tsig_store)?; let mut new_policies = foldhash::HashMap::default(); @@ -117,6 +120,7 @@ pub fn reload_all( pub fn load_all( policies: &foldhash::HashMap, Policy>, config: &Config, + tsig_store: &TsigStore, ) -> Result, PolicyVersion>, PolicyReloadError> { // Write the loaded policies to a new hashmap, so policies that no longer // exist can be detected easily. @@ -177,6 +181,8 @@ pub fn load_all( .expect("this path points to a readable file, so it must have a file name"); let policy = spec.parse(name); + + check_policy(&policy, tsig_store)?; if policies.contains_key(name) { info!("Reloaded policy '{name}'"); } else { @@ -191,6 +197,27 @@ pub fn load_all( Ok(new_policies) } +/// Perform a semantic check on the loaded policy. +fn check_policy(policy: &PolicyVersion, tsig_store: &TsigStore) -> Result<(), PolicyReloadError> { + // Check the publication nameservers for the key manager. Any TSIG key + // that is part of those nameservers has to exist in the TSIG key store. + for n in &policy.key_manager.publication_nameservers { + if let Some((_, tsig_name)) = n.split_once('^') { + let tsig_name = Name::from_str(tsig_name).map_err(|e| { + PolicyReloadError::Check(format!( + "unable to convert TSIG key name {tsig_name} to DNS name: {e}" + )) + })?; + tsig_store + .get(&tsig_name) + .ok_or(PolicyReloadError::Check(format!( + "TSIG key {tsig_name} not found in TSIG store" + )))?; + } + } + Ok(()) +} + //----------- PolicyVersion ---------------------------------------------------- /// A particular version of a policy. @@ -278,6 +305,9 @@ pub struct KeyManagerPolicy { /// Automatically remove keys that are no long in use. pub auto_remove: bool, + + /// Nameservers to check for RRSIG propagation during a key roll. + pub publication_nameservers: Vec, } //----------- SignerPolicy ----------------------------------------------------- diff --git a/src/state/v1.rs b/src/state/v1.rs index a0cf2b317..a2781a756 100644 --- a/src/state/v1.rs +++ b/src/state/v1.rs @@ -255,6 +255,9 @@ pub struct KeyManagerPolicySpec { /// Automatically remove keys that are no long in use. auto_remove: bool, + + /// Nameservers to check for RRSIG propagation during a key roll. + pub publication_nameservers: Vec, } //--- Conversion @@ -282,6 +285,7 @@ impl KeyManagerPolicySpec { ds_algorithm: self.ds_algorithm, default_ttl: self.default_ttl, auto_remove: self.auto_remove, + publication_nameservers: self.publication_nameservers, } } @@ -307,6 +311,7 @@ impl KeyManagerPolicySpec { ds_algorithm: policy.ds_algorithm.clone(), default_ttl: policy.default_ttl, auto_remove: policy.auto_remove, + publication_nameservers: policy.publication_nameservers.clone(), } } } diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index 4260a6200..0c4a2cc03 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -1,7 +1,9 @@ //! Managing TSIG keys. +use std::str::FromStr; use std::{collections::hash_map, fmt, io, sync::Arc, time::Duration}; +use domain::base::Name; use domain::tsig; use tracing::{debug, error, trace}; @@ -204,7 +206,8 @@ pub fn import_key( }); } } - state.tsig_store.mark_dirty(center); + drop(state); + save_now(center); Ok(()) } @@ -254,6 +257,56 @@ pub fn generate_key( pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), RemoveError> { // Lock the global state and try to remove the key. let mut state = center.state.lock().unwrap(); + + // Currently if a zone was added with `--source + // ip[:port]^` that would cause the TSIG key to be used + // by the loader when refreshing the zone. + // + // In future policies may refer to TSIG keys in a couple of places: + // + // 1. In server outbound settings for signing NOTIFY, SOA and XFR messages + // to downstream nameservers. + // 2. In key manager settings for instructing dnst keyset which nameserver + // to query to sanity check the signed zone contents, with a TSIG key if + // one is needed to authenticate to the specified nameserver in order to + // do XFR. + // + // So we need to check all of these places to see if a key is in use. + // + // Alternatively we would need to update the TSIG key store to track + // if (and where?) a key is being used and check with the TSIG key + // store. + + if !state.tsig_store.map.contains_key(name) { + return Err(RemoveError::NotFound); + } + + if state.zones.iter().any(|z| { + let zone_state = z.0.state.lock().unwrap(); + matches!(zone_state.loader.source, crate::loader::Source::Server { tsig_key: Some(ref key), .. } if name == key.name()) + }) { + return Err(RemoveError::Used); + } + + if state + .policies + .values() + .flat_map(|p| &p.latest.key_manager.publication_nameservers) + .filter_map(|n| n.split_once("^").map(|v| v.1)) + .any(|k| { + let nk = Name::>::from_str(k); + if let Ok(n) = nk { + n == name + } else { + // Just ignore tSIG key names that cannot be parsed as a + // DNS name. + false + } + }) + { + return Err(RemoveError::Used); + } + match state.tsig_store.map.entry(name.clone()) { hash_map::Entry::Occupied(entry) => { if !entry.get().zones.is_empty() { diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 367a520f6..800e7a97f 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -45,6 +45,7 @@ use crate::manager::Terminated; use crate::metrics::MetricsCollection; use crate::policy::SignerDenialPolicy; use crate::policy::SignerSerialPolicy; +use crate::tsig::{self, RemoveError}; use crate::units::key_manager::KmipClientCredentials; use crate::units::key_manager::KmipClientCredentialsFile; use crate::units::key_manager::KmipServerCredentialsFileMode; @@ -806,20 +807,25 @@ impl HttpServer { .collect::>(); let mut changed = false; let mut updates = Vec::new(); - let res = crate::policy::reload_all(&mut state.policies, ¢er.config, |name, change| { - changed = true; - - changes.insert( - name.clone(), - match change { - crate::policy::PolicyChange::Removed { .. } => PolicyChange::Removed, - crate::policy::PolicyChange::Updated { .. } => PolicyChange::Updated, - crate::policy::PolicyChange::Added { .. } => PolicyChange::Added, - }, - ); + let res = crate::policy::reload_all( + &mut state.policies, + ¢er.config, + &state.tsig_store, + |name, change| { + changed = true; + + changes.insert( + name.clone(), + match change { + crate::policy::PolicyChange::Removed { .. } => PolicyChange::Removed, + crate::policy::PolicyChange::Updated { .. } => PolicyChange::Updated, + crate::policy::PolicyChange::Added { .. } => PolicyChange::Added, + }, + ); - updates.push((name.clone(), change)); - }); + updates.push((name.clone(), change)); + }, + ); if let Err(err) = res { return Json(Err(err)); @@ -1122,41 +1128,17 @@ impl HttpServer { State(http_server_state): State>, Path(tsig_key_name): Path, ) -> Json> { - // TODO: Don't remove a TSIG key which is currently in use. - // - // Currently if a zone was added with `--source - // ip[:port]^` that would cause the TSIG key to be used - // by the loader when refreshing the zone. - // - // In future policies may refer to TSIG keys in a couple of places: - // - // 1. In server outbound settings for signing NOTIFY, SOA and XFR messages - // to downstream nameservers. - // 2. In key manager settings for instructing dnst keyset which nameserver - // to query to sanity check the signed zone contents, with a TSIG key if - // one is needed to authenticate to the specified nameserver in order to - // do XFR. - // - // So we need to check all of these places to see if a key is in use. - // - // Alternatively we would need to update the TSIG key store to track - // if (and where?) a key is being used and check with the TSIG key - // store. - let mut state = http_server_state.center.state.lock().unwrap(); - - if !state.tsig_store.map.contains_key(&tsig_key_name) { - return Json(Err(TsigRemoveError::NotFound)); - } - - if state.zones.iter().any(|z| { - let zone_state = z.0.state.lock().unwrap(); - matches!(zone_state.loader.source, crate::loader::Source::Server { tsig_key: Some(ref key), .. } if tsig_key_name == key.name()) - }) { - return Json(Err(TsigRemoveError::InUse)); + let result = tsig::remove_key(&http_server_state.center, &tsig_key_name) + .map_err(|e| match e { + RemoveError::NotFound => TsigRemoveError::NotFound, + RemoveError::Used => TsigRemoveError::InUse, + }) + // Map Ok value that we don't use. + .map(|_| TsigRemoveResult); + if result.is_err() { + return Json(result); } - let _ = state.tsig_store.map.remove(&tsig_key_name); - state.tsig_store.mark_dirty(&http_server_state.center); Json(Ok(TsigRemoveResult)) } diff --git a/src/units/key_manager.rs b/src/units/key_manager.rs index 065ae3062..f7a555b88 100644 --- a/src/units/key_manager.rs +++ b/src/units/key_manager.rs @@ -249,7 +249,7 @@ impl KeyManager { tokio::spawn(async move { // Keep it simple, just send all config items to keyset even // if they didn't change. - let config_commands = policy_to_commands(&new); + let config_commands = policy_to_commands(¢er, &new); for c in config_commands { let mut cmd = Self::keyset_cmd(¢er, name.clone(), RecordingMode::Record); cmd.arg("set"); @@ -381,7 +381,7 @@ impl KeyManager { // Pass `set` and `import` commands to `dnst keyset`. let config_commands = imports_to_commands(key_imports).into_iter().chain( - policy_to_commands(&policy.latest) + policy_to_commands(center, &policy.latest) .into_iter() .chain({ match var("CASCADE_FAKETIME") { @@ -687,7 +687,7 @@ macro_rules! strs { }; } -fn policy_to_commands(policy: &PolicyVersion) -> Vec> { +fn policy_to_commands(center: &Arc
, policy: &PolicyVersion) -> Vec> { let km = &policy.key_manager; let mut algorithm_cmd = vec!["algorithm".to_string()]; @@ -711,7 +711,20 @@ fn policy_to_commands(policy: &PolicyVersion) -> Vec> { let seconds = |x| format!("{x}s"); - vec![ + let mut cmds = vec![]; + + if km.publication_nameservers.iter().any(|n| n.contains("^")) { + let tsig_store_cmd = vec![ + "tsig-store-path".to_string(), + center.config.tsig_store_path.as_str().to_string(), + ]; + cmds.push(tsig_store_cmd); + } + + let mut publication_nameservers_cmd = vec!["publication-nameservers".to_string()]; + publication_nameservers_cmd.append(&mut km.publication_nameservers.clone()); + + cmds.append(&mut vec![ strs!["use-csk", km.use_csk], algorithm_cmd, strs!["ksk-validity", validity(km.ksk_validity)], @@ -757,7 +770,9 @@ fn policy_to_commands(policy: &PolicyVersion) -> Vec> { strs!["ds-algorithm", km.ds_algorithm], strs!["default-ttl".to_string(), km.default_ttl.as_secs(),], strs!["autoremove", km.auto_remove], - ] + publication_nameservers_cmd, + ]); + cmds } //============ KMIP Credential Management ==================================== diff --git a/src/zone/state/v1.rs b/src/zone/state/v1.rs index 5af808ce6..fd643e619 100644 --- a/src/zone/state/v1.rs +++ b/src/zone/state/v1.rs @@ -197,6 +197,9 @@ pub struct KeyManagerPolicySpec { /// Automatically remove keys that are no long in use. auto_remove: bool, + + /// Nameservers to check for RRSIG propagation during a key roll. + publication_nameservers: Vec, } //--- Conversion @@ -224,6 +227,7 @@ impl KeyManagerPolicySpec { ds_algorithm: self.ds_algorithm, default_ttl: self.default_ttl, auto_remove: self.auto_remove, + publication_nameservers: self.publication_nameservers, } } @@ -249,6 +253,7 @@ impl KeyManagerPolicySpec { ds_algorithm: policy.ds_algorithm.clone(), default_ttl: policy.default_ttl, auto_remove: policy.auto_remove, + publication_nameservers: policy.publication_nameservers.clone(), } } } From e6a51921f4b629110e2f9964b1b02e2ec340abd8 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:38:38 +0200 Subject: [PATCH 027/113] Fix failing upstream-tsig system test by bumping the dnst version used to include the new TSIG related set subcommands. --- integration-tests/cascade-test-image/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/cascade-test-image/Dockerfile b/integration-tests/cascade-test-image/Dockerfile index efd188728..e3f975625 100644 --- a/integration-tests/cascade-test-image/Dockerfile +++ b/integration-tests/cascade-test-image/Dockerfile @@ -50,8 +50,8 @@ RUN < Date: Wed, 15 Apr 2026 12:38:21 +0200 Subject: [PATCH 028/113] Improve the policy template documentation for send-notify-to. --- etc/policy.template.toml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/etc/policy.template.toml b/etc/policy.template.toml index 3e27515c0..848de906b 100644 --- a/etc/policy.template.toml +++ b/etc/policy.template.toml @@ -367,5 +367,24 @@ required = false # # If empty, no NOTIFY messages will be sent. # -# A collection of IP:[port], defaulting to port 53 when not specified. +# Each nameserver must be specifeid as a string in the form `":[][^]"`. +# +# A TSIG key name can optionally be specified in order to sign the NOTIFY +# message using the specified TSIG key. The TSIG key must already have been +# added to the Cascade TSIG key store with the `cascade tsig add` CLI command. +# +# Examples: +# +# send-notify-to = [ "127.0.0.1"] +# send-notify-to = [ "127.0.0.1:53"] +# send-notify-to = [ "127.0.0.1:53^my_tsig_key"] +# send-notify-to = [ "127.0.0.1^my_tsig_key"] +# send-notify-to = [ { addr = "127.0.0.1:49", tsig-key-name = "my_tsig_key" } ] +# +# You can also use the TOML array of tables syntax like so: +# +# [[server.outbound.send-notify-to]] +# addr = "127.0.0.1:53" +# tsig-key-name = "my_tsig_key" send-notify-to = [] From 3331ff98c5709a4300875ff226cdc8e4c9c12128 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:36:12 +0200 Subject: [PATCH 029/113] Add some more comments to the code. --- crates/api/src/lib.rs | 34 ++++++++++-- crates/cli/src/commands/tsig.rs | 97 ++++++++++++++++++++++----------- 2 files changed, 94 insertions(+), 37 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 34e281086..617810ea0 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -194,41 +194,59 @@ pub struct KmipKeyImport { /// The name of a TSIG key. pub type TsigKeyName = domain::tsig::KeyName; +//----------- TsigAdd --------------------------------------------------------- + +/// Add a TSIG key to Cascade. #[derive(Deserialize, Serialize, Debug, Clone)] pub struct TsigAdd { + /// The name of the TSIG key to add. pub name: TsigKeyName, + + /// The algorithm of the TSIG key. pub alg: TsigAlgorithm, + + /// The base64 encoded key material bytes. pub secret: String, } +/// The successful result of adding a TSIG key to Cascade. #[derive(Deserialize, Serialize, Debug, Clone)] pub struct TsigAddResult; +/// An error result indicating why an attempt to add a TSIG key to Cascade +/// failed. #[derive(Deserialize, Serialize, Debug, Clone)] pub enum TsigAddError { - InvalidKeyName, + /// A TSIG key by the given name already exists in Cascade. AlreadyExists, - InvalidAlgorithmName, + + /// The provided TSIG key secret was not correctly base64 encoded. InvalidBase64Secret, } impl Display for TsigAddError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TsigAddError::InvalidKeyName => write!(f, "invalid TSIG key name"), TsigAddError::AlreadyExists => write!(f, "TSIG key already exists"), - TsigAddError::InvalidAlgorithmName => write!(f, "invalid TSIG algorithm name"), TsigAddError::InvalidBase64Secret => write!(f, "invalid TSIG base64 encoded secret"), } } } +//------------ TsigRemove ---------------------------------------------------- + +/// The successful result of removing a TSIG key from Cascade. #[derive(Deserialize, Serialize, Debug, Clone)] pub struct TsigRemoveResult; +/// An error result indicating why an attempt to remove a TSIG key from +/// Cascade failed. #[derive(Deserialize, Serialize, Debug, Clone)] pub enum TsigRemoveError { + /// The specified TSIG key name was not found in Cascade. NotFound, + + /// The specified TSIG key cannot be removed as it is in use. InUse, } @@ -241,16 +259,24 @@ impl fmt::Display for TsigRemoveError { } } +//------------ TsigListResult ------------------------------------------------ + +/// The successful result of listing TSIG Cascade keys known to Cascade. #[derive(Deserialize, Serialize, Debug, Clone)] pub struct TsigListResult { + /// The set of TSIG keys known to Cascade plus information about each key. pub tsig_keys: HashMap, } +/// Information about a single listed TSIG key. #[derive(Deserialize, Serialize, Debug, Clone)] pub struct TsigListResultItem { + /// The set of zones with which this TSIG key is used. pub zones: Vec, } +//----------- ZoneAdd -------------------------------------------------------- + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct ZoneAdd { pub name: ZoneName, diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs index 314ffeb25..f1ffad67e 100644 --- a/crates/cli/src/commands/tsig.rs +++ b/crates/cli/src/commands/tsig.rs @@ -7,39 +7,6 @@ use cascade_api::{ use crate::client::CascadeApiClient; use crate::println; -#[derive(Clone, Debug, clap::ValueEnum)] -pub enum TsigAlgorithm { - HmacSha1, - HmacSha256, - HmacSha384, - HmacSha512, -} - -impl FromStr for TsigAlgorithm { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "hmac-sha1" => Ok(TsigAlgorithm::HmacSha1), - "hmac-sha256" => Ok(TsigAlgorithm::HmacSha256), - "hmac-sha384" => Ok(TsigAlgorithm::HmacSha384), - "hmac-sha512" => Ok(TsigAlgorithm::HmacSha512), - other => Err(format!("'{other}' is not a recognized TSIG algorithm")), - } - } -} - -impl From for crate::api::TsigAlgorithm { - fn from(alg: TsigAlgorithm) -> Self { - match alg { - TsigAlgorithm::HmacSha1 => cascade_api::TsigAlgorithm::Sha1, - TsigAlgorithm::HmacSha256 => cascade_api::TsigAlgorithm::Sha256, - TsigAlgorithm::HmacSha384 => cascade_api::TsigAlgorithm::Sha384, - TsigAlgorithm::HmacSha512 => cascade_api::TsigAlgorithm::Sha512, - } - } -} - #[derive(Clone, Debug, clap::Args)] pub struct Tsig { #[command(subcommand)] @@ -49,6 +16,7 @@ pub struct Tsig { #[derive(Clone, Debug, clap::Subcommand)] #[allow(clippy::large_enum_variant)] pub enum TsigCommand { + /// Add a TSIG key to Cascade. #[command(name = "add")] Add { /// The name of the TSIG key to add. @@ -71,9 +39,11 @@ pub enum TsigCommand { secret: Option, }, + /// Remove a TSIG key from Cascade. #[command(name = "remove")] Remove { name: TsigKeyName }, + /// List the TSIG keys known to Cascade. #[command(name = "list")] List, } @@ -81,11 +51,17 @@ pub enum TsigCommand { impl Tsig { pub async fn execute(self, client: CascadeApiClient) -> Result<(), String> { match self.command { + // Add a TSIG key to Cascade. TsigCommand::Add { name, alg, secret } => { let (name, alg, secret) = match (alg, secret) { + // No separate algorithm or secret argument values + // were provided, instead they must be extracted + // from the name string which should be in the form + // [algorithm]:keyname:secret. (None, None) => { let parts: Vec<&str> = name.split(':').collect(); match parts.as_slice() { + // The algorithm was provided. [alg_part, name_part, secret_part] => { let alg = TsigAlgorithm::from_str(alg_part)?; let name = name_part.to_string(); @@ -93,6 +69,7 @@ impl Tsig { (name, alg, secret) } + // The algorithm was not provided, use the default. [name_part, secret_part] => { let alg = TsigAlgorithm::HmacSha256; let name = name_part.to_string(); @@ -100,6 +77,7 @@ impl Tsig { (name, alg, secret) } + // The name value was not in the expected format. _ => { return Err( "Invalid TSIG key format, should be: [algorithm]:keyname:secret" @@ -109,14 +87,21 @@ impl Tsig { } } + // Separate name, algorithm and secret argument values + // were provided. (Some(alg), Some(secret)) => (name, alg, secret), + // An unsupported combination of arguments was provided + // but this should not be possible due to the Clap + // attributes that we used. _ => unreachable!("Excluded via Clap 'requires' rules"), }; + // Parse the TSIG key name as a domain name. let tsig_key_name = TsigKeyName::from_str(&name) .map_err(|err| format!("Invalid TSIG key name: {err}"))?; + // Send a TSIG add message to the Cascade HTTP API. let res: Result = client .post_json_with( "tsig/add", @@ -128,14 +113,20 @@ impl Tsig { ) .await?; + // Handle the API command result. match res { + // Success, the key was added! Ok(TsigAddResult) => { println!("Added TSIG key '{name}'"); Ok(()) } + + // Failure, something went wrong. Err(err) => Err(format!("Failed to add TSIG key '{name}': {err}")), } } + + // Remove a TSIG key (if possible). TsigCommand::Remove { name } => { let res: Result = client.post_json(&format!("tsig/{name}/remove")).await?; @@ -148,10 +139,14 @@ impl Tsig { Err(e) => Err(format!("Failed to remove TSIG key: {e}")), } } + + // List the set of TSIG keys known to Cascade. TsigCommand::List => { let response: TsigListResult = client.get_json("tsig/").await?; for (tsig_key_name, key_info) in response.tsig_keys { + // For each TSIG key also list the zones that it is used + // with. let zones = key_info .zones .iter() @@ -171,3 +166,39 @@ impl Tsig { } } } + +//------------ TsigAlgorithm ------------------------------------------------- + +/// The TSIG key algorithms supported by Cascade. +#[derive(Clone, Debug, clap::ValueEnum)] +pub enum TsigAlgorithm { + HmacSha1, + HmacSha256, + HmacSha384, + HmacSha512, +} + +impl FromStr for TsigAlgorithm { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "hmac-sha1" => Ok(TsigAlgorithm::HmacSha1), + "hmac-sha256" => Ok(TsigAlgorithm::HmacSha256), + "hmac-sha384" => Ok(TsigAlgorithm::HmacSha384), + "hmac-sha512" => Ok(TsigAlgorithm::HmacSha512), + other => Err(format!("'{other}' is not a recognized TSIG algorithm")), + } + } +} + +impl From for crate::api::TsigAlgorithm { + fn from(alg: TsigAlgorithm) -> Self { + match alg { + TsigAlgorithm::HmacSha1 => cascade_api::TsigAlgorithm::Sha1, + TsigAlgorithm::HmacSha256 => cascade_api::TsigAlgorithm::Sha256, + TsigAlgorithm::HmacSha384 => cascade_api::TsigAlgorithm::Sha384, + TsigAlgorithm::HmacSha512 => cascade_api::TsigAlgorithm::Sha512, + } + } +} From 855e6ce34b53150508125b57b01f60bcee5e919d Mon Sep 17 00:00:00 2001 From: ximon18 <3304436+ximon18@users.noreply.github.com> Date: Fri, 17 Apr 2026 11:42:11 +0200 Subject: [PATCH 030/113] Make tsig subcommand help text more consistent with the terminology used by the zone subcommand. --- crates/cli/src/commands/tsig.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs index f1ffad67e..a53487b1b 100644 --- a/crates/cli/src/commands/tsig.rs +++ b/crates/cli/src/commands/tsig.rs @@ -16,10 +16,10 @@ pub struct Tsig { #[derive(Clone, Debug, clap::Subcommand)] #[allow(clippy::large_enum_variant)] pub enum TsigCommand { - /// Add a TSIG key to Cascade. + /// Register a TSIG key #[command(name = "add")] Add { - /// The name of the TSIG key to add. + /// The name of the TSIG key to register. /// /// Can also be in the form `[algorithm]:keyname:secret`. name: String, @@ -31,7 +31,7 @@ pub enum TsigCommand { #[arg(requires = "secret")] alg: Option, - /// Base64 encoded secret key bytes. + /// The secret key material in base64 encoded form. /// /// Can be omitted if provided as part of the name. /// Required if `[ALG]` is provided. @@ -39,11 +39,11 @@ pub enum TsigCommand { secret: Option, }, - /// Remove a TSIG key from Cascade. + /// Remove a TSIG key #[command(name = "remove")] Remove { name: TsigKeyName }, - /// List the TSIG keys known to Cascade. + /// List registered TSIG keys #[command(name = "list")] List, } @@ -126,7 +126,7 @@ impl Tsig { } } - // Remove a TSIG key (if possible). + // Remove a TSIG key (if possible). TsigCommand::Remove { name } => { let res: Result = client.post_json(&format!("tsig/{name}/remove")).await?; From 3756c89652d8c26a58ddd9d505ed9f2f80c0d544 Mon Sep 17 00:00:00 2001 From: ximon18 <3304436+ximon18@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:22:16 +0200 Subject: [PATCH 031/113] Report the set of supported TSIG algorithms in the CLI detailed help. --- crates/cli/src/commands/tsig.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs index a53487b1b..60b872775 100644 --- a/crates/cli/src/commands/tsig.rs +++ b/crates/cli/src/commands/tsig.rs @@ -28,6 +28,12 @@ pub enum TsigCommand { /// /// Can be omitted if provided as part of the name. /// Required if `[SECRET]` is provided. + /// + /// Must be one of: + /// hmac-sha1 + /// hmac-sha256 + /// hmac-sha384 + /// hmac-sha512 #[arg(requires = "secret")] alg: Option, From 78b1db035e2c41b0f1e5671c4a38e3a4cfcb605a Mon Sep 17 00:00:00 2001 From: ximon18 <3304436+ximon18@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:22:47 +0200 Subject: [PATCH 032/113] Make tsig list output closer in style to the new zone status CLI output format. --- crates/cli/src/commands/tsig.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs index 60b872775..5d008a52a 100644 --- a/crates/cli/src/commands/tsig.rs +++ b/crates/cli/src/commands/tsig.rs @@ -160,13 +160,15 @@ impl Tsig { .collect::>() .join(", "); - print!("{tsig_key_name}: used by zones: "); + println!("{tsig_key_name}"); + print!(" zones: "); if !zones.is_empty() { println!("{zones}"); } else { println!("none"); } } + Ok(()) } } From f683960dbf519d4b5cb2c22e286e8c8b0db6f5a6 Mon Sep 17 00:00:00 2001 From: ximon18 <3304436+ximon18@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:23:05 +0200 Subject: [PATCH 033/113] Make error message less confusing. --- crates/cli/src/commands/tsig.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs index 5d008a52a..d85b8ee00 100644 --- a/crates/cli/src/commands/tsig.rs +++ b/crates/cli/src/commands/tsig.rs @@ -195,7 +195,7 @@ impl FromStr for TsigAlgorithm { "hmac-sha256" => Ok(TsigAlgorithm::HmacSha256), "hmac-sha384" => Ok(TsigAlgorithm::HmacSha384), "hmac-sha512" => Ok(TsigAlgorithm::HmacSha512), - other => Err(format!("'{other}' is not a recognized TSIG algorithm")), + other => Err(format!("'{other}' is not a supported TSIG algorithm")), } } } From 04cd2474dadd79e3f980f5f7051492862886d472 Mon Sep 17 00:00:00 2001 From: ximon18 <3304436+ximon18@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:43:03 +0200 Subject: [PATCH 034/113] Man page typo correction and remove pondered addition. --- doc/manual/source/man/cascade-zone.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/manual/source/man/cascade-zone.rst b/doc/manual/source/man/cascade-zone.rst index 19bf284a6..1a476af21 100644 --- a/doc/manual/source/man/cascade-zone.rst +++ b/doc/manual/source/man/cascade-zone.rst @@ -42,13 +42,13 @@ Commands .. subcmd:: add - Register a new zone. The zone will be loaded, signed and published. + Register a new zone. .. subcmd:: remove Remove a zone. - .. note:: Once removed downstream servers will no longer be able to fetech + .. note:: Once removed downstream servers will no longer be able to fetch the zone! .. subcmd:: list From 16d2619ebde05838e97ab45930e73dd0fb09b3e7 Mon Sep 17 00:00:00 2001 From: ximon18 <3304436+ximon18@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:50:22 +0200 Subject: [PATCH 035/113] cascade zone add man page text improvements. --- doc/manual/source/man/cascade-zone.rst | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/doc/manual/source/man/cascade-zone.rst b/doc/manual/source/man/cascade-zone.rst index 1a476af21..034d51926 100644 --- a/doc/manual/source/man/cascade-zone.rst +++ b/doc/manual/source/man/cascade-zone.rst @@ -92,19 +92,21 @@ Options for :subcmd:`zone add` or without port, defaults to port 53) or the path to a zone file locally available to the ``cascaded`` daemon.` - When specifying an upstream nameserver you may also optionally suffix it - with ``^`` to indicate that the specified RFC 8945 TSIG key - should be used to sign any SOA, AXFR and IXFR queries that will be sent to - the upstream source. + When specifying an upstream nameserver you may also optionally suffix it with + ``^`` to indicate that the specified RFC 8945 TSIG key should + be used to sign any SOA, AXFR and IXFR queries that will be sent to the + upstream source. Zones sourced from an upstream nameserver will be automatically updated if a new version is detected. This can happen if the upstream nameserver sends - an RFC 1996 NOTIFY message to Cascade, or if an IXFR or SOA query (if the - upstream responds with NOTIMP to an IXFR request) sent by Cascade (due to a - SOA timer expiring) discovers that a newer SOA SERIAL is available, or due - to an operator issuing a `zone reload` command. For zones that have already - been retrieved at least once via AXFR, subsequent refreshes will attempt to - use IXFR and fallback to AXFR if IXFR is not available. + an RFC 1996 NOTIFY message to Cascade, or if a via an IXFR or SOA query it + is discovered that the SOA SERIAL at the upstream nameserver is "higher" [1] + than the last version that Cascade loaded, or due to an operator issuing a + `zone reload` command. + + For zones that have already been retrieved at least once via AXFR, subsequent + refreshes will attempt to use IXFR and fallback to AXFR if IXFR is not + available. .. note:: When providing the path to a zone file to load, if :subcmd:`zone add` is executed on a different host than where the ``cascaded`` @@ -113,6 +115,8 @@ Options for :subcmd:`zone add` .. note:: Note: In order to use a TSIG key you MUST also supply the key to Cascade via :subcmd:`tsig add`. + [1]: https://www.rfc-editor.org/rfc/rfc1982#section-3.2 + .. option:: --policy Policy to use for this zone. From deb0a2151493174994c68c685f8bfc6b22e167a6 Mon Sep 17 00:00:00 2001 From: ximon18 <3304436+ximon18@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:52:43 +0200 Subject: [PATCH 036/113] Minor cascade-tsig man page improvement. --- doc/manual/source/man/cascade-tsig.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index f446d23f8..43509add9 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -17,7 +17,8 @@ Synopsis Description ----------- -Manage RFC 8945 TSIG keys for authenticating zone transfers. +Manage RFC 8945 TSIG keys for authenticating zone transfer (AXFR, IXFR) and +related messages (SOA and NOTIFY). .. tip:: Cascade isn't currently able to generate TSIG keys itself. One way to generate a TSIG key is to use the `tsig-keygen From e790b75c62a0f3d7b9b113ce6de8e9b47ea1efdc Mon Sep 17 00:00:00 2001 From: ximon18 <3304436+ximon18@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:59:56 +0200 Subject: [PATCH 037/113] Rebuild man pages. --- .../_sphinx_javascript_frameworks_compat.js | 123 ++++++++++++++++++ doc/manual/build/man/_static/jquery.js | 2 + doc/manual/build/man/cascade-debug.1 | 2 +- doc/manual/build/man/cascade-health.1 | 2 +- doc/manual/build/man/cascade-hsm.1 | 2 +- doc/manual/build/man/cascade-keyset.1 | 2 +- doc/manual/build/man/cascade-policy.1 | 2 +- doc/manual/build/man/cascade-status.1 | 2 +- doc/manual/build/man/cascade-template.1 | 2 +- doc/manual/build/man/cascade-tsig.1 | 7 +- doc/manual/build/man/cascade-zone.1 | 30 +++-- doc/manual/build/man/cascade.1 | 2 +- doc/manual/build/man/cascaded-config.toml.5 | 2 +- doc/manual/build/man/cascaded-policy.toml.5 | 2 +- doc/manual/build/man/cascaded.1 | 2 +- 15 files changed, 157 insertions(+), 27 deletions(-) create mode 100644 doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js create mode 100644 doc/manual/build/man/_static/jquery.js diff --git a/doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js b/doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 000000000..81415803e --- /dev/null +++ b/doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/doc/manual/build/man/_static/jquery.js b/doc/manual/build/man/_static/jquery.js new file mode 100644 index 000000000..c4c6022f2 --- /dev/null +++ b/doc/manual/build/man/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0\fP .SH DESCRIPTION .sp -Manage RFC 8945 TSIG keys for authenticating zone transfers. +Manage RFC 8945 TSIG keys for authenticating zone transfer (AXFR, IXFR) and +related messages (SOA and NOTIFY). .sp \fBTIP:\fP .INDENT 0.0 @@ -70,7 +71,7 @@ List registered TSIG keys and the zones that use them. .UNINDENT .INDENT 0.0 .TP -.B add +.B remove Remove a registered TSIG key. .sp \fBNOTE:\fP diff --git a/doc/manual/build/man/cascade-zone.1 b/doc/manual/build/man/cascade-zone.1 index d1e2caa47..ee5e12175 100644 --- a/doc/manual/build/man/cascade-zone.1 +++ b/doc/manual/build/man/cascade-zone.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-ZONE" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-ZONE" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-zone \- Manage zones .SH SYNOPSIS @@ -64,7 +64,7 @@ command. .INDENT 0.0 .TP .B add -Register a new zone. The zone will be loaded, signed and published. +Register a new zone. .UNINDENT .INDENT 0.0 .TP @@ -74,7 +74,7 @@ Remove a zone. \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 -Once removed downstream servers will no longer be able to fetech +Once removed downstream servers will no longer be able to fetch the zone! .UNINDENT .UNINDENT @@ -127,19 +127,21 @@ The zone source can be the IP address of an upstream nameserver (with or without port, defaults to port 53) or the path to a zone file locally available to the \fBcascaded\fP daemon.\(ga .sp -When specifying an upstream nameserver you may also optionally suffix it -with \fB^\fP to indicate that the specified RFC 8945 TSIG key -should be used to sign any SOA, AXFR and IXFR queries that will be sent to -the upstream source. +When specifying an upstream nameserver you may also optionally suffix it with +\fB^\fP to indicate that the specified RFC 8945 TSIG key should +be used to sign any SOA, AXFR and IXFR queries that will be sent to the +upstream source. .sp Zones sourced from an upstream nameserver will be automatically updated if a new version is detected. This can happen if the upstream nameserver sends -an RFC 1996 NOTIFY message to Cascade, or if an IXFR or SOA query (if the -upstream responds with NOTIMP to an IXFR request) sent by Cascade (due to a -SOA timer expiring) discovers that a newer SOA SERIAL is available, or due -to an operator issuing a \fIzone reload\fP command. For zones that have already -been retrieved at least once via AXFR, subsequent refreshes will attempt to -use IXFR and fallback to AXFR if IXFR is not available. +an RFC 1996 NOTIFY message to Cascade, or if a via an IXFR or SOA query it +is discovered that the SOA SERIAL at the upstream nameserver is \(dqhigher\(dq [1] +than the last version that Cascade loaded, or due to an operator issuing a +\fIzone reload\fP command. +.sp +For zones that have already been retrieved at least once via AXFR, subsequent +refreshes will attempt to use IXFR and fallback to AXFR if IXFR is not +available. .sp \fBNOTE:\fP .INDENT 7.0 @@ -157,6 +159,8 @@ Note: In order to use a TSIG key you MUST also supply the key to Cascade via \fBtsig add\fP\&. .UNINDENT .UNINDENT +.sp +[1]: \X'tty: link https://www.rfc-editor.org/rfc/rfc1982#section-3.2'\fI\%https://www.rfc\-editor.org/rfc/rfc1982#section\-3.2\fP\X'tty: link' .UNINDENT .INDENT 0.0 .TP diff --git a/doc/manual/build/man/cascade.1 b/doc/manual/build/man/cascade.1 index ba905959c..f6e4db446 100644 --- a/doc/manual/build/man/cascade.1 +++ b/doc/manual/build/man/cascade.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade \- Cascade CLI .SH SYNOPSIS diff --git a/doc/manual/build/man/cascaded-config.toml.5 b/doc/manual/build/man/cascaded-config.toml.5 index c18e61275..0aa89e11c 100644 --- a/doc/manual/build/man/cascaded-config.toml.5 +++ b/doc/manual/build/man/cascaded-config.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-CONFIG.TOML" "5" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-CONFIG.TOML" "5" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-config.toml \- Cascade configuration file .sp diff --git a/doc/manual/build/man/cascaded-policy.toml.5 b/doc/manual/build/man/cascaded-policy.toml.5 index 989fee49d..3aa149021 100644 --- a/doc/manual/build/man/cascaded-policy.toml.5 +++ b/doc/manual/build/man/cascaded-policy.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-POLICY.TOML" "5" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-POLICY.TOML" "5" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-policy.toml \- Cascade policy file format .sp diff --git a/doc/manual/build/man/cascaded.1 b/doc/manual/build/man/cascaded.1 index b6fdaa3be..a46dcba00 100644 --- a/doc/manual/build/man/cascaded.1 +++ b/doc/manual/build/man/cascaded.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED" "1" "Apr 13, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded \- DNSSEC signer .SH SYNOPSIS From b1c599ccaa85949c658012208d3346fa228fc09d Mon Sep 17 00:00:00 2001 From: ximon18 <3304436+ximon18@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:00:29 +0200 Subject: [PATCH 038/113] Remove sphinx static files. --- .../_sphinx_javascript_frameworks_compat.js | 123 ------------------ doc/manual/build/man/_static/jquery.js | 2 - 2 files changed, 125 deletions(-) delete mode 100644 doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js delete mode 100644 doc/manual/build/man/_static/jquery.js diff --git a/doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js b/doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js deleted file mode 100644 index 81415803e..000000000 --- a/doc/manual/build/man/_static/_sphinx_javascript_frameworks_compat.js +++ /dev/null @@ -1,123 +0,0 @@ -/* Compatability shim for jQuery and underscores.js. - * - * Copyright Sphinx contributors - * Released under the two clause BSD licence - */ - -/** - * small helper function to urldecode strings - * - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL - */ -jQuery.urldecode = function(x) { - if (!x) { - return x - } - return decodeURIComponent(x.replace(/\+/g, ' ')); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} diff --git a/doc/manual/build/man/_static/jquery.js b/doc/manual/build/man/_static/jquery.js deleted file mode 100644 index c4c6022f2..000000000 --- a/doc/manual/build/man/_static/jquery.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 Date: Fri, 17 Apr 2026 16:12:06 +0200 Subject: [PATCH 039/113] Split large block of text into smaller more readable chunks. --- doc/manual/source/nsd.rst | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/doc/manual/source/nsd.rst b/doc/manual/source/nsd.rst index b8dd1a2a5..214aff75a 100644 --- a/doc/manual/source/nsd.rst +++ b/doc/manual/source/nsd.rst @@ -11,11 +11,21 @@ Using NSD as a primary to Cascade ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To use NSD as an upstream name server of Cascade you must add a zone to -NSD that refers to Cascade as a secondary name server so that NSD will send -a NOTIFY message to Cascade when the zone changes and will allow Cascade to -make an XFR request to receive the update zone. Optionally NSD and Cascade -can be configured with the same TSIG key to authenticate the NOTIFY and XFR -messages. +NSD that refers to Cascade as a secondary name server. If enabled in NSD it will send +Cascade a RFC 1996 DNS NOTIFY message to Cascade notifying when changes to the zone occur. + +The NOTIFY message will trigger Cascade to perform an AXFR transfer to fetch the full +zone content from NSD, or, ifalready fetched and IXFR is enabled in NSD, an IXFR transfer +will be performed to fetch just the incremental changes since the last fetch. +make an XFR request to receive the update zone. + +If NOTIFY is not enabled in NSD, Cascade will periodically check if a newer +version of the zone is available by sending a SOA query periodically according +to the number of seconds defined by the REFRESH field of the zone apex SOA +record. + +Optionally NSD and Cascade can be configured with the same TSIG key to +authenticate the NOTIFY and XFR messages. For example the following NSD configuration file fragment adds an example.com zone to NSD that is to be served as input to a Cascade daemon running on host From 41f50cd6c3a4902fd830a26d47ae2d5ef65d866c Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:50:00 +0200 Subject: [PATCH 040/113] More zone transfer documentation. --- doc/manual/source/index.rst | 1 + doc/manual/source/nsd.rst | 40 ++++++----- doc/manual/source/zone-transfers.rst | 99 ++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 doc/manual/source/zone-transfers.rst diff --git a/doc/manual/source/index.rst b/doc/manual/source/index.rst index be59ef875..5224a697e 100644 --- a/doc/manual/source/index.rst +++ b/doc/manual/source/index.rst @@ -100,6 +100,7 @@ Examples of things we're interested in: key-management hsms review-hooks + zone-transfers .. toctree:: :maxdepth: 2 diff --git a/doc/manual/source/nsd.rst b/doc/manual/source/nsd.rst index 214aff75a..35f1e8f6f 100644 --- a/doc/manual/source/nsd.rst +++ b/doc/manual/source/nsd.rst @@ -7,29 +7,39 @@ Integrating with NSD -- https://nsd.docs.nlnetlabs.nl/ +Suggested reading +~~~~~~~~~~~~~~~~~ + +The :ref:`Zone Transfers ` page explains the general +functionality in Cascade that is referred to below. + Using NSD as a primary to Cascade ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To use NSD as an upstream name server of Cascade you must add a zone to -NSD that refers to Cascade as a secondary name server. If enabled in NSD it will send -Cascade a RFC 1996 DNS NOTIFY message to Cascade notifying when changes to the zone occur. +To use NSD as an upstream name server of Cascade you must add a zone to NSD +that refers to Cascade as a secondary name server. If enabled in NSD, NSD will +send an RFC 1996 DNS NOTIFY message to Cascade notifying it when changes to +the zone occur. -The NOTIFY message will trigger Cascade to perform an AXFR transfer to fetch the full -zone content from NSD, or, ifalready fetched and IXFR is enabled in NSD, an IXFR transfer -will be performed to fetch just the incremental changes since the last fetch. -make an XFR request to receive the update zone. +The NOTIFY message will trigger Cascade to perform an AXFR transfer to fetch +the full zone content from NSD, or, if already fetched and IXFR is enabled in +NSD, an IXFR transfer will be performed to fetch just the incremental changes +since the last fetch. -If NOTIFY is not enabled in NSD, Cascade will periodically check if a newer -version of the zone is available by sending a SOA query periodically according -to the number of seconds defined by the REFRESH field of the zone apex SOA -record. +If NOTIFY is NOT enabled in NSD, Cascade will monitor NSD for a newer version +of the zone by periodically sending SOA queries according to the number of +seconds defined by the REFRESH field of the zone apex SOA record. Optionally NSD and Cascade can be configured with the same TSIG key to authenticate the NOTIFY and XFR messages. -For example the following NSD configuration file fragment adds an example.com -zone to NSD that is to be served as input to a Cascade daemon running on host -192.168.0.2 listening on the default port 4542: +The NSD settings relevant here are: + - `notify `_ + - `provide-xfr `_ + +For example the following NSD configuration file fragment adds an +``example.com`` zone to NSD that is to be served as input to a Cascade daemon +running on host 192.168.0.2 listening on the default port 4542: .. code-block:: @@ -72,7 +82,7 @@ command, e.g. like so: $ cascade tsig add --name sec1_key --alg hmac-sha256 --secret "...==" -To use the new TAIG key it must be specified when adding a zone to +To use the new TSIG key it must be specified when adding a zone to Cascade. Assuming that NSD is running on host 192.168.0.1 on port 53 the following command instructs Cascade to add the ``example.com`` zone sourced from the NSD server using the ``sec1_key`` TSIG key to diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst new file mode 100644 index 000000000..9017f27f5 --- /dev/null +++ b/doc/manual/source/zone-transfers.rst @@ -0,0 +1,99 @@ +Zone Transfers +============== + +Cascade is designed to be deployed between a hidden upstream nameserver and +public downstream nameservers. The hidden upstream serves the unsigned zone, +Cascade signs it and passes it to the downstream nameservers for publication +to consumers. + +Communication of changed zone records from upstream to downstream should be +done via the network using the RFC 5936 DNS Zone Transfer Protocol (AXFR) and +RFC 1995 Incremental Zone Transfer (IXFR) protocols. + +Securing the transferred data can be done using RFC 8945 Secret Key +Authentication for DNS (TSIG) keys, using a shared secret communicated out of +band to the nameservers sending and receiving the zone records. + +Cascade supports timely discovery of zone changes via RFC 1996 A Mechanism +for Prompt Notification of Zone Changes (DNS NOTIFY). If no NOTIFY message +is received by Cascade, Cascade will instead discover new versions of the +zone by sending SOA queries periodically to the upstream, the frequency of +which is determined by the timers on the SOA apex record in the zone. + +.. note:: Cascade also supports loading zone records from a file. However, + if only a small fraction of the records in the zone change from + one version to the next, loading the entire zone zone every time + the zone changes will require more time, CPU and memory compared + to processing only the differences when using IXFR. Additionally, + Cascade has no built-in support for writing signed zone files to + disk, if needed this could be done by a signed review hook. + +Using zone transfers with an upstream nameserver +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To instruct Cascade to transfer a zone via the network instead of loading +it from a file you must supply an upstream nameserver name or address when +adding the zone. + +.. code-block:: bash + + $ cascade zone add --source [:] ... + +Cascade will then attempt to fetch the zone from the specified name or IP +address using AXFR. Subsequent fetches will attempt to use IXFR to transfer +only the differences, falling back to AXFR when needed. Subsequent fetches +will be triggered by NOTIFY messages received from the upstream nameserver or +expiry of the SOA REFRESH or RETRY timers. + +Securing zone transfers with an upstream nameserver +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Cascade can be instructed to authenticate the upstream nameserver by use of +a TSIG key. The TSIG key to use must be provided to Cascade _before_ adding +the zone: + +.. code-block:: bash + + $ cascade tsig add + +When adding a zone the TSIG key name can then be referred to like so: + +.. code-block:: bash + + $ cascade zone add --source [:]^ + +Using zone transfers with a downstream server +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Cascade permits zone transfers by default, no configuration is required. + +To ensure timely update by secondaries Cascade can be configured to send +RFC 1996 DNS NOTIFY messages to specified secondaries. This is done via +the policy setting ``server.outbound.send-notify-to``. + +.. note:: The policy file will need to be reloaded via ``cascade policy + reload`` before adding the zone. Also, when adding the zone you + will need to pass the `--policy` argument specifying the relevant + policy to apply to the zone. + +.. tip:: If a TSIG key has been added to Cascade via ``cascade tsig add``, + you can instruct Cascade to authentiate itself to downstreams + using a specified TSIG key by adding `^` to the + ``server.outbound.send-notify-to`` value. + +Controlling automatic key rollover zone transfer settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using automatic key rollover (the default) Cascade will attempt to verify +that certain key properties of the signed zone being served to consumers are +correct. + +This verification is done by transferring the zone and inspecting it. By +default transfer is attempted from the nameserver identified by the MNAME +field of the apex SOA record in the zone. + +If an alternate nameserver should be queried instead of the MNAME +nameserver, or if a specific port number or TSIG key should be used +to request the transfer, you will also need to configure the Cascade +key manager to fetch the zone correctly. This can be done via the +``key-manager.publication-nameservers`` policy setting. From db29fd446214afb1afccb22ac063969feede1ac1 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:54:41 +0200 Subject: [PATCH 041/113] Add the new publication_nameservers field to the policy template. --- etc/policy.template.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/etc/policy.template.toml b/etc/policy.template.toml index 848de906b..70e67ae35 100644 --- a/etc/policy.template.toml +++ b/etc/policy.template.toml @@ -156,6 +156,17 @@ ds-algorithm = "SHA-256" # TODO: Perhaps support removing keys after a certain delay? auto-remove = true +# The upstream nameservers to use when checking for RRSIG propagation during +# a key roll. +# +# Each nameserver must be specifeid as a string in the form `":[][^]"`. +# +# If not specified, the nameserver specified by the SOA MNAME field will be +# checked. +# +# publication_nameservers = [] + # The management of DNS records by the key manager. # # The key manager generates and signs several records (DNSKEY and CDS). This From 49474f7989b0ab1c3c01dc1ce6b0f77d3c090ae3 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:19:32 +0200 Subject: [PATCH 042/113] Check the mid deletion flag when looking for policies using a TSIG key. --- src/tsig/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index 0c4a2cc03..d7d625b1c 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -288,9 +288,11 @@ pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), Remo return Err(RemoveError::Used); } + // Is the TSIG key referenced by any active (not being deleted) policy? if state .policies .values() + .filter(|p| !p.mid_deletion) .flat_map(|p| &p.latest.key_manager.publication_nameservers) .filter_map(|n| n.split_once("^").map(|v| v.1)) .any(|k| { From 6426c9ab9ca5aa7e512e7b862201a0f4aee49b55 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:19:41 +0200 Subject: [PATCH 043/113] Remove outdated comment. --- src/tsig/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index d7d625b1c..89475f11a 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -272,10 +272,6 @@ pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), Remo // do XFR. // // So we need to check all of these places to see if a key is in use. - // - // Alternatively we would need to update the TSIG key store to track - // if (and where?) a key is being used and check with the TSIG key - // store. if !state.tsig_store.map.contains_key(name) { return Err(RemoveError::NotFound); From 6fbf719e024de4d1c2bd4969bdd2079ed127f25f Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:19:46 +0200 Subject: [PATCH 044/113] Fix formatting. --- src/tsig/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index 89475f11a..7c40b41e3 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -278,11 +278,11 @@ pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), Remo } if state.zones.iter().any(|z| { - let zone_state = z.0.state.lock().unwrap(); - matches!(zone_state.loader.source, crate::loader::Source::Server { tsig_key: Some(ref key), .. } if name == key.name()) - }) { - return Err(RemoveError::Used); - } + let zone_state = z.0.state.lock().unwrap(); + matches!(zone_state.loader.source, crate::loader::Source::Server { tsig_key: Some(ref key), .. } if name == key.name()) + }) { + return Err(RemoveError::Used); + } // Is the TSIG key referenced by any active (not being deleted) policy? if state From e69c452fa4b3c18c45f5281b0792a08bf5c0795a Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:20:06 +0200 Subject: [PATCH 045/113] Additional comments and whitespace. --- src/tsig/mod.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index 7c40b41e3..d582f43bd 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -296,8 +296,17 @@ pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), Remo if let Ok(n) = nk { n == name } else { - // Just ignore tSIG key names that cannot be parsed as a - // DNS name. + // Just ignore TSIG key names that cannot be parsed as a + // DNS name. It should not have been possible to add them + // into the policy in the first place so this should never + // happen. + // + // TODO: "update" from using a String type for + // publication_nameservers to a type that actually uses + // TsigKeyName, like NameserverCommsPolicy does, so that at + // this point we don't need to care about valid or invalid + // TSIG key names? That would also have the benefit of not + // having to parse the nameserver string format here again. false } }) @@ -305,6 +314,13 @@ pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), Remo return Err(RemoveError::Used); } + // Delete the TSIG key. The TSIG key store has a set of zones that + // refer to the key to avoid having to lock and inspect zone state, + // so we can also find that the TSIG key is still referenced there + // if an operation to remove a zone hasn't cleaned up the reference + // to the zone in the TSIG store yet (even though its source no + // longer refers to it in the check we did above - can this ever + // happen?). match state.tsig_store.map.entry(name.clone()) { hash_map::Entry::Occupied(entry) => { if !entry.get().zones.is_empty() { @@ -314,7 +330,9 @@ pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), Remo } hash_map::Entry::Vacant(_) => return Err(RemoveError::NotFound), } + state.tsig_store.mark_dirty(center); + Ok(()) } From bb462b34768e62ff24979e607ded4cf395f28aed Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:20:16 +0200 Subject: [PATCH 046/113] Additional comment. --- src/tsig/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index d582f43bd..0592827eb 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -277,6 +277,7 @@ pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), Remo return Err(RemoveError::NotFound); } + // Is the TSIG key in use with a zone source? if state.zones.iter().any(|z| { let zone_state = z.0.state.lock().unwrap(); matches!(zone_state.loader.source, crate::loader::Source::Server { tsig_key: Some(ref key), .. } if name == key.name()) From 3bf9f91979a02d5405f8b123c1c0655e4c1e2efc Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:30:56 +0200 Subject: [PATCH 047/113] Use domain::dep::octseq to avoid dependency mismatch. --- src/center.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/center.rs b/src/center.rs index 32f430dff..6c5d3d9c3 100644 --- a/src/center.rs +++ b/src/center.rs @@ -253,7 +253,7 @@ pub fn get_zone(center: &Arc
, name: &Name) -> Option> { pub async fn add_tsig_key( center: &Arc
, - name: Name>, + name: Name>, alg: domain::tsig::Algorithm, secret: &[u8], ) -> Result { From c3381210351d330009c0978bc147c1a63bdf46e2 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:31:22 +0200 Subject: [PATCH 048/113] Remove addition of downstream specific logic from upstream PR. --- crates/api/src/lib.rs | 12 +---- src/policy/file/v1.rs | 65 ++++++-------------------- src/policy/mod.rs | 6 +-- src/server/service.rs | 99 ---------------------------------------- src/units/http_server.rs | 10 +--- src/units/zone_server.rs | 8 +++- 6 files changed, 25 insertions(+), 175 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index aae312614..e6ed33de2 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -4,7 +4,6 @@ use std::net::{IpAddr, SocketAddr}; use std::time::{Duration, SystemTime}; use camino::{Utf8Path, Utf8PathBuf}; -use domain::tsig::KeyName; use serde::{Deserialize, Serialize}; pub use domain::base::Serial; @@ -878,19 +877,12 @@ pub struct OutboundPolicyInfo { #[derive(Deserialize, Serialize, Debug, Clone)] pub struct NameserverCommsPolicyInfo { - pub addr: Option, - pub tsig_key_name: Option, + pub addr: SocketAddr, } impl std::fmt::Display for NameserverCommsPolicyInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(addr) = self.addr { - write!(f, "{addr}")?; - } - if let Some(tsig_key_name) = &self.tsig_key_name { - write!(f, "^{tsig_key_name}")?; - } - Ok(()) + write!(f, "{}", self.addr) } } diff --git a/src/policy/file/v1.rs b/src/policy/file/v1.rs index c89520c81..64c37f357 100644 --- a/src/policy/file/v1.rs +++ b/src/policy/file/v1.rs @@ -2,11 +2,10 @@ use std::{ fmt::{self, Display}, - net::{IpAddr, SocketAddr}, + net::{AddrParseError, IpAddr, SocketAddr}, str::FromStr, }; -use domain::tsig::KeyName; use serde::{ Deserialize, Serialize, de::{self, Visitor}, @@ -913,11 +912,8 @@ pub enum NameserverCommsSpec { #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct ComplexNameserverCommsSpec { - /// The address to send NOTIFYs to and allow XFRs from. - pub addr: Option, - - /// An optional TSIG key to authenticate messages with. - pub tsig_key_name: Option, + /// The address to send NOTIFYs to. + pub addr: SocketAddr, } /// Policy for communicating with another namesever. @@ -925,10 +921,7 @@ pub struct ComplexNameserverCommsSpec { #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct SimpleNameserverCommsSpec { /// The address to send NOTIFYs to. - pub addr: Option, - - /// An optional TSIG key to authenticate messages with. - pub tsig_key_name: Option, + pub addr: SocketAddr, } //--- Conversion @@ -944,65 +937,33 @@ impl NameserverCommsSpec { /// Build into this specification. pub fn build(policy: &NameserverCommsPolicy) -> Self { - Self::Complex(ComplexNameserverCommsSpec { - addr: policy.addr, - tsig_key_name: policy.tsig_key_name.clone(), - }) + Self::Complex(ComplexNameserverCommsSpec { addr: policy.addr }) } } impl SimpleNameserverCommsSpec { /// Parse from this specification. pub fn parse(self) -> NameserverCommsPolicy { - NameserverCommsPolicy { - addr: self.addr, - tsig_key_name: self.tsig_key_name, - } + NameserverCommsPolicy { addr: self.addr } } } impl ComplexNameserverCommsSpec { /// Parse from this specification. pub fn parse(self) -> NameserverCommsPolicy { - NameserverCommsPolicy { - addr: self.addr, - tsig_key_name: self.tsig_key_name, - } + NameserverCommsPolicy { addr: self.addr } } } -/// Parse`[[:]][^]` +/// Parse`[:]` impl FromStr for SimpleNameserverCommsSpec { - type Err = String; + type Err = AddrParseError; fn from_str(s: &str) -> Result { - let (s, tsig_key_name) = s - .split_once('^') - .map(|(s, tsig_key_name)| (s, Some(tsig_key_name))) - .unwrap_or((s, None)); - - let addr = if s.is_empty() { - None - } else { - Some( - IpAddr::from_str(s) - .map(|ip| SocketAddr::new(ip, 53)) - .or_else(|_| SocketAddr::from_str(s)) - .map_err(|err| format!("Invalid IP address '{s}': {err}"))?, - ) - }; - - let tsig_key_name = tsig_key_name - .map(|name| { - KeyName::from_str(name) - .map_err(|err| format!("Invalid TSIG key name '{name}: {err}")) - }) - .transpose()?; - - Ok(SimpleNameserverCommsSpec { - addr, - tsig_key_name, - }) + let addr = IpAddr::from_str(s) + .map(|ip| SocketAddr::new(ip, 53)) + .or_else(|_| SocketAddr::from_str(s))?; + Ok(SimpleNameserverCommsSpec { addr }) } } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index bef69ee27..e02b29877 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -9,7 +9,6 @@ use bytes::Bytes; use camino::Utf8PathBuf; use domain::base::Name; use domain::base::Ttl; -use domain::tsig::KeyName; use serde::{Deserialize, Serialize}; use tracing::{debug, error, info, warn}; @@ -476,10 +475,7 @@ pub struct NameserverCommsPolicy { /// For sending the port MUST NOT be zero. /// /// TODO: Support IP prefixes? - pub addr: Option, - - /// An optional TSIG key to authenticate messages with. - pub tsig_key_name: Option, + pub addr: SocketAddr, } //----------- KeyParameters --------------------------------------------------- diff --git a/src/server/service.rs b/src/server/service.rs index 78ef7efc3..8e82a6a1d 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -84,7 +84,6 @@ mod compat { }; use futures::Stream; - use tracing::{debug, trace}; use crate::server::request::{RequestKind, ZoneRequestKind}; @@ -130,13 +129,6 @@ mod compat { return Box::pin(std::future::ready(error(old_request.message(), rcode))); }; - if !is_permitted(&old_request, zone) { - return Box::pin(std::future::ready(error( - old_request.message(), - Rcode::REFUSED, - ))); - } - match zone_request.kind { ZoneRequestKind::Soa => Box::pin({ let viewer = zone.viewer.clone(); @@ -161,97 +153,6 @@ mod compat { } } - /// Enforce any defined access controls for the zone being requested. - fn is_permitted( - request: &Request, Option>>, - zone: &ServedZone, - ) -> bool - where - V: Viewer + Send + Sync + 'static, - { - // IP address/CIDR based access control: - // TODO - - // TSIG access control: - // If the request was TSIG authenticated it would only be passed to - // our service if the TsigMiddlewareSvc verified that the key used - // exists in our key store, so that it can sign the response that we - // generate. - // - // However, having used a known TSIG key is not enough, the key used - // must also have been permitted by the operator for the zone being - // queried. - // - // The TSIG key(s) permitted to sign a zone are defined in policy - // so we have to look that up. - let zone_state = zone.handle.state.lock().unwrap(); - - let policy = zone_state - .policy - .as_ref() - .expect("A zone must always have an associated policy"); - - let outbound_policy = &policy.server.outbound; - - debug!( - "Checking access to zone {} from {} with TSIG key {:?} due to policy '{}'", - zone.handle.name, - request.client_addr(), - request.metadata(), - policy.name - ); - trace!("Policy: {policy:?}"); - - // If no rules are defined, allow the request. - if outbound_policy.accept_xfr_requests_from.is_empty() { - return true; - } - - // At least one access control rule must match in order for the - // request to be accepted. - - for rule in &outbound_policy.accept_xfr_requests_from { - match (rule.addr, &rule.tsig_key_name, request.metadata()) { - // Allow all - (None, None, None) => return true, - - // TSIG key match required - (None, Some(wanted_key), Some(actual_key)) if wanted_key == actual_key.name() => { - // Allow the request. - return true; - } - - // IP address match required - (Some(wanted_addr), None, None) if wanted_addr == request.client_addr() => { - // Allow the request. - return true; - } - - // IP address *and* TSIG key match required - (Some(wanted_addr), Some(wanted_key), Some(actual_key)) - if wanted_addr == request.client_addr() && wanted_key == actual_key.name() => - { - // Allow the request. - return true; - } - - _ => { - // Rule not matched, see if another rule matches - } - } - } - - // Deny the request. - debug!( - "Denying request to zone {} from {} with TSIG key {:?} due to policy '{}'", - zone.handle.name, - request.client_addr(), - request.metadata(), - policy.name - ); - false - } - fn soa(request: &Message>, viewer: &V) -> ResponseStream { if viewer.is_empty() { // The zone is known to exist, but we don't have any data for it. diff --git a/src/units/http_server.rs b/src/units/http_server.rs index ffd2f5cae..56d5263cf 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -918,18 +918,12 @@ impl HttpServer { accept_xfr_requests_from: p_outbound .accept_xfr_requests_from .iter() - .map(|v| NameserverCommsPolicyInfo { - addr: v.addr, - tsig_key_name: v.tsig_key_name.clone(), - }) + .map(|v| NameserverCommsPolicyInfo { addr: v.addr }) .collect(), send_notify_to: p_outbound .send_notify_to .iter() - .map(|v| NameserverCommsPolicyInfo { - addr: v.addr, - tsig_key_name: v.tsig_key_name.clone(), - }) + .map(|v| NameserverCommsPolicyInfo { addr: v.addr }) .collect(), }, }; diff --git a/src/units/zone_server.rs b/src/units/zone_server.rs index 1bf7cee76..779aed87d 100644 --- a/src/units/zone_server.rs +++ b/src/units/zone_server.rs @@ -243,7 +243,13 @@ impl ZoneServer { .outbound .send_notify_to .iter() - .filter_map(|s| s.addr.filter(|addr| addr.port() != 0)); + .filter_map(|s| { + if s.addr.port() != 0 { + Some(s.addr) + } else { + None + } + }); send_notify_to_addrs(zone_name.clone(), addrs, center); } From f6a7a5c47fcf35ede71f950908f388c76baac4ed Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:44:59 +0200 Subject: [PATCH 049/113] Remove errant linebreak. --- src/server/service.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/service.rs b/src/server/service.rs index 8e82a6a1d..b6faecf37 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -82,7 +82,6 @@ mod compat { new::base::wire::ParseBytesZC, tsig, }; - use futures::Stream; use crate::server::request::{RequestKind, ZoneRequestKind}; From 021545e5177b1acd48718c38a9c56bd002885b4a Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:46:38 +0200 Subject: [PATCH 050/113] Remove commented out line. --- src/units/http_server.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 56d5263cf..209397476 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -106,7 +106,6 @@ impl HttpServer { .route("/tsig/", get(Self::tsig_key_list)) .route("/tsig/add", post(Self::tsig_key_add)) .route("/tsig/{name}/remove", post(Self::tsig_key_remove)) - // .route("/tsig/{name}/status", get(Self::tsig_key_status)) .route("/zone/", get(Self::zones_list)) .route("/zone/add", post(Self::zone_add)) // TODO: .route("/zone/{name}/", get(Self::zone_get)) From ad2b4926ead0cbd81168a1ee3b7c0d53e8c53333 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:54:06 +0200 Subject: [PATCH 051/113] Impl From for Algorithm. --- crates/api/src/lib.rs | 12 ++++++++++++ src/units/http_server.rs | 10 ++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index e6ed33de2..27fb202f0 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -4,6 +4,7 @@ use std::net::{IpAddr, SocketAddr}; use std::time::{Duration, SystemTime}; use camino::{Utf8Path, Utf8PathBuf}; +use domain::tsig::Algorithm; use serde::{Deserialize, Serialize}; pub use domain::base::Serial; @@ -626,6 +627,17 @@ impl Display for TsigAlgorithm { } } +impl From for Algorithm { + fn from(alg: TsigAlgorithm) -> Self { + match alg { + TsigAlgorithm::Sha1 => Algorithm::Sha1, + TsigAlgorithm::Sha256 => Algorithm::Sha256, + TsigAlgorithm::Sha384 => Algorithm::Sha384, + TsigAlgorithm::Sha512 => Algorithm::Sha512, + } + } +} + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct ZoneHistory { pub history: Vec, diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 209397476..6dd6cbfc7 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -1110,14 +1110,8 @@ impl HttpServer { return Json(Err(TsigAddError::InvalidBase64Secret)); }; - let alg = match tsig_add.alg { - TsigAlgorithm::Sha1 => Algorithm::Sha1, - TsigAlgorithm::Sha256 => Algorithm::Sha256, - TsigAlgorithm::Sha384 => Algorithm::Sha384, - TsigAlgorithm::Sha512 => Algorithm::Sha512, - }; - - match center::add_tsig_key(&state.center, tsig_add.name, alg, &secret).await { + match center::add_tsig_key(&state.center, tsig_add.name, tsig_add.alg.into(), &secret).await + { Ok(TsigAddResult) => Json(Ok(TsigAddResult)), Err(err) => Json(Err(err)), } From eba31523a97610290c5437e9208c1ef82f864561 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:09:54 +0200 Subject: [PATCH 052/113] Remove unused import. --- src/units/http_server.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 6dd6cbfc7..bf6e34eae 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -18,7 +18,6 @@ use bytes::Bytes; use domain::base::Name; use domain::base::Serial; use domain::dnssec::sign::keys::keyset::KeyType; -use domain::tsig::Algorithm; use domain::utils::base64; use domain_kmip::ConnectionSettings; use domain_kmip::dep::kmip::client::pool::ConnectionManager; From abf9d924a0c02e72677ba624e46307d3cc5d0b1e Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:10:16 +0200 Subject: [PATCH 053/113] Make log output more human readable. --- src/loader/mod.rs | 16 ++++++++++++++++ src/loader/zone.rs | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/loader/mod.rs b/src/loader/mod.rs index a80c42d87..5c0bc1dbc 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -303,6 +303,22 @@ pub enum Source { }, } +impl std::fmt::Display for Source { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Source::None => f.write_str("none"), + Source::Zonefile { path } => write!(f, "zone file '{}'", path.as_std_path().display()), + Source::Server { addr, tsig_key } => { + write!(f, "{addr}")?; + if let Some(tsig_key) = &tsig_key { + write!(f, "^{}", tsig_key.name())?; + } + Ok(()) + } + } + } +} + //============ Metrics ========================================================= //----------- LoadMetrics ------------------------------------------------------ diff --git a/src/loader/zone.rs b/src/loader/zone.rs index 5988ed8be..df1c67c52 100644 --- a/src/loader/zone.rs +++ b/src/loader/zone.rs @@ -46,7 +46,7 @@ impl LoaderZoneHandle<'_> { /// A (soft) refresh will be initiated via [`Self::enqueue_refresh()`]. pub fn set_source(&mut self, source: Source) { info!( - "Setting source of zone '{}' from '{:?}' to '{source:?}'", + "Setting source of zone '{}' from '{}' to '{source}'", self.zone.name, self.state.loader.source ); From 787ad2a72082ce3db1b5fa7d1375f4c271fc9572 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:02:44 +0200 Subject: [PATCH 054/113] Revert "Remove addition of downstream specific logic from upstream PR." This reverts commit c3381210351d330009c0978bc147c1a63bdf46e2. --- crates/api/src/lib.rs | 12 ++++- src/policy/file/v1.rs | 65 ++++++++++++++++++++------ src/policy/mod.rs | 6 ++- src/server/service.rs | 99 ++++++++++++++++++++++++++++++++++++++++ src/units/http_server.rs | 10 +++- src/units/zone_server.rs | 8 +--- 6 files changed, 175 insertions(+), 25 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 27fb202f0..d4c9b9871 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -5,6 +5,7 @@ use std::time::{Duration, SystemTime}; use camino::{Utf8Path, Utf8PathBuf}; use domain::tsig::Algorithm; +use domain::tsig::KeyName; use serde::{Deserialize, Serialize}; pub use domain::base::Serial; @@ -889,12 +890,19 @@ pub struct OutboundPolicyInfo { #[derive(Deserialize, Serialize, Debug, Clone)] pub struct NameserverCommsPolicyInfo { - pub addr: SocketAddr, + pub addr: Option, + pub tsig_key_name: Option, } impl std::fmt::Display for NameserverCommsPolicyInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.addr) + if let Some(addr) = self.addr { + write!(f, "{addr}")?; + } + if let Some(tsig_key_name) = &self.tsig_key_name { + write!(f, "^{tsig_key_name}")?; + } + Ok(()) } } diff --git a/src/policy/file/v1.rs b/src/policy/file/v1.rs index 64c37f357..c89520c81 100644 --- a/src/policy/file/v1.rs +++ b/src/policy/file/v1.rs @@ -2,10 +2,11 @@ use std::{ fmt::{self, Display}, - net::{AddrParseError, IpAddr, SocketAddr}, + net::{IpAddr, SocketAddr}, str::FromStr, }; +use domain::tsig::KeyName; use serde::{ Deserialize, Serialize, de::{self, Visitor}, @@ -912,8 +913,11 @@ pub enum NameserverCommsSpec { #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct ComplexNameserverCommsSpec { - /// The address to send NOTIFYs to. - pub addr: SocketAddr, + /// The address to send NOTIFYs to and allow XFRs from. + pub addr: Option, + + /// An optional TSIG key to authenticate messages with. + pub tsig_key_name: Option, } /// Policy for communicating with another namesever. @@ -921,7 +925,10 @@ pub struct ComplexNameserverCommsSpec { #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct SimpleNameserverCommsSpec { /// The address to send NOTIFYs to. - pub addr: SocketAddr, + pub addr: Option, + + /// An optional TSIG key to authenticate messages with. + pub tsig_key_name: Option, } //--- Conversion @@ -937,33 +944,65 @@ impl NameserverCommsSpec { /// Build into this specification. pub fn build(policy: &NameserverCommsPolicy) -> Self { - Self::Complex(ComplexNameserverCommsSpec { addr: policy.addr }) + Self::Complex(ComplexNameserverCommsSpec { + addr: policy.addr, + tsig_key_name: policy.tsig_key_name.clone(), + }) } } impl SimpleNameserverCommsSpec { /// Parse from this specification. pub fn parse(self) -> NameserverCommsPolicy { - NameserverCommsPolicy { addr: self.addr } + NameserverCommsPolicy { + addr: self.addr, + tsig_key_name: self.tsig_key_name, + } } } impl ComplexNameserverCommsSpec { /// Parse from this specification. pub fn parse(self) -> NameserverCommsPolicy { - NameserverCommsPolicy { addr: self.addr } + NameserverCommsPolicy { + addr: self.addr, + tsig_key_name: self.tsig_key_name, + } } } -/// Parse`[:]` +/// Parse`[[:]][^]` impl FromStr for SimpleNameserverCommsSpec { - type Err = AddrParseError; + type Err = String; fn from_str(s: &str) -> Result { - let addr = IpAddr::from_str(s) - .map(|ip| SocketAddr::new(ip, 53)) - .or_else(|_| SocketAddr::from_str(s))?; - Ok(SimpleNameserverCommsSpec { addr }) + let (s, tsig_key_name) = s + .split_once('^') + .map(|(s, tsig_key_name)| (s, Some(tsig_key_name))) + .unwrap_or((s, None)); + + let addr = if s.is_empty() { + None + } else { + Some( + IpAddr::from_str(s) + .map(|ip| SocketAddr::new(ip, 53)) + .or_else(|_| SocketAddr::from_str(s)) + .map_err(|err| format!("Invalid IP address '{s}': {err}"))?, + ) + }; + + let tsig_key_name = tsig_key_name + .map(|name| { + KeyName::from_str(name) + .map_err(|err| format!("Invalid TSIG key name '{name}: {err}")) + }) + .transpose()?; + + Ok(SimpleNameserverCommsSpec { + addr, + tsig_key_name, + }) } } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index e02b29877..bef69ee27 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -9,6 +9,7 @@ use bytes::Bytes; use camino::Utf8PathBuf; use domain::base::Name; use domain::base::Ttl; +use domain::tsig::KeyName; use serde::{Deserialize, Serialize}; use tracing::{debug, error, info, warn}; @@ -475,7 +476,10 @@ pub struct NameserverCommsPolicy { /// For sending the port MUST NOT be zero. /// /// TODO: Support IP prefixes? - pub addr: SocketAddr, + pub addr: Option, + + /// An optional TSIG key to authenticate messages with. + pub tsig_key_name: Option, } //----------- KeyParameters --------------------------------------------------- diff --git a/src/server/service.rs b/src/server/service.rs index b6faecf37..b331d7cc8 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -83,6 +83,7 @@ mod compat { tsig, }; use futures::Stream; + use tracing::{debug, trace}; use crate::server::request::{RequestKind, ZoneRequestKind}; @@ -128,6 +129,13 @@ mod compat { return Box::pin(std::future::ready(error(old_request.message(), rcode))); }; + if !is_permitted(&old_request, zone) { + return Box::pin(std::future::ready(error( + old_request.message(), + Rcode::REFUSED, + ))); + } + match zone_request.kind { ZoneRequestKind::Soa => Box::pin({ let viewer = zone.viewer.clone(); @@ -152,6 +160,97 @@ mod compat { } } + /// Enforce any defined access controls for the zone being requested. + fn is_permitted( + request: &Request, Option>>, + zone: &ServedZone, + ) -> bool + where + V: Viewer + Send + Sync + 'static, + { + // IP address/CIDR based access control: + // TODO + + // TSIG access control: + // If the request was TSIG authenticated it would only be passed to + // our service if the TsigMiddlewareSvc verified that the key used + // exists in our key store, so that it can sign the response that we + // generate. + // + // However, having used a known TSIG key is not enough, the key used + // must also have been permitted by the operator for the zone being + // queried. + // + // The TSIG key(s) permitted to sign a zone are defined in policy + // so we have to look that up. + let zone_state = zone.handle.state.lock().unwrap(); + + let policy = zone_state + .policy + .as_ref() + .expect("A zone must always have an associated policy"); + + let outbound_policy = &policy.server.outbound; + + debug!( + "Checking access to zone {} from {} with TSIG key {:?} due to policy '{}'", + zone.handle.name, + request.client_addr(), + request.metadata(), + policy.name + ); + trace!("Policy: {policy:?}"); + + // If no rules are defined, allow the request. + if outbound_policy.accept_xfr_requests_from.is_empty() { + return true; + } + + // At least one access control rule must match in order for the + // request to be accepted. + + for rule in &outbound_policy.accept_xfr_requests_from { + match (rule.addr, &rule.tsig_key_name, request.metadata()) { + // Allow all + (None, None, None) => return true, + + // TSIG key match required + (None, Some(wanted_key), Some(actual_key)) if wanted_key == actual_key.name() => { + // Allow the request. + return true; + } + + // IP address match required + (Some(wanted_addr), None, None) if wanted_addr == request.client_addr() => { + // Allow the request. + return true; + } + + // IP address *and* TSIG key match required + (Some(wanted_addr), Some(wanted_key), Some(actual_key)) + if wanted_addr == request.client_addr() && wanted_key == actual_key.name() => + { + // Allow the request. + return true; + } + + _ => { + // Rule not matched, see if another rule matches + } + } + } + + // Deny the request. + debug!( + "Denying request to zone {} from {} with TSIG key {:?} due to policy '{}'", + zone.handle.name, + request.client_addr(), + request.metadata(), + policy.name + ); + false + } + fn soa(request: &Message>, viewer: &V) -> ResponseStream { if viewer.is_empty() { // The zone is known to exist, but we don't have any data for it. diff --git a/src/units/http_server.rs b/src/units/http_server.rs index bf6e34eae..1ac980e3f 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -916,12 +916,18 @@ impl HttpServer { accept_xfr_requests_from: p_outbound .accept_xfr_requests_from .iter() - .map(|v| NameserverCommsPolicyInfo { addr: v.addr }) + .map(|v| NameserverCommsPolicyInfo { + addr: v.addr, + tsig_key_name: v.tsig_key_name.clone(), + }) .collect(), send_notify_to: p_outbound .send_notify_to .iter() - .map(|v| NameserverCommsPolicyInfo { addr: v.addr }) + .map(|v| NameserverCommsPolicyInfo { + addr: v.addr, + tsig_key_name: v.tsig_key_name.clone(), + }) .collect(), }, }; diff --git a/src/units/zone_server.rs b/src/units/zone_server.rs index 779aed87d..1bf7cee76 100644 --- a/src/units/zone_server.rs +++ b/src/units/zone_server.rs @@ -243,13 +243,7 @@ impl ZoneServer { .outbound .send_notify_to .iter() - .filter_map(|s| { - if s.addr.port() != 0 { - Some(s.addr) - } else { - None - } - }); + .filter_map(|s| s.addr.filter(|addr| addr.port() != 0)); send_notify_to_addrs(zone_name.clone(), addrs, center); } From e6d8a5fba277d9aa05ed0a6074d95e322b53a211 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:04:55 +0200 Subject: [PATCH 055/113] Remove outdated comment. --- src/server/service.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/server/service.rs b/src/server/service.rs index b331d7cc8..af07f5ddf 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -168,9 +168,6 @@ mod compat { where V: Viewer + Send + Sync + 'static, { - // IP address/CIDR based access control: - // TODO - // TSIG access control: // If the request was TSIG authenticated it would only be passed to // our service if the TsigMiddlewareSvc verified that the key used From 80ac2e7c6c623fdc349ae13522de6bf934ac5189 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:10:49 +0200 Subject: [PATCH 056/113] Revert "Remove outdated comment." This reverts commit e6d8a5fba277d9aa05ed0a6074d95e322b53a211. --- src/server/service.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/service.rs b/src/server/service.rs index af07f5ddf..b331d7cc8 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -168,6 +168,9 @@ mod compat { where V: Viewer + Send + Sync + 'static, { + // IP address/CIDR based access control: + // TODO + // TSIG access control: // If the request was TSIG authenticated it would only be passed to // our service if the TsigMiddlewareSvc verified that the key used From d77e034327e313d7c395fe36206eab21081fd252 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:10:51 +0200 Subject: [PATCH 057/113] Reapply "Remove addition of downstream specific logic from upstream PR." This reverts commit 787ad2a72082ce3db1b5fa7d1375f4c271fc9572. --- crates/api/src/lib.rs | 12 +---- src/policy/file/v1.rs | 65 ++++++-------------------- src/policy/mod.rs | 6 +-- src/server/service.rs | 99 ---------------------------------------- src/units/http_server.rs | 10 +--- src/units/zone_server.rs | 8 +++- 6 files changed, 25 insertions(+), 175 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index d4c9b9871..27fb202f0 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -5,7 +5,6 @@ use std::time::{Duration, SystemTime}; use camino::{Utf8Path, Utf8PathBuf}; use domain::tsig::Algorithm; -use domain::tsig::KeyName; use serde::{Deserialize, Serialize}; pub use domain::base::Serial; @@ -890,19 +889,12 @@ pub struct OutboundPolicyInfo { #[derive(Deserialize, Serialize, Debug, Clone)] pub struct NameserverCommsPolicyInfo { - pub addr: Option, - pub tsig_key_name: Option, + pub addr: SocketAddr, } impl std::fmt::Display for NameserverCommsPolicyInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(addr) = self.addr { - write!(f, "{addr}")?; - } - if let Some(tsig_key_name) = &self.tsig_key_name { - write!(f, "^{tsig_key_name}")?; - } - Ok(()) + write!(f, "{}", self.addr) } } diff --git a/src/policy/file/v1.rs b/src/policy/file/v1.rs index c89520c81..64c37f357 100644 --- a/src/policy/file/v1.rs +++ b/src/policy/file/v1.rs @@ -2,11 +2,10 @@ use std::{ fmt::{self, Display}, - net::{IpAddr, SocketAddr}, + net::{AddrParseError, IpAddr, SocketAddr}, str::FromStr, }; -use domain::tsig::KeyName; use serde::{ Deserialize, Serialize, de::{self, Visitor}, @@ -913,11 +912,8 @@ pub enum NameserverCommsSpec { #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct ComplexNameserverCommsSpec { - /// The address to send NOTIFYs to and allow XFRs from. - pub addr: Option, - - /// An optional TSIG key to authenticate messages with. - pub tsig_key_name: Option, + /// The address to send NOTIFYs to. + pub addr: SocketAddr, } /// Policy for communicating with another namesever. @@ -925,10 +921,7 @@ pub struct ComplexNameserverCommsSpec { #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct SimpleNameserverCommsSpec { /// The address to send NOTIFYs to. - pub addr: Option, - - /// An optional TSIG key to authenticate messages with. - pub tsig_key_name: Option, + pub addr: SocketAddr, } //--- Conversion @@ -944,65 +937,33 @@ impl NameserverCommsSpec { /// Build into this specification. pub fn build(policy: &NameserverCommsPolicy) -> Self { - Self::Complex(ComplexNameserverCommsSpec { - addr: policy.addr, - tsig_key_name: policy.tsig_key_name.clone(), - }) + Self::Complex(ComplexNameserverCommsSpec { addr: policy.addr }) } } impl SimpleNameserverCommsSpec { /// Parse from this specification. pub fn parse(self) -> NameserverCommsPolicy { - NameserverCommsPolicy { - addr: self.addr, - tsig_key_name: self.tsig_key_name, - } + NameserverCommsPolicy { addr: self.addr } } } impl ComplexNameserverCommsSpec { /// Parse from this specification. pub fn parse(self) -> NameserverCommsPolicy { - NameserverCommsPolicy { - addr: self.addr, - tsig_key_name: self.tsig_key_name, - } + NameserverCommsPolicy { addr: self.addr } } } -/// Parse`[[:]][^]` +/// Parse`[:]` impl FromStr for SimpleNameserverCommsSpec { - type Err = String; + type Err = AddrParseError; fn from_str(s: &str) -> Result { - let (s, tsig_key_name) = s - .split_once('^') - .map(|(s, tsig_key_name)| (s, Some(tsig_key_name))) - .unwrap_or((s, None)); - - let addr = if s.is_empty() { - None - } else { - Some( - IpAddr::from_str(s) - .map(|ip| SocketAddr::new(ip, 53)) - .or_else(|_| SocketAddr::from_str(s)) - .map_err(|err| format!("Invalid IP address '{s}': {err}"))?, - ) - }; - - let tsig_key_name = tsig_key_name - .map(|name| { - KeyName::from_str(name) - .map_err(|err| format!("Invalid TSIG key name '{name}: {err}")) - }) - .transpose()?; - - Ok(SimpleNameserverCommsSpec { - addr, - tsig_key_name, - }) + let addr = IpAddr::from_str(s) + .map(|ip| SocketAddr::new(ip, 53)) + .or_else(|_| SocketAddr::from_str(s))?; + Ok(SimpleNameserverCommsSpec { addr }) } } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index bef69ee27..e02b29877 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -9,7 +9,6 @@ use bytes::Bytes; use camino::Utf8PathBuf; use domain::base::Name; use domain::base::Ttl; -use domain::tsig::KeyName; use serde::{Deserialize, Serialize}; use tracing::{debug, error, info, warn}; @@ -476,10 +475,7 @@ pub struct NameserverCommsPolicy { /// For sending the port MUST NOT be zero. /// /// TODO: Support IP prefixes? - pub addr: Option, - - /// An optional TSIG key to authenticate messages with. - pub tsig_key_name: Option, + pub addr: SocketAddr, } //----------- KeyParameters --------------------------------------------------- diff --git a/src/server/service.rs b/src/server/service.rs index b331d7cc8..b6faecf37 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -83,7 +83,6 @@ mod compat { tsig, }; use futures::Stream; - use tracing::{debug, trace}; use crate::server::request::{RequestKind, ZoneRequestKind}; @@ -129,13 +128,6 @@ mod compat { return Box::pin(std::future::ready(error(old_request.message(), rcode))); }; - if !is_permitted(&old_request, zone) { - return Box::pin(std::future::ready(error( - old_request.message(), - Rcode::REFUSED, - ))); - } - match zone_request.kind { ZoneRequestKind::Soa => Box::pin({ let viewer = zone.viewer.clone(); @@ -160,97 +152,6 @@ mod compat { } } - /// Enforce any defined access controls for the zone being requested. - fn is_permitted( - request: &Request, Option>>, - zone: &ServedZone, - ) -> bool - where - V: Viewer + Send + Sync + 'static, - { - // IP address/CIDR based access control: - // TODO - - // TSIG access control: - // If the request was TSIG authenticated it would only be passed to - // our service if the TsigMiddlewareSvc verified that the key used - // exists in our key store, so that it can sign the response that we - // generate. - // - // However, having used a known TSIG key is not enough, the key used - // must also have been permitted by the operator for the zone being - // queried. - // - // The TSIG key(s) permitted to sign a zone are defined in policy - // so we have to look that up. - let zone_state = zone.handle.state.lock().unwrap(); - - let policy = zone_state - .policy - .as_ref() - .expect("A zone must always have an associated policy"); - - let outbound_policy = &policy.server.outbound; - - debug!( - "Checking access to zone {} from {} with TSIG key {:?} due to policy '{}'", - zone.handle.name, - request.client_addr(), - request.metadata(), - policy.name - ); - trace!("Policy: {policy:?}"); - - // If no rules are defined, allow the request. - if outbound_policy.accept_xfr_requests_from.is_empty() { - return true; - } - - // At least one access control rule must match in order for the - // request to be accepted. - - for rule in &outbound_policy.accept_xfr_requests_from { - match (rule.addr, &rule.tsig_key_name, request.metadata()) { - // Allow all - (None, None, None) => return true, - - // TSIG key match required - (None, Some(wanted_key), Some(actual_key)) if wanted_key == actual_key.name() => { - // Allow the request. - return true; - } - - // IP address match required - (Some(wanted_addr), None, None) if wanted_addr == request.client_addr() => { - // Allow the request. - return true; - } - - // IP address *and* TSIG key match required - (Some(wanted_addr), Some(wanted_key), Some(actual_key)) - if wanted_addr == request.client_addr() && wanted_key == actual_key.name() => - { - // Allow the request. - return true; - } - - _ => { - // Rule not matched, see if another rule matches - } - } - } - - // Deny the request. - debug!( - "Denying request to zone {} from {} with TSIG key {:?} due to policy '{}'", - zone.handle.name, - request.client_addr(), - request.metadata(), - policy.name - ); - false - } - fn soa(request: &Message>, viewer: &V) -> ResponseStream { if viewer.is_empty() { // The zone is known to exist, but we don't have any data for it. diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 1ac980e3f..bf6e34eae 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -916,18 +916,12 @@ impl HttpServer { accept_xfr_requests_from: p_outbound .accept_xfr_requests_from .iter() - .map(|v| NameserverCommsPolicyInfo { - addr: v.addr, - tsig_key_name: v.tsig_key_name.clone(), - }) + .map(|v| NameserverCommsPolicyInfo { addr: v.addr }) .collect(), send_notify_to: p_outbound .send_notify_to .iter() - .map(|v| NameserverCommsPolicyInfo { - addr: v.addr, - tsig_key_name: v.tsig_key_name.clone(), - }) + .map(|v| NameserverCommsPolicyInfo { addr: v.addr }) .collect(), }, }; diff --git a/src/units/zone_server.rs b/src/units/zone_server.rs index 1bf7cee76..779aed87d 100644 --- a/src/units/zone_server.rs +++ b/src/units/zone_server.rs @@ -243,7 +243,13 @@ impl ZoneServer { .outbound .send_notify_to .iter() - .filter_map(|s| s.addr.filter(|addr| addr.port() != 0)); + .filter_map(|s| { + if s.addr.port() != 0 { + Some(s.addr) + } else { + None + } + }); send_notify_to_addrs(zone_name.clone(), addrs, center); } From 7228b833fccef768b9c8ef6c1292bd1edaac7668 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:30:42 +0200 Subject: [PATCH 058/113] Additional comments on the NOTIFY handler re: ACL enforcement. --- src/units/zone_server.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/units/zone_server.rs b/src/units/zone_server.rs index 779aed87d..815637a2f 100644 --- a/src/units/zone_server.rs +++ b/src/units/zone_server.rs @@ -690,8 +690,23 @@ impl Notifiable for LoaderNotifier { // Don't do anything if the notifier is disabled. if self.enabled && class == Class::IN { // Propagate a request for the zone refresh. - // This request ignores the serial and source because we will just - // do a SOA query to our configured upstreams. + // + // We ignore the serial because we will just do a SOA query to our + // configured upstream. + // + // TODO: Do we want to try enforcing IP address based access + // control at this point? Would we want CIDR matching support? + // Would we want to require a DNS COOKIE if the transport is UDP? + // + // TODO: Do we want to try enforcing TSIG key based access control + // at this point? We can determine the key that the zone source + // is configured to use but we can't actually verify that that key + // was used. The TsigMiddlewareSvc will have ensured that the a + // valid key present in our key store was used, but that may not + // be the actual key configured on the zone source. We cannot test + // for the actual correct key because NotifyMiddlewareSvc that + // invokes us doesn't pass us the Request from which we would be + // able to learn the used TSIG key. let center = &self.center; if let Some(zone) = crate::center::get_zone(center, apex_name) { info!("Instructing zone loader to refresh zone '{apex_name}"); From 9bc4f47028fa07af8bca99c5037c0d92ae8e0d84 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:33:12 +0200 Subject: [PATCH 059/113] Remove policy comments that relate to downstream TSIG. --- etc/policy.template.toml | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/etc/policy.template.toml b/etc/policy.template.toml index 70e67ae35..9fcf406df 100644 --- a/etc/policy.template.toml +++ b/etc/policy.template.toml @@ -376,26 +376,5 @@ required = false # The set of nameservers to which NOTIFY messages should be sent. # -# If empty, no NOTIFY messages will be sent. -# -# Each nameserver must be specifeid as a string in the form `":[][^]"`. -# -# A TSIG key name can optionally be specified in order to sign the NOTIFY -# message using the specified TSIG key. The TSIG key must already have been -# added to the Cascade TSIG key store with the `cascade tsig add` CLI command. -# -# Examples: -# -# send-notify-to = [ "127.0.0.1"] -# send-notify-to = [ "127.0.0.1:53"] -# send-notify-to = [ "127.0.0.1:53^my_tsig_key"] -# send-notify-to = [ "127.0.0.1^my_tsig_key"] -# send-notify-to = [ { addr = "127.0.0.1:49", tsig-key-name = "my_tsig_key" } ] -# -# You can also use the TOML array of tables syntax like so: -# -# [[server.outbound.send-notify-to]] -# addr = "127.0.0.1:53" -# tsig-key-name = "my_tsig_key" +# A collection of IP:[port], defaulting to port 53 when not specified. send-notify-to = [] From 773369a1d72cf93292bb182515a7be76c910d0bc Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:42:38 +0200 Subject: [PATCH 060/113] Restore missing line. --- etc/policy.template.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etc/policy.template.toml b/etc/policy.template.toml index 9fcf406df..7f19a5585 100644 --- a/etc/policy.template.toml +++ b/etc/policy.template.toml @@ -375,6 +375,8 @@ required = false [server.outbound] # The set of nameservers to which NOTIFY messages should be sent. +# +# If empty, no NOTIFY messages will be sent. # # A collection of IP:[port], defaulting to port 53 when not specified. send-notify-to = [] From 0fa77c82b078f5faacff58ea6acf4c159b6cabf9 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:43:35 +0200 Subject: [PATCH 061/113] Restore missing line exactly. --- etc/policy.template.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/policy.template.toml b/etc/policy.template.toml index 7f19a5585..f2ef3e706 100644 --- a/etc/policy.template.toml +++ b/etc/policy.template.toml @@ -375,8 +375,8 @@ required = false [server.outbound] # The set of nameservers to which NOTIFY messages should be sent. -# -# If empty, no NOTIFY messages will be sent. # +# If empty, no NOTIFY messages will be sent. +# # A collection of IP:[port], defaulting to port 53 when not specified. send-notify-to = [] From 1cdf7b0592203fd547713e8333f6db8ada94a9f2 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:54:18 +0200 Subject: [PATCH 062/113] Add a note about how adding a TSIG key has an effect, even without specifying a use for it. --- doc/manual/source/man/cascade-tsig.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 43509add9..7798492ee 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -38,6 +38,9 @@ Commands Register a new TSIG key. + Incoming DNS messages that are TSIG signed will be rejected if the key used + to sign the message is not registered with Cascade. + .. subcmd:: list List registered TSIG keys and the zones that use them. From d466f35b8f9d02bd5f6a503b320fb90f7a8920d7 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Tue, 21 Apr 2026 00:05:28 +0200 Subject: [PATCH 063/113] Document the new policy publication-nameservers setting. --- doc/manual/source/man/cascaded-policy.toml.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/manual/source/man/cascaded-policy.toml.rst b/doc/manual/source/man/cascaded-policy.toml.rst index 6057b9fc8..bb60fd9e9 100644 --- a/doc/manual/source/man/cascaded-policy.toml.rst +++ b/doc/manual/source/man/cascaded-policy.toml.rst @@ -62,6 +62,7 @@ Example algorithm.auto-done = true ds-algorithm = "SHA256" auto-remove = true + publication-nameservers = [] [key-manager.records] ttl = "1h" @@ -257,6 +258,17 @@ The ``[key-manager]`` section. If this is set, expired keys will be removed automatically (by deleting the files for on-disk keys or removing it from the HSM). +.. option:: publication-nameservers = [] + + A set of nameservers to use when checking for rrsiG propagation during a + key roll. + + Each nameserver is specified as a string with the syntax: + + ``:[^[TSIG-Key-Name]]`` + + If not specified then the nameserver specified in the zone apex SOA MNAME + field will be queried. The management of DNS records by the key manager. +++++++++++++++++++++++++++++++++++++++++++++++++ From b08bc846fc17a2c7b525459c61af9e440ab7c624 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Tue, 21 Apr 2026 00:06:20 +0200 Subject: [PATCH 064/113] Re-generate rendered man pages. --- doc/manual/build/man/cascade-debug.1 | 2 +- doc/manual/build/man/cascade-health.1 | 2 +- doc/manual/build/man/cascade-hsm.1 | 2 +- doc/manual/build/man/cascade-keyset.1 | 2 +- doc/manual/build/man/cascade-policy.1 | 2 +- doc/manual/build/man/cascade-status.1 | 2 +- doc/manual/build/man/cascade-template.1 | 2 +- doc/manual/build/man/cascade-tsig.1 | 5 ++++- doc/manual/build/man/cascade-zone.1 | 2 +- doc/manual/build/man/cascade.1 | 2 +- doc/manual/build/man/cascaded-config.toml.5 | 2 +- doc/manual/build/man/cascaded-policy.toml.5 | 19 ++++++++++++++++++- doc/manual/build/man/cascaded.1 | 2 +- 13 files changed, 33 insertions(+), 13 deletions(-) diff --git a/doc/manual/build/man/cascade-debug.1 b/doc/manual/build/man/cascade-debug.1 index f0bde6cda..a058a215b 100644 --- a/doc/manual/build/man/cascade-debug.1 +++ b/doc/manual/build/man/cascade-debug.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-DEBUG" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-DEBUG" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-debug \- Debug / troubleshoot Cascade .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-health.1 b/doc/manual/build/man/cascade-health.1 index 7f988c582..a219ff50c 100644 --- a/doc/manual/build/man/cascade-health.1 +++ b/doc/manual/build/man/cascade-health.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HEALTH" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HEALTH" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-health \- Check the health of Cascade .sp diff --git a/doc/manual/build/man/cascade-hsm.1 b/doc/manual/build/man/cascade-hsm.1 index 00bfd2801..3830445b4 100644 --- a/doc/manual/build/man/cascade-hsm.1 +++ b/doc/manual/build/man/cascade-hsm.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HSM" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HSM" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-hsm \- Manage HSMs .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-keyset.1 b/doc/manual/build/man/cascade-keyset.1 index c91927b83..9e31e41ec 100644 --- a/doc/manual/build/man/cascade-keyset.1 +++ b/doc/manual/build/man/cascade-keyset.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-KEYSET" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-KEYSET" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-keyset \- Execute manual key roll or key removal commands .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-policy.1 b/doc/manual/build/man/cascade-policy.1 index fb2500d39..553fe6b32 100644 --- a/doc/manual/build/man/cascade-policy.1 +++ b/doc/manual/build/man/cascade-policy.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-POLICY" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-POLICY" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-policy \- Manage policies .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-status.1 b/doc/manual/build/man/cascade-status.1 index 2848fbc84..70c9649e0 100644 --- a/doc/manual/build/man/cascade-status.1 +++ b/doc/manual/build/man/cascade-status.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-STATUS" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-STATUS" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-status \- Show the status of Cascade .sp diff --git a/doc/manual/build/man/cascade-template.1 b/doc/manual/build/man/cascade-template.1 index f2fe0e0b5..7820a9874 100644 --- a/doc/manual/build/man/cascade-template.1 +++ b/doc/manual/build/man/cascade-template.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-TEMPLATE" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-TEMPLATE" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-template \- Print example config or policy files .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-tsig.1 b/doc/manual/build/man/cascade-tsig.1 index b26f4429a..78d132985 100644 --- a/doc/manual/build/man/cascade-tsig.1 +++ b/doc/manual/build/man/cascade-tsig.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-TSIG" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-TSIG" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-tsig \- Manage TSIG keys .sp @@ -63,6 +63,9 @@ command. .TP .B add Register a new TSIG key. +.sp +Incoming DNS messages that are TSIG signed will be rejected if the key used +to sign the message is not registered with Cascade. .UNINDENT .INDENT 0.0 .TP diff --git a/doc/manual/build/man/cascade-zone.1 b/doc/manual/build/man/cascade-zone.1 index ee5e12175..daa6c6799 100644 --- a/doc/manual/build/man/cascade-zone.1 +++ b/doc/manual/build/man/cascade-zone.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-ZONE" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-ZONE" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-zone \- Manage zones .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade.1 b/doc/manual/build/man/cascade.1 index f6e4db446..3f77a7fa0 100644 --- a/doc/manual/build/man/cascade.1 +++ b/doc/manual/build/man/cascade.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade \- Cascade CLI .SH SYNOPSIS diff --git a/doc/manual/build/man/cascaded-config.toml.5 b/doc/manual/build/man/cascaded-config.toml.5 index 0aa89e11c..bddf152ae 100644 --- a/doc/manual/build/man/cascaded-config.toml.5 +++ b/doc/manual/build/man/cascaded-config.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-CONFIG.TOML" "5" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-CONFIG.TOML" "5" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-config.toml \- Cascade configuration file .sp diff --git a/doc/manual/build/man/cascaded-policy.toml.5 b/doc/manual/build/man/cascaded-policy.toml.5 index 3aa149021..11989d680 100644 --- a/doc/manual/build/man/cascaded-policy.toml.5 +++ b/doc/manual/build/man/cascaded-policy.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-POLICY.TOML" "5" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-POLICY.TOML" "5" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-policy.toml \- Cascade policy file format .sp @@ -96,6 +96,7 @@ csk.auto\-done = true algorithm.auto\-done = true ds\-algorithm = \(dqSHA256\(dq auto\-remove = true +publication\-nameservers = [] [key\-manager.records] ttl = \(dq1h\(dq @@ -338,6 +339,22 @@ Whether to automatically remove expired keys. If this is set, expired keys will be removed automatically (by deleting the files for on\-disk keys or removing it from the HSM). .UNINDENT +.INDENT 0.0 +.TP +.B publication\-nameservers = [] +A set of nameservers to use when checking for rrsiG propagation during a +key roll. +.sp +Each nameserver is specified as a string with the syntax: +.INDENT 7.0 +.INDENT 3.5 +\fB:[^[TSIG\-Key\-Name]]\fP +.UNINDENT +.UNINDENT +.sp +If not specified then the nameserver specified in the zone apex SOA MNAME +field will be queried. +.UNINDENT .SS The management of DNS records by the key manager. .sp The \fB[key\-manager.records]\fP section. diff --git a/doc/manual/build/man/cascaded.1 b/doc/manual/build/man/cascaded.1 index a46dcba00..59d33333a 100644 --- a/doc/manual/build/man/cascaded.1 +++ b/doc/manual/build/man/cascaded.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED" "1" "Apr 17, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded \- DNSSEC signer .SH SYNOPSIS From e2c46813e57559eb93bc971f5fdb9ebf5a5717a7 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Tue, 21 Apr 2026 00:18:14 +0200 Subject: [PATCH 065/113] cargo fmt. --- src/units/zone_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/units/zone_server.rs b/src/units/zone_server.rs index 815637a2f..12970da78 100644 --- a/src/units/zone_server.rs +++ b/src/units/zone_server.rs @@ -690,7 +690,7 @@ impl Notifiable for LoaderNotifier { // Don't do anything if the notifier is disabled. if self.enabled && class == Class::IN { // Propagate a request for the zone refresh. - // + // // We ignore the serial because we will just do a SOA query to our // configured upstream. // From a8ff50c47550e80b5faf0ae417efd88ce8f4d0b3 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:21:57 +0200 Subject: [PATCH 066/113] Various review feedback updates. --- crates/api/src/lib.rs | 4 +-- doc/manual/source/man/cascade-tsig.rst | 5 ++- doc/manual/source/man/cascade-zone.rst | 2 +- .../source/man/cascaded-policy.toml.rst | 2 +- doc/manual/source/nsd.rst | 4 +-- doc/manual/source/zone-transfers.rst | 33 +++++++++---------- etc/policy.template.toml | 4 +-- src/loader/mod.rs | 2 +- src/tsig/mod.rs | 2 +- 9 files changed, 28 insertions(+), 30 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 27fb202f0..33dd86db9 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -387,8 +387,8 @@ impl Display for ZoneSource { /// Support parsing of ``-source`` command line arguments. /// /// Supported forms: -/// - `[:][^]` -/// - `` +/// - `[:][^]` +/// - `` impl From<&str> for ZoneSource { fn from(mut s: &str) -> Self { // Split out any provided TSIG key from the rest of the diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 7798492ee..2ecbfc7df 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -49,9 +49,8 @@ Commands Remove a registered TSIG key. - .. note:: Returns an error if the key does not exist in the TSIG key store - or if any zone exists that is configured to authenticate with an - upstream source using the specified TSIG key. + .. note:: Returns an error if the key does not exist in the TSIG key store, + or if the key is still referenced by other configuration. Arguments for :subcmd:`tsig add` -------------------------------- diff --git a/doc/manual/source/man/cascade-zone.rst b/doc/manual/source/man/cascade-zone.rst index 034d51926..7bcead194 100644 --- a/doc/manual/source/man/cascade-zone.rst +++ b/doc/manual/source/man/cascade-zone.rst @@ -48,7 +48,7 @@ Commands Remove a zone. - .. note:: Once removed downstream servers will no longer be able to fetch + .. note:: Once removed, downstream servers will no longer be able to fetch the zone! .. subcmd:: list diff --git a/doc/manual/source/man/cascaded-policy.toml.rst b/doc/manual/source/man/cascaded-policy.toml.rst index bb60fd9e9..0eac8ee8d 100644 --- a/doc/manual/source/man/cascaded-policy.toml.rst +++ b/doc/manual/source/man/cascaded-policy.toml.rst @@ -265,7 +265,7 @@ The ``[key-manager]`` section. Each nameserver is specified as a string with the syntax: - ``:[^[TSIG-Key-Name]]`` + ``[:][^[TSIG_KEY_NAME]`` If not specified then the nameserver specified in the zone apex SOA MNAME field will be queried. diff --git a/doc/manual/source/nsd.rst b/doc/manual/source/nsd.rst index 35f1e8f6f..be80a54bc 100644 --- a/doc/manual/source/nsd.rst +++ b/doc/manual/source/nsd.rst @@ -59,7 +59,7 @@ example\: key: name: "sec1_key" algorithm: hmac-sha256 - secret: "...==" + secret: "..." zone: name: example.com @@ -83,7 +83,7 @@ command, e.g. like so: $ cascade tsig add --name sec1_key --alg hmac-sha256 --secret "...==" To use the new TSIG key it must be specified when adding a zone to -Cascade. Assuming that NSD is running on host 192.168.0.1 on port 53 +Cascade. Assuming that NSD is running on host 192.168.0.1 on port 53, the following command instructs Cascade to add the ``example.com`` zone sourced from the NSD server using the ``sec1_key`` TSIG key to authenticate with NSD: diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst index 9017f27f5..089fb63ec 100644 --- a/doc/manual/source/zone-transfers.rst +++ b/doc/manual/source/zone-transfers.rst @@ -3,7 +3,7 @@ Zone Transfers Cascade is designed to be deployed between a hidden upstream nameserver and public downstream nameservers. The hidden upstream serves the unsigned zone, -Cascade signs it and passes it to the downstream nameservers for publication +Cascade signs it, and passes it to the downstream nameservers for publication to consumers. Communication of changed zone records from upstream to downstream should be @@ -14,19 +14,18 @@ Securing the transferred data can be done using RFC 8945 Secret Key Authentication for DNS (TSIG) keys, using a shared secret communicated out of band to the nameservers sending and receiving the zone records. -Cascade supports timely discovery of zone changes via RFC 1996 A Mechanism -for Prompt Notification of Zone Changes (DNS NOTIFY). If no NOTIFY message -is received by Cascade, Cascade will instead discover new versions of the -zone by sending SOA queries periodically to the upstream, the frequency of -which is determined by the timers on the SOA apex record in the zone. +Cascade supports timely discovery of zone changes via RFC 1996 (DNS NOTIFY). +If no NOTIFY message is received by Cascade, Cascade will instead discover +new versions of the zone by sending SOA queries periodically to the upstream, +the frequency of which is determined by the timers on the zone's SOA record. -.. note:: Cascade also supports loading zone records from a file. However, - if only a small fraction of the records in the zone change from - one version to the next, loading the entire zone zone every time - the zone changes will require more time, CPU and memory compared - to processing only the differences when using IXFR. Additionally, - Cascade has no built-in support for writing signed zone files to - disk, if needed this could be done by a signed review hook. +.. note:: Cascade also supports loading the zone from a file. However, if only + a small fraction of the records in the zone change from one version + to the next, loading the entire zone every time the zone changes + will require more time, CPU and memory compared to processing + only the differences when using IXFR. Additionally, Cascade has no + built-in support for writing signed zone files to disk, if needed + this could be done by a signed review hook. Using zone transfers with an upstream nameserver ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -60,16 +59,16 @@ When adding a zone the TSIG key name can then be referred to like so: .. code-block:: bash - $ cascade zone add --source [:]^ + $ cascade zone add --source [:]^ Using zone transfers with a downstream server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Cascade permits zone transfers by default, no configuration is required. -To ensure timely update by secondaries Cascade can be configured to send -RFC 1996 DNS NOTIFY messages to specified secondaries. This is done via -the policy setting ``server.outbound.send-notify-to``. +To ensure timely update by secondaries, Cascade can be configured to send RFC +1996 DNS NOTIFY messages to specified secondaries. This is done via the policy +setting ``server.outbound.send-notify-to``. .. note:: The policy file will need to be reloaded via ``cascade policy reload`` before adding the zone. Also, when adding the zone you diff --git a/etc/policy.template.toml b/etc/policy.template.toml index f2ef3e706..0af41f893 100644 --- a/etc/policy.template.toml +++ b/etc/policy.template.toml @@ -159,8 +159,8 @@ auto-remove = true # The upstream nameservers to use when checking for RRSIG propagation during # a key roll. # -# Each nameserver must be specifeid as a string in the form `":[][^]"`. +# Each nameserver must be specifeid as a string in the form +# `":[][^]"`. # # If not specified, the nameserver specified by the SOA MNAME field will be # checked. diff --git a/src/loader/mod.rs b/src/loader/mod.rs index 5c0bc1dbc..3f9a90ff7 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -307,7 +307,7 @@ impl std::fmt::Display for Source { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Source::None => f.write_str("none"), - Source::Zonefile { path } => write!(f, "zone file '{}'", path.as_std_path().display()), + Source::Zonefile { path } => write!(f, "zone file '{path}'"), Source::Server { addr, tsig_key } => { write!(f, "{addr}")?; if let Some(tsig_key) = &tsig_key { diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index 0592827eb..bc05bb23b 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -259,7 +259,7 @@ pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), Remo let mut state = center.state.lock().unwrap(); // Currently if a zone was added with `--source - // ip[:port]^` that would cause the TSIG key to be used + // [:]^` that would cause the TSIG key to be used // by the loader when refreshing the zone. // // In future policies may refer to TSIG keys in a couple of places: From 342cbfd46dde33b472389f200fffe6729b9fc955 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:23:03 +0200 Subject: [PATCH 067/113] Use NameserverCommsSpec instead of String following internal discussion. --- src/policy/file/v1.rs | 66 +++++++++++++++++++++++++++++++++------- src/policy/mod.rs | 44 ++++++++++++++++++--------- src/state/v1.rs | 15 +++++++-- src/tsig/mod.rs | 46 ++++++++++++---------------- src/units/key_manager.rs | 14 +++++++-- src/zone/state/v1.rs | 16 +++++++--- 6 files changed, 140 insertions(+), 61 deletions(-) diff --git a/src/policy/file/v1.rs b/src/policy/file/v1.rs index 64c37f357..9dc76e105 100644 --- a/src/policy/file/v1.rs +++ b/src/policy/file/v1.rs @@ -2,10 +2,11 @@ use std::{ fmt::{self, Display}, - net::{AddrParseError, IpAddr, SocketAddr}, + net::{IpAddr, SocketAddr}, str::FromStr, }; +use domain::tsig::KeyName; use serde::{ Deserialize, Serialize, de::{self, Visitor}, @@ -150,7 +151,7 @@ pub struct KeyManagerSpec { /// syntax: `:[^].` /// The port is mandatory. The TSIG key name is optional and the name /// of the key is preceded by a caret character (`^`). - pub publication_nameservers: Vec, + pub publication_nameservers: Vec, } //--- Conversion @@ -246,7 +247,11 @@ impl KeyManagerSpec { default_ttl: self.records.ttl.as_ttl(), ds_algorithm: self.ds_algorithm, auto_remove: self.auto_remove, - publication_nameservers: self.publication_nameservers, + publication_nameservers: self + .publication_nameservers + .into_iter() + .map(|v| v.parse()) + .collect(), } } @@ -278,7 +283,11 @@ impl KeyManagerSpec { ds_algorithm: policy.ds_algorithm.clone(), auto_remove: policy.auto_remove, - publication_nameservers: policy.publication_nameservers.clone(), + publication_nameservers: policy + .publication_nameservers + .iter() + .map(NameserverCommsSpec::build) + .collect(), records: KeyManagerRecordsSpec { ttl: TimeSpan::from_ttl(policy.default_ttl), @@ -899,7 +908,10 @@ impl OutboundSpec { /// Policy for communicating with another namesever. #[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(untagged, expecting = "a string ('[:]') or an inline table")] +#[serde( + untagged, + expecting = "a string ('[:][^]') or an inline table" +)] pub enum NameserverCommsSpec { /// A simple notify specification. Simple(SimpleNameserverCommsSpec), @@ -914,6 +926,9 @@ pub enum NameserverCommsSpec { pub struct ComplexNameserverCommsSpec { /// The address to send NOTIFYs to. pub addr: SocketAddr, + + /// An optional TSIG key to sign and authenticate messages with. + tsig_key_name: Option, } /// Policy for communicating with another namesever. @@ -922,6 +937,9 @@ pub struct ComplexNameserverCommsSpec { pub struct SimpleNameserverCommsSpec { /// The address to send NOTIFYs to. pub addr: SocketAddr, + + /// An optional TSIG key to sign and authenticate messages with. + tsig_key_name: Option, } //--- Conversion @@ -937,33 +955,59 @@ impl NameserverCommsSpec { /// Build into this specification. pub fn build(policy: &NameserverCommsPolicy) -> Self { - Self::Complex(ComplexNameserverCommsSpec { addr: policy.addr }) + Self::Complex(ComplexNameserverCommsSpec { + addr: policy.addr, + tsig_key_name: policy.tsig_key_name.clone(), + }) } } impl SimpleNameserverCommsSpec { /// Parse from this specification. pub fn parse(self) -> NameserverCommsPolicy { - NameserverCommsPolicy { addr: self.addr } + NameserverCommsPolicy { + addr: self.addr, + tsig_key_name: self.tsig_key_name, + } } } impl ComplexNameserverCommsSpec { /// Parse from this specification. pub fn parse(self) -> NameserverCommsPolicy { - NameserverCommsPolicy { addr: self.addr } + NameserverCommsPolicy { + addr: self.addr, + tsig_key_name: self.tsig_key_name, + } } } /// Parse`[:]` impl FromStr for SimpleNameserverCommsSpec { - type Err = AddrParseError; + type Err = String; fn from_str(s: &str) -> Result { + let (tsig_key_name, s) = s.split_once('^').unwrap_or(("", s)); + + let tsig_key_name = if !tsig_key_name.is_empty() { + Some( + KeyName::from_str(tsig_key_name) + .map_err(|err| format!("Invalid TSIG key name '{tsig_key_name}': {err}"))?, + ) + } else { + None + }; + let addr = IpAddr::from_str(s) .map(|ip| SocketAddr::new(ip, 53)) - .or_else(|_| SocketAddr::from_str(s))?; - Ok(SimpleNameserverCommsSpec { addr }) + .or_else(|_| { + SocketAddr::from_str(s) + .map_err(|err| format!("Invalid socket address '{s}': {err}")) + })?; + Ok(SimpleNameserverCommsSpec { + addr, + tsig_key_name, + }) } } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index e02b29877..4614ffedb 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -2,13 +2,13 @@ use std::fmt::{Display, Formatter}; use std::net::SocketAddr; -use std::str::FromStr; use std::{fs, io, sync::Arc}; use bytes::Bytes; use camino::Utf8PathBuf; use domain::base::Name; use domain::base::Ttl; +use domain::tsig::KeyName; use serde::{Deserialize, Serialize}; use tracing::{debug, error, info, warn}; @@ -201,19 +201,20 @@ pub fn load_all( fn check_policy(policy: &PolicyVersion, tsig_store: &TsigStore) -> Result<(), PolicyReloadError> { // Check the publication nameservers for the key manager. Any TSIG key // that is part of those nameservers has to exist in the TSIG key store. - for n in &policy.key_manager.publication_nameservers { - if let Some((_, tsig_name)) = n.split_once('^') { - let tsig_name = Name::from_str(tsig_name).map_err(|e| { - PolicyReloadError::Check(format!( - "unable to convert TSIG key name {tsig_name} to DNS name: {e}" - )) - })?; - tsig_store - .get(&tsig_name) - .ok_or(PolicyReloadError::Check(format!( - "TSIG key {tsig_name} not found in TSIG store" - )))?; - } + let tsig_names = policy + .key_manager + .publication_nameservers + .iter() + .chain(policy.server.outbound.accept_xfr_requests_from.iter()) + .chain(policy.server.outbound.send_notify_to.iter()) + .filter_map(|ns| ns.tsig_key_name.as_ref()); + + for tsig_name in tsig_names { + tsig_store + .get(tsig_name) + .ok_or(PolicyReloadError::Check(format!( + "unknown TSIG key '{tsig_name}'" + )))?; } Ok(()) } @@ -307,7 +308,7 @@ pub struct KeyManagerPolicy { pub auto_remove: bool, /// Nameservers to check for RRSIG propagation during a key roll. - pub publication_nameservers: Vec, + pub publication_nameservers: Vec, } //----------- SignerPolicy ----------------------------------------------------- @@ -476,6 +477,19 @@ pub struct NameserverCommsPolicy { /// /// TODO: Support IP prefixes? pub addr: SocketAddr, + + /// An optional TSIG key to sign and authenticate messages with. + pub tsig_key_name: Option, +} + +impl Display for NameserverCommsPolicy { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.addr)?; + if let Some(tsig_key_name) = &self.tsig_key_name { + write!(f, "^{tsig_key_name}")?; + } + Ok(()) + } } //----------- KeyParameters --------------------------------------------------- diff --git a/src/state/v1.rs b/src/state/v1.rs index a2781a756..36a67e3d4 100644 --- a/src/state/v1.rs +++ b/src/state/v1.rs @@ -8,6 +8,7 @@ use domain::base::Ttl; use serde::{Deserialize, Serialize}; use tracing::info; +use crate::policy::file::v1::NameserverCommsSpec; use crate::policy::file::v1::OutboundSpec; use crate::policy::{AutoConfig, DsAlgorithm, KeyParameters}; use crate::tsig::TsigStore; @@ -257,7 +258,7 @@ pub struct KeyManagerPolicySpec { auto_remove: bool, /// Nameservers to check for RRSIG propagation during a key roll. - pub publication_nameservers: Vec, + pub publication_nameservers: Vec, } //--- Conversion @@ -285,7 +286,11 @@ impl KeyManagerPolicySpec { ds_algorithm: self.ds_algorithm, default_ttl: self.default_ttl, auto_remove: self.auto_remove, - publication_nameservers: self.publication_nameservers, + publication_nameservers: self + .publication_nameservers + .into_iter() + .map(|v| v.parse()) + .collect(), } } @@ -311,7 +316,11 @@ impl KeyManagerPolicySpec { ds_algorithm: policy.ds_algorithm.clone(), default_ttl: policy.default_ttl, auto_remove: policy.auto_remove, - publication_nameservers: policy.publication_nameservers.clone(), + publication_nameservers: policy + .publication_nameservers + .iter() + .map(NameserverCommsSpec::build) + .collect(), } } } diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index bc05bb23b..870dbe51c 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -1,9 +1,7 @@ //! Managing TSIG keys. -use std::str::FromStr; use std::{collections::hash_map, fmt, io, sync::Arc, time::Duration}; -use domain::base::Name; use domain::tsig; use tracing::{debug, error, trace}; @@ -286,32 +284,28 @@ pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), Remo } // Is the TSIG key referenced by any active (not being deleted) policy? - if state + let tsig_key_found = state .policies .values() - .filter(|p| !p.mid_deletion) - .flat_map(|p| &p.latest.key_manager.publication_nameservers) - .filter_map(|n| n.split_once("^").map(|v| v.1)) - .any(|k| { - let nk = Name::>::from_str(k); - if let Ok(n) = nk { - n == name - } else { - // Just ignore TSIG key names that cannot be parsed as a - // DNS name. It should not have been possible to add them - // into the policy in the first place so this should never - // happen. - // - // TODO: "update" from using a String type for - // publication_nameservers to a type that actually uses - // TsigKeyName, like NameserverCommsPolicy does, so that at - // this point we don't need to care about valid or invalid - // TSIG key names? That would also have the benefit of not - // having to parse the nameserver string format here again. - false - } - }) - { + .filter_map(|p| (!p.mid_deletion).then_some(&p.latest)) + .any(|p| { + p.key_manager + .publication_nameservers + .iter() + .any(|ns| ns.tsig_key_name.as_ref() == Some(name)) + || p.server + .outbound + .accept_xfr_requests_from + .iter() + .any(|acl| acl.tsig_key_name.as_ref() == Some(name)) + || p.server + .outbound + .send_notify_to + .iter() + .any(|acl| acl.tsig_key_name.as_ref() == Some(name)) + }); + + if tsig_key_found { return Err(RemoveError::Used); } diff --git a/src/units/key_manager.rs b/src/units/key_manager.rs index 04226fca0..cdfa5df95 100644 --- a/src/units/key_manager.rs +++ b/src/units/key_manager.rs @@ -712,7 +712,11 @@ fn policy_to_commands(center: &Arc
, policy: &PolicyVersion) -> Vec, policy: &PolicyVersion) -> Vec, + publication_nameservers: Vec, } //--- Conversion @@ -227,7 +227,11 @@ impl KeyManagerPolicySpec { ds_algorithm: self.ds_algorithm, default_ttl: self.default_ttl, auto_remove: self.auto_remove, - publication_nameservers: self.publication_nameservers, + publication_nameservers: self + .publication_nameservers + .into_iter() + .map(|v| v.parse()) + .collect(), } } @@ -253,7 +257,11 @@ impl KeyManagerPolicySpec { ds_algorithm: policy.ds_algorithm.clone(), default_ttl: policy.default_ttl, auto_remove: policy.auto_remove, - publication_nameservers: policy.publication_nameservers.clone(), + publication_nameservers: policy + .publication_nameservers + .iter() + .map(NameserverCommsSpec::build) + .collect(), } } } From 42f554d996f85592afc5301d82fd5a48f2f85c60 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:21:06 +0200 Subject: [PATCH 068/113] Various review feedback changes. - Remove unnecessary InvalidTsigKeyName error. - Take a strongly typed key name as API input, not a string. - Move FromStr parsing of ZoneSource to the CLI where it is used. - Use dash separator not underscore for publication-servers policy field. Also: - Remove unused field ZoneSource::xfr_status. --- crates/api/src/lib.rs | 45 +--------------- crates/cli/src/commands/zone.rs | 91 ++++++++++++++++++++++++++++++++- etc/policy.template.toml | 2 +- src/center.rs | 15 +----- src/units/http_server.rs | 1 - 5 files changed, 93 insertions(+), 61 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 33dd86db9..7270a0349 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use std::fmt::{self, Display}; -use std::net::{IpAddr, SocketAddr}; +use std::net::SocketAddr; use std::time::{Duration, SystemTime}; use camino::{Utf8Path, Utf8PathBuf}; @@ -11,8 +11,6 @@ pub use domain::base::Serial; pub mod dep; -const DEFAULT_AXFR_PORT: u16 = 53; - //----------- ZoneName --------------------------------------------------------- /// The name of a zone. @@ -296,7 +294,6 @@ pub enum ZoneAddError { AlreadyExists, NoSuchPolicy, PolicyMidDeletion, - InvalidTsigKeyName(String), NoSuchTsigKey, Other(String), } @@ -307,7 +304,6 @@ impl fmt::Display for ZoneAddError { Self::AlreadyExists => "a zone of this name already exists", Self::NoSuchPolicy => "no policy with that name exists", Self::PolicyMidDeletion => "the specified policy is being deleted", - Self::InvalidTsigKeyName(reason) => reason, Self::NoSuchTsigKey => "no TSIG key with that name exists", Self::Other(reason) => reason, }) @@ -350,10 +346,7 @@ pub enum ZoneSource { addr: SocketAddr, /// The name of a TSIG key, if any. - tsig_key: Option, - - /// The XFR status of the zone. - xfr_status: ZoneRefreshStatus, + tsig_key: Option, }, } @@ -384,40 +377,6 @@ impl Display for ZoneSource { } } -/// Support parsing of ``-source`` command line arguments. -/// -/// Supported forms: -/// - `[:][^]` -/// - `` -impl From<&str> for ZoneSource { - fn from(mut s: &str) -> Self { - // Split out any provided TSIG key from the rest of the - // source argument. - let tsig_key = s.split_once('^').map(|(new_s, k)| { - s = new_s; - k.to_string() - }); - - if let Ok(addr) = s.parse::() { - ZoneSource::Server { - addr, - tsig_key, - xfr_status: Default::default(), - } - } else if let Ok(addr) = s.parse::() { - ZoneSource::Server { - addr: SocketAddr::new(addr, DEFAULT_AXFR_PORT), - tsig_key, - xfr_status: Default::default(), - } - } else { - ZoneSource::Zonefile { - path: Utf8PathBuf::from(s).into_boxed_path(), - } - } - } -} - #[derive(Deserialize, Serialize, Debug, Clone)] pub struct ZonesListResult { pub zones: Vec, diff --git a/crates/cli/src/commands/zone.rs b/crates/cli/src/commands/zone.rs index d1cbaef54..04bad570e 100644 --- a/crates/cli/src/commands/zone.rs +++ b/crates/cli/src/commands/zone.rs @@ -1,6 +1,8 @@ +use std::net::{IpAddr, SocketAddr}; +use std::str::FromStr; use std::time::{Duration, SystemTime}; -use camino::Utf8PathBuf; +use camino::{Utf8Path, Utf8PathBuf}; use crate::ansi; use crate::api::*; @@ -219,7 +221,7 @@ impl Zone { "zone/add", &ZoneAdd { name, - source, + source: source.try_into()?, policy, key_imports, }, @@ -884,3 +886,88 @@ fn kmip_imports(key_type: KeyType, x: &[String]) -> Vec { }) .collect() } + +//------------ ZoneSource ---------------------------------------------------- + +const DEFAULT_NS_PORT: u16 = 53; + +/// How to load the contents of a zone. +#[derive(Debug, Clone)] +pub enum ZoneSource { + /// Don't load the zone at all. + None, + + /// From a zonefile on disk. + Zonefile { + /// The path to the zonefile. + path: Box, + }, + + /// From a DNS server via XFR. + Server { + /// The address of the server. + addr: SocketAddr, + + /// The name of a TSIG key, if any. + tsig_key: Option, + }, +} + +/// Support parsing of ``-source`` command line arguments. +/// +/// Supported forms: +/// - `[:][^]` +/// - `` +impl From<&str> for ZoneSource { + fn from(s: &str) -> Self { + // Split out any provided TSIG key from the rest of the + // source argument. + let (s, tsig_key) = s.split_once('^').unwrap_or((s, "")); + + let tsig_key = if !tsig_key.is_empty() { + Some(tsig_key.to_string()) + } else { + None + }; + + if let Ok(addr) = s.parse::() { + eprintln!( + "SocketAddr: {addr} (port={}, ipv6={})", + addr.port(), + addr.is_ipv6() + ); + ZoneSource::Server { addr, tsig_key } + } else if let Ok(addr) = s.parse::() { + eprintln!("IpAddr: {addr} (ipv6={})", addr.is_ipv6()); + ZoneSource::Server { + addr: SocketAddr::new(addr, DEFAULT_NS_PORT), + tsig_key, + } + } else { + ZoneSource::Zonefile { + path: Utf8PathBuf::from(s).into_boxed_path(), + } + } + } +} + +impl TryFrom for cascade_api::ZoneSource { + type Error = String; + + fn try_from(source: ZoneSource) -> Result { + Ok(match source { + ZoneSource::None => cascade_api::ZoneSource::None, + ZoneSource::Zonefile { path } => cascade_api::ZoneSource::Zonefile { path }, + ZoneSource::Server { addr, tsig_key } => { + let tsig_key = if let Some(tsig_key) = tsig_key { + Some(TsigKeyName::from_str(&tsig_key).map_err(|err| { + format!("TSIG key name '{tsig_key}' is not a valid domain name: {err}") + })?) + } else { + None + }; + cascade_api::ZoneSource::Server { addr, tsig_key } + } + }) + } +} diff --git a/etc/policy.template.toml b/etc/policy.template.toml index 0af41f893..d94b04fe2 100644 --- a/etc/policy.template.toml +++ b/etc/policy.template.toml @@ -165,7 +165,7 @@ auto-remove = true # If not specified, the nameserver specified by the SOA MNAME field will be # checked. # -# publication_nameservers = [] +# publication-nameservers = [] # The management of DNS records by the key manager. # diff --git a/src/center.rs b/src/center.rs index 6c5d3d9c3..0fc6aeaab 100644 --- a/src/center.rs +++ b/src/center.rs @@ -1,7 +1,6 @@ //! Cascade's central command. use std::collections::HashMap; -use std::str::FromStr; use std::{ fmt, io, sync::{Arc, Mutex}, @@ -94,16 +93,8 @@ pub async fn add_zone( source = match api_source { cascade_api::ZoneSource::None => crate::loader::Source::None, cascade_api::ZoneSource::Zonefile { path } => crate::loader::Source::Zonefile { path }, - cascade_api::ZoneSource::Server { - addr, - tsig_key, - xfr_status: _, - } => { + cascade_api::ZoneSource::Server { addr, tsig_key } => { let tsig_key = if let Some(key_name) = tsig_key { - // Verify that the key name is syntactically valid. - let key_name = Name::from_str(&key_name) - .map_err(|err| ZoneAddError::InvalidTsigKeyName(err.to_string()))?; - // Lookup the key in the TSIG key store. let key = state .tsig_store @@ -372,8 +363,6 @@ pub enum ZoneAddError { NoSuchPolicy, /// The specified policy is being deleted. PolicyMidDeletion, - /// The specified TSIG key name is invalid. - InvalidTsigKeyName(String), /// No TSIG key with that name exists. NoSuchTsigKey, /// Some other error occurred. @@ -388,7 +377,6 @@ impl fmt::Display for ZoneAddError { Self::AlreadyExists => "a zone of this name already exists", Self::NoSuchPolicy => "no policy with that name exists", Self::PolicyMidDeletion => "the specified policy is being deleted", - Self::InvalidTsigKeyName(reason) => reason, Self::NoSuchTsigKey => "no TSIG key with that name exists", Self::Other(reason) => reason, }) @@ -401,7 +389,6 @@ impl From for api::ZoneAddError { ZoneAddError::AlreadyExists => Self::AlreadyExists, ZoneAddError::NoSuchPolicy => Self::NoSuchPolicy, ZoneAddError::PolicyMidDeletion => Self::PolicyMidDeletion, - ZoneAddError::InvalidTsigKeyName(reason) => Self::InvalidTsigKeyName(reason), ZoneAddError::NoSuchTsigKey => Self::NoSuchTsigKey, ZoneAddError::Other(reason) => Self::Other(reason), } diff --git a/src/units/http_server.rs b/src/units/http_server.rs index bf6e34eae..816a67dfe 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -384,7 +384,6 @@ impl HttpServer { loader::Source::Server { addr, tsig_key: _ } => api::ZoneSource::Server { addr, tsig_key: None, - xfr_status: Default::default(), }, }; unsigned_review_addr = state From 5f449991f3518f1ee8cc2fcb43d0d9491ed12eaf Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:27:07 +0200 Subject: [PATCH 069/113] Review feedback: Remove unnecesary 'tsig' feature dependency. --- crates/api/Cargo.toml | 2 +- crates/api/src/lib.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index a7d799bd6..70f7cb4d5 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -32,7 +32,7 @@ features = ["serde1"] [dependencies.domain] workspace = true # TODO: Enable and use 'new::base'? -features = ["bytes", "serde", "tsig"] +features = ["bytes", "serde"] # The API uses 'serde' for transforming high-level types to and from the # underlying wire format. diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 7270a0349..377d61918 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -190,7 +190,7 @@ pub struct KmipKeyImport { //----------- TsigKeyName ----------------------------------------------------- /// The name of a TSIG key. -pub type TsigKeyName = domain::tsig::KeyName; +pub type TsigKeyName = domain::base::Name>; //----------- TsigAdd --------------------------------------------------------- @@ -330,6 +330,10 @@ impl fmt::Display for ZoneRemoveError { /// How to load the contents of a zone. #[derive(Deserialize, Serialize, Debug, Clone)] +// Allow the large enum variant caused by TsigKeyName using Name> +// to avoid the conversions that would be needed if Name were to be +// used instead. +#[allow(clippy::large_enum_variant)] pub enum ZoneSource { /// Don't load the zone at all. None, From 5ab8b6ac96b61f61acb579201c5ce6282ebc2de1 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:27:16 +0200 Subject: [PATCH 070/113] Remove left in debug statements. --- crates/cli/src/commands/zone.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/cli/src/commands/zone.rs b/crates/cli/src/commands/zone.rs index 04bad570e..e98fd7433 100644 --- a/crates/cli/src/commands/zone.rs +++ b/crates/cli/src/commands/zone.rs @@ -931,14 +931,8 @@ impl From<&str> for ZoneSource { }; if let Ok(addr) = s.parse::() { - eprintln!( - "SocketAddr: {addr} (port={}, ipv6={})", - addr.port(), - addr.is_ipv6() - ); ZoneSource::Server { addr, tsig_key } } else if let Ok(addr) = s.parse::() { - eprintln!("IpAddr: {addr} (ipv6={})", addr.is_ipv6()); ZoneSource::Server { addr: SocketAddr::new(addr, DEFAULT_NS_PORT), tsig_key, From 8354c5b1f7a3313450b8bcf24687de582fa62755 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:45:25 +0200 Subject: [PATCH 071/113] Review feedback: use explicit error variant. --- crates/api/src/lib.rs | 6 +++--- src/policy/mod.rs | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 377d61918..3459f9fff 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -760,14 +760,14 @@ pub struct KeyMsg { #[derive(Deserialize, Serialize, Debug, Clone)] pub enum PolicyReloadError { Io(Utf8PathBuf, String), - Check(String), + NoSuchTsigKey(TsigKeyName), } impl Display for PolicyReloadError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - PolicyReloadError::Io(p, e) => format!("{p}: {e}").fmt(f), - PolicyReloadError::Check(e) => e.to_string().fmt(f), + PolicyReloadError::Io(p, e) => write!(f, "{p}: {e}"), + PolicyReloadError::NoSuchTsigKey(k) => write!(f, "no TSIG key with name '{k}' exists"), } } } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 4614ffedb..d2cdc98c7 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -212,9 +212,7 @@ fn check_policy(policy: &PolicyVersion, tsig_store: &TsigStore) -> Result<(), Po for tsig_name in tsig_names { tsig_store .get(tsig_name) - .ok_or(PolicyReloadError::Check(format!( - "unknown TSIG key '{tsig_name}'" - )))?; + .ok_or(PolicyReloadError::NoSuchTsigKey(tsig_name.clone()))?; } Ok(()) } From c5f781976454de09c79f831d8a0d2cce8d014ac4 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:02:00 +0200 Subject: [PATCH 072/113] Review feedback: be more specific about the arguments to be supplied to the `tsig add` subcommand. --- doc/manual/source/man/cascade-tsig.rst | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 2ecbfc7df..015ea7fd9 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -56,22 +56,29 @@ Arguments for :subcmd:`tsig add` -------------------------------- .. option:: +.. option:: []:: - The name of the TSIG key to add. + The name of the TSIG key to add, or a complete TSIG key specification. - Alternatively this argument also supports dig syntax for specifying all of - the TSIG properties at once in colon separated form. The colon separated - syntax cannot be used in combination with the ``--alg`` and ``--secret`` - options. If ```` is not specified it defaults to SHA256. + TSIG key names must be valid domain names. + + A complete TSIG key specification consists of an optional algorithm + (default ``hmac-sha256``), a key name and the secret key material. When a + complete TSIG key specification is supplied, supplying the ```` + and ```` arguments as well will result in an error. + + Secret key material must be the correct length for the specified algorithm + and must be encoded using the RFC 4648 Base64 encoding. .. option:: - The TSIG algorithm of the specified TSIG key. Can be one of: hmac-sha1, - hmac-sha256, hmac-sha384 or hmac-sha512. + The TSIG algorithm of the specified TSIG key. Can be one of: ``hmac-sha1``, + ``hmac-sha256``, ``hmac-sha384`` or ``hmac-sha512``. .. option:: - A base64 encoded string defining the actual TSIG key material bytes. + RFC 4648 Base64 encoded secret key material. The number of bytes prior to + encoding must be correct for the specified ````. See Also -------- From 8cdaa29b4d2f895486bf28dcb11f0a54994d3f9b Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:09:30 +0200 Subject: [PATCH 073/113] Review feedback: Note the insecure nature of using a command-line argument to supply a secret. --- doc/manual/source/man/cascade-tsig.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 015ea7fd9..44c799a71 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -70,6 +70,10 @@ Arguments for :subcmd:`tsig add` Secret key material must be the correct length for the specified algorithm and must be encoded using the RFC 4648 Base64 encoding. + .. note:: Secret key material supplied via a command-line argument may be + visible to other processes running on the same computer as the + Cascade CLI. + .. option:: The TSIG algorithm of the specified TSIG key. Can be one of: ``hmac-sha1``, @@ -80,6 +84,10 @@ Arguments for :subcmd:`tsig add` RFC 4648 Base64 encoded secret key material. The number of bytes prior to encoding must be correct for the specified ````. + .. note:: Secret key material supplied via a command-line argument may be + visible to other processes running on the same computer as the + Cascade CLI. + See Also -------- From 5da60cb6e4268d69e5c55b66b294e0491b23f5b8 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:23:47 +0200 Subject: [PATCH 074/113] Review feedback: improvements to the `zone add` documentation. --- doc/manual/source/man/cascade-zone.rst | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/doc/manual/source/man/cascade-zone.rst b/doc/manual/source/man/cascade-zone.rst index 7bcead194..8d3cb89d2 100644 --- a/doc/manual/source/man/cascade-zone.rst +++ b/doc/manual/source/man/cascade-zone.rst @@ -86,34 +86,33 @@ Commands Options for :subcmd:`zone add` ------------------------------ -.. option:: --source +.. option:: --source [:][^] The zone source can be the IP address of an upstream nameserver (with or without port, defaults to port 53) or the path to a zone file locally available to the ``cascaded`` daemon.` - When specifying an upstream nameserver you may also optionally suffix it with - ``^`` to indicate that the specified RFC 8945 TSIG key should - be used to sign any SOA, AXFR and IXFR queries that will be sent to the - upstream source. + When specifying an upstream nameserver you may also optionally specify + the name of an :rfc:8945 TSIG key that should be used to authenticate + communication with the upstream. Zones sourced from an upstream nameserver will be automatically updated if - a new version is detected. This can happen if the upstream nameserver sends - an RFC 1996 NOTIFY message to Cascade, or if a via an IXFR or SOA query it - is discovered that the SOA SERIAL at the upstream nameserver is "higher" [1] - than the last version that Cascade loaded, or due to an operator issuing a - `zone reload` command. + a new version is detected via a SOA query, either based on the zone's SOA + record timers, or in response to an :rfc:1996 NOTIFY message from the upstream. + + Zones can also be manualy updated via :program:`cascade` :subcmd:`reload`. For zones that have already been retrieved at least once via AXFR, subsequent refreshes will attempt to use IXFR and fallback to AXFR if IXFR is not available. - .. note:: When providing the path to a zone file to load, if :subcmd:`zone - add` is executed on a different host than where the ``cascaded`` - daemon is running the path must be valid on the **daemon** host. + .. note:: When running :program:`cascade` :subcmd:`zone add` from a + different host than where the Cascade daemon is running, make + sure that the source (whether filesystem path or IP address) is + reachable by the Cascade daemon. - .. note:: Note: In order to use a TSIG key you MUST also supply the key to - Cascade via :subcmd:`tsig add`. + .. note:: If using a TSIG key the key must first be added to Cascade via + :program:`cascade` :subcmd:`tsig add`. [1]: https://www.rfc-editor.org/rfc/rfc1982#section-3.2 From 555b728650dc0b1e2dd38b55205eb00a1968a10f Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:29:26 +0200 Subject: [PATCH 075/113] Consistently use the :RFC:`NNN` linking mechanism in the docs. --- doc/manual/source/faq.rst | 2 +- doc/manual/source/limitations.rst | 2 +- doc/manual/source/man/cascade-tsig.rst | 8 ++++---- .../source/man/cascaded-policy.toml.rst | 4 ++-- doc/manual/source/nsd.rst | 2 +- doc/manual/source/zone-transfers.rst | 20 +++++++++---------- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/manual/source/faq.rst b/doc/manual/source/faq.rst index 2c2e59cf0..03dc0549f 100644 --- a/doc/manual/source/faq.rst +++ b/doc/manual/source/faq.rst @@ -57,7 +57,7 @@ Key rolls should be automatic and frequent. Frequent key rolls help to ensure that they become normal operational practice and not an exception. Key rolls should be automated as much as possible to avoid mistakes. -Unfortunately, the standard for updating DS records (CDS, RFC 8078) is not +Unfortunately, the standard for updating DS records (CDS, :RFC:`8078`) is not widely implemented so in many cases a KSK roll has to have a manual component, namely submitting and updating the DS record at the parent. diff --git a/doc/manual/source/limitations.rst b/doc/manual/source/limitations.rst index ecda1e88f..6c6535427 100644 --- a/doc/manual/source/limitations.rst +++ b/doc/manual/source/limitations.rst @@ -82,7 +82,7 @@ Other known limitations ----------------------- - No NOTIFY retry support. -- No NOTIFY "Notify Set" (RFC 1996) discovery. +- No NOTIFY "Notify Set" (:RFC:`1996`) discovery. - No KMIP batching support. - No DNS UPDATE support. - HSM algorithm support is limited to RSASHA256 and ECDSAP256SHA256. diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 44c799a71..8de5e7f80 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -17,7 +17,7 @@ Synopsis Description ----------- -Manage RFC 8945 TSIG keys for authenticating zone transfer (AXFR, IXFR) and +Manage :RFC:`8945` (TSIG) keys for authenticating zone transfer (AXFR, IXFR) and related messages (SOA and NOTIFY). .. tip:: Cascade isn't currently able to generate TSIG keys itself. @@ -68,7 +68,7 @@ Arguments for :subcmd:`tsig add` and ```` arguments as well will result in an error. Secret key material must be the correct length for the specified algorithm - and must be encoded using the RFC 4648 Base64 encoding. + and must be encoded using the :RFC:`4648` Base64 encoding. .. note:: Secret key material supplied via a command-line argument may be visible to other processes running on the same computer as the @@ -81,8 +81,8 @@ Arguments for :subcmd:`tsig add` .. option:: - RFC 4648 Base64 encoded secret key material. The number of bytes prior to - encoding must be correct for the specified ````. + :RFC:`4648` Base64 encoded secret key material. The number of bytes prior + to encoding must be correct for the specified ````. .. note:: Secret key material supplied via a command-line argument may be visible to other processes running on the same computer as the diff --git a/doc/manual/source/man/cascaded-policy.toml.rst b/doc/manual/source/man/cascaded-policy.toml.rst index 0eac8ee8d..00fbc2788 100644 --- a/doc/manual/source/man/cascaded-policy.toml.rst +++ b/doc/manual/source/man/cascaded-policy.toml.rst @@ -456,8 +456,8 @@ The ``[signer.denial]`` section. Supported options: - - ``nsec``: Use NSEC records (RFC 4034). - - ``nsec3``: Use NSEC3 records (RFC 5155). + - ``nsec``: Use NSEC records (:RFC:`4034`). + - ``nsec3``: Use NSEC3 records (:RFC:`5155`). .. option:: opt-out = false diff --git a/doc/manual/source/nsd.rst b/doc/manual/source/nsd.rst index be80a54bc..78be699d9 100644 --- a/doc/manual/source/nsd.rst +++ b/doc/manual/source/nsd.rst @@ -18,7 +18,7 @@ Using NSD as a primary to Cascade To use NSD as an upstream name server of Cascade you must add a zone to NSD that refers to Cascade as a secondary name server. If enabled in NSD, NSD will -send an RFC 1996 DNS NOTIFY message to Cascade notifying it when changes to +send an :RFC:`1996` DNS NOTIFY message to Cascade notifying it when changes to the zone occur. The NOTIFY message will trigger Cascade to perform an AXFR transfer to fetch diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst index 089fb63ec..8dfa9765c 100644 --- a/doc/manual/source/zone-transfers.rst +++ b/doc/manual/source/zone-transfers.rst @@ -6,15 +6,15 @@ public downstream nameservers. The hidden upstream serves the unsigned zone, Cascade signs it, and passes it to the downstream nameservers for publication to consumers. -Communication of changed zone records from upstream to downstream should be -done via the network using the RFC 5936 DNS Zone Transfer Protocol (AXFR) and -RFC 1995 Incremental Zone Transfer (IXFR) protocols. +Communication of changed zone records from upstream to downstream should +be done via the network using the :RFC:`5936` (AXFR) and :RFC:`1995` (IXFR) +protocols. -Securing the transferred data can be done using RFC 8945 Secret Key -Authentication for DNS (TSIG) keys, using a shared secret communicated out of -band to the nameservers sending and receiving the zone records. +Securing the transferred data can be done using :RFC:`8945` (TSIG) keys, +using a shared secret communicated out of band to the nameservers sending and +receiving the zone records. -Cascade supports timely discovery of zone changes via RFC 1996 (DNS NOTIFY). +Cascade supports timely discovery of zone changes via :RFC:`1996` (NOTIFY). If no NOTIFY message is received by Cascade, Cascade will instead discover new versions of the zone by sending SOA queries periodically to the upstream, the frequency of which is determined by the timers on the zone's SOA record. @@ -66,9 +66,9 @@ Using zone transfers with a downstream server Cascade permits zone transfers by default, no configuration is required. -To ensure timely update by secondaries, Cascade can be configured to send RFC -1996 DNS NOTIFY messages to specified secondaries. This is done via the policy -setting ``server.outbound.send-notify-to``. +To ensure timely update by secondaries, Cascade can be configured to send +:RFC:`1996` (NOTIFY) messages to specified secondaries. This is done via the +policy setting ``server.outbound.send-notify-to``. .. note:: The policy file will need to be reloaded via ``cascade policy reload`` before adding the zone. Also, when adding the zone you From f61ccb7b74b0e8fecd59d9a0d3283f08ebc8070f Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:30:07 +0200 Subject: [PATCH 076/113] Regenerate man pages. --- doc/manual/build/man/cascade-debug.1 | 2 +- doc/manual/build/man/cascade-health.1 | 2 +- doc/manual/build/man/cascade-hsm.1 | 2 +- doc/manual/build/man/cascade-keyset.1 | 2 +- doc/manual/build/man/cascade-policy.1 | 2 +- doc/manual/build/man/cascade-status.1 | 2 +- doc/manual/build/man/cascade-template.1 | 2 +- doc/manual/build/man/cascade-tsig.1 | 57 +++++++++++++++------ doc/manual/build/man/cascade-zone.1 | 33 ++++++------ doc/manual/build/man/cascade.1 | 2 +- doc/manual/build/man/cascaded-config.toml.5 | 2 +- doc/manual/build/man/cascaded-policy.toml.5 | 8 +-- doc/manual/build/man/cascaded.1 | 2 +- 13 files changed, 70 insertions(+), 48 deletions(-) diff --git a/doc/manual/build/man/cascade-debug.1 b/doc/manual/build/man/cascade-debug.1 index a058a215b..32a72ad4c 100644 --- a/doc/manual/build/man/cascade-debug.1 +++ b/doc/manual/build/man/cascade-debug.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-DEBUG" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-DEBUG" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-debug \- Debug / troubleshoot Cascade .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-health.1 b/doc/manual/build/man/cascade-health.1 index a219ff50c..369f67f71 100644 --- a/doc/manual/build/man/cascade-health.1 +++ b/doc/manual/build/man/cascade-health.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HEALTH" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HEALTH" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-health \- Check the health of Cascade .sp diff --git a/doc/manual/build/man/cascade-hsm.1 b/doc/manual/build/man/cascade-hsm.1 index 3830445b4..ab0fa0f58 100644 --- a/doc/manual/build/man/cascade-hsm.1 +++ b/doc/manual/build/man/cascade-hsm.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HSM" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HSM" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-hsm \- Manage HSMs .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-keyset.1 b/doc/manual/build/man/cascade-keyset.1 index 9e31e41ec..6fbbac550 100644 --- a/doc/manual/build/man/cascade-keyset.1 +++ b/doc/manual/build/man/cascade-keyset.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-KEYSET" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-KEYSET" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-keyset \- Execute manual key roll or key removal commands .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-policy.1 b/doc/manual/build/man/cascade-policy.1 index 553fe6b32..6ab41c8ce 100644 --- a/doc/manual/build/man/cascade-policy.1 +++ b/doc/manual/build/man/cascade-policy.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-POLICY" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-POLICY" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-policy \- Manage policies .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-status.1 b/doc/manual/build/man/cascade-status.1 index 70c9649e0..848edcc9a 100644 --- a/doc/manual/build/man/cascade-status.1 +++ b/doc/manual/build/man/cascade-status.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-STATUS" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-STATUS" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-status \- Show the status of Cascade .sp diff --git a/doc/manual/build/man/cascade-template.1 b/doc/manual/build/man/cascade-template.1 index 7820a9874..eff0f3296 100644 --- a/doc/manual/build/man/cascade-template.1 +++ b/doc/manual/build/man/cascade-template.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-TEMPLATE" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-TEMPLATE" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-template \- Print example config or policy files .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-tsig.1 b/doc/manual/build/man/cascade-tsig.1 index 78d132985..db8c5c293 100644 --- a/doc/manual/build/man/cascade-tsig.1 +++ b/doc/manual/build/man/cascade-tsig.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-TSIG" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-TSIG" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-tsig \- Manage TSIG keys .sp @@ -44,7 +44,7 @@ Added in version 0.1.0\-beta1. \fBcascade\fP \fB[GLOBAL OPTIONS]\fP tsig \fI\%remove\fP \fB\fP .SH DESCRIPTION .sp -Manage RFC 8945 TSIG keys for authenticating zone transfer (AXFR, IXFR) and +Manage \X'tty: link https://datatracker.ietf.org/doc/html/rfc8945.html'\fI\%RFC 8945\fP\X'tty: link' (TSIG) keys for authenticating zone transfer (AXFR, IXFR) and related messages (SOA and NOTIFY). .sp \fBTIP:\fP @@ -80,13 +80,8 @@ Remove a registered TSIG key. \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 -Returns an error if the key does not exist in the TSIG key store -or if any zone exists that is configured to authenticate with an -.INDENT 0.0 -.INDENT 3.5 -upstream source using the specified TSIG key. -.UNINDENT -.UNINDENT +Returns an error if the key does not exist in the TSIG key store, +or if the key is still referenced by other configuration. .UNINDENT .UNINDENT .UNINDENT @@ -94,23 +89,51 @@ upstream source using the specified TSIG key. .INDENT 0.0 .TP .B -The name of the TSIG key to add. +.UNINDENT +.INDENT 0.0 +.TP +.B []:: +The name of the TSIG key to add, or a complete TSIG key specification. +.sp +TSIG key names must be valid domain names. +.sp +A complete TSIG key specification consists of an optional algorithm +(default \fBhmac\-sha256\fP), a key name and the secret key material. When a +complete TSIG key specification is supplied, supplying the \fB\fP +and \fB\fP arguments as well will result in an error. .sp -Alternatively this argument also supports dig syntax for specifying all of -the TSIG properties at once in colon separated form. The colon separated -syntax cannot be used in combination with the \fB\-\-alg\fP and \fB\-\-secret\fP -options. If \fB\fP is not specified it defaults to SHA256. +Secret key material must be the correct length for the specified algorithm +and must be encoded using the \X'tty: link https://datatracker.ietf.org/doc/html/rfc4648.html'\fI\%RFC 4648\fP\X'tty: link' Base64 encoding. +.sp +\fBNOTE:\fP +.INDENT 7.0 +.INDENT 3.5 +Secret key material supplied via a command\-line argument may be +visible to other processes running on the same computer as the +Cascade CLI. +.UNINDENT +.UNINDENT .UNINDENT .INDENT 0.0 .TP .B -The TSIG algorithm of the specified TSIG key. Can be one of: hmac\-sha1, -hmac\-sha256, hmac\-sha384 or hmac\-sha512. +The TSIG algorithm of the specified TSIG key. Can be one of: \fBhmac\-sha1\fP, +\fBhmac\-sha256\fP, \fBhmac\-sha384\fP or \fBhmac\-sha512\fP\&. .UNINDENT .INDENT 0.0 .TP .B -A base64 encoded string defining the actual TSIG key material bytes. +\X'tty: link https://datatracker.ietf.org/doc/html/rfc4648.html'\fI\%RFC 4648\fP\X'tty: link' Base64 encoded secret key material. The number of bytes prior +to encoding must be correct for the specified \fB\fP\&. +.sp +\fBNOTE:\fP +.INDENT 7.0 +.INDENT 3.5 +Secret key material supplied via a command\-line argument may be +visible to other processes running on the same computer as the +Cascade CLI. +.UNINDENT +.UNINDENT .UNINDENT .SH SEE ALSO .INDENT 0.0 diff --git a/doc/manual/build/man/cascade-zone.1 b/doc/manual/build/man/cascade-zone.1 index daa6c6799..bc73c4c52 100644 --- a/doc/manual/build/man/cascade-zone.1 +++ b/doc/manual/build/man/cascade-zone.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-ZONE" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-ZONE" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-zone \- Manage zones .SH SYNOPSIS @@ -74,7 +74,7 @@ Remove a zone. \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 -Once removed downstream servers will no longer be able to fetch +Once removed, downstream servers will no longer be able to fetch the zone! .UNINDENT .UNINDENT @@ -122,22 +122,20 @@ Get the history of a single zone. .SH OPTIONS FOR ZONE ADD .INDENT 0.0 .TP -.B \-\-source +.B \-\-source [:][^] The zone source can be the IP address of an upstream nameserver (with or without port, defaults to port 53) or the path to a zone file locally available to the \fBcascaded\fP daemon.\(ga .sp -When specifying an upstream nameserver you may also optionally suffix it with -\fB^\fP to indicate that the specified RFC 8945 TSIG key should -be used to sign any SOA, AXFR and IXFR queries that will be sent to the -upstream source. +When specifying an upstream nameserver you may also optionally specify +the name of an :rfc:8945 TSIG key that should be used to authenticate +communication with the upstream. .sp Zones sourced from an upstream nameserver will be automatically updated if -a new version is detected. This can happen if the upstream nameserver sends -an RFC 1996 NOTIFY message to Cascade, or if a via an IXFR or SOA query it -is discovered that the SOA SERIAL at the upstream nameserver is \(dqhigher\(dq [1] -than the last version that Cascade loaded, or due to an operator issuing a -\fIzone reload\fP command. +a new version is detected via a SOA query, either based on the zone\(aqs SOA +record timers, or in response to an :rfc:1996 NOTIFY message from the upstream. +.sp +Zones can also be manualy updated via \fBcascade\fP \fI\%reload\fP\&. .sp For zones that have already been retrieved at least once via AXFR, subsequent refreshes will attempt to use IXFR and fallback to AXFR if IXFR is not @@ -146,17 +144,18 @@ available. \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 -When providing the path to a zone file to load, if \fBzone -add\fP is executed on a different host than where the \fBcascaded\fP -daemon is running the path must be valid on the \fBdaemon\fP host. +When running \fBcascade\fP \fBzone add\fP from a +different host than where the Cascade daemon is running, make +sure that the source (whether filesystem path or IP address) is +reachable by the Cascade daemon. .UNINDENT .UNINDENT .sp \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 -Note: In order to use a TSIG key you MUST also supply the key to -Cascade via \fBtsig add\fP\&. +If using a TSIG key the key must first be added to Cascade via +\fBcascade\fP \fBtsig add\fP\&. .UNINDENT .UNINDENT .sp diff --git a/doc/manual/build/man/cascade.1 b/doc/manual/build/man/cascade.1 index 3f77a7fa0..5317c1944 100644 --- a/doc/manual/build/man/cascade.1 +++ b/doc/manual/build/man/cascade.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade \- Cascade CLI .SH SYNOPSIS diff --git a/doc/manual/build/man/cascaded-config.toml.5 b/doc/manual/build/man/cascaded-config.toml.5 index bddf152ae..41318719c 100644 --- a/doc/manual/build/man/cascaded-config.toml.5 +++ b/doc/manual/build/man/cascaded-config.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-CONFIG.TOML" "5" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-CONFIG.TOML" "5" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-config.toml \- Cascade configuration file .sp diff --git a/doc/manual/build/man/cascaded-policy.toml.5 b/doc/manual/build/man/cascaded-policy.toml.5 index 11989d680..7a39b4aa2 100644 --- a/doc/manual/build/man/cascaded-policy.toml.5 +++ b/doc/manual/build/man/cascaded-policy.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-POLICY.TOML" "5" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-POLICY.TOML" "5" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-policy.toml \- Cascade policy file format .sp @@ -348,7 +348,7 @@ key roll. Each nameserver is specified as a string with the syntax: .INDENT 7.0 .INDENT 3.5 -\fB:[^[TSIG\-Key\-Name]]\fP +\fB[:][^[TSIG_KEY_NAME]\fP .UNINDENT .UNINDENT .sp @@ -571,9 +571,9 @@ The type of denial\-of\-existence records to generate. Supported options: .INDENT 7.0 .IP \(bu 2 -\fBnsec\fP: Use NSEC records (RFC 4034). +\fBnsec\fP: Use NSEC records (\X'tty: link https://datatracker.ietf.org/doc/html/rfc4034.html'\fI\%RFC 4034\fP\X'tty: link'). .IP \(bu 2 -\fBnsec3\fP: Use NSEC3 records (RFC 5155). +\fBnsec3\fP: Use NSEC3 records (\X'tty: link https://datatracker.ietf.org/doc/html/rfc5155.html'\fI\%RFC 5155\fP\X'tty: link'). .UNINDENT .UNINDENT .INDENT 0.0 diff --git a/doc/manual/build/man/cascaded.1 b/doc/manual/build/man/cascaded.1 index 59d33333a..0360020df 100644 --- a/doc/manual/build/man/cascaded.1 +++ b/doc/manual/build/man/cascaded.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED" "1" "Apr 20, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded \- DNSSEC signer .SH SYNOPSIS From bc7db248c7408235ac8a19bceb61c4721c0b293a Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:32:41 +0200 Subject: [PATCH 077/113] Review feedback. --- doc/manual/source/man/cascaded-policy.toml.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/manual/source/man/cascaded-policy.toml.rst b/doc/manual/source/man/cascaded-policy.toml.rst index 00fbc2788..8bb3cac3f 100644 --- a/doc/manual/source/man/cascaded-policy.toml.rst +++ b/doc/manual/source/man/cascaded-policy.toml.rst @@ -255,8 +255,8 @@ The ``[key-manager]`` section. Whether to automatically remove expired keys. - If this is set, expired keys will be removed automatically (by deleting the - files for on-disk keys or removing it from the HSM). + If this option is set, expired keys will be removed automatically (by + deleting the files for on-disk keys or removing it from the HSM). .. option:: publication-nameservers = [] @@ -267,8 +267,8 @@ The ``[key-manager]`` section. ``[:][^[TSIG_KEY_NAME]`` - If not specified then the nameserver specified in the zone apex SOA MNAME - field will be queried. + If this option is not set, the nameserver specified in the zone apex SOA + MNAME field will be queried. The management of DNS records by the key manager. +++++++++++++++++++++++++++++++++++++++++++++++++ From ef966b4eb09a8c5e4fbd62a1fd874ea4d30356c2 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:34:06 +0200 Subject: [PATCH 078/113] Review feedback: Link to man page. --- doc/manual/source/nsd.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/manual/source/nsd.rst b/doc/manual/source/nsd.rst index 78be699d9..d8d0bf916 100644 --- a/doc/manual/source/nsd.rst +++ b/doc/manual/source/nsd.rst @@ -75,8 +75,7 @@ information. .. tip:: Remember to reload the NSD configuration or restart NSD so that changes to the configuration take effect. -Adding the TSIG key to Cascade is done using the ``cascade tsig add`` CLI -command, e.g. like so: +To add the TSIG key to Cascade use :program:`cascade` :subcmd:`tsig add`: .. code-block:: bash From 3056a48c7da060e5528ed0bc0804dd2e1db557c5 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:37:45 +0200 Subject: [PATCH 079/113] Review feedback. --- doc/manual/source/zone-transfers.rst | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst index 8dfa9765c..02bb7fab0 100644 --- a/doc/manual/source/zone-transfers.rst +++ b/doc/manual/source/zone-transfers.rst @@ -14,18 +14,15 @@ Securing the transferred data can be done using :RFC:`8945` (TSIG) keys, using a shared secret communicated out of band to the nameservers sending and receiving the zone records. -Cascade supports timely discovery of zone changes via :RFC:`1996` (NOTIFY). -If no NOTIFY message is received by Cascade, Cascade will instead discover -new versions of the zone by sending SOA queries periodically to the upstream, -the frequency of which is determined by the timers on the zone's SOA record. - -.. note:: Cascade also supports loading the zone from a file. However, if only - a small fraction of the records in the zone change from one version - to the next, loading the entire zone every time the zone changes - will require more time, CPU and memory compared to processing - only the differences when using IXFR. Additionally, Cascade has no - built-in support for writing signed zone files to disk, if needed - this could be done by a signed review hook. +Cascade supports timely discovery of zone changes by sending SOA queries to +the upsream nameserver, either in response to an :RFC:`1996` NOTIFY message or +based on the zone's SOA timers. + +.. note:: Cascade also supports loading the zone from a file. However, if + only a small fraction of the records in the zone change from one + version to the next, loading the entire file every time the zone + file changes will require more time, CPU and memory compared to + processing only the differences when using IXFR. Using zone transfers with an upstream nameserver ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 838b6758795213794088dd1bbfd30cbe99f39f71 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:39:56 +0200 Subject: [PATCH 080/113] Review feedback. --- doc/manual/source/zone-transfers.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst index 02bb7fab0..334e3adce 100644 --- a/doc/manual/source/zone-transfers.rst +++ b/doc/manual/source/zone-transfers.rst @@ -35,11 +35,8 @@ adding the zone. $ cascade zone add --source [:] ... -Cascade will then attempt to fetch the zone from the specified name or IP -address using AXFR. Subsequent fetches will attempt to use IXFR to transfer -only the differences, falling back to AXFR when needed. Subsequent fetches -will be triggered by NOTIFY messages received from the upstream nameserver or -expiry of the SOA REFRESH or RETRY timers. +Cascade will then attempt to fetch the zone. Where possible it will fetch +newer versions of the zone incrementally, as this is more efficient. Securing zone transfers with an upstream nameserver ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From ed68983aad30a43269227884082cee714a9acf4c Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:41:27 +0200 Subject: [PATCH 081/113] Minor tweak. --- doc/manual/source/zone-transfers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst index 334e3adce..e9036bb94 100644 --- a/doc/manual/source/zone-transfers.rst +++ b/doc/manual/source/zone-transfers.rst @@ -53,7 +53,7 @@ When adding a zone the TSIG key name can then be referred to like so: .. code-block:: bash - $ cascade zone add --source [:]^ + $ cascade zone add --source [:][^] Using zone transfers with a downstream server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 399b3840b522f0bc7aed447f6c9a8f88cf2bd6e8 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:43:37 +0200 Subject: [PATCH 082/113] More review feedback. --- doc/manual/source/zone-transfers.rst | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst index e9036bb94..59f5c9a7a 100644 --- a/doc/manual/source/zone-transfers.rst +++ b/doc/manual/source/zone-transfers.rst @@ -28,12 +28,8 @@ Using zone transfers with an upstream nameserver ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To instruct Cascade to transfer a zone via the network instead of loading -it from a file you must supply an upstream nameserver name or address when -adding the zone. - -.. code-block:: bash - - $ cascade zone add --source [:] ... +it from a file you must supply an upstream nameserver IP address when +adding the zone. See :program:`cascade` :subcmd:`zone add`. Cascade will then attempt to fetch the zone. Where possible it will fetch newer versions of the zone incrementally, as this is more efficient. @@ -41,19 +37,9 @@ newer versions of the zone incrementally, as this is more efficient. Securing zone transfers with an upstream nameserver ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Cascade can be instructed to authenticate the upstream nameserver by use of -a TSIG key. The TSIG key to use must be provided to Cascade _before_ adding -the zone: - -.. code-block:: bash - - $ cascade tsig add - -When adding a zone the TSIG key name can then be referred to like so: - -.. code-block:: bash - - $ cascade zone add --source [:][^] +Cascade can be instructed to authenticate the upstream nameserver by use of a +TSIG key. The TSIG key to use must be provided to Cascade _before_ adding the +zone. See :program:`cascade` :subcmd:`tsig add`. Using zone transfers with a downstream server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From f7529db569f3c4e32e3ce7e509a7ff167a0716e6 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:48:56 +0200 Subject: [PATCH 083/113] More review feedback and tweaks. --- doc/manual/source/zone-transfers.rst | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst index 59f5c9a7a..cd47d1a4f 100644 --- a/doc/manual/source/zone-transfers.rst +++ b/doc/manual/source/zone-transfers.rst @@ -47,18 +47,15 @@ Using zone transfers with a downstream server Cascade permits zone transfers by default, no configuration is required. To ensure timely update by secondaries, Cascade can be configured to send -:RFC:`1996` (NOTIFY) messages to specified secondaries. This is done via the -policy setting ``server.outbound.send-notify-to``. - -.. note:: The policy file will need to be reloaded via ``cascade policy - reload`` before adding the zone. Also, when adding the zone you - will need to pass the `--policy` argument specifying the relevant - policy to apply to the zone. - -.. tip:: If a TSIG key has been added to Cascade via ``cascade tsig add``, - you can instruct Cascade to authentiate itself to downstreams - using a specified TSIG key by adding `^` to the - ``server.outbound.send-notify-to`` value. +:RFC:`1996` NOTIFY messages to specified secondaries. This is done via the +policy setting ``server.outbound.send-notify-to``, optionally specifying an +:RFC:8945` TSIG key to use to authenticate communication. + +.. tip:: Remember to reload the policy file after changing it. See + :program:`cascade` :subcmd:`policy reload`. + +.. tip:: Use :program:`cascade` :subcmd:`tsig add` to add a TSIG key to + Cascade _before_ reloading policy file changes. Controlling automatic key rollover zone transfer settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 4c6c181688d60c7a813323335e083631ca154fac Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:50:42 +0200 Subject: [PATCH 084/113] Regenerate man pages. --- doc/manual/build/man/cascaded-policy.toml.5 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/manual/build/man/cascaded-policy.toml.5 b/doc/manual/build/man/cascaded-policy.toml.5 index 7a39b4aa2..79838d5d4 100644 --- a/doc/manual/build/man/cascaded-policy.toml.5 +++ b/doc/manual/build/man/cascaded-policy.toml.5 @@ -336,8 +336,8 @@ Supported options: .B auto\-remove = true Whether to automatically remove expired keys. .sp -If this is set, expired keys will be removed automatically (by deleting the -files for on\-disk keys or removing it from the HSM). +If this option is set, expired keys will be removed automatically (by +deleting the files for on\-disk keys or removing it from the HSM). .UNINDENT .INDENT 0.0 .TP @@ -352,8 +352,8 @@ Each nameserver is specified as a string with the syntax: .UNINDENT .UNINDENT .sp -If not specified then the nameserver specified in the zone apex SOA MNAME -field will be queried. +If this option is not set, the nameserver specified in the zone apex SOA +MNAME field will be queried. .UNINDENT .SS The management of DNS records by the key manager. .sp From a822c42eef76cdc3b854f5bc66d181513dfc12f4 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:54:24 +0200 Subject: [PATCH 085/113] Clippy. --- crates/api/src/lib.rs | 4 ++++ src/policy/mod.rs | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 3459f9fff..11553425f 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -758,6 +758,10 @@ pub struct KeyMsg { } #[derive(Deserialize, Serialize, Debug, Clone)] +// Allow the large enum variant caused by TsigKeyName using Name> +// to avoid the conversions that would be needed if Name were to be +// used instead. +#[allow(clippy::large_enum_variant)] pub enum PolicyReloadError { Io(Utf8PathBuf, String), NoSuchTsigKey(TsigKeyName), diff --git a/src/policy/mod.rs b/src/policy/mod.rs index d2cdc98c7..f1128376e 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -50,6 +50,10 @@ pub enum PolicyChange { /// Reload all policies. /// /// Any changes are reported via the `on_change` callback. +// Allow the large enum variant caused by TsigKeyName using Name> +// to avoid the conversions that would be needed if Name were to be +// used instead. +#[allow(clippy::result_large_err)] pub fn reload_all( policies: &mut foldhash::HashMap, Policy>, config: &Config, @@ -117,6 +121,10 @@ pub fn reload_all( /// /// The current policies are used for logging purposes so we can log whether /// a policy is new, updated, unchanged or removed. +// Allow the large enum variant caused by TsigKeyName using Name> +// to avoid the conversions that would be needed if Name were to be +// used instead. +#[allow(clippy::result_large_err)] pub fn load_all( policies: &foldhash::HashMap, Policy>, config: &Config, @@ -198,6 +206,10 @@ pub fn load_all( } /// Perform a semantic check on the loaded policy. +// Allow the large enum variant caused by TsigKeyName using Name> +// to avoid the conversions that would be needed if Name were to be +// used instead. +#[allow(clippy::result_large_err)] fn check_policy(policy: &PolicyVersion, tsig_store: &TsigStore) -> Result<(), PolicyReloadError> { // Check the publication nameservers for the key manager. Any TSIG key // that is part of those nameservers has to exist in the TSIG key store. From 880cc3fc30d64fb4924ad3e7b73bb4534f12764b Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:59:17 +0200 Subject: [PATCH 086/113] Increase note to warning. --- doc/manual/source/man/cascade-tsig.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 8de5e7f80..113cc007b 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -70,9 +70,9 @@ Arguments for :subcmd:`tsig add` Secret key material must be the correct length for the specified algorithm and must be encoded using the :RFC:`4648` Base64 encoding. - .. note:: Secret key material supplied via a command-line argument may be - visible to other processes running on the same computer as the - Cascade CLI. + .. warning:: Secret key material supplied via a command-line argument may + be visible to other processes running on the same computer as + the Cascade CLI. .. option:: From 44282b92cd532963a91c9b7c75a1a9975721b6c3 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:59:35 +0200 Subject: [PATCH 087/113] Regenerate man pages. --- doc/manual/build/man/cascade-tsig.1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/manual/build/man/cascade-tsig.1 b/doc/manual/build/man/cascade-tsig.1 index db8c5c293..1fe95733b 100644 --- a/doc/manual/build/man/cascade-tsig.1 +++ b/doc/manual/build/man/cascade-tsig.1 @@ -105,12 +105,12 @@ and \fB\fP arguments as well will result in an error. Secret key material must be the correct length for the specified algorithm and must be encoded using the \X'tty: link https://datatracker.ietf.org/doc/html/rfc4648.html'\fI\%RFC 4648\fP\X'tty: link' Base64 encoding. .sp -\fBNOTE:\fP +\fBWARNING:\fP .INDENT 7.0 .INDENT 3.5 -Secret key material supplied via a command\-line argument may be -visible to other processes running on the same computer as the -Cascade CLI. +Secret key material supplied via a command\-line argument may +be visible to other processes running on the same computer as +the Cascade CLI. .UNINDENT .UNINDENT .UNINDENT From ebfc0f3c2f6cd560336f05bf985ad90f44d17ce0 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Thu, 23 Apr 2026 00:03:22 +0200 Subject: [PATCH 088/113] Fix broken RFC links. --- doc/manual/build/man/cascade-zone.1 | 11 +++++------ doc/manual/source/man/cascade-zone.rst | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/doc/manual/build/man/cascade-zone.1 b/doc/manual/build/man/cascade-zone.1 index bc73c4c52..209cbbf64 100644 --- a/doc/manual/build/man/cascade-zone.1 +++ b/doc/manual/build/man/cascade-zone.1 @@ -128,12 +128,13 @@ or without port, defaults to port 53) or the path to a zone file locally available to the \fBcascaded\fP daemon.\(ga .sp When specifying an upstream nameserver you may also optionally specify -the name of an :rfc:8945 TSIG key that should be used to authenticate +the name of an \X'tty: link https://datatracker.ietf.org/doc/html/rfc8945.html'\fI\%RFC 8945\fP\X'tty: link' TSIG key that should be used to authenticate communication with the upstream. .sp -Zones sourced from an upstream nameserver will be automatically updated if -a new version is detected via a SOA query, either based on the zone\(aqs SOA -record timers, or in response to an :rfc:1996 NOTIFY message from the upstream. +Zones sourced from an upstream nameserver will be automatically updated +if a new version is detected via a SOA query, either based on the zone\(aqs +SOA record timers, or in response to an \X'tty: link https://datatracker.ietf.org/doc/html/rfc1996.html'\fI\%RFC 1996\fP\X'tty: link' NOTIFY message from +the upstream. .sp Zones can also be manualy updated via \fBcascade\fP \fI\%reload\fP\&. .sp @@ -158,8 +159,6 @@ If using a TSIG key the key must first be added to Cascade via \fBcascade\fP \fBtsig add\fP\&. .UNINDENT .UNINDENT -.sp -[1]: \X'tty: link https://www.rfc-editor.org/rfc/rfc1982#section-3.2'\fI\%https://www.rfc\-editor.org/rfc/rfc1982#section\-3.2\fP\X'tty: link' .UNINDENT .INDENT 0.0 .TP diff --git a/doc/manual/source/man/cascade-zone.rst b/doc/manual/source/man/cascade-zone.rst index 8d3cb89d2..cb896cb03 100644 --- a/doc/manual/source/man/cascade-zone.rst +++ b/doc/manual/source/man/cascade-zone.rst @@ -93,12 +93,13 @@ Options for :subcmd:`zone add` available to the ``cascaded`` daemon.` When specifying an upstream nameserver you may also optionally specify - the name of an :rfc:8945 TSIG key that should be used to authenticate + the name of an :RFC:`8945` TSIG key that should be used to authenticate communication with the upstream. - Zones sourced from an upstream nameserver will be automatically updated if - a new version is detected via a SOA query, either based on the zone's SOA - record timers, or in response to an :rfc:1996 NOTIFY message from the upstream. + Zones sourced from an upstream nameserver will be automatically updated + if a new version is detected via a SOA query, either based on the zone's + SOA record timers, or in response to an :RFC:`1996` NOTIFY message from + the upstream. Zones can also be manualy updated via :program:`cascade` :subcmd:`reload`. @@ -114,8 +115,6 @@ Options for :subcmd:`zone add` .. note:: If using a TSIG key the key must first be added to Cascade via :program:`cascade` :subcmd:`tsig add`. - [1]: https://www.rfc-editor.org/rfc/rfc1982#section-3.2 - .. option:: --policy Policy to use for this zone. From 5198800dd4637c7eb786740d17cab63a85a6c130 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Thu, 23 Apr 2026 00:04:33 +0200 Subject: [PATCH 089/113] Remove errant back-tick character in docs. --- doc/manual/build/man/cascade-zone.1 | 2 +- doc/manual/source/man/cascade-zone.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/manual/build/man/cascade-zone.1 b/doc/manual/build/man/cascade-zone.1 index 209cbbf64..f6a54cabe 100644 --- a/doc/manual/build/man/cascade-zone.1 +++ b/doc/manual/build/man/cascade-zone.1 @@ -125,7 +125,7 @@ Get the history of a single zone. .B \-\-source [:][^] The zone source can be the IP address of an upstream nameserver (with or without port, defaults to port 53) or the path to a zone file locally -available to the \fBcascaded\fP daemon.\(ga +available to the \fBcascaded\fP daemon. .sp When specifying an upstream nameserver you may also optionally specify the name of an \X'tty: link https://datatracker.ietf.org/doc/html/rfc8945.html'\fI\%RFC 8945\fP\X'tty: link' TSIG key that should be used to authenticate diff --git a/doc/manual/source/man/cascade-zone.rst b/doc/manual/source/man/cascade-zone.rst index cb896cb03..f1a9250aa 100644 --- a/doc/manual/source/man/cascade-zone.rst +++ b/doc/manual/source/man/cascade-zone.rst @@ -90,7 +90,7 @@ Options for :subcmd:`zone add` The zone source can be the IP address of an upstream nameserver (with or without port, defaults to port 53) or the path to a zone file locally - available to the ``cascaded`` daemon.` + available to the ``cascaded`` daemon. When specifying an upstream nameserver you may also optionally specify the name of an :RFC:`8945` TSIG key that should be used to authenticate From 36f49617dd27c54328b890e22dfad9f119b64d4f Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 22 Apr 2026 11:00:51 +0200 Subject: [PATCH 090/113] Add last error to zone status --- crates/api/src/lib.rs | 6 ++++++ crates/cli/src/commands/zone.rs | 15 +++++++++++++++ src/loader/mod.rs | 4 +++- src/units/http_server.rs | 19 +++++++++++++++++++ src/zone/machine.rs | 4 ++++ src/zone/mod.rs | 12 ++++++++++++ 6 files changed, 59 insertions(+), 1 deletion(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 5e26081ab..b60b0bf75 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -353,6 +353,7 @@ pub struct ZoneStatus { pub progress: Progress, pub keys: Vec, pub key_status: String, + pub error: Option, pub receipt_report: Option, pub unsigned_serial: Option, pub unsigned_review_status: Option, @@ -532,6 +533,8 @@ pub struct HistoryItem { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum HistoricalEventType { + StartedLoad, + StartedResign, Added, Removed, PolicyChanged, @@ -547,6 +550,8 @@ pub enum HistoricalEventType { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum HistoricalEvent { + StartedLoad, + StartedResign, Added, Removed, PolicyChanged, @@ -575,6 +580,7 @@ pub enum HistoricalEvent { err: String, elapsed: Duration, }, + Error(String), } /// The trigger for a (re-)signing operation. diff --git a/crates/cli/src/commands/zone.rs b/crates/cli/src/commands/zone.rs index cc7b1cc33..40030d569 100644 --- a/crates/cli/src/commands/zone.rs +++ b/crates/cli/src/commands/zone.rs @@ -402,6 +402,8 @@ impl Zone { None => "-".to_string(), }; let what = match &history_item.event { + HistoricalEvent::StartedLoad => "Started load".to_string(), + HistoricalEvent::StartedResign => "Started resign".to_string(), HistoricalEvent::Added => "Zone added".to_string(), HistoricalEvent::Removed => "Zone removed".to_string(), HistoricalEvent::PolicyChanged => "Policy changed".to_string(), @@ -501,6 +503,7 @@ impl Zone { elapsed.as_secs() ) } + HistoricalEvent::Error(s) => s.clone(), }; println!("{when} {serial:10} {what}"); } @@ -576,6 +579,18 @@ impl Zone { println!("Published zone available at {}", zone.publish_addr); } + if let Some(error) = zone.error { + println!(""); + println!("An error occurred during the last operation:"); + println!(" {}ERROR: {error}{}", ansi::RED, ansi::RESET); + println!( + " Run {}`cascade zone history {}`{} for more information.", + ansi::BLUE, + zone.name, + ansi::RESET + ); + } + if detailed { println!(""); println!("DNSSEC keys:"); diff --git a/src/loader/mod.rs b/src/loader/mod.rs index a80c42d87..364ab0163 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -25,7 +25,7 @@ use crate::{ common::scheduler::Scheduler, loader::zone::EnqueuedRefresh, util::AbortOnDrop, - zone::{Zone, ZoneByPtr, ZoneHandle}, + zone::{HistoricalEvent, Zone, ZoneByPtr, ZoneHandle}, }; mod server; @@ -260,6 +260,8 @@ async fn refresh( // Cancel the load handle.abandon_load(builder); + + state.record_event(HistoricalEvent::Error(err.to_string()), None); } } } diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 3fc3e9cac..78a8c5bb2 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -353,6 +353,7 @@ impl HttpServer { let signed_serial; let published_serial; let last_published; + let error; { let locked_state = state.center.state.lock().unwrap(); let keys_dir = &state.center.config.keys_dir; @@ -465,6 +466,23 @@ impl HttpServer { loaded_serial: p.loaded_serial, signed_serial: p.signed_serial, }); + + let mut found_error = None; + for item in zone_state.history.iter().rev() { + // TODO: When we have instance IDs we should only look through + // history items related to that ID. + match &item.event { + HistoricalEvent::StartedLoad | HistoricalEvent::StartedResign => { + break; + } + HistoricalEvent::Error(s) => { + found_error = Some(s); + break; + } + _ => {} + } + } + error = found_error.cloned(); } // Query key status @@ -586,6 +604,7 @@ impl HttpServer { published_serial, publish_addr, halted_reason, + error, }) } diff --git a/src/zone/machine.rs b/src/zone/machine.rs index 0ff3aec31..21cf7cf72 100644 --- a/src/zone/machine.rs +++ b/src/zone/machine.rs @@ -120,6 +120,8 @@ impl<'a> ZoneHandle<'a> { transition.move_to(ZoneStateMachine::Loading(waiting.start_load())); + self.state.record_event(HistoricalEvent::StartedLoad, None); + Some(builder) } @@ -144,6 +146,8 @@ impl<'a> ZoneHandle<'a> { transition.move_to(ZoneStateMachine::Signing(waiting.start_resign())); + self.state.record_event(HistoricalEvent::StartedLoad, None); + Some(builder) } } diff --git a/src/zone/mod.rs b/src/zone/mod.rs index c2bf5071f..9521f0174 100644 --- a/src/zone/mod.rs +++ b/src/zone/mod.rs @@ -318,6 +318,8 @@ impl HistoryItem { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum HistoricalEventType { + StartedLoad, + StartedResign, Added, Removed, PolicyChanged, @@ -329,10 +331,13 @@ pub enum HistoricalEventType { SignedZoneReview, KeySetCommand, KeySetError, + Error, } #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum HistoricalEvent { + StartedLoad, + StartedResign, Added, Removed, PolicyChanged, @@ -370,11 +375,14 @@ pub enum HistoricalEvent { )] elapsed: Duration, }, + Error(String), } impl HistoricalEvent { fn get_type(&self) -> HistoricalEventType { match self { + HistoricalEvent::StartedLoad => HistoricalEventType::StartedLoad, + HistoricalEvent::StartedResign => HistoricalEventType::StartedResign, HistoricalEvent::Added => HistoricalEventType::Added, HistoricalEvent::Removed => HistoricalEventType::Removed, HistoricalEvent::PolicyChanged => HistoricalEventType::PolicyChanged, @@ -386,6 +394,7 @@ impl HistoricalEvent { HistoricalEvent::SignedZoneReview { .. } => HistoricalEventType::SignedZoneReview, HistoricalEvent::KeySetCommand { .. } => HistoricalEventType::KeySetCommand, HistoricalEvent::KeySetError { .. } => HistoricalEventType::KeySetError, + HistoricalEvent::Error { .. } => HistoricalEventType::Error, } } @@ -397,6 +406,8 @@ impl HistoricalEvent { impl From for api::HistoricalEvent { fn from(value: HistoricalEvent) -> Self { match value { + HistoricalEvent::StartedLoad => Self::StartedLoad, + HistoricalEvent::StartedResign => Self::StartedResign, HistoricalEvent::Added => Self::Added, HistoricalEvent::Removed => Self::Removed, HistoricalEvent::PolicyChanged => Self::PolicyChanged, @@ -420,6 +431,7 @@ impl From for api::HistoricalEvent { HistoricalEvent::KeySetError { cmd, err, elapsed } => { Self::KeySetError { cmd, err, elapsed } } + HistoricalEvent::Error(s) => Self::Error(s), } } } From 144363ac7f3eb0327bd092da0ae141d5d634302c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 22 Apr 2026 15:15:34 +0200 Subject: [PATCH 091/113] Add more errors to zone status --- crates/api/src/lib.rs | 12 +++++++++++- crates/cli/src/commands/zone.rs | 8 +++++++- src/loader/mod.rs | 7 ++++++- src/units/http_server.rs | 30 +++++++++++++++++++++++++++--- src/units/zone_server.rs | 12 ++++++++++++ src/zone/mod.rs | 20 +++++++++++++++++--- 6 files changed, 80 insertions(+), 9 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index b60b0bf75..ba80bdf7e 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -544,6 +544,8 @@ pub enum HistoricalEventType { SigningFailed, UnsignedZoneReview, SignedZoneReview, + UnsignedHookFailed, + SignedHookFailed, KeySetCommand, KeySetError, } @@ -570,6 +572,12 @@ pub enum HistoricalEvent { SignedZoneReview { status: ZoneReviewStatus, }, + UnsignedHookFailed { + err: String, + }, + SignedHookFailed { + err: String, + }, KeySetCommand { cmd: String, warning: Option, @@ -580,7 +588,9 @@ pub enum HistoricalEvent { err: String, elapsed: Duration, }, - Error(String), + LoadingFailed { + reason: String, + }, } /// The trigger for a (re-)signing operation. diff --git a/crates/cli/src/commands/zone.rs b/crates/cli/src/commands/zone.rs index 40030d569..054ec8f5b 100644 --- a/crates/cli/src/commands/zone.rs +++ b/crates/cli/src/commands/zone.rs @@ -477,6 +477,12 @@ impl Zone { ZoneReviewStatus::Rejected => "rejected", } ), + HistoricalEvent::UnsignedHookFailed { err, .. } => { + format!("Could not execute loaded review hook: {err}",) + } + HistoricalEvent::SignedHookFailed { err, .. } => { + format!("Could not execute signed review hook: {err}",) + } HistoricalEvent::KeySetCommand { cmd, elapsed, @@ -503,7 +509,7 @@ impl Zone { elapsed.as_secs() ) } - HistoricalEvent::Error(s) => s.clone(), + HistoricalEvent::LoadingFailed { reason } => reason.clone(), }; println!("{when} {serial:10} {what}"); } diff --git a/src/loader/mod.rs b/src/loader/mod.rs index 364ab0163..b6d883ee5 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -261,7 +261,12 @@ async fn refresh( // Cancel the load handle.abandon_load(builder); - state.record_event(HistoricalEvent::Error(err.to_string()), None); + state.record_event( + HistoricalEvent::LoadingFailed { + reason: err.to_string(), + }, + None, + ); } } } diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 78a8c5bb2..f9569d87d 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -475,14 +475,38 @@ impl HttpServer { HistoricalEvent::StartedLoad | HistoricalEvent::StartedResign => { break; } - HistoricalEvent::Error(s) => { - found_error = Some(s); + HistoricalEvent::LoadingFailed { reason } => { + found_error = Some(reason.clone()); + break; + } + HistoricalEvent::SigningFailed { trigger: _, reason } => { + found_error = Some(format!("signing failed: {reason}")); + break; + } + HistoricalEvent::UnsignedZoneReview { + status: ZoneReviewStatus::Rejected, + } => { + found_error = Some("loaded zone was rejected".into()); + break; + } + HistoricalEvent::SignedZoneReview { + status: ZoneReviewStatus::Rejected, + } => { + found_error = Some("signed zone was rejected".into()); + break; + } + HistoricalEvent::UnsignedHookFailed { err } => { + found_error = Some(format!("could not execute loaded review hook: {err}")); + break; + } + HistoricalEvent::SignedHookFailed { err } => { + found_error = Some(format!("could not execute signed review hook: {err}")); break; } _ => {} } } - error = found_error.cloned(); + error = found_error; } // Query key status diff --git a/src/units/zone_server.rs b/src/units/zone_server.rs index 3071fd1c8..792c92526 100644 --- a/src/units/zone_server.rs +++ b/src/units/zone_server.rs @@ -455,10 +455,22 @@ impl ZoneServer { let mut zone_state = zone.state.lock().unwrap(); match self.source { Source::Unsigned => { + zone_state.record_event( + HistoricalEvent::UnsignedHookFailed { + err: err.to_string(), + }, + Some(zone_serial), + ); zone_state.unsigned.get_mut(&zone_serial).unwrap().review = ZoneVersionReviewState::Rejected; } Source::Signed => { + zone_state.record_event( + HistoricalEvent::SignedHookFailed { + err: err.to_string(), + }, + Some(zone_serial), + ); zone_state.signed.get_mut(&zone_serial).unwrap().review = ZoneVersionReviewState::Rejected; } diff --git a/src/zone/mod.rs b/src/zone/mod.rs index 9521f0174..d1ed28d4f 100644 --- a/src/zone/mod.rs +++ b/src/zone/mod.rs @@ -329,6 +329,8 @@ pub enum HistoricalEventType { SigningFailed, UnsignedZoneReview, SignedZoneReview, + UnsignedHookFailed, + SignedHookFailed, KeySetCommand, KeySetError, Error, @@ -343,6 +345,9 @@ pub enum HistoricalEvent { PolicyChanged, SourceChanged, NewVersionReceived, + LoadingFailed { + reason: String, + }, SigningSucceeded { trigger: cascade_api::SigningTrigger, }, @@ -356,6 +361,12 @@ pub enum HistoricalEvent { SignedZoneReview { status: ZoneReviewStatus, }, + UnsignedHookFailed { + err: String, + }, + SignedHookFailed { + err: String, + }, KeySetCommand { cmd: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -375,7 +386,6 @@ pub enum HistoricalEvent { )] elapsed: Duration, }, - Error(String), } impl HistoricalEvent { @@ -392,9 +402,11 @@ impl HistoricalEvent { HistoricalEvent::SigningFailed { .. } => HistoricalEventType::SigningFailed, HistoricalEvent::UnsignedZoneReview { .. } => HistoricalEventType::UnsignedZoneReview, HistoricalEvent::SignedZoneReview { .. } => HistoricalEventType::SignedZoneReview, + HistoricalEvent::UnsignedHookFailed { .. } => HistoricalEventType::UnsignedHookFailed, + HistoricalEvent::SignedHookFailed { .. } => HistoricalEventType::SignedHookFailed, HistoricalEvent::KeySetCommand { .. } => HistoricalEventType::KeySetCommand, HistoricalEvent::KeySetError { .. } => HistoricalEventType::KeySetError, - HistoricalEvent::Error { .. } => HistoricalEventType::Error, + HistoricalEvent::LoadingFailed { .. } => HistoricalEventType::Error, } } @@ -419,6 +431,8 @@ impl From for api::HistoricalEvent { } HistoricalEvent::UnsignedZoneReview { status } => Self::UnsignedZoneReview { status }, HistoricalEvent::SignedZoneReview { status } => Self::SignedZoneReview { status }, + HistoricalEvent::UnsignedHookFailed { err } => Self::UnsignedHookFailed { err }, + HistoricalEvent::SignedHookFailed { err } => Self::SignedHookFailed { err }, HistoricalEvent::KeySetCommand { cmd, warning, @@ -431,7 +445,7 @@ impl From for api::HistoricalEvent { HistoricalEvent::KeySetError { cmd, err, elapsed } => { Self::KeySetError { cmd, err, elapsed } } - HistoricalEvent::Error(s) => Self::Error(s), + HistoricalEvent::LoadingFailed { reason } => Self::LoadingFailed { reason }, } } } From d4fc4351f587c1bc00dbf92b236eaf9400aeb0a3 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:44:13 +0200 Subject: [PATCH 092/113] Fully remove use of domain::tsig in the api crate. --- crates/api/src/lib.rs | 12 ------------ src/units/http_server.rs | 10 ++++++++-- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 5a06fbd8d..24bb68dd2 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -4,7 +4,6 @@ use std::net::SocketAddr; use std::time::{Duration, SystemTime}; use camino::{Utf8Path, Utf8PathBuf}; -use domain::tsig::Algorithm; use serde::{Deserialize, Serialize}; pub use domain::base::Serial; @@ -590,17 +589,6 @@ impl Display for TsigAlgorithm { } } -impl From for Algorithm { - fn from(alg: TsigAlgorithm) -> Self { - match alg { - TsigAlgorithm::Sha1 => Algorithm::Sha1, - TsigAlgorithm::Sha256 => Algorithm::Sha256, - TsigAlgorithm::Sha384 => Algorithm::Sha384, - TsigAlgorithm::Sha512 => Algorithm::Sha512, - } - } -} - #[derive(Deserialize, Serialize, Debug, Clone)] pub struct ZoneHistory { pub history: Vec, diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 1799eec7f..d6db0c11e 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -1109,8 +1109,14 @@ impl HttpServer { return Json(Err(TsigAddError::InvalidBase64Secret)); }; - match center::add_tsig_key(&state.center, tsig_add.name, tsig_add.alg.into(), &secret).await - { + let alg = match tsig_add.alg { + TsigAlgorithm::Sha1 => domain::tsig::Algorithm::Sha1, + TsigAlgorithm::Sha256 => domain::tsig::Algorithm::Sha256, + TsigAlgorithm::Sha384 => domain::tsig::Algorithm::Sha384, + TsigAlgorithm::Sha512 => domain::tsig::Algorithm::Sha512, + }; + + match center::add_tsig_key(&state.center, tsig_add.name, alg, &secret).await { Ok(TsigAddResult) => Json(Ok(TsigAddResult)), Err(err) => Json(Err(err)), } From b625ff5f0f2e255f3aebb1106632306711d86beb Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:45:30 +0200 Subject: [PATCH 093/113] Support reading TSIG secret from a file. And use add instead of register as we say add in some places and register in others, and because remove is used as the opposite of add, we don't say de/unregister. --- crates/cli/src/commands/tsig.rs | 25 +++++++++++++++++++++---- crates/cli/src/commands/zone.rs | 2 +- doc/manual/source/man/cascade-tsig.rst | 10 ++++++---- doc/manual/source/man/cascade-zone.rst | 2 +- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs index d85b8ee00..4d6442900 100644 --- a/crates/cli/src/commands/tsig.rs +++ b/crates/cli/src/commands/tsig.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use camino::Utf8PathBuf; use cascade_api::{ TsigAddError, TsigAddResult, TsigKeyName, TsigListResult, TsigRemoveError, TsigRemoveResult, }; @@ -16,10 +17,10 @@ pub struct Tsig { #[derive(Clone, Debug, clap::Subcommand)] #[allow(clippy::large_enum_variant)] pub enum TsigCommand { - /// Register a TSIG key + /// Add a TSIG key #[command(name = "add")] Add { - /// The name of the TSIG key to register. + /// The name of the TSIG key to add. /// /// Can also be in the form `[algorithm]:keyname:secret`. name: String, @@ -37,10 +38,13 @@ pub enum TsigCommand { #[arg(requires = "secret")] alg: Option, - /// The secret key material in base64 encoded form. + /// Base64 encoded secret key material. /// /// Can be omitted if provided as part of the name. /// Required if `[ALG]` is provided. + /// + /// Can also be a path to a file containing the Base64 encoded secret + /// key material. #[arg(requires = "alg")] secret: Option, }, @@ -95,7 +99,20 @@ impl Tsig { // Separate name, algorithm and secret argument values // were provided. - (Some(alg), Some(secret)) => (name, alg, secret), + (Some(alg), Some(secret)) => { + let path = Utf8PathBuf::from_str(&secret).unwrap(); + if path.exists() { + // Assume that the secret is contained in the + // specified file. + let secret = std::fs::read_to_string(&path).map_err(|err| { + format!("Failed to read TSIG key file '{path}': {err}") + })?; + (name, alg, secret) + } else { + // Assume that the secret was provided directly. + (name, alg, secret) + } + } // An unsupported combination of arguments was provided // but this should not be possible due to the Clap diff --git a/crates/cli/src/commands/zone.rs b/crates/cli/src/commands/zone.rs index 0cb5720d8..10cc88867 100644 --- a/crates/cli/src/commands/zone.rs +++ b/crates/cli/src/commands/zone.rs @@ -18,7 +18,7 @@ pub struct Zone { #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug, clap::Subcommand)] pub enum ZoneCommand { - /// Register a new zone + /// Add a new zone #[command(name = "add")] Add { name: ZoneName, diff --git a/doc/manual/source/man/cascade-tsig.rst b/doc/manual/source/man/cascade-tsig.rst index 113cc007b..2aae53f4e 100644 --- a/doc/manual/source/man/cascade-tsig.rst +++ b/doc/manual/source/man/cascade-tsig.rst @@ -36,18 +36,18 @@ Commands .. subcmd:: add - Register a new TSIG key. + Add a new TSIG key. Incoming DNS messages that are TSIG signed will be rejected if the key used to sign the message is not registered with Cascade. .. subcmd:: list - List registered TSIG keys and the zones that use them. + List registered TSIG keys. .. subcmd:: remove - Remove a registered TSIG key. + Remove a TSIG key. .. note:: Returns an error if the key does not exist in the TSIG key store, or if the key is still referenced by other configuration. @@ -84,9 +84,11 @@ Arguments for :subcmd:`tsig add` :RFC:`4648` Base64 encoded secret key material. The number of bytes prior to encoding must be correct for the specified ````. + Can also be a path to a file containing the Base64 encoded secret material. + .. note:: Secret key material supplied via a command-line argument may be visible to other processes running on the same computer as the - Cascade CLI. + Cascade CLI. Consider supplying a file name instead. See Also -------- diff --git a/doc/manual/source/man/cascade-zone.rst b/doc/manual/source/man/cascade-zone.rst index f1a9250aa..1716b3bb7 100644 --- a/doc/manual/source/man/cascade-zone.rst +++ b/doc/manual/source/man/cascade-zone.rst @@ -42,7 +42,7 @@ Commands .. subcmd:: add - Register a new zone. + Add a new zone. .. subcmd:: remove From a2db6be7a48a683e9b568088e48b9fea9b6e7446 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:31:10 +0200 Subject: [PATCH 094/113] Also test with TSIG key from file. --- integration-tests/system-tests.yml | 3 ++- .../tests/upstream-tsig/action.yml | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/integration-tests/system-tests.yml b/integration-tests/system-tests.yml index d9ceb889a..be189554d 100644 --- a/integration-tests/system-tests.yml +++ b/integration-tests/system-tests.yml @@ -267,7 +267,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - rust: [stable] + key-source: [cmdline, file] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/set-build-profile @@ -276,3 +276,4 @@ jobs: - uses: ./integration-tests/tests/upstream-tsig with: log-level: ${{ inputs.log-level }} + key-source: ${{ matrix.key-source }} diff --git a/integration-tests/tests/upstream-tsig/action.yml b/integration-tests/tests/upstream-tsig/action.yml index a60f67a34..5068b36f3 100644 --- a/integration-tests/tests/upstream-tsig/action.yml +++ b/integration-tests/tests/upstream-tsig/action.yml @@ -18,6 +18,14 @@ inputs: - info - debug - trace + key-source: + description: Where to take the TSIG key from. + required: true + type: choice + options: + - cmdline + - file + - none runs: using: "composite" steps: @@ -46,7 +54,17 @@ runs: - name: Remove and re-add the zone with the required TSIG key. run: | cascade zone remove example-tsig.test - cascade tsig add tsig-key hmac-sha256 "COzoVsYQmXeXiyq1Quhp0bbVnMyxjPxsaGSoIWR98i0=" + + case "${{ inputs.key-source }}" in + "cmdline") + cascade tsig add tsig-key hmac-sha256 "COzoVsYQmXeXiyq1Quhp0bbVnMyxjPxsaGSoIWR98i0=" + ;; + "file") + echo -n "COzoVsYQmXeXiyq1Quhp0bbVnMyxjPxsaGSoIWR98i0=" >/tmp/tsig.key + cascade tsig add tsig-key hmac-sha256 /tmp/tsig.key + ;; + esac + cascade zone add --policy default --source 127.0.0.1:1055^tsig-key example-tsig.test - name: Check zone status From 154d923d064236cae8e91331cde06d6b10eabe79 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:41:57 +0200 Subject: [PATCH 095/113] Review feedback: Don't wait 10 seconds, detect the XFR failure ASAP. --- integration-tests/tests/upstream-tsig/action.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/integration-tests/tests/upstream-tsig/action.yml b/integration-tests/tests/upstream-tsig/action.yml index 5068b36f3..bb9821d1a 100644 --- a/integration-tests/tests/upstream-tsig/action.yml +++ b/integration-tests/tests/upstream-tsig/action.yml @@ -42,14 +42,19 @@ runs: run: | timeout=10 # seconds start=$(date +%s) - until cascade zone status example-tsig.test | grep -q "Published zone available"; do + + # TODO: The error message we are checking for is the one that Cascade + # currently produces, but doesn't seem like the correct error... + # See: https://github.com/NLnetLabs/cascade/issues/603 + until cascade zone status example-tsig.test | grep -q "ERROR: the AXFR failed: the server's response was semantically incorrect: Not a valid XFR response"; do if (($(date +%s) > (start + timeout))); then - exit 0 + echo "::error:: zone status failed to report the expected error" + cascade zone status example-tsig.test + exit 1 fi sleep 1 done - echo "::error:: zone status unexpectedly reports TSIG required zone was retrieved and published without using the TSIG key" - exit 1 + exit 0 - name: Remove and re-add the zone with the required TSIG key. run: | From ee3cb3d5002bb6d1fa0d2a6e14e6f425507dd259 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:50:31 +0200 Subject: [PATCH 096/113] Review feedback: Use a better zonefile path in NSD examples. Updated based on the NSD documentation at https://nsd.docs.nlnetlabs.nl/en/latest/manpages/nsd.conf.html#EXAMPLE. --- doc/manual/source/nsd.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/manual/source/nsd.rst b/doc/manual/source/nsd.rst index d8d0bf916..15922aedc 100644 --- a/doc/manual/source/nsd.rst +++ b/doc/manual/source/nsd.rst @@ -45,7 +45,7 @@ running on host 192.168.0.2 listening on the default port 4542: zone: name: example.com - zonefile: "zonefile.name" + zonefile: /etc/nsd/example.com.zone notify: 192.168.0.2@4542 NOKEY provide-xfr: 192.168.0.2 NOKEY store-ixfr: yes @@ -63,7 +63,7 @@ example\: zone: name: example.com - zonefile: "zonefile.name" + zonefile: /etc/nsd/example.com.zone notify: 192.168.0.2@4542 sec1_key provide-xfr: 192.168.0.2 sec1_key store-ixfr: yes From aab66bf120392b6fc09d13621e394c89858689f0 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:55:41 +0200 Subject: [PATCH 097/113] Review feedback: Use RustDoc markdown, not reST markdown. --- crates/cli/src/commands/zone.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/commands/zone.rs b/crates/cli/src/commands/zone.rs index 10cc88867..2494d3646 100644 --- a/crates/cli/src/commands/zone.rs +++ b/crates/cli/src/commands/zone.rs @@ -913,7 +913,7 @@ pub enum ZoneSource { }, } -/// Support parsing of ``-source`` command line arguments. +/// Support parsing of `-source` command line arguments. /// /// Supported forms: /// - `[:][^]` From b851e8128c2376497daf4a007b5ea864015449d8 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:14:46 +0200 Subject: [PATCH 098/113] Minor correction. --- doc/manual/source/zone-transfers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst index cd47d1a4f..8426d509f 100644 --- a/doc/manual/source/zone-transfers.rst +++ b/doc/manual/source/zone-transfers.rst @@ -3,7 +3,7 @@ Zone Transfers Cascade is designed to be deployed between a hidden upstream nameserver and public downstream nameservers. The hidden upstream serves the unsigned zone, -Cascade signs it, and passes it to the downstream nameservers for publication +Cascade signs it, and serves it to downstream nameservers for publication to consumers. Communication of changed zone records from upstream to downstream should From 078436e02554474c749d7bbca0ea71cfaeec2b6d Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:15:16 +0200 Subject: [PATCH 099/113] Review feedback: Refer to authenticating communication in general. --- doc/manual/source/zone-transfers.rst | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst index 8426d509f..8f5019175 100644 --- a/doc/manual/source/zone-transfers.rst +++ b/doc/manual/source/zone-transfers.rst @@ -10,9 +10,9 @@ Communication of changed zone records from upstream to downstream should be done via the network using the :RFC:`5936` (AXFR) and :RFC:`1995` (IXFR) protocols. -Securing the transferred data can be done using :RFC:`8945` (TSIG) keys, -using a shared secret communicated out of band to the nameservers sending and -receiving the zone records. +Authentication of transfering parties can be done using :RFC:`8945` (TSIG) +keys, using a shared secret communicated out of band to the nameservers +sending and receiving the zone records. Cascade supports timely discovery of zone changes by sending SOA queries to the upsream nameserver, either in response to an :RFC:`1996` NOTIFY message or @@ -29,14 +29,12 @@ Using zone transfers with an upstream nameserver To instruct Cascade to transfer a zone via the network instead of loading it from a file you must supply an upstream nameserver IP address when -adding the zone. See :program:`cascade` :subcmd:`zone add`. +adding the zone. See :program:`cascade` :subcmd:`zone add`, and optionally +a TSIG key to use to authenticate communication. Cascade will then attempt to fetch the zone. Where possible it will fetch newer versions of the zone incrementally, as this is more efficient. -Securing zone transfers with an upstream nameserver -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Cascade can be instructed to authenticate the upstream nameserver by use of a TSIG key. The TSIG key to use must be provided to Cascade _before_ adding the zone. See :program:`cascade` :subcmd:`tsig add`. From 106094b6da8f55864074b3d2c4b1f738e1d75463 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:28:41 +0200 Subject: [PATCH 100/113] Review feedback. --- doc/manual/source/zone-transfers.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst index 8f5019175..823bd0c84 100644 --- a/doc/manual/source/zone-transfers.rst +++ b/doc/manual/source/zone-transfers.rst @@ -42,7 +42,8 @@ zone. See :program:`cascade` :subcmd:`tsig add`. Using zone transfers with a downstream server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Cascade permits zone transfers by default, no configuration is required. +By default, Cascade allows downstream servers to access published zones by +zone transfer, no configuration is needed. To ensure timely update by secondaries, Cascade can be configured to send :RFC:`1996` NOTIFY messages to specified secondaries. This is done via the From 239a92adc53928b61fbe181bc9c89f95833ff548 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:44:51 +0200 Subject: [PATCH 101/113] Review feedback: consistent Display styling. --- src/loader/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loader/mod.rs b/src/loader/mod.rs index 3f9a90ff7..57ad177c2 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -311,7 +311,7 @@ impl std::fmt::Display for Source { Source::Server { addr, tsig_key } => { write!(f, "{addr}")?; if let Some(tsig_key) = &tsig_key { - write!(f, "^{}", tsig_key.name())?; + write!(f, " with TSIG key '{}'", tsig_key.name())?; } Ok(()) } From 982274ec8590eeab4a209aebfbf0e04df778fa64 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 17:24:13 +0200 Subject: [PATCH 102/113] Review feedback: Note that we might want a more useful error in future. --- src/tsig/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index 870dbe51c..e89233854 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -306,6 +306,7 @@ pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), Remo }); if tsig_key_found { + // TODO: Indicate to the operator where the key is in use. return Err(RemoveError::Used); } @@ -319,6 +320,7 @@ pub fn remove_key(center: &Arc
, name: &tsig::KeyName) -> Result<(), Remo match state.tsig_store.map.entry(name.clone()) { hash_map::Entry::Occupied(entry) => { if !entry.get().zones.is_empty() { + // TODO: Indicate to the operator where the key is in use. return Err(RemoveError::Used); } entry.remove_entry(); From 27bdc149e78fa854fced380dfaca63c1333bf62c Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Fri, 24 Apr 2026 22:58:44 +0200 Subject: [PATCH 103/113] Update policy docs and regenerate man pages. --- doc/manual/build/man/cascade-debug.1 | 2 +- doc/manual/build/man/cascade-health.1 | 2 +- doc/manual/build/man/cascade-hsm.1 | 2 +- doc/manual/build/man/cascade-keyset.1 | 2 +- doc/manual/build/man/cascade-policy.1 | 2 +- doc/manual/build/man/cascade-status.1 | 2 +- doc/manual/build/man/cascade-template.1 | 2 +- doc/manual/build/man/cascade-tsig.1 | 12 ++++---- doc/manual/build/man/cascade-zone.1 | 4 +-- doc/manual/build/man/cascade.1 | 2 +- doc/manual/build/man/cascaded-config.toml.5 | 2 +- doc/manual/build/man/cascaded-policy.toml.5 | 29 +++++++++++------- doc/manual/build/man/cascaded.1 | 2 +- .../source/man/cascaded-policy.toml.rst | 30 ++++++++++++------- etc/policy.template.toml | 29 ++++++++++++------ 15 files changed, 77 insertions(+), 47 deletions(-) diff --git a/doc/manual/build/man/cascade-debug.1 b/doc/manual/build/man/cascade-debug.1 index 32a72ad4c..fa379a47b 100644 --- a/doc/manual/build/man/cascade-debug.1 +++ b/doc/manual/build/man/cascade-debug.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-DEBUG" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-DEBUG" "1" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-debug \- Debug / troubleshoot Cascade .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-health.1 b/doc/manual/build/man/cascade-health.1 index 369f67f71..85821acb5 100644 --- a/doc/manual/build/man/cascade-health.1 +++ b/doc/manual/build/man/cascade-health.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HEALTH" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HEALTH" "1" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-health \- Check the health of Cascade .sp diff --git a/doc/manual/build/man/cascade-hsm.1 b/doc/manual/build/man/cascade-hsm.1 index ab0fa0f58..62ab56db8 100644 --- a/doc/manual/build/man/cascade-hsm.1 +++ b/doc/manual/build/man/cascade-hsm.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-HSM" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-HSM" "1" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-hsm \- Manage HSMs .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-keyset.1 b/doc/manual/build/man/cascade-keyset.1 index 6fbbac550..fb0f43cf1 100644 --- a/doc/manual/build/man/cascade-keyset.1 +++ b/doc/manual/build/man/cascade-keyset.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-KEYSET" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-KEYSET" "1" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-keyset \- Execute manual key roll or key removal commands .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-policy.1 b/doc/manual/build/man/cascade-policy.1 index 6ab41c8ce..0b5ad6a69 100644 --- a/doc/manual/build/man/cascade-policy.1 +++ b/doc/manual/build/man/cascade-policy.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-POLICY" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-POLICY" "1" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-policy \- Manage policies .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-status.1 b/doc/manual/build/man/cascade-status.1 index 848edcc9a..8de2073be 100644 --- a/doc/manual/build/man/cascade-status.1 +++ b/doc/manual/build/man/cascade-status.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-STATUS" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-STATUS" "1" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-status \- Show the status of Cascade .sp diff --git a/doc/manual/build/man/cascade-template.1 b/doc/manual/build/man/cascade-template.1 index eff0f3296..6f96a458c 100644 --- a/doc/manual/build/man/cascade-template.1 +++ b/doc/manual/build/man/cascade-template.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-TEMPLATE" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-TEMPLATE" "1" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-template \- Print example config or policy files .SH SYNOPSIS diff --git a/doc/manual/build/man/cascade-tsig.1 b/doc/manual/build/man/cascade-tsig.1 index 1fe95733b..4ec29e03f 100644 --- a/doc/manual/build/man/cascade-tsig.1 +++ b/doc/manual/build/man/cascade-tsig.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-TSIG" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-TSIG" "1" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-tsig \- Manage TSIG keys .sp @@ -62,7 +62,7 @@ command. .INDENT 0.0 .TP .B add -Register a new TSIG key. +Add a new TSIG key. .sp Incoming DNS messages that are TSIG signed will be rejected if the key used to sign the message is not registered with Cascade. @@ -70,12 +70,12 @@ to sign the message is not registered with Cascade. .INDENT 0.0 .TP .B list -List registered TSIG keys and the zones that use them. +List registered TSIG keys. .UNINDENT .INDENT 0.0 .TP .B remove -Remove a registered TSIG key. +Remove a TSIG key. .sp \fBNOTE:\fP .INDENT 7.0 @@ -126,12 +126,14 @@ The TSIG algorithm of the specified TSIG key. Can be one of: \fBhmac\-sha1\fP, \X'tty: link https://datatracker.ietf.org/doc/html/rfc4648.html'\fI\%RFC 4648\fP\X'tty: link' Base64 encoded secret key material. The number of bytes prior to encoding must be correct for the specified \fB\fP\&. .sp +Can also be a path to a file containing the Base64 encoded secret material. +.sp \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 Secret key material supplied via a command\-line argument may be visible to other processes running on the same computer as the -Cascade CLI. +Cascade CLI. Consider supplying a file name instead. .UNINDENT .UNINDENT .UNINDENT diff --git a/doc/manual/build/man/cascade-zone.1 b/doc/manual/build/man/cascade-zone.1 index f6a54cabe..e1923b128 100644 --- a/doc/manual/build/man/cascade-zone.1 +++ b/doc/manual/build/man/cascade-zone.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE-ZONE" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE-ZONE" "1" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade-zone \- Manage zones .SH SYNOPSIS @@ -64,7 +64,7 @@ command. .INDENT 0.0 .TP .B add -Register a new zone. +Add a new zone. .UNINDENT .INDENT 0.0 .TP diff --git a/doc/manual/build/man/cascade.1 b/doc/manual/build/man/cascade.1 index 5317c1944..1c68e3994 100644 --- a/doc/manual/build/man/cascade.1 +++ b/doc/manual/build/man/cascade.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADE" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADE" "1" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascade \- Cascade CLI .SH SYNOPSIS diff --git a/doc/manual/build/man/cascaded-config.toml.5 b/doc/manual/build/man/cascaded-config.toml.5 index 41318719c..e9d409acf 100644 --- a/doc/manual/build/man/cascaded-config.toml.5 +++ b/doc/manual/build/man/cascaded-config.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-CONFIG.TOML" "5" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-CONFIG.TOML" "5" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-config.toml \- Cascade configuration file .sp diff --git a/doc/manual/build/man/cascaded-policy.toml.5 b/doc/manual/build/man/cascaded-policy.toml.5 index 79838d5d4..e12339485 100644 --- a/doc/manual/build/man/cascaded-policy.toml.5 +++ b/doc/manual/build/man/cascaded-policy.toml.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED-POLICY.TOML" "5" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED-POLICY.TOML" "5" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded-policy.toml \- Cascade policy file format .sp @@ -342,18 +342,22 @@ deleting the files for on\-disk keys or removing it from the HSM). .INDENT 0.0 .TP .B publication\-nameservers = [] -A set of nameservers to use when checking for rrsiG propagation during a +The set of nameservers to use when checking for RRSIG propagation during a key roll. .sp -Each nameserver is specified as a string with the syntax: +Each nameserver must be specified as a string in the form: .INDENT 7.0 .INDENT 3.5 -\fB[:][^[TSIG_KEY_NAME]\fP +\fB\(dq:[][^]\(dq\fP .UNINDENT .UNINDENT .sp -If this option is not set, the nameserver specified in the zone apex SOA -MNAME field will be queried. +If a TSIG key name is specified, a key by that name must exist in the +Cascade TSIG key store and will be used to authenticate communication with +the nameserver. +.sp +If no nameservers are specified, the nameserver specified by the SOA MNAME +field will be checked. .UNINDENT .SS The management of DNS records by the key manager. .sp @@ -637,12 +641,17 @@ The \fB[server.outbound]\fP section. .INDENT 0.0 .TP .B send\-notify\-to = [] -The set of nameservers to which NOTIFY messages should be sent. +The set of nameservers to which NOTIFY messages should be sent +.sp +Each nameserver must be specified as a string in the form: +.sp +\fI\(dq:[][^]\(dq\fP .sp -If empty, no NOTIFY messages will be sent. +If a TSIG key name is specified, a key by that name must exist in the +Cascade TSIG key store and will be used to authenticate communication with +the nameserver. .sp -A collection of \fBIP:[port]\fP, defaulting to port 53 when not specified, e.g.: -\fBsend\-notify\-to = [\(dq[::1]:53\(dq]\fP +If not specified, no NOTIFY messages will be sent. .UNINDENT .SH FILES .INDENT 0.0 diff --git a/doc/manual/build/man/cascaded.1 b/doc/manual/build/man/cascaded.1 index 0360020df..e4a58dd89 100644 --- a/doc/manual/build/man/cascaded.1 +++ b/doc/manual/build/man/cascaded.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CASCADED" "1" "Apr 22, 2026" "0.1.0-alpha5" "Cascade" +.TH "CASCADED" "1" "Apr 24, 2026" "0.1.0-alpha5" "Cascade" .SH NAME cascaded \- DNSSEC signer .SH SYNOPSIS diff --git a/doc/manual/source/man/cascaded-policy.toml.rst b/doc/manual/source/man/cascaded-policy.toml.rst index 8bb3cac3f..171395aad 100644 --- a/doc/manual/source/man/cascaded-policy.toml.rst +++ b/doc/manual/source/man/cascaded-policy.toml.rst @@ -260,15 +260,19 @@ The ``[key-manager]`` section. .. option:: publication-nameservers = [] - A set of nameservers to use when checking for rrsiG propagation during a + The set of nameservers to use when checking for RRSIG propagation during a key roll. - Each nameserver is specified as a string with the syntax: - - ``[:][^[TSIG_KEY_NAME]`` - - If this option is not set, the nameserver specified in the zone apex SOA - MNAME field will be queried. + Each nameserver must be specified as a string in the form: + + ``":[][^]"`` + + If a TSIG key name is specified, a key by that name must exist in the + Cascade TSIG key store and will be used to authenticate communication with + the nameserver. + + If no nameservers are specified, the nameserver specified by the SOA MNAME + field will be checked. The management of DNS records by the key manager. +++++++++++++++++++++++++++++++++++++++++++++++++ @@ -516,13 +520,17 @@ The ``[server.outbound]`` section. .. option:: send-notify-to = [] - The set of nameservers to which NOTIFY messages should be sent. + The set of nameservers to which NOTIFY messages should be sent + + Each nameserver must be specified as a string in the form: - If empty, no NOTIFY messages will be sent. + `":[][^]"` - A collection of ``IP:[port]``, defaulting to port 53 when not specified, e.g.: - ``send-notify-to = ["[::1]:53"]`` + If a TSIG key name is specified, a key by that name must exist in the + Cascade TSIG key store and will be used to authenticate communication with + the nameserver. + If not specified, no NOTIFY messages will be sent. Files ----- diff --git a/etc/policy.template.toml b/etc/policy.template.toml index d94b04fe2..0beaea885 100644 --- a/etc/policy.template.toml +++ b/etc/policy.template.toml @@ -156,14 +156,19 @@ ds-algorithm = "SHA-256" # TODO: Perhaps support removing keys after a certain delay? auto-remove = true -# The upstream nameservers to use when checking for RRSIG propagation during -# a key roll. +# The set of nameservers to use when checking for RRSIG propagation during a +# key roll. # -# Each nameserver must be specifeid as a string in the form -# `":[][^]"`. +# Each nameserver must be specified as a string in the form: # -# If not specified, the nameserver specified by the SOA MNAME field will be -# checked. +# `":[][^]"` +# +# If a TSIG key name is specified, a key by that name must exist in the +# Cascade TSIG key store and will be used to authenticate communication with +# the nameserver. +# +# If no nameservers are specified, the nameserver specified by the SOA MNAME +# field will be checked. # # publication-nameservers = [] @@ -375,8 +380,14 @@ required = false [server.outbound] # The set of nameservers to which NOTIFY messages should be sent. -# -# If empty, no NOTIFY messages will be sent. # -# A collection of IP:[port], defaulting to port 53 when not specified. +# Each nameserver must be specified as a string in the form: +# +# `":[][^]"` +# +# If a TSIG key name is specified, a key by that name must exist in the +# Cascade TSIG key store and will be used to authenticate communication with +# the nameserver. +# +# If not specified, no NOTIFY messages will be sent. send-notify-to = [] From 49043f89ae2f121dfe58becbeff7279c699f373e Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:28:13 +0200 Subject: [PATCH 104/113] Record that a zone uses a TSIG key when adding the zone. Also, reverse the change on failure to register the zone. Also, add some missing zone server updates and failure to mark state as dirty. --- src/center.rs | 80 +++++++++++++++++++++++++++++++++++++++---------- src/tsig/mod.rs | 4 +++ 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/center.rs b/src/center.rs index 58a654250..1e94861b0 100644 --- a/src/center.rs +++ b/src/center.rs @@ -21,7 +21,7 @@ use crate::server::{LoadedReviewServer, PublicationServer, SignedReviewServer}; use crate::tsig::ImportError; use crate::units::key_manager::KeyManager; use crate::units::zone_signer::ZoneSigner; -use crate::zone::{HistoricalEvent, ZoneHandle}; +use crate::zone::{HistoricalEvent, ZoneByPtr, ZoneHandle}; use crate::{ api, config::Config, @@ -90,6 +90,20 @@ pub async fn add_zone( return Err(ZoneAddError::AlreadyExists); } + // Look up the requested policy. + { + let policy = state + .policies + .get(&policy_name) + .ok_or(ZoneAddError::NoSuchPolicy)?; + if policy.mid_deletion { + return Err(ZoneAddError::PolicyMidDeletion); + } + } + + // Create the zone and initialize its state. + zone = Arc::new(Zone::new(name)); + source = match api_source { cascade_api::ZoneSource::None => crate::loader::Source::None, cascade_api::ZoneSource::Zonefile { path } => crate::loader::Source::Zonefile { path }, @@ -98,10 +112,15 @@ pub async fn add_zone( // Lookup the key in the TSIG key store. let key = state .tsig_store - .get(&key_name) - .ok_or(ZoneAddError::NoSuchTsigKey)? - .inner - .clone(); + .get_mut(&key_name) + .ok_or(ZoneAddError::NoSuchTsigKey)?; + + // Record that this zone uses this key. + key.zones.insert(ZoneByPtr(zone.clone())); + + let key = key.inner.clone(); + + state.tsig_store.mark_dirty(center); // Use the found key. Some(key) @@ -113,20 +132,10 @@ pub async fn add_zone( } }; - // Look up the requested policy. - let policy = state - .policies - .get_mut(&policy_name) - .ok_or(ZoneAddError::NoSuchPolicy)?; - if policy.mid_deletion { - return Err(ZoneAddError::PolicyMidDeletion); - } - - // Create the zone and initialize its state. - zone = Arc::new(Zone::new(name)); let (loaded_reviewer, signed_reviewer, viewer); { let mut zone_state = zone.state.lock().unwrap(); + let policy = state.policies.get_mut(&policy_name).unwrap(); zone_state.policy = Some(policy.latest.clone()); policy.zones.insert(zone.name.clone()); loaded_reviewer = zone_state.storage.loaded_reviewer.take().unwrap(); @@ -156,11 +165,35 @@ pub async fn add_zone( register_zone(center, zone.name.clone(), policy_name.clone(), key_imports).await { // Remove in reverse order what was added above. + LoadedReviewServer::remove_zone(center, &zone); + SignedReviewServer::remove_zone(center, &zone); + PublicationServer::remove_zone(center, &zone); + let mut state = center.state.lock().unwrap(); + state.zones.remove(&zone.name); + if let Some(policy) = state.policies.get_mut(&policy_name) { policy.zones.remove(&zone.name); } + + state.mark_dirty(center); + + if let crate::loader::Source::Server { + tsig_key: Some(key_name), + .. + } = &source + { + state + .tsig_store + .get_mut(key_name.name()) + .unwrap() + .zones + .remove(&ZoneByPtr(zone)); + + state.tsig_store.mark_dirty(center); + } + return Err(err); } @@ -231,6 +264,21 @@ pub fn remove_zone(center: &Arc
, name: Name) -> Result<(), ZoneRe state.mark_dirty(center); } + // Update the TSIG key's referenced zones. + if let crate::loader::Source::Server { + tsig_key: Some(key_name), + .. + } = &zone_state.loader.source + { + state + .tsig_store + .get_mut(key_name.name()) + .unwrap() + .zones + .remove(&ZoneByPtr(zone.clone())); + state.tsig_store.mark_dirty(center); + } + info!("Removed zone '{name}'"); zone_state.record_event(HistoricalEvent::Removed, None); zone.mark_dirty(&mut zone_state, center); diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index e89233854..b83771d53 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -85,6 +85,10 @@ impl TsigStore { pub fn get(&self, key_name: &tsig::KeyName) -> Option<&TsigKey> { self.map.get(key_name) } + + pub fn get_mut(&mut self, key_name: &tsig::KeyName) -> Option<&mut TsigKey> { + self.map.get_mut(key_name) + } } //----------- Actions ---------------------------------------------------------- From 5acd00bab09b3a74ba71465da40c309a0245f34e Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:36:49 +0200 Subject: [PATCH 105/113] Review feedback: Document why save_now() is invoked. --- src/tsig/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tsig/mod.rs b/src/tsig/mod.rs index b83771d53..ace134997 100644 --- a/src/tsig/mod.rs +++ b/src/tsig/mod.rs @@ -208,8 +208,16 @@ pub fn import_key( }); } } + + // Release the lock before calling save_now() as it will attempt to + // acquire the same lock. drop(state); + + // Ensure that the TSIG key store is persisted to disk before a zone add + // causes `dnst keyset` to attempt to read the added TSIG key from the + // on-disk copy of the key store. save_now(center); + Ok(()) } From 1453aa62541329d13b585761b16c65201374cb25 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:45:08 +0200 Subject: [PATCH 106/113] Review feedback: Use the Hmac prefix consistently. --- crates/api/src/lib.rs | 16 ++++++++-------- crates/cli/src/commands/tsig.rs | 8 ++++---- src/units/http_server.rs | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 24bb68dd2..4c9b55b79 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -571,19 +571,19 @@ impl Display for KeyType { #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum TsigAlgorithm { - Sha1, - Sha256, - Sha384, - Sha512, + HmacSha1, + HmacSha256, + HmacSha384, + HmacSha512, } impl Display for TsigAlgorithm { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TsigAlgorithm::Sha1 => "hmac-sha1", - TsigAlgorithm::Sha256 => "hmac-sha256", - TsigAlgorithm::Sha384 => "hmac-sha384", - TsigAlgorithm::Sha512 => "hmac-sha512", + TsigAlgorithm::HmacSha1 => "hmac-sha1", + TsigAlgorithm::HmacSha256 => "hmac-sha256", + TsigAlgorithm::HmacSha384 => "hmac-sha384", + TsigAlgorithm::HmacSha512 => "hmac-sha512", } .fmt(f) } diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs index 4d6442900..6a97ef4df 100644 --- a/crates/cli/src/commands/tsig.rs +++ b/crates/cli/src/commands/tsig.rs @@ -220,10 +220,10 @@ impl FromStr for TsigAlgorithm { impl From for crate::api::TsigAlgorithm { fn from(alg: TsigAlgorithm) -> Self { match alg { - TsigAlgorithm::HmacSha1 => cascade_api::TsigAlgorithm::Sha1, - TsigAlgorithm::HmacSha256 => cascade_api::TsigAlgorithm::Sha256, - TsigAlgorithm::HmacSha384 => cascade_api::TsigAlgorithm::Sha384, - TsigAlgorithm::HmacSha512 => cascade_api::TsigAlgorithm::Sha512, + TsigAlgorithm::HmacSha1 => cascade_api::TsigAlgorithm::HmacSha1, + TsigAlgorithm::HmacSha256 => cascade_api::TsigAlgorithm::HmacSha256, + TsigAlgorithm::HmacSha384 => cascade_api::TsigAlgorithm::HmacSha384, + TsigAlgorithm::HmacSha512 => cascade_api::TsigAlgorithm::HmacSha512, } } } diff --git a/src/units/http_server.rs b/src/units/http_server.rs index d6db0c11e..d2dc59d87 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -1110,10 +1110,10 @@ impl HttpServer { }; let alg = match tsig_add.alg { - TsigAlgorithm::Sha1 => domain::tsig::Algorithm::Sha1, - TsigAlgorithm::Sha256 => domain::tsig::Algorithm::Sha256, - TsigAlgorithm::Sha384 => domain::tsig::Algorithm::Sha384, - TsigAlgorithm::Sha512 => domain::tsig::Algorithm::Sha512, + TsigAlgorithm::HmacSha1 => domain::tsig::Algorithm::Sha1, + TsigAlgorithm::HmacSha256 => domain::tsig::Algorithm::Sha256, + TsigAlgorithm::HmacSha384 => domain::tsig::Algorithm::Sha384, + TsigAlgorithm::HmacSha512 => domain::tsig::Algorithm::Sha512, }; match center::add_tsig_key(&state.center, tsig_add.name, alg, &secret).await { From a82b8a073e490db70fe7d4808789e9056b226f8e Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:46:46 +0200 Subject: [PATCH 107/113] Review feedback: Better naming. --- crates/api/src/lib.rs | 4 ++-- src/units/http_server.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 4c9b55b79..617590bb5 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -262,12 +262,12 @@ impl fmt::Display for TsigRemoveError { #[derive(Deserialize, Serialize, Debug, Clone)] pub struct TsigListResult { /// The set of TSIG keys known to Cascade plus information about each key. - pub tsig_keys: HashMap, + pub tsig_keys: HashMap, } /// Information about a single listed TSIG key. #[derive(Deserialize, Serialize, Debug, Clone)] -pub struct TsigListResultItem { +pub struct TsigKeyInfo { /// The set of zones with which this TSIG key is used. pub zones: Vec, } diff --git a/src/units/http_server.rs b/src/units/http_server.rs index d2dc59d87..72e4a9712 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -1158,7 +1158,7 @@ impl HttpServer { } }) .collect::>(); - tsig_keys.insert(tsig_key_name.clone(), TsigListResultItem { zones }); + tsig_keys.insert(tsig_key_name.clone(), TsigKeyInfo { zones }); } Json(TsigListResult { tsig_keys }) } From df1cfa238f63d80ccbd15faf47369e23f5ae6740 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:49:18 +0200 Subject: [PATCH 108/113] Review feedback: Trim whitespace from TSIG secret value read from file. --- crates/cli/src/commands/tsig.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/commands/tsig.rs b/crates/cli/src/commands/tsig.rs index 6a97ef4df..93b85ac76 100644 --- a/crates/cli/src/commands/tsig.rs +++ b/crates/cli/src/commands/tsig.rs @@ -104,9 +104,12 @@ impl Tsig { if path.exists() { // Assume that the secret is contained in the // specified file. - let secret = std::fs::read_to_string(&path).map_err(|err| { - format!("Failed to read TSIG key file '{path}': {err}") - })?; + let secret = std::fs::read_to_string(&path) + .map_err(|err| { + format!("Failed to read TSIG key file '{path}': {err}") + })? + .trim() + .to_string(); (name, alg, secret) } else { // Assume that the secret was provided directly. From 52a919c4e585c1e34eb0b1e0604ea5b588459623 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:51:39 +0200 Subject: [PATCH 109/113] Review feedback: Errant colon position in docs. --- doc/manual/source/man/cascaded-policy.toml.rst | 2 +- etc/policy.template.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/source/man/cascaded-policy.toml.rst b/doc/manual/source/man/cascaded-policy.toml.rst index 171395aad..66cf95db1 100644 --- a/doc/manual/source/man/cascaded-policy.toml.rst +++ b/doc/manual/source/man/cascaded-policy.toml.rst @@ -265,7 +265,7 @@ The ``[key-manager]`` section. Each nameserver must be specified as a string in the form: - ``":[][^]"`` + ``"[:][^]"`` If a TSIG key name is specified, a key by that name must exist in the Cascade TSIG key store and will be used to authenticate communication with diff --git a/etc/policy.template.toml b/etc/policy.template.toml index 0beaea885..80a434c3a 100644 --- a/etc/policy.template.toml +++ b/etc/policy.template.toml @@ -161,7 +161,7 @@ auto-remove = true # # Each nameserver must be specified as a string in the form: # -# `":[][^]"` +# `"[:][^]"` # # If a TSIG key name is specified, a key by that name must exist in the # Cascade TSIG key store and will be used to authenticate communication with @@ -383,7 +383,7 @@ required = false # # Each nameserver must be specified as a string in the form: # -# `":[][^]"` +# `"[:][^]"` # # If a TSIG key name is specified, a key by that name must exist in the # Cascade TSIG key store and will be used to authenticate communication with From 79559a79a08011eb74040d22b2989db8a1b59c68 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:53:15 +0200 Subject: [PATCH 110/113] Review feedback: Quote to prevent special treatment of '^'. --- doc/manual/source/nsd.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/source/nsd.rst b/doc/manual/source/nsd.rst index 15922aedc..8834355e4 100644 --- a/doc/manual/source/nsd.rst +++ b/doc/manual/source/nsd.rst @@ -89,7 +89,7 @@ authenticate with NSD: .. code-block:: bash - $ cascade zone add --source 192.168.0.1^sec1_key --policy default example.com + $ cascade zone add --source "192.168.0.1^sec1_key" --policy default example.com Using NSD as a secondary to Cascade ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 8be30fb03ffd2863dadac27fa83cfc6702c264c5 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:53:42 +0200 Subject: [PATCH 111/113] Indent consistent with the other similar example like this. --- doc/manual/source/man/cascaded-policy.toml.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/source/man/cascaded-policy.toml.rst b/doc/manual/source/man/cascaded-policy.toml.rst index 66cf95db1..a876c73e9 100644 --- a/doc/manual/source/man/cascaded-policy.toml.rst +++ b/doc/manual/source/man/cascaded-policy.toml.rst @@ -524,7 +524,7 @@ The ``[server.outbound]`` section. Each nameserver must be specified as a string in the form: - `":[][^]"` + `"[:][^]"` If a TSIG key name is specified, a key by that name must exist in the Cascade TSIG key store and will be used to authenticate communication with From 3130f6263b8c7cfe3cafba9027a511273cb9c9fb Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:55:32 +0200 Subject: [PATCH 112/113] Review feedback: Avoid unnecessary Vec. --- src/units/key_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/units/key_manager.rs b/src/units/key_manager.rs index cdfa5df95..752320552 100644 --- a/src/units/key_manager.rs +++ b/src/units/key_manager.rs @@ -733,7 +733,7 @@ fn policy_to_commands(center: &Arc
, policy: &PolicyVersion) -> Vec Date: Thu, 30 Apr 2026 11:56:30 +0200 Subject: [PATCH 113/113] Review feedback: Be less strict about Cascade not supporting zone files. --- doc/manual/source/zone-transfers.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/manual/source/zone-transfers.rst b/doc/manual/source/zone-transfers.rst index 823bd0c84..738930123 100644 --- a/doc/manual/source/zone-transfers.rst +++ b/doc/manual/source/zone-transfers.rst @@ -1,7 +1,7 @@ Zone Transfers ============== -Cascade is designed to be deployed between a hidden upstream nameserver and +Cascade is expected to be deployed between a hidden upstream nameserver and public downstream nameservers. The hidden upstream serves the unsigned zone, Cascade signs it, and serves it to downstream nameservers for publication to consumers. @@ -22,7 +22,10 @@ based on the zone's SOA timers. only a small fraction of the records in the zone change from one version to the next, loading the entire file every time the zone file changes will require more time, CPU and memory compared to - processing only the differences when using IXFR. + processing only the differences when using IXFR. Cascade doesn't + yet support direct writing of signed zones to a file, though a + signed zone review hook could be used to AXFR the signed zone to + a file on disk to achieve this. Using zone transfers with an upstream nameserver ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~