add tsig auth support

This commit is contained in:
Hannaeko 2022-04-27 19:42:25 +02:00
parent c8906c0060
commit 424f830e5a
6 changed files with 162 additions and 56 deletions

44
Cargo.lock generated
View file

@ -447,6 +447,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 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]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.0.1" version = "1.0.1"
@ -983,6 +998,33 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 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]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.0.0" version = "6.0.0"
@ -1802,6 +1844,7 @@ dependencies = [
"futures-util", "futures-util",
"lazy_static", "lazy_static",
"log", "log",
"openssl",
"radix_trie", "radix_trie",
"rand", "rand",
"thiserror", "thiserror",
@ -1827,6 +1870,7 @@ dependencies = [
"ipnet", "ipnet",
"lazy_static", "lazy_static",
"log", "log",
"openssl",
"rand", "rand",
"smallvec", "smallvec",
"thiserror", "thiserror",

View file

@ -7,7 +7,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
trust-dns-client = { version = "0.21", features = ["dnssec"] } trust-dns-client = { version = "0.21", features = ["dnssec-openssl"] }
trust-dns-proto = "0.21" trust-dns-proto = "0.21"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View file

@ -5,8 +5,14 @@ log:
- target: stderr - target: stderr
any: debug any: debug
key:
- id: dev
algorithm: hmac-sha256
secret: mbmz4J3Efm1BUjqe12M1RHsOnPjYhKQe+2iKO4tL+a4=
acl: acl:
- id: example_acl - id: example_acl
key: dev
address: [ 127.0.0.1, ::1] address: [ 127.0.0.1, ::1]
action: [transfer, update] action: [transfer, update]

View file

@ -1,13 +1,11 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use std::time::Duration as StdDuration;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use chrono::Duration; use chrono::Duration as ChronoDuration;
use crate::models::name::SerdeName;
use rocket::{Request, State, http::Status, request::{FromRequest, Outcome}}; use crate::dns::TsigAlgorithm;
use rocket::outcome::try_outcome;
use crate::dns::{DnsClient, DnsConnectorClient, RecordConnector, ZoneConnector};
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -18,66 +16,70 @@ pub struct Config {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct DnsClientConfig { pub struct DnsClientConfig {
pub server: SocketAddr pub server: SocketAddr,
#[serde(deserialize_with = "from_std_duration")]
pub timeout: StdDuration,
pub tsig: Option<TsigConfig>,
}
#[derive(Debug, Deserialize)]
pub struct TsigConfig {
pub name: SerdeName,
#[serde(deserialize_with = "from_base64")]
pub key: Vec<u8>,
#[serde(deserialize_with = "from_tsigalg")]
pub algorithm: TsigAlgorithm,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct WebAppConfig { pub struct WebAppConfig {
#[serde(deserialize_with = "from_duration")] #[serde(deserialize_with = "from_chrono_duration")]
pub token_duration: Duration, pub token_duration: ChronoDuration,
} }
fn from_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
fn from_std_duration<'de, D>(deserializer: D) -> Result<StdDuration, D::Error>
where D: Deserializer<'de> where D: Deserializer<'de>
{ {
use serde::de::Error; use serde::de::Error;
String::deserialize(deserializer) String::deserialize(deserializer)
.and_then(|string| humantime::parse_duration(&string).map_err(|err| Error::custom(err.to_string()))) .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 fn from_chrono_duration<'de, D>(deserializer: D) -> Result<ChronoDuration, D::Error>
#[rocket::async_trait] where D: Deserializer<'de>
impl<'r> FromRequest<'r> for DnsClient { {
type Error = (); use serde::de::Error;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { String::deserialize(deserializer)
let config = try_outcome!(request.guard::<&State<Config>>().await); .and_then(|string| humantime::parse_duration(&string).map_err(|err| Error::custom(err.to_string())))
match DnsClient::new(config.dns.server).await { .and_then(|duration| ChronoDuration::from_std(duration).map_err(|err| Error::custom(err.to_string())))
Err(e) => {
error!("Failed to connect to DNS server: {}", e);
Outcome::Failure((Status::InternalServerError, ()))
},
Ok(c) => Outcome::Success(c)
}
}
} }
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Box<dyn RecordConnector> { fn from_base64<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
type Error = (); where D: Deserializer<'de>
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { {
let config = try_outcome!(request.guard::<&State<Config>>().await); use serde::de::Error;
match DnsClient::new(config.dns.server).await { String::deserialize(deserializer)
Err(e) => { .and_then(|string| base64::decode(&string).map_err(|err| Error::custom(err.to_string())))
error!("Failed to connect to DNS server: {}", e);
Outcome::Failure((Status::InternalServerError, ()))
},
Ok(c) => Outcome::Success(Box::new(DnsConnectorClient::new(c)))
}
}
} }
#[rocket::async_trait] fn from_tsigalg<'de, D>(deserializer: D) -> Result<TsigAlgorithm, D::Error>
impl<'r> FromRequest<'r> for Box<dyn ZoneConnector> { where D: Deserializer<'de>
type Error = (); {
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { use serde::de::Error;
let config = try_outcome!(request.guard::<&State<Config>>().await);
match DnsClient::new(config.dns.server).await { let algo = match String::deserialize(deserializer)?.as_str() {
Err(e) => { "hmac-sha256" => TsigAlgorithm::HmacSha256,
error!("Failed to connect to DNS server: {}", e); "hmac-sha384" => TsigAlgorithm::HmacSha384,
Outcome::Failure((Status::InternalServerError, ())) "hmac-sha512" => TsigAlgorithm::HmacSha512,
}, _ => return Err(Error::custom("Unsupported mac algorithm"))
Ok(c) => Outcome::Success(Box::new(DnsConnectorClient::new(c))) };
}
if !algo.supported() {
Err(Error::custom("Unsupported mac algorithm"))
} else {
Ok(algo)
} }
} }

View file

@ -1,13 +1,17 @@
use std::{future::Future, pin::Pin, task::{Context, Poll}}; use std::{future::Future, pin::Pin, task::{Context, Poll}};
use std::sync::Arc;
use futures_util::{ready, Stream, StreamExt}; use futures_util::{ready, Stream, StreamExt};
use std::net::SocketAddr;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use tokio::{net::TcpStream as TokioTcpStream, task}; use tokio::{net::TcpStream as TokioTcpStream, task};
use trust_dns_client::{client::AsyncClient, error::ClientError, op::DnsResponse, tcp::TcpClientStream}; 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::error::{ProtoError, ProtoErrorKind};
use trust_dns_proto::iocompat::AsyncIoTokioAsStd; use trust_dns_proto::iocompat::AsyncIoTokioAsStd;
use crate::config::DnsClientConfig;
pub struct DnsClient(AsyncClient); pub struct DnsClient(AsyncClient);
@ -26,13 +30,25 @@ impl DerefMut for DnsClient {
} }
impl DnsClient { impl DnsClient {
pub async fn new(addr: SocketAddr) -> Result<Self, ProtoError> { pub async fn from_config(dns_config: &DnsClientConfig) -> Result<Self, ProtoError> {
let (stream, handle) = TcpClientStream::<AsyncIoTokioAsStd<TokioTcpStream>>::new(addr); let (stream, handle) = TcpClientStream::<AsyncIoTokioAsStd<TokioTcpStream>>::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( let client = AsyncClient::with_timeout(
stream, stream,
handle, handle,
std::time::Duration::from_secs(5), dns_config.timeout,
None); signer,
);
let (client, bg) = client.await?; let (client, bg) = client.await?;
task::spawn(bg); task::spawn(bg);
return Ok(DnsClient(client)) return Ok(DnsClient(client))
@ -61,3 +77,4 @@ where
) )
} }
} }

View file

@ -10,8 +10,45 @@ pub use trust_dns_client::rr::{
RData, DNSClass, Record RData, DNSClass, Record
}; };
pub use trust_dns_proto::rr::Name; pub use trust_dns_proto::rr::Name;
pub use trust_dns_proto::rr::dnssec::rdata::tsig::TsigAlgorithm;
// Reexport module types // Reexport module types
pub use connector::{RecordConnector, ZoneConnector, ConnectorError}; pub use connector::{RecordConnector, ZoneConnector, ConnectorError};
pub use dns_connector::{DnsConnectorClient, DnsConnectorError}; pub use dns_connector::{DnsConnectorClient, DnsConnectorError};
pub use client::DnsClient; 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<dyn RecordConnector> {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let config = try_outcome!(request.guard::<&State<Config>>().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<dyn ZoneConnector> {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let config = try_outcome!(request.guard::<&State<Config>>().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)))
}
}
}