From 424f830e5aebea77e74a193942db3109d0040ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Berthaud-M=C3=BCller?= Date: Wed, 27 Apr 2022 19:42:25 +0200 Subject: [PATCH] add tsig auth support --- Cargo.lock | 44 +++++++++++++++ Cargo.toml | 2 +- dev-scripts/config/knot.conf | 6 +++ src/config.rs | 102 ++++++++++++++++++----------------- src/dns/client.rs | 27 ++++++++-- src/dns/mod.rs | 37 +++++++++++++ 6 files changed, 162 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e1e1de..1743a8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -447,6 +447,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -983,6 +998,33 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-sys" +version = "0.9.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_str_bytes" version = "6.0.0" @@ -1802,6 +1844,7 @@ dependencies = [ "futures-util", "lazy_static", "log", + "openssl", "radix_trie", "rand", "thiserror", @@ -1827,6 +1870,7 @@ dependencies = [ "ipnet", "lazy_static", "log", + "openssl", "rand", "smallvec", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index a8bb330..83bfd03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -trust-dns-client = { version = "0.21", features = ["dnssec"] } +trust-dns-client = { version = "0.21", features = ["dnssec-openssl"] } trust-dns-proto = "0.21" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/dev-scripts/config/knot.conf b/dev-scripts/config/knot.conf index e6e04ef..0615199 100644 --- a/dev-scripts/config/knot.conf +++ b/dev-scripts/config/knot.conf @@ -5,8 +5,14 @@ log: - target: stderr any: debug +key: + - id: dev + algorithm: hmac-sha256 + secret: mbmz4J3Efm1BUjqe12M1RHsOnPjYhKQe+2iKO4tL+a4= + acl: - id: example_acl + key: dev address: [ 127.0.0.1, ::1] action: [transfer, update] diff --git a/src/config.rs b/src/config.rs index e922a91..8921197 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,13 +1,11 @@ use std::net::SocketAddr; +use std::time::Duration as StdDuration; use serde::{Deserialize, Deserializer}; -use chrono::Duration; +use chrono::Duration as ChronoDuration; - -use rocket::{Request, State, http::Status, request::{FromRequest, Outcome}}; -use rocket::outcome::try_outcome; - -use crate::dns::{DnsClient, DnsConnectorClient, RecordConnector, ZoneConnector}; +use crate::models::name::SerdeName; +use crate::dns::TsigAlgorithm; #[derive(Debug, Deserialize)] @@ -18,66 +16,70 @@ pub struct Config { #[derive(Debug, Deserialize)] pub struct DnsClientConfig { - pub server: SocketAddr + pub server: SocketAddr, + #[serde(deserialize_with = "from_std_duration")] + pub timeout: StdDuration, + pub tsig: Option, +} + +#[derive(Debug, Deserialize)] +pub struct TsigConfig { + pub name: SerdeName, + #[serde(deserialize_with = "from_base64")] + pub key: Vec, + #[serde(deserialize_with = "from_tsigalg")] + pub algorithm: TsigAlgorithm, } #[derive(Debug, Deserialize)] pub struct WebAppConfig { - #[serde(deserialize_with = "from_duration")] - pub token_duration: Duration, + #[serde(deserialize_with = "from_chrono_duration")] + pub token_duration: ChronoDuration, } -fn from_duration<'de, D>(deserializer: D) -> Result + +fn from_std_duration<'de, D>(deserializer: D) -> Result where D: Deserializer<'de> { use serde::de::Error; String::deserialize(deserializer) .and_then(|string| humantime::parse_duration(&string).map_err(|err| Error::custom(err.to_string()))) - .and_then(|duration| Duration::from_std(duration).map_err(|err| Error::custom(err.to_string()))) } -// TODO: Maybe remove this -#[rocket::async_trait] -impl<'r> FromRequest<'r> for DnsClient { - type Error = (); - async fn from_request(request: &'r Request<'_>) -> Outcome { - let config = try_outcome!(request.guard::<&State>().await); - match DnsClient::new(config.dns.server).await { - Err(e) => { - error!("Failed to connect to DNS server: {}", e); - Outcome::Failure((Status::InternalServerError, ())) - }, - Ok(c) => Outcome::Success(c) - } - } +fn from_chrono_duration<'de, D>(deserializer: D) -> Result + where D: Deserializer<'de> +{ + use serde::de::Error; + String::deserialize(deserializer) + .and_then(|string| humantime::parse_duration(&string).map_err(|err| Error::custom(err.to_string()))) + .and_then(|duration| ChronoDuration::from_std(duration).map_err(|err| Error::custom(err.to_string()))) } -#[rocket::async_trait] -impl<'r> FromRequest<'r> for Box { - type Error = (); - async fn from_request(request: &'r Request<'_>) -> Outcome { - let config = try_outcome!(request.guard::<&State>().await); - match DnsClient::new(config.dns.server).await { - Err(e) => { - error!("Failed to connect to DNS server: {}", e); - Outcome::Failure((Status::InternalServerError, ())) - }, - Ok(c) => Outcome::Success(Box::new(DnsConnectorClient::new(c))) - } - } + +fn from_base64<'de, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de> +{ + use serde::de::Error; + String::deserialize(deserializer) + .and_then(|string| base64::decode(&string).map_err(|err| Error::custom(err.to_string()))) } -#[rocket::async_trait] -impl<'r> FromRequest<'r> for Box { - type Error = (); - async fn from_request(request: &'r Request<'_>) -> Outcome { - let config = try_outcome!(request.guard::<&State>().await); - match DnsClient::new(config.dns.server).await { - Err(e) => { - error!("Failed to connect to DNS server: {}", e); - Outcome::Failure((Status::InternalServerError, ())) - }, - Ok(c) => Outcome::Success(Box::new(DnsConnectorClient::new(c))) - } +fn from_tsigalg<'de, D>(deserializer: D) -> Result + where D: Deserializer<'de> +{ + use serde::de::Error; + + let algo = match String::deserialize(deserializer)?.as_str() { + "hmac-sha256" => TsigAlgorithm::HmacSha256, + "hmac-sha384" => TsigAlgorithm::HmacSha384, + "hmac-sha512" => TsigAlgorithm::HmacSha512, + _ => return Err(Error::custom("Unsupported mac algorithm")) + }; + + if !algo.supported() { + Err(Error::custom("Unsupported mac algorithm")) + } else { + Ok(algo) } + } diff --git a/src/dns/client.rs b/src/dns/client.rs index 276fbda..d68df3b 100644 --- a/src/dns/client.rs +++ b/src/dns/client.rs @@ -1,13 +1,17 @@ use std::{future::Future, pin::Pin, task::{Context, Poll}}; +use std::sync::Arc; use futures_util::{ready, Stream, StreamExt}; -use std::net::SocketAddr; + use std::ops::{Deref, DerefMut}; use tokio::{net::TcpStream as TokioTcpStream, task}; use trust_dns_client::{client::AsyncClient, error::ClientError, op::DnsResponse, tcp::TcpClientStream}; +use trust_dns_client::rr::dnssec::tsig::TSigner; use trust_dns_proto::error::{ProtoError, ProtoErrorKind}; use trust_dns_proto::iocompat::AsyncIoTokioAsStd; +use crate::config::DnsClientConfig; + pub struct DnsClient(AsyncClient); @@ -26,13 +30,25 @@ impl DerefMut for DnsClient { } impl DnsClient { - pub async fn new(addr: SocketAddr) -> Result { - let (stream, handle) = TcpClientStream::>::new(addr); + pub async fn from_config(dns_config: &DnsClientConfig) -> Result { + let (stream, handle) = TcpClientStream::>::new(dns_config.server); + let signer = if let Some(tsig_config) = dns_config.tsig.as_ref() { + Some(Arc::new(TSigner::new( + tsig_config.key.clone(), + tsig_config.algorithm.clone(), + tsig_config.name.clone().into_inner(), + 60, + )?.into())) + } else { + None + }; + let client = AsyncClient::with_timeout( stream, handle, - std::time::Duration::from_secs(5), - None); + dns_config.timeout, + signer, + ); let (client, bg) = client.await?; task::spawn(bg); return Ok(DnsClient(client)) @@ -61,3 +77,4 @@ where ) } } + diff --git a/src/dns/mod.rs b/src/dns/mod.rs index c1e2c65..e788503 100644 --- a/src/dns/mod.rs +++ b/src/dns/mod.rs @@ -10,8 +10,45 @@ pub use trust_dns_client::rr::{ RData, DNSClass, Record }; pub use trust_dns_proto::rr::Name; +pub use trust_dns_proto::rr::dnssec::rdata::tsig::TsigAlgorithm; // Reexport module types pub use connector::{RecordConnector, ZoneConnector, ConnectorError}; pub use dns_connector::{DnsConnectorClient, DnsConnectorError}; pub use client::DnsClient; + + +use rocket::{Request, State, http::Status, request::{FromRequest, Outcome}}; +use rocket::outcome::try_outcome; + +use crate::config::Config; + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for Box { + type Error = (); + async fn from_request(request: &'r Request<'_>) -> Outcome { + let config = try_outcome!(request.guard::<&State>().await); + match DnsClient::from_config(&config.dns).await { + Err(e) => { + error!("Failed to connect to DNS server: {}", e); + Outcome::Failure((Status::InternalServerError, ())) + }, + Ok(c) => Outcome::Success(Box::new(DnsConnectorClient::new(c))) + } + } +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for Box { + type Error = (); + async fn from_request(request: &'r Request<'_>) -> Outcome { + let config = try_outcome!(request.guard::<&State>().await); + match DnsClient::from_config(&config.dns).await { + Err(e) => { + error!("Failed to connect to DNS server: {}", e); + Outcome::Failure((Status::InternalServerError, ())) + }, + Ok(c) => Outcome::Success(Box::new(DnsConnectorClient::new(c))) + } + } +}