From 1f3aa12401f25cd5ba3378f21a097686d7fb9bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Berthaud-M=C3=BCller?= Date: Sun, 2 May 2021 17:19:32 +0200 Subject: [PATCH] basic zone configuration --- dev-scripts/config/knot.conf | 24 +++++++++++++++ dev-scripts/docker-compose.yml | 8 +++++ dev-scripts/zones/example.com.zone | 13 ++++++++ src/main.rs | 1 + src/models/users.rs | 26 +++++++++++++++- src/routes/zones.rs | 49 ++++++++++++++++++++++++------ 6 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 dev-scripts/config/knot.conf create mode 100644 dev-scripts/docker-compose.yml create mode 100644 dev-scripts/zones/example.com.zone diff --git a/dev-scripts/config/knot.conf b/dev-scripts/config/knot.conf new file mode 100644 index 0000000..34b3dba --- /dev/null +++ b/dev-scripts/config/knot.conf @@ -0,0 +1,24 @@ +server: + listen: [ 0.0.0.0@5353, ::@5353 ] + +log: + - target: stderr + any: debug + +acl: + - id: example_acl + address: [ 127.0.0.1, ::1] + action: transfer + +template: + - id: default + file: "zones/%s.zone" + journal-content: all + zonefile-load: difference-no-serial + zonefile-sync: -1 + serial-policy: dateserial + +zone: + - domain: example.com + acl: example_acl + template: default diff --git a/dev-scripts/docker-compose.yml b/dev-scripts/docker-compose.yml new file mode 100644 index 0000000..c509d4b --- /dev/null +++ b/dev-scripts/docker-compose.yml @@ -0,0 +1,8 @@ +services: + knot: + image: cznic/knot + volumes: + - $PWD/zones:/storage/zones:ro + - $PWD/config:/config:ro + command: knotd + network_mode: host diff --git a/dev-scripts/zones/example.com.zone b/dev-scripts/zones/example.com.zone new file mode 100644 index 0000000..aea8c99 --- /dev/null +++ b/dev-scripts/zones/example.com.zone @@ -0,0 +1,13 @@ +example.com. IN SOA ns.example.com. admin.example.com. ( + 2020250101 ; serial + 28800 ; refresh (8 hours) + 7200 ; retry (2 hours) + 2419200 ; expire (4 weeks) + 300 ; minimum (5 minutes) + ) + +example.com. 84600 IN NS ns.example.com. + +srv1.example.com. 600 IN A 198.51.100.3 +srv1.example.com. 600 IN AAAA 2001:db8:cafe:bc68::2 +www 600 IN CNAME srv1 diff --git a/src/main.rs b/src/main.rs index 7dff5a5..676e88e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ async fn rocket() -> rocket::Rocket { .mount("/api/v1", routes![ get_zone_records, get_zones, + create_zone, add_member_to_zone, create_auth_token, create_user diff --git a/src/models/users.rs b/src/models/users.rs index 2c9d334..eb713b4 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -21,7 +21,7 @@ use crate::schema::*; use crate::DbConn; use crate::config::Config; use crate::models::errors::{ErrorResponse, make_500}; - +use crate::models::dns::AbsoluteName; const BEARER: &str = "Bearer "; const AUTH_HEADER: &str = "Authentication"; @@ -82,6 +82,11 @@ pub struct AddZoneMemberRequest { pub id: String, } +#[derive(Debug, Deserialize)] +pub struct CreateZoneRequest { + pub name: AbsoluteName, +} + // pub struct LdapUserAssociation { // user_id: Uuid, // ldap_id: String @@ -366,6 +371,25 @@ impl Zone { }) } + pub fn create_zone(conn: &diesel::SqliteConnection, zone_request: CreateZoneRequest) -> Result { + use crate::schema::zone::dsl::*; + + let new_zone = Zone { + id: Uuid::new_v4().to_simple().to_string(), + name: zone_request.name.to_utf8(), + }; + + diesel::insert_into(zone) + .values(&new_zone) + .execute(conn) + .map_err(|e| match e { + DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _) => UserError::UserConflict, + other => UserError::DbError(other) + })?; + Ok(new_zone) + } + + pub fn add_member(&self, conn: &diesel::SqliteConnection, new_member: &UserInfo) -> Result<(), UserError> { use crate::schema::user_zone::dsl::*; diff --git a/src/routes/zones.rs b/src/routes/zones.rs index c893b5d..c29f2b1 100644 --- a/src/routes/zones.rs +++ b/src/routes/zones.rs @@ -9,7 +9,7 @@ use trust_dns_client::rr::{DNSClass, RecordType}; use crate::{DbConn, models::dns}; use crate::models::errors::{ErrorResponse, make_500}; -use crate::models::users::{LocalUser, UserInfo, Zone, AddZoneMemberRequest}; +use crate::models::users::{LocalUser, UserInfo, Zone, AddZoneMemberRequest, CreateZoneRequest}; #[get("/zones//records")] @@ -21,22 +21,23 @@ pub async fn get_zone_records( ) -> Result>, ErrorResponse> { let user_info = user_info?; + let zone_name = zone.to_string(); - if !user_info.is_admin() { - let zone_name = zone.clone().to_string(); - conn.run(move |c| { + conn.run(move |c| { + if user_info.is_admin() { + Zone::get_by_name(c, &zone_name) + } else { user_info.get_zone(c, &zone_name) - }).await?; - } + } + }).await?; let response = { let query = client.query(zone.clone(), DNSClass::IN, RecordType::AXFR); query.await.map_err(make_500)? }; - // TODO: Better error handling (ex. not authorized should be 500) if response.response_code() != ResponseCode::NoError { - println!("Querrying of zone {} failed with code {}", *zone, response.response_code()); + println!("Querrying AXFR of zone {} failed with code {}", *zone, response.response_code()); return ErrorResponse::new( Status::NotFound, format!("Zone {} could not be found", *zone) @@ -55,7 +56,6 @@ pub async fn get_zone_records( Ok(Json(records)) } -// TODO: the post version of that #[get("/zones")] pub async fn get_zones( conn: DbConn, @@ -74,6 +74,37 @@ pub async fn get_zones( Ok(Json(zones)) } +#[post("/zones", data = "")] +pub async fn create_zone( + conn: DbConn, + mut client: dns::DnsClient, + user_info: Result, + zone_request: Json, +) -> Result, ErrorResponse> { + user_info?.check_admin()?; + + + // Check if the zone exists in the DNS server + let response = { + let query = client.query(zone_request.name.clone(), DNSClass::IN, RecordType::SOA); + query.await.map_err(make_500)? + }; + + if response.response_code() != ResponseCode::NoError { + println!("Querrying SOA of zone {} failed with code {}", *zone_request.name, response.response_code()); + return ErrorResponse::new( + Status::NotFound, + format!("Zone {} could not be found", *zone_request.name) + ).err() + } + + let zone = conn.run(move |c| { + Zone::create_zone(c, zone_request.into_inner()) + }).await?; + + Ok(Json(zone)) +} + #[post("/zones//members", data = "")] pub async fn add_member_to_zone<'r>(