Template passtrough for dns server information

This commit is contained in:
Slatian 2023-08-05 18:19:28 +02:00
parent cc6a025f89
commit 727d9a77cd
5 changed files with 146 additions and 48 deletions

View File

@ -64,5 +64,5 @@ burst = 15
[dns.resolver.digitalcourage] [dns.resolver.digitalcourage]
display_name = "Digitalcourage 3" display_name = "Digitalcourage 3"
servers = ["5.9.164.112:853","[2a01:4f8:251:554::2]:853"] servers = ["5.9.164.112:853","[2a01:4f8:251:554::2]:853"]
protocol = "Tls" protocol = "tls"
tls_dns_name = "dns3.digitalcourage.de" tls_dns_name = "dns3.digitalcourage.de"

View File

@ -16,6 +16,7 @@ pub struct DnsConfig {
} }
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
#[serde(rename_all="lowercase")]
pub enum DnsProtocol { pub enum DnsProtocol {
Udp, Udp,
Tcp, Tcp,

View File

@ -50,13 +50,13 @@ use crate::geoip::{
LocationResult, LocationResult,
}; };
use crate::idna::IdnaName; use crate::idna::IdnaName;
use crate::simple_dns::DnsLookupResult;
use crate::templating_engine::{ use crate::templating_engine::{
View, View,
ResponseFormat, ResponseFormat,
TemplateSettings, TemplateSettings,
Selectable,
}; };
use crate::ipinfo::{AddressCast,AddressInfo,AddressScope}; use crate::ipinfo::{AddressCast,AddressInfo,AddressScope};
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
@ -85,6 +85,7 @@ pub struct IpResult {
asn: Option<AsnResult>, asn: Option<AsnResult>,
location: Option<LocationResult>, location: Option<LocationResult>,
ip_info: AddressInfo, ip_info: AddressInfo,
used_dns_resolver: Option<String>,
} }
// We need this one to hide the partial lookup field when irelevant // We need this one to hide the partial lookup field when irelevant
@ -97,18 +98,23 @@ pub struct DigResult {
idn: IdnaName, idn: IdnaName,
#[serde(skip_serializing_if = "not")] #[serde(skip_serializing_if = "not")]
partial_lookup: bool, partial_lookup: bool,
used_dns_resolver: String,
} }
struct ServiceSharedState { struct ServiceSharedState {
templating_engine: templating_engine::Engine, templating_engine: templating_engine::Engine,
//dns_resolver: TokioAsyncResolver,
dns_resolvers: HashMap<String,TokioAsyncResolver>, dns_resolvers: HashMap<String,TokioAsyncResolver>,
asn_db: geoip::MMDBCarrier, asn_db: geoip::MMDBCarrier,
location_db: geoip::MMDBCarrier, location_db: geoip::MMDBCarrier,
config: config::EchoIpServiceConfig, config: config::EchoIpServiceConfig,
} }
// Stores configuration that is derived from the original configuration
#[derive(Clone)]
struct DerivedConfiguration {
dns_resolver_selectables: Vec<Selectable>,
}
#[derive(Parser)] #[derive(Parser)]
#[command(author, version, long_about="A web service that tells you your ip-address and more …")] #[command(author, version, long_about="A web service that tells you your ip-address and more …")]
struct CliArgs { struct CliArgs {
@ -234,6 +240,8 @@ async fn main() {
// Initalize DNS resolver with os defaults // Initalize DNS resolver with os defaults
println!("Initalizing dns resolver ..."); println!("Initalizing dns resolver ...");
let mut dns_resolver_selectables = Vec::<Selectable>::new();
println!("Initalizing System resolver ..."); println!("Initalizing System resolver ...");
let res = TokioAsyncResolver::tokio_from_system_conf(); let res = TokioAsyncResolver::tokio_from_system_conf();
//let res = TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()); //let res = TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default());
@ -245,22 +253,39 @@ async fn main() {
} }
}; };
dns_resolver_selectables.push(Selectable {
id: "default".to_string(),
name: "System".to_string()
});
//FIXME: Not release ready,must be configurable and have better error handling. //FIXME: Not release ready,must be configurable and have better error handling.
println!("Initalizing Quad9 resolver ..."); println!("Initalizing Quad9 resolver ...");
let quad9_resolver = TokioAsyncResolver::tokio( let quad9_resolver = TokioAsyncResolver::tokio(
trust_dns_resolver::config::ResolverConfig::quad9_tls(), trust_dns_resolver::config::ResolverConfig::quad9_tls(),
Default::default() Default::default()
).unwrap(); ).unwrap();
dns_resolver_selectables.push(Selectable {
id: "quad9".to_string(),
name: "Quad9".to_string()
});
println!("Initalizing Google resolver ..."); println!("Initalizing Google resolver ...");
let google_resolver = TokioAsyncResolver::tokio( let google_resolver = TokioAsyncResolver::tokio(
trust_dns_resolver::config::ResolverConfig::google(), trust_dns_resolver::config::ResolverConfig::google(),
Default::default() Default::default()
).unwrap(); ).unwrap();
dns_resolver_selectables.push(Selectable {
id: "google".to_string(),
name: "Google".to_string()
});
println!("Initalizing Cloudflare resolver ..."); println!("Initalizing Cloudflare resolver ...");
let cloudflare_resolver = TokioAsyncResolver::tokio( let cloudflare_resolver = TokioAsyncResolver::tokio(
trust_dns_resolver::config::ResolverConfig::cloudflare_tls(), trust_dns_resolver::config::ResolverConfig::cloudflare_tls(),
Default::default() Default::default()
).unwrap(); ).unwrap();
dns_resolver_selectables.push(Selectable {
id: "cloudflare".to_string(),
name: "Cloudflare".to_string()
});
let mut dns_resolver_map: HashMap<String,TokioAsyncResolver> = HashMap::new(); let mut dns_resolver_map: HashMap<String,TokioAsyncResolver> = HashMap::new();
@ -271,6 +296,10 @@ async fn main() {
Default::default() Default::default()
).unwrap(); ).unwrap();
dns_resolver_map.insert(key.clone(), resolver); dns_resolver_map.insert(key.clone(), resolver);
dns_resolver_selectables.push(Selectable {
id: key.clone(),
name: resolver_config.display_name.clone(),
});
} }
dns_resolver_map.insert("default".to_string(), dns_resolver); dns_resolver_map.insert("default".to_string(), dns_resolver);
@ -292,6 +321,10 @@ async fn main() {
config: config.clone(), config: config.clone(),
}); });
let derived_config = DerivedConfiguration {
dns_resolver_selectables: dns_resolver_selectables,
};
let signal_usr1_handlers_state = shared_state.clone(); let signal_usr1_handlers_state = shared_state.clone();
task::spawn(async move { task::spawn(async move {
@ -317,6 +350,8 @@ async fn main() {
.route("/", get(handle_default_route)) .route("/", get(handle_default_route))
.route("/dig/:name", get(handle_dig_route_with_path)) .route("/dig/:name", get(handle_dig_route_with_path))
.route("/ip/:address", get(handle_ip_route_with_path)) .route("/ip/:address", get(handle_ip_route_with_path))
.route("/dns_resolver/:resolver", get(handle_dns_resolver_route_with_path))
.route("/dns_resolver", get(handle_dns_resolver_route))
.route("/ua", get(user_agent_handler)) .route("/ua", get(user_agent_handler))
.route("/hi", get(hello_world_handler)) .route("/hi", get(hello_world_handler))
.fallback_service( .fallback_service(
@ -331,6 +366,7 @@ async fn main() {
config.ratelimit.per_minute, config.ratelimit.burst)) config.ratelimit.per_minute, config.ratelimit.burst))
.layer(middleware::from_fn(ratelimit::rate_limit_middleware)) .layer(middleware::from_fn(ratelimit::rate_limit_middleware))
.layer(Extension(config)) .layer(Extension(config))
.layer(Extension(derived_config))
.layer(middleware::from_fn(settings_query_middleware)) .layer(middleware::from_fn(settings_query_middleware))
) )
; ;
@ -347,6 +383,7 @@ async fn main() {
async fn settings_query_middleware<B>( async fn settings_query_middleware<B>(
Query(query): Query<SettingsQuery>, Query(query): Query<SettingsQuery>,
Extension(config): Extension<config::EchoIpServiceConfig>, Extension(config): Extension<config::EchoIpServiceConfig>,
Extension(derived_config): Extension<DerivedConfiguration>,
user_agent_header: Option<TypedHeader<headers::UserAgent>>, user_agent_header: Option<TypedHeader<headers::UserAgent>>,
mut req: Request<B>, mut req: Request<B>,
next: Next<B> next: Next<B>
@ -369,6 +406,7 @@ async fn settings_query_middleware<B>(
template: TemplateSettings{ template: TemplateSettings{
format: format.unwrap_or(ResponseFormat::TextHtml), format: format.unwrap_or(ResponseFormat::TextHtml),
lang: query.lang.unwrap_or("en".to_string()), lang: query.lang.unwrap_or("en".to_string()),
available_dns_resolvers: derived_config.dns_resolver_selectables,
}, },
dns_resolver_id: query.dns.unwrap_or(config.dns.default_resolver), dns_resolver_id: query.dns.unwrap_or(config.dns.default_resolver),
}); });
@ -377,24 +415,24 @@ async fn settings_query_middleware<B>(
async fn not_found_handler( async fn not_found_handler(
State(arc_state): State<Arc<ServiceSharedState>>, State(arc_state): State<Arc<ServiceSharedState>>,
Extension(settings): Extension<TemplateSettings>, Extension(settings): Extension<QuerySettings>,
) -> Response { ) -> Response {
let state = Arc::clone(&arc_state); let state = Arc::clone(&arc_state);
state.templating_engine.render_view( state.templating_engine.render_view(
&settings, &settings.template,
&View::NotFound, &View::NotFound,
).await ).await
} }
async fn hello_world_handler( async fn hello_world_handler(
State(arc_state): State<Arc<ServiceSharedState>>, State(arc_state): State<Arc<ServiceSharedState>>,
Extension(settings): Extension<TemplateSettings>, Extension(settings): Extension<QuerySettings>,
) -> Response { ) -> Response {
let state = Arc::clone(&arc_state); let state = Arc::clone(&arc_state);
state.templating_engine.render_view( state.templating_engine.render_view(
&settings, &settings.template,
&View::Message{ &View::Message{
title: "Hey There!".to_string(), title: "Hey There!".to_string(),
message: "You,You are an awesome Creature!".to_string() message: "You,You are an awesome Creature!".to_string()
@ -499,6 +537,37 @@ async fn handle_search_request(
} }
async fn handle_dns_resolver_route(
Extension(settings): Extension<QuerySettings>,
State(arc_state): State<Arc<ServiceSharedState>>,
) -> Response {
let state = Arc::clone(&arc_state);
state.templating_engine.render_view(
&settings.template,
&View::DnsResolverList,
).await
}
async fn handle_dns_resolver_route_with_path(
Extension(settings): Extension<QuerySettings>,
State(arc_state): State<Arc<ServiceSharedState>>,
extract::Path(query): extract::Path<String>,
) -> Response {
let state = Arc::clone(&arc_state);
if let Some(resolver) = state.config.dns.resolver.get(&query) {
state.templating_engine.render_view(
&settings.template,
&View::DnsResolver{ config: resolver.clone() },
).await
} else {
state.templating_engine.render_view(
&settings.template,
&View::NotFound,
).await
}
}
async fn handle_ip_route_with_path( async fn handle_ip_route_with_path(
Extension(settings): Extension<QuerySettings>, Extension(settings): Extension<QuerySettings>,
State(arc_state): State<Arc<ServiceSharedState>>, State(arc_state): State<Arc<ServiceSharedState>>,
@ -547,20 +616,20 @@ async fn get_ip_result(
asn: None, asn: None,
location: None, location: None,
ip_info: ip_info, ip_info: ip_info,
used_dns_resolver: None,
} }
} }
} }
// do reverse lookup // do reverse lookup
let hostname = if state.config.dns.allow_reverse_lookup { let mut hostname: Option<String> = None;
let mut used_dns_resolver: Option<String> = None;
if state.config.dns.allow_reverse_lookup {
if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) { if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) {
simple_dns::reverse_lookup(&dns_resolver, &address).await hostname = simple_dns::reverse_lookup(&dns_resolver, &address).await;
} else { used_dns_resolver = Some(dns_resolver_name.clone());
None
} }
} else { }
None
};
// asn lookup // asn lookup
let asn_result = state.asn_db.query_asn_for_ip(address); let asn_result = state.asn_db.query_asn_for_ip(address);
@ -572,23 +641,20 @@ async fn get_ip_result(
); );
// filter reverse lookup // filter reverse lookup
let final_hostname = match hostname { if let Some(name) = &hostname {
Some(name) => { if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) {
if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) { hostname = None;
None used_dns_resolver = None;
} else { }
Some(name.to_owned()) }
}
},
None => None,
};
IpResult{ IpResult{
address: *address, address: *address,
hostname: final_hostname, hostname: hostname,
asn: asn_result, asn: asn_result,
location: location_result, location: location_result,
ip_info: ip_info, ip_info: ip_info,
used_dns_resolver: used_dns_resolver,
} }
} }
@ -630,11 +696,23 @@ async fn get_dig_result(
do_full_lookup: bool, do_full_lookup: bool,
) -> DigResult { ) -> DigResult {
let name = &dig_query.trim().trim_end_matches(".").to_string(); let name = &dig_query.trim().trim_end_matches(".").to_string();
if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) { let idna_name = IdnaName::from_string(&name);
Default::default() if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) {
} else { if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) {
if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) { // Try to hide the fact that we didn't do dns resolution at all
let idna_name = IdnaName::from_string(&name); // We resolve example.org as basic avoidance of timing sidechannels.
// WARNING: this timing sidechannel avoidance is very crude.
simple_dns::lookup(
&dns_resolver,
&("example.org.".to_string()),
do_full_lookup).await;
DigResult {
records: DnsLookupResult{ nxdomain: true , ..Default::default() },
idn: idna_name,
partial_lookup: !do_full_lookup,
used_dns_resolver: dns_resolver_name.clone(),
}
} else {
DigResult { DigResult {
records: simple_dns::lookup( records: simple_dns::lookup(
&dns_resolver, &dns_resolver,
@ -642,9 +720,10 @@ async fn get_dig_result(
do_full_lookup).await, do_full_lookup).await,
idn: idna_name, idn: idna_name,
partial_lookup: !do_full_lookup, partial_lookup: !do_full_lookup,
used_dns_resolver: dns_resolver_name.clone(),
} }
} else {
return Default::default();
} }
} else {
return Default::default();
} }
} }

View File

@ -27,20 +27,20 @@ use std::net::IpAddr;
#[derive(serde::Deserialize, serde::Serialize, Default, Clone)] #[derive(serde::Deserialize, serde::Serialize, Default, Clone)]
pub struct DnsLookupResult { pub struct DnsLookupResult {
a: Option<Vec<IpAddr>>, pub a: Option<Vec<IpAddr>>,
aaaa: Option<Vec<IpAddr>>, pub aaaa: Option<Vec<IpAddr>>,
aname: Option<Vec<String>>, pub aname: Option<Vec<String>>,
cname: Option<Vec<String>>, pub cname: Option<Vec<String>>,
mx: Option<Vec<MxRecord>>, pub mx: Option<Vec<MxRecord>>,
ns: Option<Vec<String>>, pub ns: Option<Vec<String>>,
soa: Option<Vec<SoaRecord>>, pub soa: Option<Vec<SoaRecord>>,
txt: Option<Vec<String>>, pub txt: Option<Vec<String>>,
srv: Option<Vec<SrvRecord>>, pub srv: Option<Vec<SrvRecord>>,
caa: Option<Vec<String>>, pub caa: Option<Vec<String>>,
other_error: bool, pub other_error: bool,
dns_error: bool, pub dns_error: bool,
nxdomain: bool, pub nxdomain: bool,
timeout: bool, pub timeout: bool,
} }
#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq)] #[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq)]

View File

@ -15,6 +15,7 @@ use toml::Table;
use crate::DigResult; use crate::DigResult;
use crate::IpResult; use crate::IpResult;
use crate::config::DnsResolverConfig;
/* Response format */ /* Response format */
@ -54,6 +55,13 @@ impl ResponseFormat {
pub struct TemplateSettings { pub struct TemplateSettings {
pub format: ResponseFormat, pub format: ResponseFormat,
pub lang: String, pub lang: String,
pub available_dns_resolvers: Vec<Selectable>,
}
#[derive(serde::Deserialize, serde::Serialize, Clone)]
pub struct Selectable {
pub id: String,
pub name: String,
} }
/* The echoip view */ /* The echoip view */
@ -63,6 +71,8 @@ pub struct TemplateSettings {
pub enum View { pub enum View {
Asn { asn: u32 }, Asn { asn: u32 },
Dig { query: String, result: DigResult }, Dig { query: String, result: DigResult },
DnsResolver{ config: DnsResolverConfig },
DnsResolverList,
Index { result: IpResult, user_agent: Option<String> }, Index { result: IpResult, user_agent: Option<String> },
Ip { result: IpResult }, Ip { result: IpResult },
Message{ title: String, message: String }, Message{ title: String, message: String },
@ -75,6 +85,8 @@ impl View {
match self { match self {
View::Asn{..} => "asn", View::Asn{..} => "asn",
View::Dig{..} => "dig", View::Dig{..} => "dig",
View::DnsResolver{..} => "dns_resolver",
View::DnsResolverList => "dns_resolver_list",
View::Index{..} => "index", View::Index{..} => "index",
View::Ip{..} => "ip", View::Ip{..} => "ip",
View::Message{..} => "message", View::Message{..} => "message",
@ -133,6 +145,12 @@ impl Engine {
View::Index{result, ..} | View::Ip{result, ..} => { View::Index{result, ..} | View::Ip{result, ..} => {
Json(result).into_response() Json(result).into_response()
}, },
View::DnsResolverList => {
Json(settings.available_dns_resolvers.clone()).into_response()
},
View::DnsResolver{ config } => {
Json(config).into_response()
}
_ => Json(view).into_response(), _ => Json(view).into_response(),
} }
} }