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 01/38] 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 02/38] 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 03/38] (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 04/38] 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 05/38] (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 06/38] 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 07/38] 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 08/38] 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 09/38] 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 10/38] 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 11/38] 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 12/38] 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 13/38] 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 14/38] 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 15/38] 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 16/38] 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 17/38] 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 18/38] 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 19/38] 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 20/38] 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 21/38] 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 22/38] 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 zcmeHQdyHIHdG~8yv+MQR@#f(qa7l3N*gG?^F^SnaNmIv;gY9)16AThCzB6-n?!9~G z-pPGnqEeu(iq!s5p{5mWkt!keA6f~dRzijT zzH{!muX*f@vu=!3OFlbu?suxO!OxrIxZk_pUK28-) z<^=6m+V?)+ey)9xPk8#W?}lEDwfPpbs2QeR_n6auf7{r?$9z@`OxKCq8zaW3G4}rU zK|CA@%)n-;M||w0?$`7>Yf}@n*zqp5LUSb#UTD*_e6*!wj-YMK_*I=3^dRueY8U`U zf@!;1_c1>otJ%8mqY|G-9K9(&jn~6g@};WRmOQpdZ9+a4=nZ<*Hl~ez#w4Gp#4l`Y z=cDR9PnGL>pqCA|$;vBc({;+eS1Z>f?CNhZXtv%~M*nhCcS7AR%id+vsk4=m=Lc=R z!!{k(KD#Zf@?Cz*FrAf>Uav>E-r+j-Y6>|C-X`$!dc)IOM(Qzt>s7pL5Vimst6$Zw z)=j?#y7&yAn|R{ZUFWJmEa83v@63QgkWG;?!ug2V)El|l5&jRNc79$nf}rKkm&<0e zQSuFw*>%5Uy5)u$7-6+cAQWqEvuOt9g>w!I&goTOVdUYK9vBbqKYAKSK`GI4kF8gk z{UCMRfAqcx{DP)w^ST|(Yv)j}p}nukN8(AKdkuz%3Klte3G;SNT9(lGpn~ailA~=L zFdj5+2iF*Pg5}m#mGg$Q59mTqU-5SgFS4!4fnyg zLBclKsyx3CON5RYtd<-bJPHnkhO^6w2p_p`AikK0r%?>?m_llfA%=aEwsHQswsFC* z1(l~FD!(tGvNa%;4=aEMdFR(h&EMaEno0?k7hT(Sm&-?v9J~J=NAEv!^yu;9$B!Jj z|JbpI>n=&@gT7lcbvr|xd;DP4X3>*`&gUb=)>SQZ8K(S;F~tX>@VJ^l5m;CF%n^9= zUQUtzB|-X`i1g1Sq+c75^!F%GH^4&&)mcyN5n}420;0obMEt-(8tl{VXUMgu>Beem?{Ja{Kg7jaM;v=0*{)|$7BgQX@@4c*!@i;o1HO@sk zfHXZBDbn$1Q0-3u)3A&s!%ivTgaA{(>({&CRUy)V zC!`4QwQddhM2y7l=%*<<(f_A}ex+wN#J*~H!)RNQfJ7qJas>HMe}cSdtQqUZOA$dx zH6{d^0k*!^4bL$_K4rY32=ZFDhJ0LMt2;ethKL@$btB)wR$3mMdLwAsZJMu0+A)%- z9T;LVtQv2NrRmom(H1?EIdyw=PV)mectzcI9j490-JQSu=)%Q|r%q}oOegT%hc0Wr z*>Fs!q4};YbgYI28LC)a3*15^5;Zu8+OnYsni(AM;WkqT46K>H#&mxbO*M?iJiQiZ z%kbDs(RyH(-`WeB#FzUie7QM3)dI*Ex3U69GFdx}@Y$|#tCifS>4?Qnyx17A3R|~q z5mcY$qw;OzxvXhC!^$qBS zXVFB0pNd0~A(=};YK>u>SSuBWqLO$EYiDWz>nQ?`bp0&9sSB->*)O4ChlpFZqIOUL zzlgR23AnYNMkNtd(ydd)z|w6UrsgwwF3n5$Eoq|M(u0E&1aw3H-2*Uq7o@KOz6j|v zdErPVd={-X#yGIlQ>89rglok33xvWH#%UVfQ=dwVgJO|b_eJ%CuS?_;{B$G89w`rL zjnD=EpG)vRnQEF0{hI@!zMnOF_1aPYyE)W%nzHqjDU+zryhhHFW%zt+DS1nYz`-mJ z2I;gs{D9K2uq8q<%UA*VG1A}B8m?~p^GbaV++LYR>)Tx@kyEg-b-W`5;}*UvS=Cl- z6Y<-`mq}u?dvlE{X26=MmhUNJ{?j&FF{`GH2vp%+us-UgTV{)qm!x^21h&S7F>B5Q_mk6PV-29avP`7VaK7 zflLc&ObjhKTaH|4Aq);nOa7*4FRUMosq1Og5D|U&s%{g*QlP)2JsCQh>l9laYnm7l zjihH#sM&5;VbOx7G6kbnxdqn zdE(H&=PtDWe`;<-O+luE@D84N<8YN|tRD%s$htsp9Yip?7=iyG_K}{?bXKa8`FKdJB*cQR&uR-IKZRM(d4Trhaz&%^2Yt=~cW2dhH+`+LulI zmtqNtzzD3JiAC!Pn915wys)IWhw7#IEC^r?eT}SU9oDLVP(X-<+p1P&*m-PGs02pH zS60aS*EATrJHSv@sLPYs7QMF%Q{`Ncv@O~X96Tks$oh}e>`LQyiz2HdpfC>Fs0W8Tf?bLM<6e&bkq2b13{hZ#H;9C*zUVFVgtGt^E)@WzY$RCtpbG6vwIQn0CT0^f_tcX*9@7-091f;PfU zrl?L7Lsg+u1cM;SJw$m|4mKso9rdvo|Bk?YZ?+;_rd-N5_b9Vr1MooR^WiU2?(gvc(t-cl)21RlF$vgM-Tx=TK!o>B><=5Pxk)}mtN zh=nP#n><)_wOxW}f~zpgG^iDG(ZqHMX$yBzOoS!3+)u$GyAXYR2KzB{@B}D2t+m1` zmi@@mYG8Y%yFaiq^I}Sc+68uWy_0=n}>LTbTHo#_1bx{{7HGQW@t1c3>UaIRG7qI_X zlyNZWzQL*GVTP@ea5(MgvmJJ1wvW6_iDnen4&`G{_xoZcC?9q-CmNL3i zTTKQyp|W_EmzOgcQDrp;G92M8VV&ZaAG%zTK@pO*{637^EO z%FD{WNiot4MIaXm(y^cyl@ufMBuYNcc8pa@PpZTjTrn=JReCEZ{%7!)7lboJ8|C%( z*{#Vt&vI=eWszsmzA7}<(r?2Y?-O{XA`2zAMM(%plHLPq5}Fk3RZz32 zjiOzuf{&kOtWJjzluRZSvAX;+M%_SN(t!o*k7)KJ=V%@?CrKTD2^?I&t_p3SQC0{1 z;Rf>YvA}kbNTCPVp;?STDQSkw(dmG`Ipw0QFjjK~UuF|wQ%bvxj17kTUt2ptW#G=&ncb}8i` zQLkv@>2gZHV%eWSQbRgasqP?ST=!}vWP6JP0V4amjAxF+Y`<)y0R}Ea6|`$b@dHZ2 z(k?vm`_!AepRx|nel0&lyS!V0%jz_P~-xBLIgP`xqLp#UrvVW zpR7w3KFLUVa`Wk#Nc=Yi)1{2kL$8n4iod7Q9FsCB-y!B5;n~N7Sd45BYX#%**cc4L zh_NxCR}QOLwNOq-GssvE<&40T>{JvypVIMBy_B0I>o8H_-y>Ke6>hELi%{JXLPTkH zW<;kC)$ScwtQ3L9l^mvs^Fsgt;v|m-AxzR*66N+jV%?VpZ|biRawx~NMWIJh=uSZC zVl98xAJu!i>6<V%VZ7ZB7(gl6*Uz7I{$ZlWnrl-<|cx}>Ol zOj5aD<3T9it-n|!GOsPEADDR(HL+y=wssg_QlSId}mU59Ko`covGRVE#gz7Br8^f;07g45nFKX7VAZ?qW5GbdtofLEFbp`Umkw8#(DJ?fTIym|OT{HMDIO^v)}r zpp3J8G8up3aS^*l*c%&@;$XH6X}7RVM*08b#Osc~41ZO`#h*qnojlMvEnvtAgZnH_ z3oNq&^0=@8E?vuH(Qv&cn7s}I$AtLw5QI1lLbQn$b7u|mOVv-IO|*cdE+40_;>4}> z3O>e*xvNhw4|@gJm#i_d3)y5%7r6wXPq}p>ogG>0HR3fazf^iU4z6qFqTIy5_QDdj z7YfUQc1?zK20=LoOEW(qn_i{;5%GjJJ+M$>9kjavfSKiQED$kI`~j#msBoSEfxm?I zL+}K3>BSSzEg)BuGB8Qe;OswADMLqGil9>hs79HtRmQ|=1zW^u(F8|$0t1Wm7LM@X z$O6P%FDzow9YSFRbifCs*N7)*B~P*og*K-_zfpoG6bXqo9kJ+OiW>o%S^fq?5wpc# z55X4i1dYEn99vMAUTpD6oRcAiK>0<8(68dqjJHIQ0VoFF&|5@j%GHJHPENYe!#G+a zR05s1DHu8qx#&1>6KGI3Fllx00Ufku+%y5$M=R~DeBUTr9S+q8{74ifoQot^26yERB~cU9Mi%zBFJqEZQTR= z=`C!U1zCtfIDoYT%+JTEMnZ$87yPS~tcz;IEcopG75LwpN8L}_)==2FUk zc>WCsN%`D203jbwve+LP z#~IU>8#?vQ6!@apd>lHu;h~iMz7(${r#Yflg99*D-m~?XTBkhGQr>&@V z3QIl%M=EM$2}QeXf-hm_Kf)c{Rv-7|t!)Ill^Q87nF ziHiGBFghH(QfIBez)d~4Qb=!+2i@kokhCa*;1}t{BKB7Jj)v`4+v|MPFza>PfV9SU zMsr0K>wH{F5beZZV@BM6 z(4mM^cLbMAc*RU&)y#KABq}cB;vfY4QH_gkdi99bYc%Ixnlrz*uz)b>EFLJ&SMFb<0#jxcZkpPD9>)OenEUWf%5%r>mmHH-iIQG=??(ZGgO_%^f+O< zMxzV#S|`y!Vl@5a{~C=EQZhIgeJq4O;17j`pc4xQu&|iNLP7AG96yhT?6Uzdfs;04 zbdViFkkTXkj%XLC2!Eys=f^?l2e*n9$u;tocJJRPR=gj=3-ntC@QIbCJ@4Z%6pnn4 z!yt;y9-$k51zkJD;H*9xft$ccBMnF4 z&l4%L8l{ET`vg^|`TTaGu(&NvOjx8vOgv$poEXzdz~q7~!E*c_Cf6Xo(1;KTYV6@Nm-pP|TVm>Kequ9*sdj`4C-SOFEjNY!a7 z>?JAyw*Khv)oKvDe-KkDWzKJds38r>Tq)oZAJ0YhYGF03nuMGvSFTTsSlIpsL-rFk z>tkqOeVpDEjM!biJ_7m5fOhz#)DZAEDFPB=L)E605|+I(=tXO)Wt=L+T3Z1-RG%39 z*ERwC7xUmxA;|!=BwuMhjx}a0V*q}z2>`5h05Gq-nR)G~LeNyEG6vwsn*hLi9)R(A z=?ukhpH##XZySC6obytC;yp+w5U$(2_DKB-Md<44{kuJiftlF^U_P1$=B9blsq@4O zMLh>i*Jm*}Z`uTKUe1Fv(KI8au27PyiKm|1gsDH4pL!B$k#y#{xa7wvFqyh|`uROnmpZ|>RYU{YA6?pd(zbif7n>;(Nw!K^^qHqX$;$lGlZgXre03mS&y@c zK`332z3IVmOF+v;@I~~P$vnYXgw9%uzo3@iro3bF4yvAs3p2Gxb?iD|g<4a4f>E0i zHi_t>%hZ*Z$p5q$We#@^Q)UjXbH$CL0j^GsX(GH3IZsJkbOjjZIy-A81zAGrnrf%9 z*49qb#t3dxb+0d2X^v1JCer7V;DuByQ?W|Lvs64s#RsW)o{C?k;sq*Rq+*?lkD|!R z>(qI1wXgOB?Y9lM!a8WZOoM$w3?|l`ZG6m$@2e2sMXFAV?+l4AXo1Uwi^AFIkl=1D z1`*Un*}a)}XkTWD1`2X|(F9mP!sZaCMeP zZIo5H9z)0W;WvP<*WqvSsS~)Axx!i|E|tap_TjTz5%@}OKP=JpdmhdPBm0lC{jcHz z4v1_Gj-2-F0m776Hy)VR-sOc1x3@-@Aa#2d!~A-LwTSE=$K%DPpS+*Ob3ox3T3Ohcoyn7A>tXXfGJk3w)x%9GQ8G zktW3L&H<7X@MY3--2k8>NV$UOY#BEtNf3}A0BKs_h3ISO+4x3=yFxj{LLPAe9mNq> z+kDR=hQU=aEcFmUNxG9AamdOFmU&G?DW`GX-E38&*o%(=Z}}7h26gj9s5q(me&2KnGkgv7O|4G*$&y z`{=lY0WU&M7^=pUs6gzd@G{6YafICA+0Lk0MMd8E&GZSAAebd&lQ}e69V|JCU^qx6 zznop)z zv(!=mfR>fm|3cW0wTk=iI|h7CdyBQ)@KlowA78Od7cUXZE-|lUvS3ln0fkT|9|@C9 z;9e$3QJpps#`&EPpJpW%&RDBfWaM$3&-klOps!R6oQAjQL_Gag0No|Xr$pSU@^lEP z_JOFI#CQ3%V!JgF;6hygPNZDYnK0%_c>|UO3K8{M3R~Eupx<^7`wx*CSO@pu+HGeM zJP_%HJ)|gbuXC=Mxb_!w=~5W!M+}y5M?S;WU>y>SYr1^{c`KM>7S}=POBIofTfsF| zq<00>yJ=+7#y+zbrA}Mt+nIBvjg199F0~277o13${*aBJBG2cD-*VWUzie7)bK@(< zXN~U~Uo(DWyk>osY!X@5knhQ~7O_n6D-oG%kd3dTZ{;<k%qyR9vRwSt?ej_-!h_K*e8C@hvK9G|^=$UZvvG zC_+9#(9(sc0-*xlbph*|fOE|v7}o`SYXY`40oR&yf-(3(0F(xH8uF^{jY>;0gu^~PQZ2U9T32bbkmloK#n?4C_oQ|rzlz++rmBry9rZ&m zNislmVa+TrpHb@qO$xPkSA6d3E=p*Ux7#UKGj+kBG1lM3$0`b`p!+O_c746Wk;qfo ziaX`R{k}UMUpRe%+y!yNT^r^r`f-*AXk}KDp|q9Ks-;jBDxegQ5FlE$D5aD?s+$bK2y5?QLeARL_C|ZJJ!9W|WDFdviza}rx#XK|TUXL|?zh6w zahD9J$`7r&aoP!O<6bVa;RJ?nL4~V^Wd^H8b=44JQVG+ja@O0N^}?d(8y1RNzHy&b zbxhYV-MaBewc~~z1MlelaWrw*42{>jq2;%IC$Nm8j@wx=rjH+f{n5QtqEPc%Z5U0p zX&J$45L&Ik2s$;}FazVbS6g1?B}&HKP1NCn@|4wFoi$dyj!{FSe#d1}6z&7D<(DL) zKMtZQBYAC_ksK7pu^AS{j0Hw=m$J(5ze#{;HUrP7b`S=XQ7rmi%Yc>*Zuv$H;l$_! zWD@t9{*pIq9Pb3-o`4rCa1}CB5QbvFb71OziPIZRD-4+SNORKkJXlY&TKH3=(*rIW8Ohu!udZ_K9 z&!Xoo({a1D6K%#{;c6TG*G5J>iNCFtcGIajVfQS1ecJTh?ip4NF%PDIj!ua}@$-$s zHMWAMIk?5H=(3^4qvLIjjLOpCs8;E4h{`q34Z*wRYub>#>KW;eLmt>Y1eybn^9U?s zpZrs?``%tn^(sWNdnwrcg;9zj#GV1sb6JQYz}ds}w8r&gCp_81pnH*_K!gn*;av+o z2t}XUZY@@w&^TrJ0mdYTw`h2-h5r@s61|G(fyC?JfJEY@Y(k;?$*kz^0&pK^-ScF% ziSDZMmW~%DMp3UT8d8=MzO&+3#=ch<;%E1G-tuYBZyEdc>rXq)W?-#?c3(ky@?;ec zt+3K`FgVfNS&iT(=uHK|w57d{b_gI<*T!SR)>a9jpBzLaK1Ul8s| z#GeOtUzA=F?G(Ifp!9P4UBT@jQpNB0vhsU5Sa=~TzeJqm_fpAkpSl#hu}m?IG-V}& z_Dad{VpbV;gRLKCm4S#M!|+HKjdL^WM(c}gF#4C8wmy^A%%?SCa*G&-LvnnK9^W#| zQ>N22X@E#$N{R*x3udc++pP-=<%NZn+i$DTuZh6{qC;~v?(DBgsZaH3jceDhTNls( zdUF%L;hRm}J0?`SQYmAMkGS~FtoXUyjT3<{L90QFdO4Wt7d1 zbD`sM2hXA1O9vq)ElL0}T=mou$X?@0*hx}>r9%$Bii}Ph-&htOpS67t-y^L93uZf9 zFy!FSaqHF!57#s*yQHsNEaM|gM*ERyN~oq1gudr4=^C0kkZ4BYwGnN`6;%d+6>Sy; zyJk<{*fBY8hzTIJ7HU{>e_{>0~(6s>(V=IB$Y(ZJLtnre0{dPzzIEkAbaS_ zjl(BS9D6u6j%!k?jWzrngQ4*R{-@k1lWDwf0Dg@I9uBh#YKd8ekt&8#66Y?PncTp^ zZLI2|+^_6H433S*3kJT1i#5v+okgbxrwjPAj^AXzEV-Tb68o(gRExgV#3w#xNNAd7 zSF~pJDG@(D07u;p8)rb!I7>g?i63Vh@4`=EagAs3d!Lov)MsVJ-E}@Ty#Lt!#-eHKHqn(8qUNP*yb**{AR+t0-QT zP*|$-g7ToC{89>(`Ay)KJ`=ci-a9?Zf(MNX7CnkCzzLHSEzCN=Iy`*JQS@Y8&WW9t5QFRyPS`>j(=yK~I6MoUE*&ag0b#<$1yqWCm;0@FD_A(hK=J4E*+n$()*=q#O zl55qA)zyPJ?e%1ehhQqAvr+~ZG`aYA;f861u8Ig5Q;8m<(au7dq5I`+4m%KbVG}L)jU~;yy`RAR3{IhTLCwlR| z=c>u_0D;c0H@>S+f7i>GYg0Jaze4UA1lDDJL!}yD z?T}tRoIo)#m%>DT^S`eTzPs;U#OJ024t>}xqI=);_FFnmZMn$ivKOdx!*r%x%F#Y{ zQ>uzRy)C)2f;UO?XUJa>dZe+%iGFaN75*6|2c(Q9X?u&!|Yvhis2e& znGNg?am^-0fMT)ZI^n@y{8H%@OaGO^QhorR;EazB!n9b0b*OZrvk3Wpi&Z@jdI_{C z>v6vGL54>8rxO3ljoD`ws_29+2mPq7kTeL{8n4VqcPXRm72qXWjuX2;UdrXTz9tmX zcjQ324g_!G$o#n+;zVyK%55z39<4g;zqNGBt;Y1x6A$mjs+(x|L9^9bHTLfx@am9P zGn>Z#TjE#TDn2J$kPE!=TGnNv*1iULNwqT4I0k0L@N0M(1;z0kP%Ht8c?L!D%cpnT zAQ$9_{s@PzUyeR>dgWN^m!rRolEcX%hYh~09C8qSpB$G9eGF5jQb;7V&_^jF!Egd4 z-qSh6bHMJCIm9D+F5d7E*ti@Y_>J8(v4p51*YHTe_h)EHF5?ls@TC}@3%8Q@AqHx4 zDJ^{g@3+!bAs6td0rr`oFW?PoHOm6t^XJg+=mk8^$#A9XLxAN$s(UHc@V?5Gl7C_5 zTLbVJIVVFcG3^XruH(#PL#U+?N{*BICS6h|WBz@BBzla^KMwK~Z`yd|g0I1C*mRE5 zZv|E`*$ItQHMPW4onsA-bC*r`fx{6 zY^!jOrl16yldEvl2#YyGR^d1k+2YIF6_iQyeVHvlb@k8@E#oG`}0a zL)o6@#%sS8_cZs({Nuhe10)_f7@(GzV`f>4<7{Q~(swDOl3tpB1o)u=GM!(q{H)?l z(_)5oHql3Apj0OKdnwH8{qnmNTS>@5bxM^h;r<@cmtR(xxolz*K~$dSLG67A{~ z(ez;TtBUC)*lCMzg3B~A#nWsGPx;~gHO09GcaB9jAwiB3N|-xSgwX;1uwrvGz`aXw zLjdk2Q#?JG!c%^LKcYC-03NvzCwLx7qZocHg<&1U-%+e2K}=nT6C9<|DX#1kuJXh9 zF~$4n`1}m$_;OV$oqe9GcAMxNvYe^se<%Q+O#whx%O}9{J3&9HlO%c)?U{^pKg;Mw zt=N6>M79HT!;WDW#4+sKXN^1P$o9Tl=%;odTOGs|N3#c>%q|=ghuPQYu+E1s3aR1^ zPakD=l6RruldM#7mQ()-fVIel25Nwfd-R2dL9J$4X!whBXm|8N1LtJ4tICr?zYqKW z2p1<_BWm+QKQ(NG!eJrPSah)HN*NsL#sT{MzyjM?yxM7%*a?xuHeQ@`pp)Y4&BAN} z`6TE}xQ$!}at{#8NWo5|2j(fuaKzRhI&QRkc+0VVzs(R!>bIPeF&Sd^WRxUs{e=Pg ziZ&4}$&*UaCf~y|@k+BEVxL*ho~-Do2*@SRrrAG=HM=g_7&>iz6a2>>a*ab)-SbVA z;{lp%;uMl`gV@kb=Y3c<3Iw5PM4{U$6y0Gsixich&Cv4-E_TJx5kPF5^F@8gA%S;D zLIK4FhU;m2;(2wVS+~=w;_UFOVU?Cjg`)>_c_H>(m)Qd9dUlUg{^Q-tL1g%G2fUDpQV~}$MPM`_cgq{a zRUCd~d7C8CBWV;jkEC#uAIdK)o;4`v9$2I<%Q$ViA?+yVhh2{yx%c7g_w6fE2tbyk zhv#N-Iu_}7^7pv`fMuQheHY4(>R^XW{=OgO)@Jhe zn@|qHk~aCvq02U2{nj9bXPwOzSZ5_ycKZhd)X?+Mr;;jsTQQjQt(0k9!CDHH;^?y} z9Od_^q9-B-m-Qx`N9zGR4zBr0|pSWRSH-6Vf18lz*^rJdTq9^h0%t&_!qwAdjrnIVVPuj4nxg>_uqNJj19u~8>lnEj5Jhyj3 zqftY}(y>xXDljaR$(kACKOv-0nK3Wf0me)isZ`{sEJL69a~~L?D642PdVr~Bc1X`T?XiQ@DT%?qNt zVCimo-coA?xgeNx;M^i6oRKJ`?IC9*GRRQ1mcGgC4i9`G7-m-t|$lYyvaW(MJ4X2z(Z<;6uLM@4z0QBHz|nb9V4WkHf zJy2lYv{f?jb&ARg2{st7lzXj-1e>TCiAC!yti<)u*Q{0NX&Q+SgXe1W3cl4s_9j+2 z<$guhWTav%6!=s-t;7Kt50?z`H2%Y>}nNI&i?C z*^B~m9`VFE`Z;A(FV85J0*dcpIvH|cIW?5NCQ)~1RSGljxDCc>1pQ00TI1y#;W0}7fkXnA#{bAmouR@_C#60=c; z7&MAZPe_LevuRs3P6-4Vbb@oFSEiHD%(CN5Im~!bEfO!=@EIht#u;=vze{Y@twpob z48_a9_D)kSFOg`IX#EaL3?A&ANmC45ws_nqpECV&)2Yg&+DakfHDx&xIx90rbVOoo z{dD4QTny1c2F#0tSryEm&k)Sz3f_wd>VF7&1ah^4_vmfdYyJOmk;m73agkXJEk)jS ze)z=;hM#B3q+Yf^J>o5SyB}%Lvw3>0n3u1n8&>0QKm(g1w%Pp7^%=UnxCJh4<0~kw zHv4ZDJ)&fOBl^d<#0%%Z(+6MPoog%{ha)-a9Wuc*w{iUdSB_qD9{scg{JiQ=QRzqN zijtFMa}%*F$xq|MY$iHofH0bdj}D}4YgeHXw6JCLSUUH|_S|F)M;xW2SD(Z9Mv8q- zWtD`xQzX>Bq~~_TyeCyTl^jq>&UzoKMyFF6%@6C&m^BU7>8t}g$#M1qN|xJGWYGb9 zE1$VDAI7r$_{}vZ3amU#O5xG)tvN`J6Sjz zGW6Ep4IOu5IC6fKD7!tGB71%xyfPMhWH;CN6uJ*Fx%A)KLDGajz%Qp3GnZG>N#Hk?s_u>y<$NvYN+!Oa4KK&rCy2j>u16u^!Dn zG-vT0nat~ruYHxXnSITxL`7>|t%CGdazJ_`LHd3MLDEU`pW6CpO0z>udHD91x#^fW z&7uyyNnFN56Cq+FqsnGW3IAwL3Uga5N0j(vk2B==rQBJo>Jy^%qXbb1P35%GhFfj15KSXTxo{{4 zSy{;ylb=apa)=WR-zPqIA{Ir9fH%hjWPaI`KKxaS@X6tCt};rZuck<(d&HwLuSwW< zv%xLx9v9!d{hP-}ktk;VK82b5@IDr^oF03)bEease~gmbqC_;8e<~ox3fYISPmqWk zP#{a3dXYKJ$P#v@6`_@OqtE!<~5+ym8R>h?_ZiqJRRfM5S!!9JNK z+%3m1avuvlTnyhmvMwq}8c2R*U589*eKaNZdsW26?DNr9`2^c=tnPfY1$Q{nRx8|M ze+jPn<=5KdvM=mEaGsH(oT3y-n>-*qyhh=A(%v3)k(M*j9y_Ca8zP zwwqQPe@yzg1^}$WPFHY&Wx0#(fjALbvv6suy9|PY_!B4n@YlA`t5xWtd3p<0#;`}t zU+qRa7f}su$kbk*iMG-G(>QcmS)q+<(DM{lY^p!UqDwG!8{$qx%Ed2<#-cIYpVV1G@hQ3q z87R`NiI&;IlL@G!W|2t5pW_0v3d|CFjaLVI5pg?yqzPj)iTCv=|)%eyy>Z;@j)1RG{x(Ud61>AOupzT9PJ<(V0(crf=L&b zDvEmyfp{IqqBmG>H@eifYMx(5Xrha;(F|=gnvcf$9w|b^sY`>f$D=8;6MB{SQM7?J zZEmY~TJ4H5NdU_p;9e}cp1w&HZf8%Jv6J@WM{OJ%t$P*R!3e4yaA@2{R?$l`D?v0k z<*<7Ysjdn(0@B$~2tqr#@MyDi6}a722t%70+4MZ#Z4+b_woR;ivKzgUenTU;>5;`{ zanckjpY+YsP!&6P+C8#9x`g}WS4Q7ykmuntPKQ&vK_XXXlw#|ci~0#(C!}D5?uv=)&?GY0_bQua5ytQnp~+1+6`fcC1IZGs;tp8Fe6W)PzE za;J^kG^fBFIz-^l!@yZsk5_ZtMc9Cd3p*)LpyTWzV>`LgmdTxQ6AMW&8JuFT!-H4B zfo)lI$@#7tsFo`hu0U&6aQ8dKU9fj1x`b+`o;a|Yi#(>CjW$~DsV;UgM_YI_f&0P| zvAffOZ-f=Tv6%^XNpEjX(Y)~(bx5qgjdmsosK+9TWS`6E18Qth5zy7{PMy_8{np7Y z>$%Jz+Ds(;&2-}})R0R*K17|{2j~au4XF%n{~=l$m9RdBQJ-a$XBpL*j(ycr-Dy^V zJ;_Sjv%H(6flFh7DyH8$(Kg*gm;~E!dpz?eGE4r1B7vmi=;%*49Si~{)v9_;Wp#}7 z6+I6X^+ck^3E3(uv@=mZg1R4)i|*>JOT0nfia1sCIuwPVgBq?v z--YN$)(7wC9*6I>O&?wi%>{ID05*+%V^e&$jC`v4ReOxzTcCgY-6y|SarA(`TyaG^ zHzAh44%LX@OMB~~NH2QF8T!|6`Q22pFfkd8EBD3f*>A1CKr;eV( 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 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 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 23/38] 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 24/38] 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 86010bbe9d58cfb3f9ecaa2b5b9aa272f101a221 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 13 Apr 2026 13:01:15 +0200 Subject: [PATCH 25/38] Introduce public-nameservers and send it to keyset. Handle TSIG related aspects as well. --- crates/api/src/lib.rs | 7 +++++-- src/main.rs | 11 ++++++++--- src/policy/file/v1.rs | 10 ++++++++++ src/policy/mod.rs | 41 +++++++++++++++++++++++++++++++++++++++- src/state/v1.rs | 5 +++++ src/units/http_server.rs | 31 +++++++++++++++++------------- src/units/key_manager.rs | 25 +++++++++++++++++++----- src/zone/state/v1.rs | 5 +++++ 8 files changed, 111 insertions(+), 24 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 41702244b..6a63d1886 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -702,12 +702,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 16a71c038..fdeb31c16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,9 +107,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..059c2a1da 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. Current each string + /// has the following syntax: :[^]. + /// The port is mandatory. The TSIG key name is option and the name + /// of the key is preceeded 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..836dec5ac 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,36 @@ 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 { + let mut iter = n.split('^'); + + // Skip address:port + let Some(_addr) = iter.next() else { + continue; + }; + + // Get TSIG key name. + let Some(tsig_name) = iter.next() else { + continue; + }; + 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 +314,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 51d4a1f77..9ebccc10f 100644 --- a/src/state/v1.rs +++ b/src/state/v1.rs @@ -284,6 +284,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 @@ -311,6 +314,7 @@ impl KeyManagerPolicySpec { ds_algorithm: self.ds_algorithm, default_ttl: self.default_ttl, auto_remove: self.auto_remove, + publication_nameservers: self.publication_nameservers, } } @@ -336,6 +340,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/units/http_server.rs b/src/units/http_server.rs index 3ab69b408..a4ca8eee3 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -812,20 +812,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)); 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 18f464f7dad307eba8e1ae870be7b5b161be8238 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 13 Apr 2026 13:12:19 +0200 Subject: [PATCH 26/38] Something went wrong during the merge. First fix. --- src/main.rs | 36 +----------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/src/main.rs b/src/main.rs index 90d2cfe5a..913565e10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,41 +79,7 @@ fn main() -> ExitCode { if err.kind() != io::ErrorKind::NotFound { error!("Could not load the state file: {err}"); return ExitCode::FAILURE; - }; - } - - // Load all policies. - let mut updates = Vec::new(); - 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}"); - return ExitCode::FAILURE; - } - - for name in updates { - let pol = state - .policies - .get(&name) - .expect("we just reloaded these policies"); - - for zone_name in &pol.zones { - let zone = state - .zones - .get(zone_name) - .expect("zones and policies are consistent"); - - let mut state = zone.0.state.lock().expect("lock isn't poisoned"); - state.policy = Some(pol.latest.clone()); - } + } info!("State file not found; starting from scratch"); From 63256bdb1c9edf9ff70704ce90f8e1fbd582934f Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 13 Apr 2026 13:13:34 +0200 Subject: [PATCH 27/38] Fix call to reloa_all. --- src/main.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 913565e10..6fcb13831 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,7 +79,7 @@ fn main() -> ExitCode { if err.kind() != io::ErrorKind::NotFound { error!("Could not load the state file: {err}"); return ExitCode::FAILURE; - } + } info!("State file not found; starting from scratch"); @@ -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}"); 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 28/38] 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 e79a1ad18b9ce0a4dc17f15b48bbe6be23be1cc9 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:30:13 +0200 Subject: [PATCH 29/38] WIP --- crates/api/src/lib.rs | 12 +- .../scripts/manage-test-environment.sh | 26 + integration-tests/system-tests.yml | 20 + .../tests/outbound-tsig/action.yml | 110 ++++ src/policy/file/v1.rs | 82 ++- src/policy/mod.rs | 9 +- src/server/service.rs | 537 ++++++++++++++++++ src/units/http_server.rs | 10 +- src/units/zone_server.rs | 8 +- 9 files changed, 783 insertions(+), 31 deletions(-) create mode 100644 integration-tests/tests/outbound-tsig/action.yml create mode 100644 src/server/service.rs diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 015640817..34e281086 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::KeyName; use serde::{Deserialize, Serialize}; pub use domain::base::Serial; @@ -841,12 +842,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/integration-tests/scripts/manage-test-environment.sh b/integration-tests/scripts/manage-test-environment.sh index 0d1d833ee..349ce5d69 100755 --- a/integration-tests/scripts/manage-test-environment.sh +++ b/integration-tests/scripts/manage-test-environment.sh @@ -289,6 +289,11 @@ remote-control: control-enable: yes control-interface: "${base_dir}/nsd/nsd.sock" +key: + name: "tsig-key" + algorithm: hmac-sha256 + secret: "COzoVsYQmXeXiyq1Quhp0bbVnMyxjPxsaGSoIWR98i0=" + pattern: name: secondary zonefile: "%s.secondary-zone" @@ -300,6 +305,27 @@ pattern: zone: name: example.test include-pattern: secondary + +zone: + name: notify-tsig.test + zonefile: "notify-tsig.test.secondary-zone" + allow-notify: 127.0.0.1 tsig-key + request-xfr: AXFR 127.0.0.1@${_cascade_port} NOKEY + provide-xfr: 127.0.0.1 NOKEY + +zone: + name: xfr-tsig.test + zonefile: "xfr-tsig.test.secondary-zone" + allow-notify: 127.0.0.1 NOKEY + request-xfr: AXFR 127.0.0.1@${_cascade_port} tsig-key + provide-xfr: 127.0.0.1 NOKEY + +zone: + name: notify-and-xfr-tsig.test + zonefile: "notify-and-xfr-tsig.test.secondary-zone" + allow-notify: 127.0.0.1 tsig-key + request-xfr: AXFR 127.0.0.1@${_cascade_port} tsig-key + provide-xfr: 127.0.0.1 NOKEY EOF tee "${base_dir}/nsd-primary.conf" <&2 diff --git a/integration-tests/system-tests.yml b/integration-tests/system-tests.yml index dbc0ca7d9..7d1741b99 100644 --- a/integration-tests/system-tests.yml +++ b/integration-tests/system-tests.yml @@ -274,3 +274,23 @@ jobs: - uses: ./integration-tests/tests/upstream-tsig with: log-level: ${{ inputs.log-level }} + + outbound-tsig: + name: Using TSIG with downstream nameservers. + runs-on: ubuntu-latest + strategy: + matrix: + send-notify-to: [ '' ] #, '127.0.0.1:1054', '127.0.0.1:1054^tsig-key' ] + accept-xfr-from: [ '' ] #, '127.0.0.1:1054', '127.0.0.1:1054^tsig-key', '^tsig-key' ] + zone-name: ['notify-tsig.test.'] #['example.test.' ], 'notify-tsig.test.', 'xfr-tsig.test.', 'notify-and-xfr-tsig.test.'] + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/set-build-profile + with: + build-profile: ${{ inputs.build-profile }} + - uses: ./integration-tests/tests/outbound-tsig + with: + log-level: ${{ inputs.log-level }} + send-notify-to: ${{ matrix.send-notify-to }} + accept-xfr-from: ${{ matrix.accept-xfr-from }} + zone-name: ${{ matrix.zone-name }} diff --git a/integration-tests/tests/outbound-tsig/action.yml b/integration-tests/tests/outbound-tsig/action.yml new file mode 100644 index 000000000..8403aa380 --- /dev/null +++ b/integration-tests/tests/outbound-tsig/action.yml @@ -0,0 +1,110 @@ +# 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: 'Using TSIG with downstream nameservers' +description: 'Using TSIG with downstream namservers' +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 + send-notify-to: + description: The IP address, port and name of the TSIG key to use when sending NOTIFY messages to downstream nameservers. + required: false + default: '' + type: string + accept-xfr-from: + description: The IP address, port and name of the TSIG key to permit when replying to SOA and XFR requests from downstream nameservers. + required: false + default: '' + type: string + # Note: Changing the zone name can be used to select the TSIG configuration used by the downstream NSD. + zone-name: + description: The name of the zone to serve to the downstream. Should end with a period. + required: true + type: string +runs: + using: "composite" + steps: + - uses: ./.github/actions/prepare-systest-env + - uses: ./.github/actions/setup-and-start-cascade + with: + log-level: ${{ inputs.log-level }} + + - name: Make a zonefile for zone ${{ inputs.zone-name }} + run: | + tee test.zone <<'EOF' + $TTL 5 ; use a very short TTL for sped up keyset rolls + ${{ inputs.zone-name }} IN SOA ns1.${{ inputs.zone-name }} mail.${{ inputs.zone-name }} ( + 1 ; serial + 60 ; refresh (60 seconds) + 60 ; retry (60 seconds) + 3600 ; expire (1 hour) + 5 ; minimum (5 seconds) + ) + @ NS ${{ inputs.zone-name }} + @ NS ns1.${{ inputs.zone-name }} + @ A 127.0.0.1 + ns1 A 127.0.0.1 + + www A 169.254.1.1 + mail MX 10 ${{ inputs.zone-name }} + text TXT "Hello World!" + EOF + + - name: Add a custom policy + run: | + POLICY_DIR=$(integration-tests/scripts/get-default-path.sh policy-dir) + cascade template policy | grep -Ev '(send-notify-to|accept-xfr-requests-from)' > "${POLICY_DIR}/restricted.toml" + if [ "${{ inputs.accept-xfr-from }}" != "" ]; then + echo 'accept-xfr-requests-from = ["${{ inputs.accept-xfr-from }}"]' >> "${POLICY_DIR}/restricted.toml" + fi + if [ "${{ inputs.send-notify-to }}" != "" ]; then + echo 'send-notify-to = ["${{ inputs.send-notify-to }}"]' >> "${POLICY_DIR}/restricted.toml" + fi + cascade policy reload + + - name: Add zone ${{ inputs.zone-name }} using the custom policy + run: | + cascade zone add --policy restricted --source $PWD/test.zone ${{ inputs.zone-name }} + + - name: Wait for zone ${{ inputs.zone-name }} to be signed and published + run: | + timeout=10 # seconds + start=$(date +%s) + until cascade zone status ${{ inputs.zone-name }}| grep -q "Published zone available"; do + if (($(date +%s) > (start + timeout))); then + cascade zone status ${{ inputs.zone-name }} + echo "timeout: zone status did not report published zone available" >&2 + exit 1 + fi + sleep 1 + done + + - name: Verify that the ${{ inputs.zone-name }} zone is being served + run: | + dig @127.0.0.1 -p 4542 ${{ inputs.zone-name }} SOA + + - name: Tell NSD that zone ${{ inputs.zone-name }} is now available + # Only necessary if NOTIFY is not setup for cascade + if: ${{ inputs.send-notify-to == '' }} + run: | + ./integration-tests/scripts/manage-test-environment.sh control nsd-secondary transfer ${{ inputs.zone-name }} + + - name: The ${{ inputs.zone-name }} zone should now be available via the resolver + run: | + dig text.${{ inputs.zone-name }} TXT | grep "Hello World" + + - name: Print log files on any failure in this job + uses: ./.github/actions/print-logfiles + if: failure() diff --git a/src/policy/file/v1.rs b/src/policy/file/v1.rs index 059c2a1da..983a2e4a7 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}, @@ -899,7 +900,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), @@ -912,18 +916,32 @@ 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, - // TODO: Support TSIG key names? + /// The address to send NOTIFYs to or receive XFRs from. + /// + /// For sending NOTIFY it must not be None. + /// + /// For receiving XFR, if None the only restriction is the TSIG key name, + /// if defined, otherwise access is unrestricted. + pub addr: Option, + + /// An optional TSIG key to sign messages with. + pub tsig_key_name: Option, } /// Policy for communicating with another namesever. #[derive(Clone, Debug, DeserializeFromStr, Serialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct SimpleNameserverCommsSpec { - /// The address to send NOTIFYs to. - pub addr: SocketAddr, - // TODO: Support TSIG key names? + /// The address to send NOTIFYs to or receive XFRs from. + /// + /// For sending NOTIFY it must not be None. + /// + /// For receiving XFR, if None the only restriction is the TSIG key name, + /// if defined, otherwise access is unrestricted. + pub addr: Option, + + /// An optional TSIG key to sign messages with. + pub tsig_key_name: Option, } //--- Conversion @@ -939,33 +957,63 @@ 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 as an IpAddr (assuming port 53), or as a SocketAddr. +/// 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, n)| (s, Some(n))) + .unwrap_or((s, None)); + + let tsig_key_name = tsig_key_name + .map(KeyName::from_str) + .transpose() + .map_err(|err| format!("TSIG key name is invalid: {err}"))?; + + 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!("Nameserver address is invalid: {err}"))?, + ) + }; + + Ok(SimpleNameserverCommsSpec { + addr, + tsig_key_name, + }) } } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 836dec5ac..15e94c504 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}; @@ -465,7 +466,7 @@ pub struct InboundPolicy { /// /// If empty, the nameserver from which the zone was received will be /// contacted. - pub send_xfr_requests_to: Vec, + pub send_xfr_and_soa_requests_to: Vec, /// The set of nameservers from which may NOTIFY messages may be received. /// @@ -484,8 +485,10 @@ pub struct NameserverCommsPolicy { /// For sending the port MUST NOT be zero. /// /// TODO: Support IP prefixes? - pub addr: SocketAddr, - // TODO: Support TSIG key names? + pub addr: Option, + + /// An optional TSIG key to sign messages with. + pub tsig_key_name: Option, } //----------- KeyParameters --------------------------------------------------- diff --git a/src/server/service.rs b/src/server/service.rs new file mode 100644 index 000000000..78ef7efc3 --- /dev/null +++ b/src/server/service.rs @@ -0,0 +1,537 @@ +//! Servicing DNS requests. + +use std::sync::{Arc, RwLock}; + +use cascade_zonedata::{ + LoadedZoneReviewer, RegularRecord, SignedZoneReviewer, SoaRecord, ZoneViewer, +}; +use domain::{ + new::base::{ + name::{RevName, RevNameBuf}, + wire::ParseBytes, + }, + utils::dst::UnsizedCopy, +}; + +use crate::zone::Zone; + +//----------- ZoneService ------------------------------------------------------ + +/// Cascade's DNS service. +/// +/// This type is responsible for answering DNS queries received by Cascade, +/// using zone viewer objects. When a query is received, the corresponding zone +/// is looked up, the appropriate information is retrieved from its viewer, and +/// the appropriate DNS answer is synthesized and returned. +/// +/// The viewer type has to implement [`Viewer`]. It should only be +/// [`LoadedZoneReviewer`], [`SignedZoneReviewer`], or [`ZoneViewer`]. +/// +/// The service can be interacted with through a [`ZoneServiceHandle`], which +/// is also returned by [`ZoneService::new()`]. +pub struct ZoneService { + /// The underlying state. + // + // TODO: The state is currently wrapped in an 'RwLock'. This is necessary + // as the state might have to change, e.g. in response to changes to zones. + // It would be preferable to hold the state directly, and use channels to + // communicate the need for changes. This is currently impossible because + // of the limitations of 'domain::net::server'; that architecture should be + // gradually replaced locally for better flexibility and efficiency. + state: Arc>>, +} + +impl ZoneService { + /// Construct a new [`ZoneService`]. + /// + /// In addition to the service, a [`ZoneServiceHandle`] is returned through + /// which the service can be interacted with. + pub fn new() -> (ZoneService, ZoneServiceHandle) { + let state = Arc::new(std::sync::RwLock::default()); + let service = ZoneService { + state: state.clone(), + }; + let handle = ZoneServiceHandle { state }; + (service, handle) + } +} + +impl Clone for ZoneService { + fn clone(&self) -> Self { + Self { + state: self.state.clone(), + } + } +} + +/// A compatibility layer to [`domain::net::server`]. +/// +/// In the future, the network server stack should be gradually inlined here, +/// so it can use [`domain::new`] and support more functionality (e.g. handling +/// XFRs by spawning OS threads). +mod compat { + use std::{pin::Pin, sync::Arc}; + + use cascade_zonedata::OldRecord; + use domain::{ + base::{Message, MessageBuilder, iana::Rcode}, + net::server::{ + message::Request, + service::{CallResult, Service, ServiceResult}, + }, + new::base::wire::ParseBytesZC, + tsig, + }; + + use futures::Stream; + use tracing::{debug, trace}; + + use crate::server::request::{RequestKind, ZoneRequestKind}; + + use super::{ServedZone, Viewer, ZoneService}; + + impl Service, Option>> for ZoneService + where + V: Viewer + Send + Sync + 'static, + { + type Target = Vec; + type Stream = ResponseStream; + type Future = Response; + + fn call(&self, old_request: Request, Option>>) -> Response { + // Parse the request. + let message = old_request.message().as_slice(); + let message = domain::new::base::Message::parse_bytes_by_ref(message) + .expect("'message' was already checked to be a valid DNS message"); + let request = match crate::server::request::parse(message) { + Ok(request) => request, + Err(_error) => { + // TODO: Generate the response using 'error'. + return Box::pin(std::future::ready(error( + old_request.message(), + Rcode::FORMERR, + ))); + } + }; + + // Determine how to handle the request. + match request.kind { + RequestKind::Zone(zone_request) => { + // Look up the relevant zone. + let state = self.state.read().unwrap(); + let Some(zone) = state.zones.get(&*zone_request.name) else { + // No such zone could be found. + let rcode = match zone_request.kind { + // Return NXDOMAIN for normal queries. + ZoneRequestKind::Soa => Rcode::NXDOMAIN, + // Return NOTAUTH for zone transfers. + ZoneRequestKind::Axfr | ZoneRequestKind::Ixfr { .. } => Rcode::NOTAUTH, + }; + 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(); + async move { + let viewer = viewer.read_owned().await; + soa(old_request.message(), &*viewer) + } + }) as Response, + + ZoneRequestKind::Axfr => { + Box::pin(axfr(old_request, zone.clone())) as Response + } + + // TODO: Support IXFR. + ZoneRequestKind::Ixfr { .. } => Box::pin(std::future::ready(error( + old_request.message(), + Rcode::NOTIMP, + ))), + } + } + } + } + } + + /// 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. + return error(request, Rcode::NXDOMAIN); + } + let soa = viewer.soa().clone(); + + let builder = MessageBuilder::new_stream_vec(); + let mut builder = builder.start_answer(request, Rcode::NOERROR).unwrap(); + builder.header_mut().set_aa(true); + builder.push(OldRecord::from(soa)).unwrap(); + + let response = builder.additional(); + let result = Ok(CallResult::new(response)); + Box::new(futures::stream::once(std::future::ready(result))) as _ + } + + async fn axfr( + request: Request, Option>>, + zone: ServedZone, + ) -> ResponseStream { + // Refuse AXFR requests over UDP. + if request.transport_ctx().is_udp() { + return error(request.message(), Rcode::NOTIMP); + } + + // Obtain a read lock to read the zone for an extended duration. + let viewer = zone.viewer.read_owned().await; + + if viewer.is_empty() { + // The zone is known to exist, but we don't have any data for it. + return error(request.message(), Rcode::NOTAUTH); + } + + // NOTE: The following code is a bit tricky. Ideally, we would elide + // the channel and return the `messages` iterator as an async `Stream`; + // but the iterator borrows from `viewer` via `.non_soa_records()`, and + // this prevents the iterator from satisfying `'static`. Rust actually + // _does_ have machinery to work around this, in async functions, so we + // prepare the messages in an async function (as a Tokio task) and send + // them over a channel from there. + // + // In the future, AXFRs could be implemented by spawning an OS thread + // and doing all the work there. This is incompatible with the API of + // `domain::net::server`, as the underlying TCP connection cannot be + // extracted, but we plan to stop using that API anyway. + + let (tx, mut rx) = tokio::sync::mpsc::channel(1024); + + tokio::task::spawn(async move { + // Extract the records to serve. + let soa = viewer.soa().clone(); + let mut records = [soa.clone().into()] + .into_iter() + .chain(viewer.non_soa_records()) + .chain([soa.into()]) + .peekable(); + + // Divide the records into DNS messages. + let mut max_message_size = u16::MAX; // TCP + max_message_size -= request.num_reserved_bytes(); + let messages = std::iter::from_fn(move || { + records.peek()?; + + let mut builder = MessageBuilder::new_stream_vec(); + builder.set_push_limit(max_message_size as usize); + let mut builder = builder + .start_answer(request.message(), Rcode::NOERROR) + .unwrap(); + builder.header_mut().set_aa(true); + + while let Some(record) = records.peek() { + match builder.push(OldRecord::from(record.clone())) { + // On success, consume the record. + Ok(()) => { + let _ = records.next(); + } + + // Once the message runs out of space, stop. + Err(_) => break, + } + } + + let response = builder.additional(); + Some(CallResult::new(response)) + }); + + for message in messages { + if tx.send(message).await.is_err() { + // The channel has closed; stop. + break; + } + } + }); + + let stream = futures::stream::poll_fn(move |cx| rx.poll_recv(cx).map(|m| m.map(Ok))); + + Box::new(stream) as _ + } + + fn error(request: &Message>, rcode: Rcode) -> ResponseStream { + let response = MessageBuilder::new_stream_vec() + .start_error(request, rcode) + .additional(); + let result = Ok(CallResult::new(response)); + Box::new(futures::stream::once(std::future::ready(result))) as _ + } + + type ResponseStream = Box>> + Unpin + Send + Sync>; + type Response = Pin + Send + Sync>>; +} + +//----------- Viewer ----------------------------------------------------------- + +/// A viewer through which zone data can be served. +trait Viewer { + /// Whether the zone instance is empty. + fn is_empty(&self) -> bool; + + /// Return the SOA record. + fn soa(&self) -> &SoaRecord; + + /// Return all records in the zone (excluding SOA). + fn non_soa_records(&self) -> impl Iterator + Send; +} + +impl Viewer for LoadedZoneReviewer { + fn is_empty(&self) -> bool { + self.read_loaded().is_none() + } + + fn soa(&self) -> &SoaRecord { + self.read_loaded().unwrap().soa() + } + + fn non_soa_records(&self) -> impl Iterator + Send { + self.read_loaded() + .unwrap() + .regular_records() + .iter() + .cloned() + } +} + +impl Viewer for SignedZoneReviewer { + fn is_empty(&self) -> bool { + self.read().is_none() + } + + fn soa(&self) -> &SoaRecord { + self.read().unwrap().soa() + } + + fn non_soa_records(&self) -> impl Iterator + Send { + let reader = self.read().unwrap(); + reader + .loaded_records() + .chain(reader.generated_records().iter().cloned()) + } +} + +impl Viewer for ZoneViewer { + fn is_empty(&self) -> bool { + self.read().is_none() + } + + fn soa(&self) -> &SoaRecord { + self.read().unwrap().soa() + } + + fn non_soa_records(&self) -> impl Iterator + Send { + let reader = self.read().unwrap(); + reader + .loaded_records() + .chain(reader.generated_records().iter().cloned()) + } +} + +//----------- ZoneServiceHandle ------------------------------------------------ + +/// A handle for controlling a [`ZoneService`]. +pub struct ZoneServiceHandle { + /// The underlying state. + state: Arc>>, +} + +impl ZoneServiceHandle { + /// Register a new zone. + /// + /// ## Panics + /// + /// Panics if the zone is already registered. + pub fn add_zone(&self, zone: Arc, viewer: V) { + let mut state = self.state.write().unwrap(); + let name = RevNameBuf::parse_bytes(zone.name.as_slice()).unwrap(); + let zone = ServedZone { + handle: zone, + viewer: Arc::new(tokio::sync::RwLock::new(viewer)), + }; + let previous = state.zones.insert(name.unsized_copy_into(), zone); + assert!(previous.is_none(), "the zone is already registered"); + } + + /// Update the viewer of a zone. + /// + /// The old viewer is returned. + /// + /// ## Panics + /// + /// Panics if the zone has not been registered. + pub async fn update_viewer(&self, zone: &Arc, viewer: V) -> V { + // Locate the slot for the viewer. + let slot = { + let state = self.state.read().unwrap(); + let name = RevNameBuf::parse_bytes(zone.name.as_slice()).unwrap(); + let zone = state + .zones + .get(&*name) + .expect("the zone has been registered"); + zone.viewer.clone() + }; + let mut slot = slot.write().await; + std::mem::replace(&mut *slot, viewer) + } + + /// Remove a zone. + /// + /// ## Panics + /// + /// Panics if the zone is not known to the service. + pub fn remove_zone(&self, zone: &Arc) { + let mut state = self.state.write().unwrap(); + let name = RevNameBuf::parse_bytes(zone.name.as_slice()).unwrap(); + let existed = state.zones.remove(&*name); + let ServedZone { handle, viewer } = existed.expect("the zone exists"); + assert!( + Arc::ptr_eq(&handle, zone), + "distinct 'Arc's had the same name" + ); + let _ = viewer; + } +} + +//----------- ZoneServiceState ------------------------------------------------- + +/// State for serving zone data. +struct ZoneServiceState { + /// Zones being served. + zones: foldhash::HashMap, ServedZone>, +} + +impl Default for ZoneServiceState { + fn default() -> Self { + Self { + zones: Default::default(), + } + } +} + +/// A zone being served. +struct ServedZone { + /// The zone handle. + handle: Arc, + + /// The viewer for the zone. + /// + /// [`tokio::sync::RwLock`] is used as viewers might be held for extended + /// periods of time (e.g. across whole AXFRs). + // + // TODO: Use a more advanced double-buffering mechanism that allows new + // readers to use the updated value, while old readers can retain their + // lock. + viewer: Arc>, +} + +impl Clone for ServedZone { + fn clone(&self) -> Self { + Self { + handle: self.handle.clone(), + viewer: self.viewer.clone(), + } + } +} diff --git a/src/units/http_server.rs b/src/units/http_server.rs index 659d7458f..579b9e6f2 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -917,12 +917,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 9dd3f3ccc..117237d2e 100644 --- a/src/units/zone_server.rs +++ b/src/units/zone_server.rs @@ -299,13 +299,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 596bbb92fe036f4dce7a2184d4ce9606ba1360aa Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:34:09 +0200 Subject: [PATCH 30/38] WIP --- integration-tests/system-tests.yml | 6 +++--- .../tests/{outbound-tsig => downstream-tsig}/action.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename integration-tests/tests/{outbound-tsig => downstream-tsig}/action.yml (97%) diff --git a/integration-tests/system-tests.yml b/integration-tests/system-tests.yml index 7d1741b99..fcbec4789 100644 --- a/integration-tests/system-tests.yml +++ b/integration-tests/system-tests.yml @@ -275,12 +275,12 @@ jobs: with: log-level: ${{ inputs.log-level }} - outbound-tsig: + downstream-tsig: name: Using TSIG with downstream nameservers. runs-on: ubuntu-latest strategy: matrix: - send-notify-to: [ '' ] #, '127.0.0.1:1054', '127.0.0.1:1054^tsig-key' ] + send-notify-to: [ '', '127.0.0.1:1054', '127.0.0.1:1054^tsig-key' ] accept-xfr-from: [ '' ] #, '127.0.0.1:1054', '127.0.0.1:1054^tsig-key', '^tsig-key' ] zone-name: ['notify-tsig.test.'] #['example.test.' ], 'notify-tsig.test.', 'xfr-tsig.test.', 'notify-and-xfr-tsig.test.'] steps: @@ -288,7 +288,7 @@ jobs: - uses: ./.github/actions/set-build-profile with: build-profile: ${{ inputs.build-profile }} - - uses: ./integration-tests/tests/outbound-tsig + - uses: ./integration-tests/tests/downstream-tsig with: log-level: ${{ inputs.log-level }} send-notify-to: ${{ matrix.send-notify-to }} diff --git a/integration-tests/tests/outbound-tsig/action.yml b/integration-tests/tests/downstream-tsig/action.yml similarity index 97% rename from integration-tests/tests/outbound-tsig/action.yml rename to integration-tests/tests/downstream-tsig/action.yml index 8403aa380..311e405ae 100644 --- a/integration-tests/tests/outbound-tsig/action.yml +++ b/integration-tests/tests/downstream-tsig/action.yml @@ -101,9 +101,9 @@ runs: run: | ./integration-tests/scripts/manage-test-environment.sh control nsd-secondary transfer ${{ inputs.zone-name }} - - name: The ${{ inputs.zone-name }} zone should now be available via the resolver + - name: The ${{ inputs.zone-name }} zone should now be available via the secondary run: | - dig text.${{ inputs.zone-name }} TXT | grep "Hello World" + dig @127.0.0.1 -p 1054 text.${{ inputs.zone-name }} TXT | grep "Hello World" - name: Print log files on any failure in this job uses: ./.github/actions/print-logfiles From 575c1526084d3659c857ecf794175aa2889f833e Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 15 Apr 2026 02:49:23 +0200 Subject: [PATCH 31/38] Bump dnst for set publication-server and set tsig-store-path support. --- 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 02:52:00 +0200 Subject: [PATCH 32/38] WIP --- integration-tests/system-tests.yml | 11 ++- .../tests/downstream-tsig/action.yml | 75 ++++++++++++++----- src/policy/file/v1.rs | 14 +++- src/policy/mod.rs | 44 ++++++----- src/state/v1.rs | 15 +++- src/units/http_server.rs | 38 ++++------ src/units/key_manager.rs | 14 +++- src/units/zone_server.rs | 72 ++++++++++-------- src/zone/state/v1.rs | 16 +++- 9 files changed, 188 insertions(+), 111 deletions(-) diff --git a/integration-tests/system-tests.yml b/integration-tests/system-tests.yml index fcbec4789..c814e20e5 100644 --- a/integration-tests/system-tests.yml +++ b/integration-tests/system-tests.yml @@ -279,10 +279,12 @@ jobs: name: Using TSIG with downstream nameservers. runs-on: ubuntu-latest strategy: + fail-fast: true matrix: - send-notify-to: [ '', '127.0.0.1:1054', '127.0.0.1:1054^tsig-key' ] - accept-xfr-from: [ '' ] #, '127.0.0.1:1054', '127.0.0.1:1054^tsig-key', '^tsig-key' ] - zone-name: ['notify-tsig.test.'] #['example.test.' ], 'notify-tsig.test.', 'xfr-tsig.test.', 'notify-and-xfr-tsig.test.'] + send-notify-to: [ '', '127.0.0.1:1054', '127.0.0.1:1054^tsig-key' ] + accept-xfr-from: [ '', '127.0.0.1:1054', '127.0.0.1:1054^tsig-key', '^tsig-key' ] + downstream-expects-tsig: [ 0, 1 ] + downstream-uses-tsig: [ 0, 1 ] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/set-build-profile @@ -293,4 +295,5 @@ jobs: log-level: ${{ inputs.log-level }} send-notify-to: ${{ matrix.send-notify-to }} accept-xfr-from: ${{ matrix.accept-xfr-from }} - zone-name: ${{ matrix.zone-name }} + downstream-expects-tsig: ${{ matrix.downstream-expects-tsig }} + downstream-uses-tsig: ${{ matrix.downstream-uses-tsig }} diff --git a/integration-tests/tests/downstream-tsig/action.yml b/integration-tests/tests/downstream-tsig/action.yml index 311e405ae..e26bd1590 100644 --- a/integration-tests/tests/downstream-tsig/action.yml +++ b/integration-tests/tests/downstream-tsig/action.yml @@ -28,11 +28,14 @@ inputs: required: false default: '' type: string - # Note: Changing the zone name can be used to select the TSIG configuration used by the downstream NSD. - zone-name: - description: The name of the zone to serve to the downstream. Should end with a period. + downstream-expects-tsig: + description: Whether or not the downstream secondary NSD should expect the NOTIFY from Cascade to use TSIG. required: true - type: string + type: number + downstream-uses-tsig: + description: Whether or not the downstream secondary NSD should use TSIG with XFR to Cascade. + required: true + type: number runs: using: "composite" steps: @@ -41,24 +44,52 @@ runs: with: log-level: ${{ inputs.log-level }} - - name: Make a zonefile for zone ${{ inputs.zone-name }} + - name: Determine the downstrem secondary zone config to use + id: set-zone-name + run: | + if [[ ${{ inputs.downstream-expects-tsig }} -eq 0 && ${{ inputs.downstream-uses-tsig }} -eq 0 ]]; then + ZONE_NAME="example.test." + elif [[ ${{ inputs.downstream-expects-tsig }} -eq 0 && ${{ inputs.downstream-uses-tsig }} -eq 1 ]]; then + ZONE_NAME="xfr-tsig.test." + elif [[ ${{ inputs.downstream-expects-tsig }} -eq 1 && ${{ inputs.downstream-uses-tsig }} -eq 0 ]]; then + ZONE_NAME="notify-tsig.test." + else + ZONE_NAME="notify-and-xfr-tsig.test." + fi + echo "ZONE_NAME=${ZONE_NAME}" >> "$GITHUB_OUTPUT" + + - name: Determine if we expect this test to succeed or fail + id: set-expected-outcome + run: | + if [[ "${{ inputs.send-notify-to }}" != *"^"* && ${{ inputs.downstream-expects-tsig }} -eq 0 ]]; then + SUCCESS=1 + elif [[ "${{ inputs.send-notify-to }}" != *"^"* && ${{ inputs.downstream-expects-tsig }} -eq 1 ]]; then + SUCCESS=0 + elif [[ "${{ inputs.send-notify-to }}" == *"^"* && ${{ inputs.downstream-expects-tsig }} -eq 0 ]]; then + SUCCESS=0 + else + SUCCESS=1 + fi + echo "SUCCESS=${SUCCESS}" >> "$GITHUB_OUTPUT" + + - name: Make a zonefile to load zone ${{ steps.set-zone-name.outputs.ZONE_NAME }} run: | tee test.zone <<'EOF' $TTL 5 ; use a very short TTL for sped up keyset rolls - ${{ inputs.zone-name }} IN SOA ns1.${{ inputs.zone-name }} mail.${{ inputs.zone-name }} ( + ${{ steps.set-zone-name.outputs.ZONE_NAME }} IN SOA ns1.${{ steps.set-zone-name.outputs.ZONE_NAME }} mail.${{ steps.set-zone-name.outputs.ZONE_NAME }} ( 1 ; serial 60 ; refresh (60 seconds) 60 ; retry (60 seconds) 3600 ; expire (1 hour) 5 ; minimum (5 seconds) ) - @ NS ${{ inputs.zone-name }} - @ NS ns1.${{ inputs.zone-name }} + @ NS ${{ steps.set-zone-name.outputs.ZONE_NAME }} + @ NS ns1.${{ steps.set-zone-name.outputs.ZONE_NAME }} @ A 127.0.0.1 ns1 A 127.0.0.1 www A 169.254.1.1 - mail MX 10 ${{ inputs.zone-name }} + mail MX 10 ${{ steps.set-zone-name.outputs.ZONE_NAME }} text TXT "Hello World!" EOF @@ -72,38 +103,42 @@ runs: if [ "${{ inputs.send-notify-to }}" != "" ]; then echo 'send-notify-to = ["${{ inputs.send-notify-to }}"]' >> "${POLICY_DIR}/restricted.toml" fi + if [[ "${{ inputs.accept-xfr-from}}" == *"^tsig-key"* || "${{ inputs.send-notify-to }}" == *"^tsig-key"* ]]; then + cascade tsig add tsig-key hmac-sha256 "COzoVsYQmXeXiyq1Quhp0bbVnMyxjPxsaGSoIWR98i0=" + fi cascade policy reload - - name: Add zone ${{ inputs.zone-name }} using the custom policy + - name: Add zone ${{ steps.set-zone-name.outputs.ZONE_NAME }} using the custom policy run: | - cascade zone add --policy restricted --source $PWD/test.zone ${{ inputs.zone-name }} + cascade zone add --policy restricted --source $PWD/test.zone ${{ steps.set-zone-name.outputs.ZONE_NAME }} - - name: Wait for zone ${{ inputs.zone-name }} to be signed and published + - name: Wait for zone ${{ steps.set-zone-name.outputs.ZONE_NAME }} to be signed and published run: | timeout=10 # seconds start=$(date +%s) - until cascade zone status ${{ inputs.zone-name }}| grep -q "Published zone available"; do + until cascade zone status ${{ steps.set-zone-name.outputs.ZONE_NAME }}| grep -q "Published zone available"; do if (($(date +%s) > (start + timeout))); then - cascade zone status ${{ inputs.zone-name }} + cascade zone status ${{ steps.set-zone-name.outputs.ZONE_NAME }} echo "timeout: zone status did not report published zone available" >&2 exit 1 fi sleep 1 done - - name: Verify that the ${{ inputs.zone-name }} zone is being served + - name: Verify that the ${{ steps.set-zone-name.outputs.ZONE_NAME }} zone is being served run: | - dig @127.0.0.1 -p 4542 ${{ inputs.zone-name }} SOA + dig @127.0.0.1 -p 4542 ${{ steps.set-zone-name.outputs.ZONE_NAME }} SOA - - name: Tell NSD that zone ${{ inputs.zone-name }} is now available + - name: Tell NSD that zone ${{ steps.set-zone-name.outputs.ZONE_NAME }} is now available # Only necessary if NOTIFY is not setup for cascade if: ${{ inputs.send-notify-to == '' }} run: | - ./integration-tests/scripts/manage-test-environment.sh control nsd-secondary transfer ${{ inputs.zone-name }} + ./integration-tests/scripts/manage-test-environment.sh control nsd-secondary transfer ${{ steps.set-zone-name.outputs.ZONE_NAME }} - - name: The ${{ inputs.zone-name }} zone should now be available via the secondary + - name: The ${{ steps.set-zone-name.outputs.ZONE_NAME }} zone should now be available via the secondary + if: ${{ steps.set-expected-outcome.output.SUCCESS }} run: | - dig @127.0.0.1 -p 1054 text.${{ inputs.zone-name }} TXT | grep "Hello World" + dig @127.0.0.1 -p 1054 text.${{ steps.set-zone-name.outputs.ZONE_NAME }} TXT | grep 'Hello World' - name: Print log files on any failure in this job uses: ./.github/actions/print-logfiles diff --git a/src/policy/file/v1.rs b/src/policy/file/v1.rs index 983a2e4a7..91f6166de 100644 --- a/src/policy/file/v1.rs +++ b/src/policy/file/v1.rs @@ -151,7 +151,7 @@ pub struct KeyManagerSpec { /// has the following syntax: :[^]. /// The port is mandatory. The TSIG key name is option and the name /// of the key is preceeded by a caret character (^). - pub publication_nameservers: Vec, + pub publication_nameservers: Vec, } //--- Conversion @@ -247,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(), } } @@ -279,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), diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 15e94c504..ced787e0a 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -2,7 +2,6 @@ use std::fmt::{Display, Formatter}; use std::net::SocketAddr; -use std::str::FromStr; use std::{fs, io, sync::Arc}; use bytes::Bytes; @@ -202,29 +201,22 @@ 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 { - let mut iter = n.split('^'); - - // Skip address:port - let Some(_addr) = iter.next() else { - continue; - }; - - // Get TSIG key name. - let Some(tsig_name) = iter.next() else { - continue; - }; - 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}" - )) - })?; + 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!( - "TSIG key {tsig_name} not found in TSIG store" + "unknown TSIG key '{tsig_name}'" )))?; } + Ok(()) } @@ -317,7 +309,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 ----------------------------------------------------- @@ -491,6 +483,18 @@ pub struct NameserverCommsPolicy { pub tsig_key_name: Option, } +impl Display for NameserverCommsPolicy { + fn fmt(&self, f: &mut Formatter<'_>) -> std::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(()) + } +} + //----------- KeyParameters --------------------------------------------------- #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] 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/units/http_server.rs b/src/units/http_server.rs index 579b9e6f2..b35202475 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -45,6 +45,8 @@ use crate::manager::Terminated; use crate::metrics::MetricsCollection; use crate::policy::SignerDenialPolicy; use crate::policy::SignerSerialPolicy; +use crate::tsig; +use crate::tsig::RemoveError; use crate::units::key_manager::KmipClientCredentials; use crate::units::key_manager::KmipClientCredentialsFile; use crate::units::key_manager::KmipServerCredentialsFileMode; @@ -1130,30 +1132,20 @@ 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. - // - // 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!() + 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); + } + Json(Ok(TsigRemoveResult)) } async fn tsig_key_list(State(http_state): State>) -> Json { diff --git a/src/units/key_manager.rs b/src/units/key_manager.rs index f7a555b88..8309fd3f5 100644 --- a/src/units/key_manager.rs +++ b/src/units/key_manager.rs @@ -713,7 +713,11 @@ fn policy_to_commands(center: &Arc
, policy: &PolicyVersion) -> Vec, policy: &PolicyVersion) -> Vec>(), + ); cmds.append(&mut vec![ strs!["use-csk", km.use_csk], diff --git a/src/units/zone_server.rs b/src/units/zone_server.rs index 117237d2e..cf041f57f 100644 --- a/src/units/zone_server.rs +++ b/src/units/zone_server.rs @@ -1,6 +1,6 @@ use std::future::{Future, ready}; use std::marker::Sync; -use std::net::{IpAddr, SocketAddr}; +use std::net::IpAddr; use std::pin::Pin; use std::process::Stdio; use std::sync::Arc; @@ -43,6 +43,7 @@ use crate::config::SocketConfig; use crate::daemon::SocketProvider; use crate::manager::Terminated; use crate::manager::record_zone_event; +use crate::policy::NameserverCommsPolicy; use crate::util::AbortOnDrop; use crate::zone::{ HistoricalEvent, SignedZoneVersionState, UnsignedZoneVersionState, Zone, ZoneHandle, @@ -293,13 +294,17 @@ impl ZoneServer { "[{unit_name}]: Found {} NOTIFY targets", policy.server.outbound.send_notify_to.len() ); + trace!( + "NOTIFY targets: {:?}", + policy.server.outbound.send_notify_to + ); if !policy.server.outbound.send_notify_to.is_empty() { let addrs = policy .server .outbound .send_notify_to .iter() - .filter_map(|s| s.addr.filter(|addr| addr.port() != 0)); + .filter(|s| s.addr.filter(|addr| addr.port() != 0).is_some()); send_notify_to_addrs(zone_name.clone(), addrs, center); } @@ -838,10 +843,10 @@ fn zone_server_service( Ok(CallResult::new(additional)) } -pub fn send_notify_to_addrs( +pub fn send_notify_to_addrs<'a>( apex_name: StoredName, - notify_set: impl Iterator, - _center: &Arc
, + notify_set: impl Iterator, + center: &Arc
, ) { let mut dgram_config = domain::net::client::dgram::Config::new(); dgram_config.set_max_parallel(1); @@ -854,32 +859,19 @@ pub fn send_notify_to_addrs( let mut msg = msg.question(); msg.push((apex_name, Rtype::SOA)).unwrap(); - for nameserver_addr in notify_set { + for nameserver in notify_set.filter(|ns| ns.addr.is_some()) { let dgram_config = dgram_config.clone(); let req = RequestMessage::new(msg.clone()).unwrap(); - // let tsig_key = zone_info - // .config - // .send_notify_to - // .dst(&nameserver_addr) - // .and_then(|cfg| cfg.tsig_key.as_ref()) - // .and_then(|(name, alg)| key_store.get_key(name, *alg)); - // - // if let Some(key) = tsig_key.as_ref() { - // debug!( - // "Found TSIG key '{}' (algorith {}) for NOTIFY to {nameserver_addr}", - // key.as_ref().name(), - // key.as_ref().algorithm() - // ); - // } - + let nameserver = nameserver.clone(); + let center = center.clone(); tokio::spawn(async move { // TODO: Use the connection factory here. - let udp_connect = UdpConnect::new(nameserver_addr); + let udp_connect = UdpConnect::new(nameserver.addr.unwrap()); let client = Connection::with_config(udp_connect, dgram_config.clone()); - trace!("Sending NOTIFY to nameserver {nameserver_addr}"); - let span = tracing::trace_span!("auth", addr = %nameserver_addr); + trace!("Sending NOTIFY to nameserver {nameserver}"); + let span = tracing::trace_span!("auth", addr = %nameserver); let _guard = span.enter(); // https://datatracker.ietf.org/doc/html/rfc1996 @@ -891,16 +883,32 @@ pub fn send_notify_to_addrs( // // TODO: We have no retry queue at the moment. Do we need one? - // let res = if let Some(key) = tsig_key { - // let client = net::client::tsig::Connection::new(key.clone(), client); - // client.send_request(req.clone()).get_response().await - // } else { - // client.send_request(req.clone()).get_response().await - // }; - let res = client.send_request(req.clone()).get_response().await; + let tsig_key = { + let state = center.state.lock().unwrap(); + nameserver + .tsig_key_name + .as_ref() + .and_then(|tsig_key_name| state.tsig_store.get(tsig_key_name)) + .map(|key| key.inner.clone()) + }; + + if let Some(key) = &tsig_key { + debug!( + "Found TSIG key '{}' (algorith {}) for NOTIFY to {nameserver}", + key.name(), + key.algorithm() + ); + } + let res = if let Some(key) = tsig_key { + let client = domain::net::client::tsig::Connection::new(key.clone(), client); + client.send_request(req.clone()).get_response().await + } else { + client.send_request(req.clone()).get_response().await + }; + // let res = client.send_request(req.clone()).get_response().await; if let Err(err) = res { - warn!("Unable to send NOTIFY to nameserver {nameserver_addr}: {err}"); + warn!("Unable to send NOTIFY to nameserver {nameserver}: {err}"); } }); } diff --git a/src/zone/state/v1.rs b/src/zone/state/v1.rs index fd643e619..a888d0411 100644 --- a/src/zone/state/v1.rs +++ b/src/zone/state/v1.rs @@ -9,7 +9,7 @@ use domain::{base::Name, rdata::dnssec::Timestamp}; use serde::{Deserialize, Serialize}; use crate::loader::Source; -use crate::policy::file::v1::OutboundSpec; +use crate::policy::file::v1::{NameserverCommsSpec, OutboundSpec}; use crate::policy::{AutoConfig, DsAlgorithm, KeyParameters}; use crate::zone::HistoryItem; use crate::{ @@ -199,7 +199,7 @@ pub struct KeyManagerPolicySpec { auto_remove: bool, /// Nameservers to check for RRSIG propagation during a key roll. - publication_nameservers: 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 67becab358c7500789063cb1b282ef4d2e3986f1 Mon Sep 17 00:00:00 2001 From: Philip-NLnetLabs Date: Wed, 15 Apr 2026 08:36:17 +0200 Subject: [PATCH 33/38] 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 34/38] 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 35/38] 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 787c1b89c4ba79be2cc7be48948c10c97b401867 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:54:22 +0200 Subject: [PATCH 36/38] Add more RustDoc on the NameserverCommsPolicy type. --- src/policy/mod.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 351bc71cc..2d6de1e1d 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -470,12 +470,28 @@ pub struct InboundPolicy { //----------- NameserverCommsPolicy ------------------------------------------- /// Policy for communicating with another namesever. +/// +/// This type serves a dual purpose: +/// - For outbound communication it specifies the address and port of the +/// nameserver to contact, and optionally a TSIG key that should be used +/// to sign outbound requests. When used for this purpose the address and +/// port are mandatory. +/// - For inbound communication this type is intended to support the access +/// control use case, acting as a white list entry. When used for this +/// purpose all fields are optional and typically a port is not specified +/// as the sending port is not known in advance. If neither an address +/// nor TSIG key are specified the meaning is to allow all inbound +/// communication. If a TSIG key is specified a user of this type could +/// for example use it to reject incoming requests that are not signed +/// with that key, either in combination with a sender address check or +/// to allow requests from any sender address as long as the request is +/// signed using the correct TSIG key. How multiple instances of this type +/// are combined is not defined here, e.g. whether all rules must be +/// satisfied or only one or whether order of rules matters. #[derive(Clone, Debug, PartialEq, Eq)] pub struct NameserverCommsPolicy { /// The address to send to/receive from. /// - /// For sending the port MUST NOT be zero. - /// /// TODO: Support IP prefixes? pub addr: Option, From 91462acef4a8618eb9a8c179f306a346692817a8 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Thu, 16 Apr 2026 16:36:51 +0200 Subject: [PATCH 37/38] FIX: Restore zone TSIG key state on startup. --- src/main.rs | 48 +++++++++++++++++++++++++++--------------- src/zone/state/mod.rs | 41 +++++++++++++++++++++++++++++++----- src/zone/state/v1.rs | 49 ++++++++++++++++++++++++++++++------------- 3 files changed, 102 insertions(+), 36 deletions(-) diff --git a/src/main.rs b/src/main.rs index 542b2e7e0..bee480705 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,7 @@ fn main() -> ExitCode { } // Load the global state file or build one from scratch. - let mut state = match center::State::init_from_file(&config) { + let state = match center::State::init_from_file(&config) { Err(err) => { if err.kind() != io::ErrorKind::NotFound { error!("Could not load the state file: {err}"); @@ -143,6 +143,23 @@ fn main() -> ExitCode { Ok(mut state) => { info!("Successfully loaded the global state file"); + // Load the TSIG store file. Do this before restoring zones + // because zones may contain references to TSIG keys which can + // only be reconstituted if the key material has already been + // loaded into the store. + // + // TODO: Track which TSIG keys are in use by zones. + match state.tsig_store.init_from_file(&config) { + Ok(()) => debug!("Loaded the TSIG store"), + Err(err) if err.kind() == io::ErrorKind::NotFound => { + debug!("No TSIG store found; will create one"); + } + Err(err) => { + error!("Failed to load the TSIG store: {err}"); + return ExitCode::FAILURE; + } + } + let zone_state_dir = &config.zone_state_dir; let policies = &mut state.policies; for zone in &state.zones { @@ -154,12 +171,23 @@ fn main() -> ExitCode { spec } Err(err) => { - error!("Failed to load zone state '{name}' from '{path}': {err}"); + error!("Failed to load state of zone '{name}' from '{path}': {err}"); return ExitCode::FAILURE; } }; + let tsig_store = &state.tsig_store; let mut state = zone.0.state.lock().unwrap(); - *state = spec.parse(&zone.0, policies); + match spec.parse(&zone.0, policies, tsig_store) { + Ok(restored_state) => *state = restored_state, + Err(err) => { + // We have no way to register a zone in a broken + // state, so even if all other zones and state are + // fine we can't load those and show just this one to + // the user as broken, we can only abort. + error!("Failed to parse state of zone '{name}': {err}"); + return ExitCode::FAILURE; + } + }; } state @@ -178,20 +206,6 @@ fn main() -> ExitCode { ); } - // Load the TSIG store file. - // - // TODO: Track which TSIG keys are in use by zones. - match state.tsig_store.init_from_file(&config) { - Ok(()) => debug!("Loaded the TSIG store"), - Err(err) if err.kind() == io::ErrorKind::NotFound => { - debug!("No TSIG store found; will create one"); - } - Err(err) => { - error!("Failed to load the TSIG store: {err}"); - return ExitCode::FAILURE; - } - } - // Bind to listen addresses before daemonizing. let Ok(socket_provider) = bind_to_listen_sockets_as_needed(&config) else { return ExitCode::FAILURE; diff --git a/src/zone/state/mod.rs b/src/zone/state/mod.rs index 23c1b204d..d28a5476e 100644 --- a/src/zone/state/mod.rs +++ b/src/zone/state/mod.rs @@ -7,14 +7,17 @@ use std::{ sync::Arc, }; +use bytes::Bytes; use camino::Utf8Path; +use domain::base::Name; use serde::{Deserialize, Serialize}; use tracing::warn; use crate::{ loader::zone::LoaderState, policy::{Policy, PolicyVersion}, - zone::{Zone, ZoneState}, + tsig::TsigStore, + zone::{Zone, ZoneState, state::v1::ZoneLoadSourceSpecParseError}, }; pub mod v1; @@ -37,7 +40,8 @@ impl Spec { self, zone: &Arc, policies: &mut foldhash::HashMap, Policy>, - ) -> ZoneState { + tsig_store: &TsigStore, + ) -> Result { /// Synchronize a loaded policy with global state. fn sync_policy( policy: PolicyVersion, @@ -89,7 +93,7 @@ impl Spec { history, }) => { let loader = LoaderState { - source: source.parse(), + source: source.parse(tsig_store)?, ..Default::default() }; @@ -102,14 +106,14 @@ impl Spec { p.zones.insert(zone.name.clone()); } - ZoneState { + Ok(ZoneState { policy, min_expiration, next_min_expiration, loader, history, ..Default::default() - } + }) } } } @@ -136,3 +140,30 @@ impl Spec { crate::util::write_file(path, text.as_bytes()) } } + +pub enum ZoneStateParseError { + InvalidTsigKeyName(Name), + UnknownTsigKey(Name), +} + +impl From for ZoneStateParseError { + fn from(err: ZoneLoadSourceSpecParseError) -> Self { + match err { + ZoneLoadSourceSpecParseError::InvalidTsigKeyName(name) => { + Self::InvalidTsigKeyName(name) + } + ZoneLoadSourceSpecParseError::UnknownTsigKey(name) => Self::UnknownTsigKey(name), + } + } +} + +impl std::fmt::Display for ZoneStateParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ZoneStateParseError::InvalidTsigKeyName(name) => { + write!(f, "Invalid TSIG key name '{name}'") + } + ZoneStateParseError::UnknownTsigKey(name) => write!(f, "Unknown TSIG key '{name}'"), + } + } +} diff --git a/src/zone/state/v1.rs b/src/zone/state/v1.rs index a888d0411..2d3c4dc0a 100644 --- a/src/zone/state/v1.rs +++ b/src/zone/state/v1.rs @@ -1,16 +1,19 @@ //! Version 1 of the zone state file. use std::net::SocketAddr; +use std::str::FromStr; use bytes::Bytes; use camino::Utf8Path; -use domain::base::Ttl; +use domain::base::{ToName, Ttl}; +use domain::tsig::KeyName; use domain::{base::Name, rdata::dnssec::Timestamp}; use serde::{Deserialize, Serialize}; use crate::loader::Source; use crate::policy::file::v1::{NameserverCommsSpec, OutboundSpec}; use crate::policy::{AutoConfig, DsAlgorithm, KeyParameters}; +use crate::tsig::TsigStore; use crate::zone::HistoryItem; use crate::{ policy::{ @@ -497,16 +500,33 @@ pub enum ZoneLoadSourceSpec { impl ZoneLoadSourceSpec { /// Parse from this specification. - pub fn parse(self) -> Source { - match self { + pub fn parse(self, tsig_store: &TsigStore) -> Result { + Ok(match self { Self::None => Source::None, Self::Zonefile { path } => Source::Zonefile { path }, - // TODO: Look up the TSIG key in the key store. - Self::Server { addr, tsig_key: _ } => Source::Server { + Self::Server { + addr, + tsig_key: Some(tsig_key), + } => { + let key_name = KeyName::from_str(&tsig_key.to_string()).map_err(|_| { + ZoneLoadSourceSpecParseError::InvalidTsigKeyName(tsig_key.clone()) + })?; + let Some(tsig_key) = tsig_store.get(&key_name).map(|k| k.inner.clone()) else { + return Err(ZoneLoadSourceSpecParseError::UnknownTsigKey(tsig_key)); + }; + Source::Server { + addr, + tsig_key: Some(tsig_key), + } + } + Self::Server { + addr, + tsig_key: None, + } => Source::Server { addr, tsig_key: None, }, - } + }) } /// Build into this specification. @@ -514,14 +534,15 @@ impl ZoneLoadSourceSpec { match source.clone() { Source::None => Self::None, Source::Zonefile { path } => Self::Zonefile { path }, - Source::Server { addr, tsig_key } => Self::Server { - addr, - tsig_key: tsig_key.map(|key| { - let bytes = key.name().as_slice(); - let bytes = Bytes::copy_from_slice(bytes); - Name::from_octets(bytes).unwrap() - }), - }, + Source::Server { addr, tsig_key } => { + let tsig_key = tsig_key.map(|key| key.name().to_bytes()); + Self::Server { addr, tsig_key } + } } } } + +pub enum ZoneLoadSourceSpecParseError { + InvalidTsigKeyName(Name), + UnknownTsigKey(Name), +} 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 38/38] 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, + } + } +}