unified dns api error and move zone operations to management api

This commit is contained in:
Hannaeko 2022-03-04 18:20:18 +01:00
parent 247f72871e
commit 96f66e1fd7
5 changed files with 94 additions and 80 deletions

View file

@ -16,9 +16,12 @@ pub trait RecordApi {
// Zone management api, todo // Zone management api, todo
// E.g.: Manage catalog zone, dynamically generate knot / bind / nsd config... // E.g.: Manage catalog zone, dynamically generate knot / bind / nsd config...
#[async_trait]
pub trait ZoneApi { pub trait ZoneApi {
type Error;
// get_zones // get_zones
// add_zone // add_zone
// delete_zone // delete_zone
// exists // zone_exists
async fn zone_exists(&mut self, zone: dns::Name, class: dns::DNSClass) -> Result<(), Self::Error>;
} }

View file

@ -6,11 +6,11 @@ use trust_dns_client::proto::xfer::{DnsRequestOptions};
use super::{Name, Record, RData}; use super::{Name, Record, RData};
use super::client::{ClientResponse, DnsClient}; use super::client::{ClientResponse, DnsClient};
use super::api::RecordApi; use super::api::{RecordApi, ZoneApi};
#[derive(Debug)] #[derive(Debug)]
pub enum MessageError { pub enum DnsApiError {
RecordNotInZone { RecordNotInZone {
zone: Name, zone: Name,
class: DNSClass, class: DNSClass,
@ -18,7 +18,10 @@ pub enum MessageError {
mismatched_zone: Vec<Record>, mismatched_zone: Vec<Record>,
}, },
ClientError(ClientError), ClientError(ClientError),
ResponceNotOk(ResponseCode) ResponceNotOk {
code: ResponseCode,
zone: Name,
},
} }
pub struct DnsApiClient { pub struct DnsApiClient {
@ -36,18 +39,21 @@ impl DnsApiClient {
#[async_trait] #[async_trait]
impl RecordApi for DnsApiClient { impl RecordApi for DnsApiClient {
type Error = MessageError; type Error = DnsApiError;
async fn get_records(&mut self, zone: Name, class: DNSClass) -> Result<Vec<Record>, Self::Error> async fn get_records(&mut self, zone: Name, class: DNSClass) -> Result<Vec<Record>, Self::Error>
{ {
let response = { let response = {
let mut query = Query::query(zone, RecordType::AXFR); let mut query = Query::query(zone.clone(), RecordType::AXFR);
query.set_query_class(class); query.set_query_class(class);
ClientResponse(self.client.lookup(query, DnsRequestOptions::default())).await.map_err(|e| MessageError::ClientError(e))? ClientResponse(self.client.lookup(query, DnsRequestOptions::default())).await.map_err(|e| DnsApiError::ClientError(e))?
}; };
if response.response_code() != ResponseCode::NoError { if response.response_code() != ResponseCode::NoError {
return Err(MessageError::ResponceNotOk(response.response_code())); return Err(DnsApiError::ResponceNotOk {
code: response.response_code(),
zone: zone,
});
} }
let answers = response.answers(); let answers = response.answers();
@ -75,7 +81,7 @@ impl RecordApi for DnsApiClient {
} }
if mismatched_class.len() > 0 || mismatched_zone.len() > 0 { if mismatched_class.len() > 0 || mismatched_zone.len() > 0 {
return Err(MessageError::RecordNotInZone { return Err(DnsApiError::RecordNotInZone {
zone, zone,
class, class,
mismatched_zone, mismatched_zone,
@ -104,12 +110,40 @@ impl RecordApi for DnsApiClient {
edns.set_version(0); edns.set_version(0);
} }
let response = ClientResponse(self.client.send(message)).await.map_err(|e| MessageError::ClientError(e))?; let response = ClientResponse(self.client.send(message)).await.map_err(|e| DnsApiError::ClientError(e))?;
if response.response_code() != ResponseCode::NoError { if response.response_code() != ResponseCode::NoError {
return Err(MessageError::ResponceNotOk(response.response_code())); return Err(DnsApiError::ResponceNotOk {
code: response.response_code(),
zone: zone,
});
} }
Ok(()) Ok(())
} }
} }
#[async_trait]
impl ZoneApi for DnsApiClient {
type Error = DnsApiError;
async fn zone_exists(&mut self, zone: Name, class: DNSClass) -> Result<(), Self::Error>
{
let response = {
let mut query = Query::query(zone.clone(), RecordType::SOA);
query.set_query_class(class);
ClientResponse(self.client.lookup(query, DnsRequestOptions::default())).await.map_err(|e| DnsApiError::ClientError(e))?
};
if response.response_code() != ResponseCode::NoError {
return Err(DnsApiError::ResponceNotOk {
code: response.response_code(),
zone: zone,
});
}
Ok(())
}
}

View file

@ -13,3 +13,4 @@ pub use trust_dns_proto::rr::Name;
// Reexport module types // Reexport module types
pub use api::{RecordApi, ZoneApi}; pub use api::{RecordApi, ZoneApi};
pub use dns_api::DnsApiClient;

View file

@ -6,7 +6,8 @@ use rocket_contrib::json::Json;
use serde_json::Value; use serde_json::Value;
use djangohashers::{HasherError}; use djangohashers::{HasherError};
use diesel::result::Error as DieselError; use diesel::result::Error as DieselError;
use crate::dns::dns_api::DnsApiError;
use crate::models;
#[derive(Debug)] #[derive(Debug)]
pub enum UserError { pub enum UserError {
@ -98,6 +99,37 @@ impl From<UserError> for ErrorResponse {
} }
} }
impl From<DnsApiError> for ErrorResponse {
fn from(e: DnsApiError) -> Self {
match e {
DnsApiError::RecordNotInZone { zone, class, mismatched_class, mismatched_zone} => {
ErrorResponse::new(
Status::BadRequest,
"Record list contains records that do not belong to the zone".into()
).with_details(
json!({
"zone_name": zone.to_utf8(),
"class": models::DNSClass::from(class),
"mismatched_class": mismatched_class.into_iter().map(|r| r.clone().into()).collect::<Vec<models::Record>>(),
"mismatched_zone": mismatched_zone.into_iter().map(|r| r.clone().into()).collect::<Vec<models::Record>>(),
})
)
},
DnsApiError::ResponceNotOk { code, zone } => {
println!("Query for zone {} failed with code {}", zone, code);
ErrorResponse::new(
Status::NotFound,
"Zone could not be found".into()
).with_details(json!({
"zone_name": zone.to_utf8()
}))
},
DnsApiError::ClientError(e) => make_500(e)
}
}
}
impl<S> From<ErrorResponse> for Outcome<S, ErrorResponse> { impl<S> From<ErrorResponse> for Outcome<S, ErrorResponse> {
fn from(e: ErrorResponse) -> Self { fn from(e: ErrorResponse) -> Self {

View file

@ -5,16 +5,10 @@ use rocket::http::Status;
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use trust_dns_client::client::ClientHandle;
use trust_dns_client::op::ResponseCode;
use trust_dns_client::rr::{DNSClass, RecordType};
use crate::DbConn; use crate::DbConn;
use crate::dns;
use crate::dns::api::RecordApi;
use crate::dns::dns_api::DnsApiClient;
use crate::dns::dns_api::MessageError;
use crate::models; use crate::models;
use crate::dns;
use crate::dns::{RecordApi, ZoneApi};
#[get("/zones/<zone>/records")] #[get("/zones/<zone>/records")]
@ -36,22 +30,10 @@ pub async fn get_zone_records(
} }
}).await?; }).await?;
let mut dns_api = DnsApiClient::new(client); let mut dns_api = dns::DnsApiClient::new(client);
let records: Vec<_> = match dns_api.get_records(zone.clone(), DNSClass::IN).await { let dns_records = dns_api.get_records(zone.clone(), dns::DNSClass::IN).await?;
Ok(records) => records.into_iter().map(models::Record::from).collect(), let records: Vec<_> = dns_records.into_iter().map(models::Record::from).collect();
Err(MessageError::ResponceNotOk(code)) => {
println!("Querrying AXFR of zone {} failed with code {}", *zone, code);
return models::ErrorResponse::new(
Status::NotFound,
"Zone could not be found".into()
).with_details(json!({
"zone_name": zone.to_utf8()
})).err();
},
Err(err) => { return models::make_500(err).err(); },
};
Ok(Json(records)) Ok(Json(records))
} }
@ -101,37 +83,10 @@ pub async fn create_zone_records(
).err(); ).err();
} }
let mut dns_api = DnsApiClient::new(client); let mut dns_api = dns::DnsApiClient::new(client);
dns_api.add_records(zone.clone(), dns::DNSClass::IN, records).await?;
match dns_api.add_records(zone.clone(), DNSClass::IN, records).await {
Ok(_) => {
return Ok(Json(())); return Ok(Json(()));
//query.await.map_err(models::make_500)?;
}
Err(MessageError::RecordNotInZone { zone, class, mismatched_class, mismatched_zone}) => {
return models::ErrorResponse::new(
Status::BadRequest,
"Record list contains records that do not belong to the zone".into()
).with_details(
json!({
"zone_name": zone.to_utf8(),
"class": models::DNSClass::from(class),
"mismatched_class": mismatched_class.into_iter().map(|r| r.clone().into()).collect::<Vec<models::Record>>(),
"mismatched_zone": mismatched_zone.into_iter().map(|r| r.clone().into()).collect::<Vec<models::Record>>(),
})
).err();
},
Err(MessageError::ResponceNotOk(code)) => {
println!("Update of zone {} failed with code {}", *zone, code);
return models::ErrorResponse::new(
Status::NotFound,
"Update of zone failed".into()
).with_details(json!({
"zone_name": zone.to_utf8()
})).err();
},
Err(e) => return models::make_500(e).err()
};
} }
#[get("/zones")] #[get("/zones")]
@ -155,25 +110,14 @@ pub async fn get_zones(
#[post("/zones", data = "<zone_request>")] #[post("/zones", data = "<zone_request>")]
pub async fn create_zone( pub async fn create_zone(
conn: DbConn, conn: DbConn,
mut client: dns::client::DnsClient, client: dns::client::DnsClient,
user_info: Result<models::UserInfo, models::ErrorResponse>, user_info: Result<models::UserInfo, models::ErrorResponse>,
zone_request: Json<models::CreateZoneRequest>, zone_request: Json<models::CreateZoneRequest>,
) -> Result<Json<models::Zone>, models::ErrorResponse> { ) -> Result<Json<models::Zone>, models::ErrorResponse> {
user_info?.check_admin()?; user_info?.check_admin()?;
// Check if the zone exists in the DNS server let mut dns_api = dns::DnsApiClient::new(client);
let response = { dns_api.zone_exists(zone_request.name.clone(), dns::DNSClass::IN).await?;
let query = client.query(zone_request.name.clone(), DNSClass::IN, RecordType::SOA);
query.await.map_err(models::make_500)?
};
if response.response_code() != ResponseCode::NoError {
println!("Querrying SOA of zone {} failed with code {}", *zone_request.name, response.response_code());
return models::ErrorResponse::new(
Status::NotFound,
format!("Zone {} could not be found", *zone_request.name)
).err()
}
let zone = conn.run(move |c| { let zone = conn.run(move |c| {
models::Zone::create_zone(c, zone_request.into_inner()) models::Zone::create_zone(c, zone_request.into_inner())