From 5a4ff4ca65391695735e81f805fc241b07e85503 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sun, 16 Jul 2023 13:55:06 +0200 Subject: [PATCH] Add secret service.http.form_method field for debugging --- .dev-config.yml | 2 + src/domain/config.rs | 1 + src/service.rs | 37 +--------- src/service/config.rs | 35 +++++++++ src/service/http.rs | 102 +++++++++++++++----------- src/service/http/config.rs | 34 +++++++++ src/service/http/tpl/domain.html | 2 +- src/service/http/tpl/domain_init.html | 4 +- src/service/http/tpl/index.html | 2 +- 9 files changed, 139 insertions(+), 80 deletions(-) create mode 100644 src/service/config.rs diff --git a/.dev-config.yml b/.dev-config.yml index 8557b56..df4a59c 100644 --- a/.dev-config.yml +++ b/.dev-config.yml @@ -3,6 +3,8 @@ origin: domain: store_dir_path: /tmp/domani_dev_env/domain service: + http: + form_method: GET passphrase: foobar dns_records: - kind: A diff --git a/src/domain/config.rs b/src/domain/config.rs index 22352f1..a346025 100644 --- a/src/domain/config.rs +++ b/src/domain/config.rs @@ -41,5 +41,6 @@ pub struct Config { #[serde(default)] pub dns: ConfigDNS, pub acme: Option, + #[serde(default)] pub builtins: collections::HashMap, } diff --git a/src/service.rs b/src/service.rs index af5f4f1..82ec5be 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,38 +1,5 @@ +mod config; pub mod http; mod util; -use crate::domain; -use serde::{Deserialize, Serialize}; -use std::{net, str::FromStr}; - -fn default_primary_domain() -> domain::Name { - domain::Name::from_str("localhost").unwrap() -} - -#[derive(Serialize, Deserialize, Clone, PartialEq)] -#[serde(tag = "kind")] -pub enum ConfigDNSRecord { - A { addr: net::Ipv4Addr }, - AAAA { addr: net::Ipv6Addr }, - CNAME { name: domain::Name }, -} - -impl From for domain::checker::DNSRecord { - fn from(r: ConfigDNSRecord) -> Self { - match r { - ConfigDNSRecord::A { addr } => Self::A(addr), - ConfigDNSRecord::AAAA { addr } => Self::AAAA(addr), - ConfigDNSRecord::CNAME { name } => Self::CNAME(name), - } - } -} - -#[derive(Deserialize)] -pub struct Config { - pub passphrase: String, - pub dns_records: Vec, - #[serde(default = "default_primary_domain")] - pub primary_domain: domain::Name, - #[serde(default)] - pub http: self::http::Config, -} +pub use config::*; diff --git a/src/service/config.rs b/src/service/config.rs new file mode 100644 index 0000000..f8f11d2 --- /dev/null +++ b/src/service/config.rs @@ -0,0 +1,35 @@ +use crate::{domain, service}; +use serde::{Deserialize, Serialize}; +use std::{net, str::FromStr}; + +fn default_primary_domain() -> domain::Name { + domain::Name::from_str("localhost").unwrap() +} + +#[derive(Serialize, Deserialize, Clone, PartialEq)] +#[serde(tag = "kind")] +pub enum ConfigDNSRecord { + A { addr: net::Ipv4Addr }, + AAAA { addr: net::Ipv6Addr }, + CNAME { name: domain::Name }, +} + +impl From for domain::checker::DNSRecord { + fn from(r: ConfigDNSRecord) -> Self { + match r { + ConfigDNSRecord::A { addr } => Self::A(addr), + ConfigDNSRecord::AAAA { addr } => Self::AAAA(addr), + ConfigDNSRecord::CNAME { name } => Self::CNAME(name), + } + } +} + +#[derive(Deserialize)] +pub struct Config { + pub passphrase: String, + pub dns_records: Vec, + #[serde(default = "default_primary_domain")] + pub primary_domain: domain::Name, + #[serde(default)] + pub http: service::http::Config, +} diff --git a/src/service/http.rs b/src/service/http.rs index e8bbb3d..af3d492 100644 --- a/src/service/http.rs +++ b/src/service/http.rs @@ -4,6 +4,7 @@ mod tpl; pub use config::*; +use http::request::Parts; use hyper::{Body, Method, Request, Response}; use serde::{Deserialize, Serialize}; @@ -48,6 +49,7 @@ pub fn new( #[derive(Serialize)] struct BasePresenter<'a, T> { page_name: &'a str, + form_method: &'a str, data: T, } @@ -59,12 +61,18 @@ struct DomainGetArgs { #[derive(Deserialize)] struct DomainInitArgs { domain: domain::Name, + + #[serde(flatten)] + domain_config: service::util::FlatConfig, } #[derive(Deserialize)] struct DomainSyncArgs { domain: domain::Name, passphrase: String, + + #[serde(flatten)] + domain_config: service::util::FlatConfig, } impl<'svc> Service { @@ -121,6 +129,7 @@ impl<'svc> Service { "/base.html", BasePresenter { page_name: "/error.html", + form_method: self.config.http.form_method.as_str(), data: &Response { error_msg: e }, }, ) @@ -143,6 +152,7 @@ impl<'svc> Service { "/base.html", BasePresenter { page_name: name, + form_method: self.config.http.form_method.as_str(), data, }, ) @@ -174,14 +184,33 @@ impl<'svc> Service { } } - async fn with_query_req<'a, F, In, Out>(&self, req: &'a Request, f: F) -> Response + async fn with_query_req<'a, F, In, Out>( + &self, + req: &'a Parts, + body: Body, + f: F, + ) -> Response where - In: Deserialize<'a>, + In: for<'d> Deserialize<'d>, F: FnOnce(In) -> Out, Out: future::Future>, { - let query = req.uri().query().unwrap_or(""); - match serde_urlencoded::from_str::(query) { + let res = match self.config.http.form_method { + ConfigFormMethod::GET => { + serde_urlencoded::from_str::(req.uri.query().unwrap_or("")) + } + ConfigFormMethod::POST => { + let body = match hyper::body::to_bytes(body).await { + Ok(bytes) => bytes.to_vec(), + Err(e) => { + return self.internal_error(format!("failed to read body: {e}").as_str()) + } + }; + serde_urlencoded::from_bytes::(body.as_ref()) + } + }; + + match res { Ok(args) => f(args).await, Err(err) => { self.render_error_page(400, format!("failed to parse query args: {}", err).as_str()) @@ -219,11 +248,7 @@ impl<'svc> Service { ) } - fn domain_init( - &self, - args: DomainInitArgs, - domain_config: service::util::FlatConfig, - ) -> Response { + fn domain_init(&self, args: DomainInitArgs) -> Response { #[derive(Serialize)] struct Response<'a> { domain: domain::Name, @@ -235,7 +260,7 @@ impl<'svc> Service { dns_records_have_cname: bool, } - let config: domain::Domain = match domain_config.try_into() { + let config: domain::Domain = match args.domain_config.try_into() { Ok(Some(config)) => config, Ok(None) => return self.render_error_page(400, "domain config is required"), Err(e) => { @@ -272,16 +297,12 @@ impl<'svc> Service { ) } - async fn domain_sync( - &self, - args: DomainSyncArgs, - domain_config: service::util::FlatConfig, - ) -> Response { + async fn domain_sync(&self, args: DomainSyncArgs) -> Response { if args.passphrase != self.config.passphrase.as_str() { return self.render_error_page(401, "Incorrect passphrase"); } - let config: domain::Domain = match domain_config.try_into() { + let config: domain::Domain = match args.domain_config.try_into() { Ok(Some(config)) => config, Ok(None) => return self.render_error_page(400, "domain config is required"), Err(e) => { @@ -341,12 +362,14 @@ impl<'svc> Service { } async fn handle_request(&self, req: Request) -> Response { + let (req, body) = req.into_parts(); + let maybe_host = match ( - req.headers() + req.headers .get("Host") .and_then(|v| v.to_str().ok()) .map(strip_port), - req.uri().host().map(strip_port), + req.uri.host().map(strip_port), ) { (Some(h), _) if h != self.config.primary_domain.as_str() => Some(h), (_, Some(h)) if h != self.config.primary_domain.as_str() => Some(h), @@ -354,13 +377,12 @@ impl<'svc> Service { } .and_then(|h| domain::Name::from_str(h).ok()); - let method = req.method(); - let path = req.uri().path(); + let path = req.uri.path(); // Serving acme challenges always takes priority. We serve them from the same store no // matter the domain, presumably they are cryptographically random enough that it doesn't // matter. - if method == Method::GET && path.starts_with("/.well-known/acme-challenge/") { + if req.method == Method::GET && path.starts_with("/.well-known/acme-challenge/") { let token = path.trim_start_matches("/.well-known/acme-challenge/"); if let Ok(key) = self.domain_manager.get_acme_http01_challenge_key(token) { @@ -369,7 +391,7 @@ impl<'svc> Service { } // Serving domani challenges similarly takes priority. - if method == Method::GET && path == "/.well-known/domani-challenge" { + if req.method == Method::GET && path == "/.well-known/domani-challenge" { if let Some(ref domain) = maybe_host { match self .domain_manager @@ -388,38 +410,36 @@ impl<'svc> Service { // If a managed domain was given then serve that from its origin if let Some(domain) = maybe_host { - return self.serve_origin(domain, req.uri().path()); + return self.serve_origin(domain, req.uri.path()); } // Serve main domani site - if method == Method::GET && path.starts_with("/static/") { + if req.method == Method::GET && path.starts_with("/static/") { return self.render(200, path, ()); } - match (method, path) { + let config_form_method = self.config.http.form_method.as_ref(); + + match (&req.method, path) { (&Method::GET, "/") | (&Method::GET, "/index.html") => { self.render_page("/index.html", ()) } - (&Method::GET, "/domain.html") => { - self.with_query_req(&req, |args: DomainGetArgs| async { self.domain_get(args) }) - .await - } - (&Method::GET, "/domain_init.html") => { - self.with_query_req(&req, |args: DomainInitArgs| async { - self.with_query_req(&req, |config: service::util::FlatConfig| async { - self.domain_init(args, config) - }) - .await + (form_method, "/domain.html") if form_method == config_form_method => { + self.with_query_req(&req, body, |args: DomainGetArgs| async { + self.domain_get(args) }) .await } - (&Method::GET, "/domain_sync.html") => { - self.with_query_req(&req, |args: DomainSyncArgs| async { - self.with_query_req(&req, |config: service::util::FlatConfig| async { - self.domain_sync(args, config).await - }) - .await + (form_method, "/domain_init.html") if form_method == config_form_method => { + self.with_query_req(&req, body, |args: DomainInitArgs| async { + self.domain_init(args) + }) + .await + } + (form_method, "/domain_sync.html") if form_method == config_form_method => { + self.with_query_req(&req, body, |args: DomainSyncArgs| async { + self.domain_sync(args).await }) .await } diff --git a/src/service/http/config.rs b/src/service/http/config.rs index ba571db..85cb5f0 100644 --- a/src/service/http/config.rs +++ b/src/service/http/config.rs @@ -5,11 +5,44 @@ fn default_http_addr() -> net::SocketAddr { net::SocketAddr::from_str("[::]:3030").unwrap() } +#[derive(Deserialize)] +pub enum ConfigFormMethod { + GET, + POST, +} + +impl ConfigFormMethod { + pub fn as_str(&self) -> &str { + match self { + Self::GET => "GET", + Self::POST => "POST", + } + } +} + +impl Default for ConfigFormMethod { + fn default() -> Self { + Self::POST + } +} + +impl AsRef for ConfigFormMethod { + fn as_ref(&self) -> &hyper::Method { + match self { + Self::GET => &hyper::Method::GET, + Self::POST => &hyper::Method::POST, + } + } +} + #[derive(Deserialize)] pub struct Config { #[serde(default = "default_http_addr")] pub http_addr: net::SocketAddr, pub https_addr: Option, + + #[serde(default)] + pub form_method: ConfigFormMethod, } impl Default for Config { @@ -17,6 +50,7 @@ impl Default for Config { Self { http_addr: default_http_addr(), https_addr: None, + form_method: Default::default(), } } } diff --git a/src/service/http/tpl/domain.html b/src/service/http/tpl/domain.html index 02351a2..bba4dee 100644 --- a/src/service/http/tpl/domain.html +++ b/src/service/http/tpl/domain.html @@ -20,7 +20,7 @@ automatically updated too!

In the future Domani will support more backends than just git repos.

-
+ diff --git a/src/service/http/tpl/domain_init.html b/src/service/http/tpl/domain_init.html index ca780c4..96c9367 100644 --- a/src/service/http/tpl/domain_init.html +++ b/src/service/http/tpl/domain_init.html @@ -3,7 +3,7 @@

This step requires a passphrase that has been given to you by the administrator of the Domani server:

- + {{ #each data.flat_config }} @@ -47,7 +47,7 @@ query for your domain name. It can be one or more of:

{{ #each data.dns_records }} - {{ this.type }} + {{ this.kind }} {{ lookup ../data "domain" }} {{ #if this.name }} {{ this.name }} diff --git a/src/service/http/tpl/index.html b/src/service/http/tpl/index.html index baa316d..ed580e8 100644 --- a/src/service/http/tpl/index.html +++ b/src/service/http/tpl/index.html @@ -13,7 +13,7 @@ server, and you're done!

Input your domain name below to set it up, or to reconfigure it has already been set up.

- +