add tsig auth support

main
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"
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",

View File

@ -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"

View File

@ -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]

View File

@ -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<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)]
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<Duration, D::Error>
fn from_std_duration<'de, D>(deserializer: D) -> Result<StdDuration, D::Error>
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<Self, Self::Error> {
let config = try_outcome!(request.guard::<&State<Config>>().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<ChronoDuration, D::Error>
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<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::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<Vec<u8>, 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<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::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<TsigAlgorithm, D::Error>
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)
}
}

View File

@ -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<Self, ProtoError> {
let (stream, handle) = TcpClientStream::<AsyncIoTokioAsStd<TokioTcpStream>>::new(addr);
pub async fn from_config(dns_config: &DnsClientConfig) -> Result<Self, ProtoError> {
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(
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
)
}
}

View File

@ -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<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)))
}
}
}