From dcbf45ec85ec12a34ddd55f6e9c8796947d5ab94 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Thu, 3 Aug 2023 14:54:59 +0200 Subject: [PATCH] Allow for disabling https for proxied domains --- README.md | 66 ++++++++++++++++++++----------------------- src/domain/config.rs | 5 ++-- src/domain/manager.rs | 47 +++++++++++++++++++++--------- src/service/http.rs | 2 ++ 4 files changed, 69 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 018a64d..8b2b000 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,36 @@ domain: # domain list, but will not be configurable in the web interface #public: false + #proxied: + + # An example built-in domain backed by an gemini and HTTP reverse-proxies to + # other backends. + # + # HTTP requests will be proxied to http_url, and gemini requests will be + # proxied to gemini_url. Either can be null. + # + # HTTP requests to the backing service will automatically have + # X-Forwarded-For and (if HTTPS) X-Forwarded-Proto headers added to them. + # + # Proxies are currently limited in the following ways: + # * http_url must be to an http endpoint (not https) + # * dns.resolver_addr is ignored and the system-wide dns is used + # + #example.com: + + #http_url: "http://some.other.service.com" + #gemini_url: "gemini://some.other.service.com" + + # Extra headers to add to proxied requests + #http_request_headers: + # - name: Host + # value: "yet.another.service.com" + # - name: X-HEADER-TO-DELETE + # value: "" + + # Set to true to prevent the domain from being served over https. + #https_disabled: false + service: # Passphrase which must be given by users who are configuring new domains via @@ -91,7 +121,7 @@ service: # # A CNAME record with the interface_domain of this server is automatically # included, if it's not null itself. - dns_records: + #dns_records: #- kind: A # addr: 127.0.0.1 @@ -119,45 +149,11 @@ service: # enabled. You can enable HTTPS by setting this to "[::]:443". #https_addr: null - #proxied_domains: - - # An example built-in domain backed by an HTTP reverse-proxy to some - # other web-service. Requests to the backing service will automatically - # have X-Forwarded-For and (if HTTPS) X-Forwarded-Proto headers added to - # them. - # - # Proxies are currently limited in the following ways: - # * url must be to an http endpoint (not https) - # * dns.resolver_addr is ignored and the system-wide dns is used - # - #proxy.example.com: - # url: "http://some.other.service.com" - # - # # Extra headers to add to proxied requests - # request_headers: - # - name: Host - # value: "yet.another.service.com" - # - name: X-HEADER-TO-DELETE - # value: "" - #gemini: # The address to listen for gemini requests on. Set this to null to disable # gemini support. #gemini_addr: "[::]:3965" - - #proxied_domains: - - # An example built-in domain backed by a reverse-proxy to some other - # gemini server. Requests to this domain will have connections - # transparently proxied to the backing server. - # - # Proxies are currently limited in the following ways: - # * url must be to a gemini endpoint - # * dns.resolver_addr is ignored and the system-wide dns is used - # - #proxy.example.com: - # url: "gemini://some.other.service.com" ``` The YAML config file can be passed to the Domani process via the `--config-path` diff --git a/src/domain/config.rs b/src/domain/config.rs index 5811bee..ee7f321 100644 --- a/src/domain/config.rs +++ b/src/domain/config.rs @@ -40,17 +40,18 @@ pub struct ConfigBuiltinDomain { #[serde_as] #[derive(Clone, Deserialize, Serialize)] pub struct ConfigProxiedDomain { - #[serde(default)] #[serde_as(as = "Option>")] pub gemini_url: Option, - #[serde(default)] #[serde_as(as = "Option>")] pub http_url: Option, #[serde(default)] #[serde_as(as = "TryFromInto>")] pub http_request_headers: proxied_domain::HttpRequestHeaders, + + #[serde(default)] + pub https_disabled: bool, } #[derive(Deserialize, Serialize)] diff --git a/src/domain/manager.rs b/src/domain/manager.rs index 7678e1c..6265509 100644 --- a/src/domain/manager.rs +++ b/src/domain/manager.rs @@ -187,7 +187,7 @@ impl ManagerImpl { task_stack.push_spawn(|canceller| { let manager = manager.clone(); async move { - manager.sync_all_domains(canceller).await; + manager.sync_all_domains_job(canceller).await; Ok(()) } }); @@ -204,7 +204,7 @@ impl ManagerImpl { self.origin_store.sync(origin_descr) } - async fn sync_domain_certs(&self, domain: &domain::Name) -> unexpected::Result<()> { + fn sync_domain_gemini_cert(&self, domain: &domain::Name) -> unexpected::Result<()> { if let Some(ref gemini_store) = self.gemini_store { log::info!("Syncing gemini certificate for domain {domain}"); if let Some(_) = gemini_store.get_certificate(domain).or_unexpected()? { @@ -218,7 +218,10 @@ impl ManagerImpl { gemini_store.set_certificate(domain, pkey, cert)?; } + Ok(()) + } + async fn sync_domain_https_cert(&self, domain: &domain::Name) -> unexpected::Result<()> { if let Some(ref acme_manager) = self.acme_manager { log::info!("Syncing HTTPS certificate for domain {domain}"); acme_manager.sync_domain(domain.clone()).await?; @@ -227,20 +230,23 @@ impl ManagerImpl { Ok(()) } - async fn sync_all_domains_once(&self) -> unexpected::Result<()> { + async fn sync_all_domains(&self) -> unexpected::Result<()> { let domains = self .all_domains() .or_unexpected_while("fetching all domains")? .into_iter(); for ManagedDomain { domain, .. } in domains { - let settings = match self + let (settings, https_cert, gemini_cert) = match self .get_settings(&domain) .map_unexpected_while(|| format!("fetching settings for {domain}"))? { - GetSettingsResult::Stored(settings) => Some(settings), - GetSettingsResult::Builtin(config) => Some(config.settings), - GetSettingsResult::Proxied(_) => None, + GetSettingsResult::Stored(settings) => (Some(settings), true, true), + GetSettingsResult::Builtin(config) => (Some(config.settings), true, true), + + // A proxied domain never needs gemini certs, since gemini requests will be + // transparently proxied to the backing server anyway. + GetSettingsResult::Proxied(config) => (None, !config.https_disabled, false), }; if let Some(settings) = settings { @@ -253,20 +259,27 @@ impl ManagerImpl { })?; } - self.sync_domain_certs(&domain) - .await - .map_unexpected_while(|| format!("syncing certs for domain {domain}",))?; + if gemini_cert { + self.sync_domain_gemini_cert(&domain) + .map_unexpected_while(|| format!("syncing gemini cert for domain {domain}"))?; + } + + if https_cert { + self.sync_domain_https_cert(&domain) + .await + .map_unexpected_while(|| format!("syncing https cert for domain {domain}",))?; + } } Ok(()) } - async fn sync_all_domains(&self, canceller: CancellationToken) { + async fn sync_all_domains_job(&self, canceller: CancellationToken) { let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(20 * 60)); loop { tokio::select! { _ = canceller.cancelled() => return, - _ = interval.tick() => if let Err(err) = self.sync_all_domains_once().await { + _ = interval.tick() => if let Err(err) = self.sync_all_domains().await { log::error!("Failed to sync all domains: {err}") }, } @@ -326,10 +339,16 @@ impl Manager for ManagerImpl { .or_unexpected_while("calculating config hash")?; self.domain_checker.check_domain(&domain, &hash).await?; + self.sync_domain_origin(&domain, &settings.origin_descr)?; - self.sync_domain_certs(&domain) + + self.sync_domain_gemini_cert(&domain) + .or_unexpected_while("syncing domain gemini cert")?; + + self.sync_domain_https_cert(&domain) .await - .or_unexpected_while("syncing domain certs")?; + .or_unexpected_while("syncing domain https cert")?; + self.domain_store.set(&domain, &settings)?; Ok(()) diff --git a/src/service/http.rs b/src/service/http.rs index 29bb7c1..b334fca 100644 --- a/src/service/http.rs +++ b/src/service/http.rs @@ -473,6 +473,8 @@ impl Service { .as_str(), ) }); + } else { + return self.render_error_page(404, "Domain not found"); } }