basic zone configuration

main
Hannaeko 2021-05-02 17:19:32 +02:00
parent 76f222e1c4
commit 1f3aa12401
6 changed files with 111 additions and 10 deletions

View File

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

View File

@ -0,0 +1,8 @@
services:
knot:
image: cznic/knot
volumes:
- $PWD/zones:/storage/zones:ro
- $PWD/config:/config:ro
command: knotd
network_mode: host

View File

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

View File

@ -27,6 +27,7 @@ async fn rocket() -> rocket::Rocket {
.mount("/api/v1", routes![ .mount("/api/v1", routes![
get_zone_records, get_zone_records,
get_zones, get_zones,
create_zone,
add_member_to_zone, add_member_to_zone,
create_auth_token, create_auth_token,
create_user create_user

View File

@ -21,7 +21,7 @@ use crate::schema::*;
use crate::DbConn; use crate::DbConn;
use crate::config::Config; use crate::config::Config;
use crate::models::errors::{ErrorResponse, make_500}; use crate::models::errors::{ErrorResponse, make_500};
use crate::models::dns::AbsoluteName;
const BEARER: &str = "Bearer "; const BEARER: &str = "Bearer ";
const AUTH_HEADER: &str = "Authentication"; const AUTH_HEADER: &str = "Authentication";
@ -82,6 +82,11 @@ pub struct AddZoneMemberRequest {
pub id: String, pub id: String,
} }
#[derive(Debug, Deserialize)]
pub struct CreateZoneRequest {
pub name: AbsoluteName,
}
// pub struct LdapUserAssociation { // pub struct LdapUserAssociation {
// user_id: Uuid, // user_id: Uuid,
// ldap_id: String // ldap_id: String
@ -366,6 +371,25 @@ impl Zone {
}) })
} }
pub fn create_zone(conn: &diesel::SqliteConnection, zone_request: CreateZoneRequest) -> Result<Zone, UserError> {
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> { pub fn add_member(&self, conn: &diesel::SqliteConnection, new_member: &UserInfo) -> Result<(), UserError> {
use crate::schema::user_zone::dsl::*; use crate::schema::user_zone::dsl::*;

View File

@ -9,7 +9,7 @@ use trust_dns_client::rr::{DNSClass, RecordType};
use crate::{DbConn, models::dns}; use crate::{DbConn, models::dns};
use crate::models::errors::{ErrorResponse, make_500}; 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/<zone>/records")] #[get("/zones/<zone>/records")]
@ -21,22 +21,23 @@ pub async fn get_zone_records(
) -> Result<Json<Vec<dns::Record>>, ErrorResponse> { ) -> Result<Json<Vec<dns::Record>>, ErrorResponse> {
let user_info = user_info?; 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) user_info.get_zone(c, &zone_name)
}).await?;
} }
}).await?;
let response = { let response = {
let query = client.query(zone.clone(), DNSClass::IN, RecordType::AXFR); let query = client.query(zone.clone(), DNSClass::IN, RecordType::AXFR);
query.await.map_err(make_500)? query.await.map_err(make_500)?
}; };
// TODO: Better error handling (ex. not authorized should be 500)
if response.response_code() != ResponseCode::NoError { 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( return ErrorResponse::new(
Status::NotFound, Status::NotFound,
format!("Zone {} could not be found", *zone) format!("Zone {} could not be found", *zone)
@ -55,7 +56,6 @@ pub async fn get_zone_records(
Ok(Json(records)) Ok(Json(records))
} }
// TODO: the post version of that
#[get("/zones")] #[get("/zones")]
pub async fn get_zones( pub async fn get_zones(
conn: DbConn, conn: DbConn,
@ -74,6 +74,37 @@ pub async fn get_zones(
Ok(Json(zones)) Ok(Json(zones))
} }
#[post("/zones", data = "<zone_request>")]
pub async fn create_zone(
conn: DbConn,
mut client: dns::DnsClient,
user_info: Result<UserInfo, ErrorResponse>,
zone_request: Json<CreateZoneRequest>,
) -> Result<Json<Zone>, 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/<zone>/members", data = "<zone_member_request>")] #[post("/zones/<zone>/members", data = "<zone_member_request>")]
pub async fn add_member_to_zone<'r>( pub async fn add_member_to_zone<'r>(