From 7049252787a60fa29111932f1c06214b8d3d237d Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 17 Jul 2023 20:22:22 +0200 Subject: [PATCH] Support `serve_protocols` field on domain settings --- README.md | 12 ++++++++ src/domain.rs | 25 ++--------------- src/domain/settings.rs | 38 ++++++++++++++++++++++++++ src/service/http.rs | 40 +++++++++++++++------------ src/service/util.rs | 62 +++++++++++++++++++++++++++++++----------- 5 files changed, 121 insertions(+), 56 deletions(-) create mode 100644 src/domain/settings.rs diff --git a/README.md b/README.md index d7a0c48..f0eeec2 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,12 @@ domain: # url: "https://somewhere.com/some/repo.git" # branch_name: main # public: false + # + # # Which protocols to serve the domain on. The given list overwrites the + # # default, which is to serve on all available protocols. + # #serve_protocols: + # #- protocol: http + # #- protocol: https # An example built-in domain backed by a reverse-proxy to some other # web-service. Requests to the backing service will automatically have @@ -85,6 +91,12 @@ domain: # value: "" # # public: false + # + # # Which protocols to serve the domain on. The given list overwrites the + # # default, which is to serve on all available protocols. + # #serve_protocols: + # #- protocol: http + # #- protocol: https service: diff --git a/src/domain.rs b/src/domain.rs index 4b3d336..06501ae 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -3,30 +3,9 @@ pub mod checker; mod config; pub mod manager; mod name; +mod settings; pub mod store; pub use config::*; pub use name::*; - -use crate::error::unexpected::{self, Mappable}; -use crate::origin; - -use hex::ToHex; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -/// Defines how a domain will behave when it is accessed. These are configured by the owner of the -/// domain during setup. -pub struct Settings { - #[serde(flatten)] - pub origin_descr: origin::Descr, -} - -impl Settings { - pub fn hash(&self) -> Result { - let mut h = Sha256::new(); - serde_json::to_writer(&mut h, self).or_unexpected()?; - Ok(h.finalize().encode_hex::()) - } -} +pub use settings::*; diff --git a/src/domain/settings.rs b/src/domain/settings.rs new file mode 100644 index 0000000..6b6bb5a --- /dev/null +++ b/src/domain/settings.rs @@ -0,0 +1,38 @@ +use crate::error::unexpected::{self, Mappable}; +use crate::origin; + +use hex::ToHex; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(tag = "protocol")] +pub enum SettingsServeProtocol { + #[serde(rename = "http")] + Http, + #[serde(rename = "https")] + Https, +} + +fn default_serve_protocols() -> Vec { + vec![SettingsServeProtocol::Http, SettingsServeProtocol::Https] +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +/// Defines how a domain will behave when it is accessed. These are configured by the owner of the +/// domain during setup. +pub struct Settings { + #[serde(flatten)] + pub origin_descr: origin::Descr, + + #[serde(default = "default_serve_protocols")] + pub serve_protocols: Vec, +} + +impl Settings { + pub fn hash(&self) -> Result { + let mut h = Sha256::new(); + serde_json::to_writer(&mut h, self).or_unexpected()?; + Ok(h.finalize().encode_hex::()) + } +} diff --git a/src/service/http.rs b/src/service/http.rs index 625e3a3..f30ddc8 100644 --- a/src/service/http.rs +++ b/src/service/http.rs @@ -165,23 +165,8 @@ impl<'svc> Service { req: Request, req_is_https: bool, ) -> Response { - // first check if the domain is backed by a proxy, and deal with that first - match self.domain_manager.get_settings(&domain) { - Ok(settings) => { - if let origin::Descr::Proxy { .. } = settings.origin_descr { - return origin::proxy::serve_http_request( - &settings, - client_ip, - req, - req_is_https, - ) - .await - .unwrap_or_else(|e| { - self.internal_error(format!("proxying {domain}: {e}").as_str()) - }); - } - // fall out of match - } + let settings = match self.domain_manager.get_settings(&domain) { + Ok(settings) => settings, Err(domain::manager::GetSettingsError::NotFound) => { return self.render_error_page(404, "Domain not found"); } @@ -190,8 +175,29 @@ impl<'svc> Service { format!("failed to fetch settings for domain {domain}: {e}").as_str(), ); } + }; + + let allowed = settings.serve_protocols.iter().any(|p| match p { + domain::SettingsServeProtocol::Http => !req_is_https, + domain::SettingsServeProtocol::Https => req_is_https, + }); + + if !allowed { + return self.render_error_page( + 421, + "The requested protocol is not supported by this domain", + ); } + // if the domain is backed by a proxy then that is handled specially. + if let origin::Descr::Proxy { .. } = settings.origin_descr { + return origin::proxy::serve_http_request(&settings, client_ip, req, req_is_https) + .await + .unwrap_or_else(|e| { + self.internal_error(format!("proxying {domain}: {e}").as_str()) + }); + }; + let mut path_owned; let path = req.uri().path(); diff --git a/src/service/util.rs b/src/service/util.rs index 695d735..7912f44 100644 --- a/src/service/util.rs +++ b/src/service/util.rs @@ -6,23 +6,24 @@ use crate::{domain, error::unexpected, origin}; #[derive(Serialize, Deserialize, Default)] pub struct FlatDomainSettings { - domain_setting_origin_descr_kind: Option, + domain_setting_origin_descr_kind: String, domain_setting_origin_descr_git_url: Option, domain_setting_origin_descr_git_branch_name: Option, domain_setting_origin_descr_proxy_url: Option, + + #[serde(default)] + domain_setting_serve_protocol_http: bool, + #[serde(default)] + domain_setting_serve_protocol_https: bool, } impl TryFrom for domain::Settings { type Error = String; fn try_from(v: FlatDomainSettings) -> Result { - let origin_descr = match v - .domain_setting_origin_descr_kind - .unwrap_or("".to_string()) - .as_str() - { + let origin_descr = match v.domain_setting_origin_descr_kind.as_str() { "git" => Ok(origin::Descr::Git { url: v .domain_setting_origin_descr_git_url @@ -35,7 +36,20 @@ impl TryFrom for domain::Settings { _ => Err("invalid domain_setting_origin_descr_kind".to_string()), }?; - Ok(Self { origin_descr }) + let mut serve_protocols = Vec::::default(); + + if v.domain_setting_serve_protocol_http { + serve_protocols.push(domain::SettingsServeProtocol::Http); + } + + if v.domain_setting_serve_protocol_https { + serve_protocols.push(domain::SettingsServeProtocol::Https); + } + + Ok(Self { + origin_descr, + serve_protocols, + }) } } @@ -43,16 +57,32 @@ impl TryFrom for FlatDomainSettings { type Error = unexpected::Error; fn try_from(v: domain::Settings) -> Result { + let mut res = FlatDomainSettings::default(); + match v.origin_descr { - origin::Descr::Git { url, branch_name } => Ok(FlatDomainSettings { - domain_setting_origin_descr_kind: Some("git".to_string()), - domain_setting_origin_descr_git_url: Some(url), - domain_setting_origin_descr_git_branch_name: Some(branch_name), - ..Default::default() - }), - origin::Descr::Proxy { .. } => Err(unexpected::Error::from( - "proxy origins not supported for forms", - )), + origin::Descr::Git { url, branch_name } => { + res.domain_setting_origin_descr_kind = "git".to_string(); + res.domain_setting_origin_descr_git_url = Some(url); + res.domain_setting_origin_descr_git_branch_name = Some(branch_name); + } + origin::Descr::Proxy { .. } => { + return Err(unexpected::Error::from( + "proxy origins not supported for forms", + )); + } } + + for serve_protocol in v.serve_protocols { + match serve_protocol { + domain::SettingsServeProtocol::Http => { + res.domain_setting_serve_protocol_http = true + } + domain::SettingsServeProtocol::Https => { + res.domain_setting_serve_protocol_https = true + } + } + } + + Ok(res) } }