Add domain validation and cleanup
This commit is contained in:
parent
c2431b6225
commit
b6ecf6eda3
|
@ -1308,6 +1308,7 @@ dependencies = [
|
|||
"futures 0.3.14",
|
||||
"log",
|
||||
"nest",
|
||||
"regex",
|
||||
"rocket",
|
||||
"rocket_contrib",
|
||||
"serde 1.0.125",
|
||||
|
|
|
@ -22,6 +22,7 @@ rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", branch = "ma
|
|||
serde = "1.0.125"
|
||||
dotenv = "0.15.0"
|
||||
tera = "1"
|
||||
regex = "1"
|
||||
|
||||
[[bin]]
|
||||
name = "main"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
|
||||
// this is the base domain which peach-dyndns creates subdomains under
|
||||
// this regex is used to validate that domains are in the correct format
|
||||
// e.g. blue.dyn.peachcloud.org
|
||||
pub const BASE_DOMAIN: &str = "dyn.commoninternet.net";
|
||||
pub const DOMAIN_REGEX: &str = r"^.*\.dyn\.commointernet\.net$";
|
|
@ -7,6 +7,7 @@ pub enum PeachDynError {
|
|||
GenerateTsigParseError(std::string::FromUtf8Error),
|
||||
DomainAlreadyExistsError(String),
|
||||
BindConfigurationError(String),
|
||||
InvalidDomainError(String)
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for PeachDynError {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
/* For each subdomain,
|
||||
- generate a new ddns key (tsig-keygen -a hmac-md5 {{subdomain}}.dyn.commoninternet.net) and append it to /etc/bind/dyn.commoninternet.net.keys
|
||||
- add a zone section to /etc/bind/named.conf.local, associating the key with the subdomain
|
||||
- add a minimal zone file to /var/lib/bind/subdomain.dyn.commoninternet.net
|
||||
- reload bind and return the secret key to the client
|
||||
/*
|
||||
* Functions for generating bind9 configurations to enable dynamic dns for a subdomain via TSIG authentication
|
||||
* which is unique to that subdomain
|
||||
*/
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
|
@ -10,7 +8,7 @@ use std::io::Write;
|
|||
use std::process::Command;
|
||||
use tera::{Tera, Context};
|
||||
use crate::errors::PeachDynError;
|
||||
use crate::constants::BASE_DOMAIN;
|
||||
use crate::constants::DOMAIN_REGEX;
|
||||
|
||||
|
||||
/// function to generate the text of a TSIG key file
|
||||
|
@ -24,6 +22,13 @@ pub fn generate_tsig_key(full_domain: &str) -> Result<String, PeachDynError> {
|
|||
Ok(key_file_text)
|
||||
}
|
||||
|
||||
// function returns true if domain is of the format something.dyn.peachcloud.org
|
||||
pub fn validate_domain(domain: &str) -> bool {
|
||||
use regex::Regex;
|
||||
let re = Regex::new(DOMAIN_REGEX).unwrap();
|
||||
re.is_match(domain)
|
||||
}
|
||||
|
||||
/// function which helps us guarantee that a given domain is not already being used by bind
|
||||
/// it checks three places for the domain, and only returns true if it is not found in all three places
|
||||
/// - no already extant tsig key for the given domain
|
||||
|
@ -54,13 +59,20 @@ pub fn check_domain_available(full_domain: &str) -> bool {
|
|||
|
||||
/// function which generates all necessary bind configuration to serve the given
|
||||
/// subdomain using dynamic DNS authenticated via a new TSIG key which is unique to that subdomain
|
||||
/// - thus only the possessor of that key can use nsupdate to modify the records
|
||||
/// thus only the possessor of that key can use nsupdate to modify the records
|
||||
/// for that subodmain
|
||||
/// - generate a new ddns key (tsig-keygen -a hmac-md5 {{subdomain}}.dyn.commoninternet.net) and append it to /etc/bind/dyn.commoninternet.net.keys
|
||||
/// - add a zone section to /etc/bind/named.conf.local, associating the key with the subdomain
|
||||
/// - add a minimal zone file to /var/lib/bind/subdomain.dyn.commoninternet.net
|
||||
/// - reload bind and return the secret key to the client
|
||||
pub fn generate_zone(full_domain: &str) -> Result<String, PeachDynError> {
|
||||
|
||||
// TODO: confirm that domain matches correct format
|
||||
// first safety check domain is in correct format
|
||||
if !validate_domain(full_domain) {
|
||||
return Err(PeachDynError::InvalidDomainError(full_domain.to_string()));
|
||||
}
|
||||
|
||||
// first safety check if the domain is available
|
||||
// safety check if the domain is available
|
||||
let is_available = check_domain_available(full_domain);
|
||||
if !is_available {
|
||||
return Err(PeachDynError::DomainAlreadyExistsError(full_domain.to_string()));
|
||||
|
@ -74,7 +86,7 @@ pub fn generate_zone(full_domain: &str) -> Result<String, PeachDynError> {
|
|||
let mut file = OpenOptions::new().append(true).open(key_file_path)
|
||||
.expect(&format!("failed to open {}", key_file_path));
|
||||
if let Err(e) = writeln!(file, "{}", key_file_text) {
|
||||
eprintln!("Couldn't write to file: {}", e);
|
||||
error!("Couldn't write to file: {}", e);
|
||||
}
|
||||
|
||||
// append zone section to /etc/bind/named.conf.local
|
||||
|
@ -95,15 +107,13 @@ pub fn generate_zone(full_domain: &str) -> Result<String, PeachDynError> {
|
|||
",
|
||||
full_domain = full_domain
|
||||
);
|
||||
if let Err(e) = writeln!(file, "{}", zone_section_text) {
|
||||
eprintln!("Couldn't write to file: {}", e);
|
||||
}
|
||||
writeln!(file, "{}", zone_section_text).expect(&format!("Couldn't write to file: {}", bind_conf_path));
|
||||
|
||||
// use tera to render the zone file
|
||||
let tera = match Tera::new("templates/*.tera") {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
println!("Parsing error(s): {}", e);
|
||||
info!("Parsing error(s): {}", e);
|
||||
::std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
@ -115,9 +125,7 @@ pub fn generate_zone(full_domain: &str) -> Result<String, PeachDynError> {
|
|||
let zone_file_path = format!("/var/lib/bind/{}", full_domain);
|
||||
let mut file = File::create(&zone_file_path)
|
||||
.expect(&format!("failed to create {}", zone_file_path));
|
||||
if let Err(e) = writeln!(file, "{}", result) {
|
||||
eprintln!("Couldn't write to file: {}", e)
|
||||
};
|
||||
writeln!(file, "{}", result).expect(&format!("Couldn't write to file: {}", zone_file_path));
|
||||
|
||||
// restart bind
|
||||
// we use the /etc/sudoers.d/bindctl to allow peach-dyndns user to restart bind as sudo without entering a password
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
extern crate rocket;
|
||||
|
||||
use crate::routes::{index, register_domain, check_available};
|
||||
use std::io;
|
||||
use tokio::task;
|
||||
|
||||
mod cli;
|
||||
mod routes;
|
||||
|
@ -15,6 +13,8 @@ mod generate_zone;
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let _args = cli::args().expect("error parsing args");
|
||||
|
||||
let rocket_result = rocket::build()
|
||||
.mount("/", routes![index, register_domain, check_available])
|
||||
.launch()
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* /user/register sends an email verification to create a new account) NOT IMPLEMENTED
|
||||
* /user/verify (for clicking the link in the email) NOT IMPLEMENTED
|
||||
*/
|
||||
use crate::generate_zone::{check_domain_available, generate_zone};
|
||||
use crate::generate_zone::{check_domain_available, generate_zone, validate_domain};
|
||||
use rocket_contrib::json::{Json, JsonValue};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -41,24 +41,32 @@ pub struct RegisterDomainPost {
|
|||
pub async fn register_domain(data: Json<RegisterDomainPost>) -> Json<JsonResponse> {
|
||||
info!("++ post request to register new domain: {:?}", data);
|
||||
// TODO: grab/create a mutex, so that only one rocket thread is calling register_domain at a time
|
||||
// TODO: first confirm domain is in the right format ("*.dyn.peachcloud.org")
|
||||
let is_domain_available = check_domain_available(&data.domain);
|
||||
if !is_domain_available{
|
||||
// check if its a valid domain
|
||||
if !validate_domain(&data.domain) {
|
||||
let status = "error".to_string();
|
||||
let msg = "can't register a domain that is already registered".to_string();
|
||||
let msg = "domain is not in a valid format".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
} else {
|
||||
let result = generate_zone(&data.domain);
|
||||
match result {
|
||||
Ok(key_file_text) => {
|
||||
let status = "success".to_string();
|
||||
let msg = key_file_text.to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(_err) => {
|
||||
let status = "error".to_string();
|
||||
let msg = "there was an error creating the zone file".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
// check if the domain is available
|
||||
let is_domain_available = check_domain_available(&data.domain);
|
||||
if !is_domain_available {
|
||||
let status = "error".to_string();
|
||||
let msg = "can't register a domain that is already registered".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
} else {
|
||||
// generate configs for the zone
|
||||
let result = generate_zone(&data.domain);
|
||||
match result {
|
||||
Ok(key_file_text) => {
|
||||
let status = "success".to_string();
|
||||
let msg = key_file_text.to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(_err) => {
|
||||
let status = "error".to_string();
|
||||
let msg = "there was an error creating the zone file".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,9 +81,14 @@ pub struct CheckAvailableDomainPost {
|
|||
#[post("/domain/check-available", data = "<data>")]
|
||||
pub async fn check_available(data: Json<CheckAvailableDomainPost>) -> Json<JsonResponse> {
|
||||
info!("post request to check if domain is available {:?}", data);
|
||||
// TODO: validate that domain is in correct format
|
||||
let status = "success".to_string();
|
||||
let is_available = check_domain_available(&data.domain);
|
||||
let msg = is_available.to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
if !validate_domain(&data.domain) {
|
||||
let status = "error".to_string();
|
||||
let msg = "domain is not in a valid format".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
} else {
|
||||
let status = "success".to_string();
|
||||
let is_available = check_domain_available(&data.domain);
|
||||
let msg = is_available.to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue