Compare commits

...

2 Commits

Author SHA1 Message Date
Brian Picciano
92f7d3d52a Ensure that the interface_domain gets a cert requested for it 2023-08-04 12:31:04 +02:00
Brian Picciano
818c728258 Move interface_domain under domain configuration 2023-08-04 10:34:09 +02:00
7 changed files with 84 additions and 57 deletions

View File

@ -64,10 +64,17 @@ domain:
# renewed.
#contact_email: REQUIRED if service.http.https_addr is set
# The domain name which will be used to serve the web interface of Domani. If
# service.http.https_addr is enabled then an HTTPS certificate for this domain
# will be retrieved automatically.
#
# This can be set to null to disable the web interface entirely.
#interface_domain: "localhost"
# builtins are domains whose configuration is built into domani. These domains
# are not able to be configured via the web interface, and will be hidden from
# it unless the `public` key is set to true.
#builtins:
#builtin_domains:
# An example built-in domain backed by a git repo.
#git.example.com:
@ -79,13 +86,14 @@ domain:
# domain list, but will not be configurable in the web interface
#public: false
#proxied:
#proxied_domains:
# An example built-in domain backed by an gemini and HTTP reverse-proxies to
# An example proxied 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.
# proxied to gemini_url. Either can be null to disable serving on that
# protocol.
#
# HTTP requests to the backing service will automatically have
# X-Forwarded-For and (if HTTPS) X-Forwarded-Proto headers added to them.
@ -106,7 +114,8 @@ domain:
# - name: X-HEADER-TO-DELETE
# value: ""
# Set to true to prevent the domain from being served over https.
# Set to true to prevent the domain from being served over https, even if
# http_url is set.
#https_disabled: false
service:
@ -132,13 +141,6 @@ service:
#- kind: CNAME
# name: domain.com
# The domain name which will be used to serve the web interface of Domani. If
# service.http.https_addr is enabled then an HTTPS certificate for this domain
# will be retrieved automatically.
#
# This can be set to null to disable the web interface entirely.
#interface_domain: "localhost"
#http:
# The address to listen for HTTP requests on. This must use port 80 if

View File

@ -9,7 +9,11 @@ fn default_resolver_addr() -> net::SocketAddr {
net::SocketAddr::from_str("1.1.1.1:53").unwrap()
}
#[derive(Deserialize, Serialize)]
fn default_interface_domain() -> Option<domain::Name> {
Some(domain::Name::from_str("localhost").unwrap())
}
#[derive(Clone, Deserialize, Serialize)]
pub struct ConfigDNS {
#[serde(default = "default_resolver_addr")]
pub resolver_addr: net::SocketAddr,
@ -23,7 +27,7 @@ impl Default for ConfigDNS {
}
}
#[derive(Deserialize, Serialize)]
#[derive(Clone, Deserialize, Serialize)]
pub struct ConfigACME {
pub contact_email: String,
}
@ -54,7 +58,7 @@ pub struct ConfigProxiedDomain {
pub https_disabled: bool,
}
#[derive(Deserialize, Serialize)]
#[derive(Clone, Deserialize, Serialize)]
pub struct Config {
pub store_dir_path: path::PathBuf,
#[serde(default)]
@ -62,8 +66,11 @@ pub struct Config {
pub acme: Option<ConfigACME>,
#[serde(default)]
pub builtins: collections::HashMap<domain::Name, ConfigBuiltinDomain>,
pub builtin_domains: collections::HashMap<domain::Name, ConfigBuiltinDomain>,
#[serde(default)]
pub proxied: collections::HashMap<domain::Name, ConfigProxiedDomain>,
pub proxied_domains: collections::HashMap<domain::Name, ConfigProxiedDomain>,
#[serde(default = "default_interface_domain")]
pub interface_domain: Option<domain::Name>,
}

View File

@ -1,14 +1,15 @@
use crate::domain::{self, acme, checker, config, gemini, store, tls};
use crate::domain::{self, acme, checker, gemini, store, tls};
use crate::error::unexpected::{self, Mappable};
use crate::{origin, task_stack, util};
use std::{collections, sync};
use std::sync;
use tokio_util::sync::CancellationToken;
pub enum GetSettingsResult {
Stored(domain::Settings),
Builtin(domain::config::ConfigBuiltinDomain),
Proxied(domain::config::ConfigProxiedDomain),
Interface,
}
#[derive(thiserror::Error, Debug)]
@ -153,8 +154,7 @@ pub struct ManagerImpl {
domain_checker: checker::DNSChecker,
acme_manager: Option<Box<dyn acme::manager::Manager + Send + Sync>>,
gemini_store: Option<Box<dyn gemini::Store + Send + Sync>>,
builtins: collections::HashMap<domain::Name, config::ConfigBuiltinDomain>,
proxied: collections::HashMap<domain::Name, config::ConfigProxiedDomain>,
config: domain::Config,
}
impl ManagerImpl {
@ -170,8 +170,7 @@ impl ManagerImpl {
domain_checker: checker::DNSChecker,
acme_manager: Option<AcmeManager>,
gemini_store: Option<GeminiStore>,
builtins: collections::HashMap<domain::Name, config::ConfigBuiltinDomain>,
proxied: collections::HashMap<domain::Name, config::ConfigProxiedDomain>,
config: domain::Config,
) -> sync::Arc<Self> {
let manager = sync::Arc::new(ManagerImpl {
origin_store: Box::from(origin_store),
@ -180,8 +179,7 @@ impl ManagerImpl {
acme_manager: acme_manager
.map(|m| Box::new(m) as Box<dyn acme::manager::Manager + Send + Sync>),
gemini_store: gemini_store.map(|m| Box::new(m) as Box<dyn gemini::Store + Send + Sync>),
builtins,
proxied,
config,
});
task_stack.push_spawn(|canceller| {
@ -247,6 +245,8 @@ impl ManagerImpl {
// 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),
GetSettingsResult::Interface => (None, true, false),
};
if let Some(settings) = settings {
@ -289,11 +289,15 @@ impl ManagerImpl {
impl Manager for ManagerImpl {
fn get_settings(&self, domain: &domain::Name) -> Result<GetSettingsResult, GetSettingsError> {
if let Some(config) = self.builtins.get(domain) {
if Some(domain) == self.config.interface_domain.as_ref() {
return Ok(GetSettingsResult::Interface);
}
if let Some(config) = self.config.builtin_domains.get(domain) {
return Ok(GetSettingsResult::Builtin(config.clone()));
}
if let Some(config) = self.proxied.get(domain) {
if let Some(config) = self.config.proxied_domains.get(domain) {
return Ok(GetSettingsResult::Proxied(config.clone()));
}
@ -313,6 +317,11 @@ impl Manager for ManagerImpl {
unexpected::Error::from("can't call get_file on proxied domain").into(),
);
}
GetSettingsResult::Interface => {
return Err(
unexpected::Error::from("can't call get_file on interface domain").into(),
);
}
};
let path = settings.process_path(path);
@ -330,7 +339,11 @@ impl Manager for ManagerImpl {
settings: domain::Settings,
) -> util::BoxFuture<'mgr, Result<(), SyncWithSettingsError>> {
Box::pin(async move {
if self.builtins.contains_key(&domain) || self.proxied.contains_key(&domain) {
let is_interface = Some(&domain) == self.config.interface_domain.as_ref();
let is_builtin = self.config.builtin_domains.contains_key(&domain);
let is_proxied = self.config.proxied_domains.contains_key(&domain);
if is_interface || is_builtin || is_proxied {
return Err(SyncWithSettingsError::NotModifiable);
}
@ -384,7 +397,8 @@ impl Manager for ManagerImpl {
})
.collect();
self.builtins
self.config
.builtin_domains
.iter()
.map(|(domain, config)| ManagedDomain {
domain: domain.clone(),
@ -392,7 +406,8 @@ impl Manager for ManagerImpl {
})
.collect_into(&mut res);
self.proxied
self.config
.proxied_domains
.iter()
.map(|(domain, _)| ManagedDomain {
domain: domain.clone(),
@ -400,6 +415,13 @@ impl Manager for ManagerImpl {
})
.collect_into(&mut res);
if let Some(ref interface_domain) = self.config.interface_domain {
res.push(ManagedDomain {
domain: interface_domain.clone(),
public: false,
})
}
Ok(res)
}
}

View File

@ -60,7 +60,7 @@ async fn main() {
// Since the interface domain _must_ point to the service (otherwise it wouldn't work) it's
// reasonable to assume that a CNAME on any domain would suffice to point that domain to
// the service.
if let Some(ref interface_domain) = config.service.interface_domain {
if let Some(ref interface_domain) = config.domain.interface_domain {
let interface_cname = domani::service::ConfigDNSRecord::CNAME {
name: interface_domain.clone(),
};
@ -106,6 +106,7 @@ async fn main() {
let acme_config = config
.domain
.acme
.clone()
.expect("acme configuration must be set if https is enabled");
let domain_acme_store =
@ -143,8 +144,7 @@ async fn main() {
domain_checker,
domain_acme_manager,
domain_gemini_store,
config.domain.builtins.clone(),
config.domain.proxied.clone(),
config.domain.clone(),
);
let _ = domani::service::http::Service::new(
@ -152,7 +152,8 @@ async fn main() {
domain_manager.clone(),
domani::domain::manager::HttpsCertResolver::from(domain_manager.clone()),
config.service.clone(),
config.domain.proxied.clone(),
config.domain.proxied_domains.clone(),
config.domain.interface_domain.clone(),
);
if gemini_enabled {
@ -161,7 +162,7 @@ async fn main() {
domain_manager.clone(),
domani::domain::manager::GeminiCertResolver::from(domain_manager.clone()),
config.service.gemini.clone(),
config.domain.proxied.clone(),
config.domain.proxied_domains.clone(),
);
}

View File

@ -1,10 +1,6 @@
use crate::{domain, service};
use serde::{Deserialize, Serialize};
use std::{net, str::FromStr};
fn default_interface_domain() -> Option<domain::Name> {
Some(domain::Name::from_str("localhost").unwrap())
}
use std::net;
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "kind")]
@ -31,9 +27,6 @@ pub struct Config {
#[serde(default)]
pub dns_records: Vec<ConfigDNSRecord>,
#[serde(default = "default_interface_domain")]
pub interface_domain: Option<domain::Name>,
#[serde(default)]
pub http: service::http::Config,

View File

@ -15,14 +15,6 @@ use std::{collections, future, net, sync};
use crate::error::unexpected;
use crate::{domain, service, task_stack};
pub struct Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
handlebars: handlebars::Handlebars<'static>,
config: service::Config,
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
}
#[derive(Serialize)]
struct BasePresenter<'a, T> {
page_name: &'a str,
@ -52,13 +44,23 @@ struct DomainSyncArgs {
url_encoded_domain_settings: util::UrlEncodedDomainSettings,
}
pub struct Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
handlebars: handlebars::Handlebars<'static>,
config: service::Config,
proxied_domains: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
interface_domain: Option<domain::Name>,
}
impl Service {
pub fn new<CertResolver>(
task_stack: &mut task_stack::TaskStack<unexpected::Error>,
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: CertResolver,
config: service::Config,
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
proxied_domains: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
interface_domain: Option<domain::Name>,
) -> sync::Arc<Service>
where
CertResolver: rustls::server::ResolvesServerCert + 'static,
@ -70,7 +72,8 @@ impl Service {
cert_resolver: sync::Arc::from(cert_resolver),
handlebars: tpl::get(),
config,
proxied,
proxied_domains,
interface_domain,
});
task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller));
@ -224,6 +227,7 @@ impl Service {
config.public.then(|| config.settings)
}
Ok(domain::manager::GetSettingsResult::Proxied(_)) => None,
Ok(domain::manager::GetSettingsResult::Interface) => None,
Err(domain::manager::GetSettingsError::NotFound) => None,
Err(domain::manager::GetSettingsError::Unexpected(e)) => {
return self.internal_error(
@ -457,7 +461,7 @@ impl Service {
}
}
if let Some(config) = self.proxied.get(&domain) {
if let Some(config) = self.proxied_domains.get(&domain) {
if let Some(ref http_url) = config.http_url {
return service::http::proxy::serve_http_request(
http_url.original_url.as_str(),
@ -478,7 +482,7 @@ impl Service {
}
}
if Some(&domain) == self.config.interface_domain.as_ref() {
if Some(&domain) == self.interface_domain.as_ref() {
return self.serve_interface(req).await;
}

View File

@ -16,7 +16,6 @@ pub async fn listen_http(
// only used for logging
let listen_host = service
.config
.interface_domain
.clone()
.map_or(addr.ip().to_string(), |ref d| d.as_str().to_string());
@ -56,7 +55,6 @@ pub async fn listen_https(
// only used for logging
let listen_host = service
.config
.interface_domain
.clone()
.map_or(addr.ip().to_string(), |ref d| d.as_str().to_string());