Compare commits
2 Commits
7f8e40c19f
...
b44fd575a9
Author | SHA1 | Date | |
---|---|---|---|
|
b44fd575a9 | ||
|
e67cd6725a |
@ -89,8 +89,8 @@ service:
|
|||||||
# Domani can serve the domains. All records given must route to this Domani
|
# Domani can serve the domains. All records given must route to this Domani
|
||||||
# instance.
|
# instance.
|
||||||
#
|
#
|
||||||
# A CNAME record with the primary_domain of this server is automatically
|
# A CNAME record with the interface_domain of this server is automatically
|
||||||
# included.
|
# included, if it's not null itself.
|
||||||
dns_records:
|
dns_records:
|
||||||
#- kind: A
|
#- kind: A
|
||||||
# addr: 127.0.0.1
|
# addr: 127.0.0.1
|
||||||
@ -105,7 +105,9 @@ service:
|
|||||||
# The domain name which will be used to serve the web interface of Domani. If
|
# 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
|
# service.http.https_addr is enabled then an HTTPS certificate for this domain
|
||||||
# will be retrieved automatically.
|
# will be retrieved automatically.
|
||||||
#primary_domain: "localhost"
|
#
|
||||||
|
# This can be set to null to disable the web interface entirely.
|
||||||
|
#interface_domain: "localhost"
|
||||||
|
|
||||||
#http:
|
#http:
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ domain:
|
|||||||
public: true
|
public: true
|
||||||
service:
|
service:
|
||||||
passphrase: foobar
|
passphrase: foobar
|
||||||
primary_domain: localhost
|
|
||||||
dns_records:
|
dns_records:
|
||||||
- kind: A
|
- kind: A
|
||||||
addr: 127.0.0.1
|
addr: 127.0.0.1
|
||||||
|
@ -32,14 +32,12 @@ pub struct DNSChecker {
|
|||||||
// TODO we should use some kind of connection pool here, I suppose
|
// TODO we should use some kind of connection pool here, I suppose
|
||||||
client: tokio::sync::Mutex<AsyncClient>,
|
client: tokio::sync::Mutex<AsyncClient>,
|
||||||
token_store: Box<dyn token::Store + Send + Sync>,
|
token_store: Box<dyn token::Store + Send + Sync>,
|
||||||
service_primary_domain: domain::Name,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DNSChecker {
|
impl DNSChecker {
|
||||||
pub async fn new<TokenStore>(
|
pub async fn new<TokenStore>(
|
||||||
token_store: TokenStore,
|
token_store: TokenStore,
|
||||||
config: &domain::ConfigDNS,
|
config: &domain::ConfigDNS,
|
||||||
service_primary_domain: domain::Name,
|
|
||||||
) -> Result<Self, unexpected::Error>
|
) -> Result<Self, unexpected::Error>
|
||||||
where
|
where
|
||||||
TokenStore: token::Store + Send + Sync + 'static,
|
TokenStore: token::Store + Send + Sync + 'static,
|
||||||
@ -52,7 +50,6 @@ impl DNSChecker {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
token_store: Box::from(token_store),
|
token_store: Box::from(token_store),
|
||||||
client: tokio::sync::Mutex::new(client),
|
client: tokio::sync::Mutex::new(client),
|
||||||
service_primary_domain,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +108,7 @@ impl DNSChecker {
|
|||||||
|
|
||||||
let body = match reqwest::get(format!(
|
let body = match reqwest::get(format!(
|
||||||
"http://{}/.well-known/domani-challenge",
|
"http://{}/.well-known/domani-challenge",
|
||||||
self.service_primary_domain.as_str()
|
domain.as_str(),
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
@ -139,7 +139,9 @@ impl From<store::SetError> for SyncWithSettingsError {
|
|||||||
|
|
||||||
pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyError;
|
pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyError;
|
||||||
|
|
||||||
//#[mockall::automock]
|
pub type StoredDomain = domain::store::StoredDomain;
|
||||||
|
|
||||||
|
#[mockall::automock]
|
||||||
pub trait Manager: Sync + Send {
|
pub trait Manager: Sync + Send {
|
||||||
fn get_settings(&self, domain: &domain::Name) -> Result<GetSettingsResult, GetSettingsError>;
|
fn get_settings(&self, domain: &domain::Name) -> Result<GetSettingsResult, GetSettingsError>;
|
||||||
|
|
||||||
@ -165,7 +167,7 @@ pub trait Manager: Sync + Send {
|
|||||||
domain: &domain::Name,
|
domain: &domain::Name,
|
||||||
) -> unexpected::Result<Option<String>>;
|
) -> unexpected::Result<Option<String>>;
|
||||||
|
|
||||||
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error>;
|
fn all_domains(&self) -> Result<Vec<StoredDomain>, unexpected::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ManagerImpl {
|
pub struct ManagerImpl {
|
||||||
@ -247,13 +249,13 @@ impl ManagerImpl {
|
|||||||
}
|
}
|
||||||
.into_iter();
|
.into_iter();
|
||||||
|
|
||||||
for domain in domains {
|
for StoredDomain { domain, .. } in domains {
|
||||||
log::info!("Syncing domain {}", &domain);
|
log::info!("Syncing domain {}", &domain);
|
||||||
|
|
||||||
let get_res = match self.domain_store.get(&domain) {
|
let get_res = match self.domain_store.get(&domain) {
|
||||||
Ok(get_res) => get_res,
|
Ok(get_res) => get_res,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("Error syncing {domain}: {err}");
|
log::error!("Failed to fetch settings for domain {domain}: {err}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -334,7 +336,7 @@ impl Manager for ManagerImpl {
|
|||||||
self.domain_checker.get_challenge_token(domain)
|
self.domain_checker.get_challenge_token(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error> {
|
fn all_domains(&self) -> Result<Vec<StoredDomain>, unexpected::Error> {
|
||||||
self.domain_store.all_domains()
|
self.domain_store.all_domains()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
use std::{collections, fs, io, path, str::FromStr};
|
use std::{collections, fs, io, path};
|
||||||
|
|
||||||
use crate::domain;
|
use crate::domain;
|
||||||
use crate::error::unexpected::{self, Intoable, Mappable};
|
use crate::error::unexpected::{self, Intoable, Mappable};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct GetResult {
|
/// Extra information about a domain which is related to how its stored.
|
||||||
pub settings: domain::Settings,
|
pub struct Metadata {
|
||||||
pub builtin: bool,
|
pub builtin: bool,
|
||||||
pub public: bool,
|
pub public: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct GetResult {
|
||||||
|
pub settings: domain::Settings,
|
||||||
|
pub metadata: Metadata,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum GetError {
|
pub enum GetError {
|
||||||
#[error("not found")]
|
#[error("not found")]
|
||||||
@ -28,11 +34,16 @@ pub enum SetError {
|
|||||||
Unexpected(#[from] unexpected::Error),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct StoredDomain {
|
||||||
|
pub domain: domain::Name,
|
||||||
|
pub metadata: Metadata,
|
||||||
|
}
|
||||||
|
|
||||||
#[mockall::automock]
|
#[mockall::automock]
|
||||||
pub trait Store {
|
pub trait Store {
|
||||||
fn get(&self, domain: &domain::Name) -> Result<GetResult, GetError>;
|
fn get(&self, domain: &domain::Name) -> Result<GetResult, GetError>;
|
||||||
fn set(&self, domain: &domain::Name, settings: &domain::Settings) -> Result<(), SetError>;
|
fn set(&self, domain: &domain::Name, settings: &domain::Settings) -> Result<(), SetError>;
|
||||||
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error>;
|
fn all_domains(&self) -> Result<Vec<StoredDomain>, unexpected::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FSStore {
|
pub struct FSStore {
|
||||||
@ -71,8 +82,10 @@ impl Store for FSStore {
|
|||||||
|
|
||||||
Ok(GetResult {
|
Ok(GetResult {
|
||||||
settings,
|
settings,
|
||||||
public: true,
|
metadata: Metadata {
|
||||||
builtin: false,
|
public: true,
|
||||||
|
builtin: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,18 +104,23 @@ impl Store for FSStore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error> {
|
fn all_domains(&self) -> Result<Vec<StoredDomain>, unexpected::Error> {
|
||||||
fs::read_dir(&self.dir_path)
|
fs::read_dir(&self.dir_path)
|
||||||
.or_unexpected()?
|
.or_unexpected()?
|
||||||
.map(
|
.map(
|
||||||
|dir_entry_res: io::Result<fs::DirEntry>| -> Result<domain::Name, unexpected::Error> {
|
|dir_entry_res: io::Result<fs::DirEntry>| -> Result<StoredDomain, unexpected::Error> {
|
||||||
let domain = dir_entry_res.or_unexpected()?.file_name();
|
let domain = dir_entry_res.or_unexpected()?.file_name();
|
||||||
let domain = domain.to_str().ok_or(unexpected::Error::from(
|
let domain = domain.to_str().ok_or(unexpected::Error::from(
|
||||||
"couldn't convert os string to &str",
|
"couldn't convert os string to &str",
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
domain::Name::from_str(domain)
|
Ok(StoredDomain{
|
||||||
.map_unexpected_while(|| format!("parsing {domain} as domain name"))
|
domain: domain.parse().map_unexpected_while(|| format!("parsing {domain} as domain name"))?,
|
||||||
|
metadata: Metadata{
|
||||||
|
public: true,
|
||||||
|
builtin: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.try_collect()
|
.try_collect()
|
||||||
@ -131,8 +149,10 @@ impl<S: Store> Store for StoreWithBuiltin<S> {
|
|||||||
if let Some(domain) = self.domains.get(domain) {
|
if let Some(domain) = self.domains.get(domain) {
|
||||||
return Ok(GetResult {
|
return Ok(GetResult {
|
||||||
settings: domain.settings.clone(),
|
settings: domain.settings.clone(),
|
||||||
public: domain.public,
|
metadata: Metadata {
|
||||||
builtin: true,
|
public: domain.public,
|
||||||
|
builtin: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.inner.get(domain)
|
self.inner.get(domain)
|
||||||
@ -145,13 +165,18 @@ impl<S: Store> Store for StoreWithBuiltin<S> {
|
|||||||
self.inner.set(domain, settings)
|
self.inner.set(domain, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error> {
|
fn all_domains(&self) -> Result<Vec<StoredDomain>, unexpected::Error> {
|
||||||
let inner_domains = self.inner.all_domains()?;
|
let inner_domains = self.inner.all_domains()?;
|
||||||
let mut domains: Vec<domain::Name> = self
|
let mut domains: Vec<StoredDomain> = self
|
||||||
.domains
|
.domains
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, v)| v.public)
|
.map(|(domain, v)| StoredDomain {
|
||||||
.map(|(k, _)| k.clone())
|
domain: domain.clone(),
|
||||||
|
metadata: Metadata {
|
||||||
|
public: v.public,
|
||||||
|
builtin: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
domains.extend(inner_domains);
|
domains.extend(inner_domains);
|
||||||
Ok(domains)
|
Ok(domains)
|
||||||
@ -193,8 +218,10 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
GetResult {
|
GetResult {
|
||||||
settings,
|
settings,
|
||||||
public: true,
|
metadata: Metadata {
|
||||||
builtin: false,
|
public: true,
|
||||||
|
builtin: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
store.get(&domain).expect("settings retrieved")
|
store.get(&domain).expect("settings retrieved")
|
||||||
);
|
);
|
||||||
@ -211,8 +238,10 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
GetResult {
|
GetResult {
|
||||||
settings: new_settings,
|
settings: new_settings,
|
||||||
public: true,
|
metadata: Metadata {
|
||||||
builtin: false,
|
public: true,
|
||||||
|
builtin: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
store.get(&domain).expect("settings retrieved")
|
store.get(&domain).expect("settings retrieved")
|
||||||
);
|
);
|
||||||
|
32
src/main.rs
32
src/main.rs
@ -56,26 +56,25 @@ async fn main() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// primary_cname is a CNAME record which points to the primary domain of the service. Since
|
// inteface_cname is a CNAME record which points to the interface domain of the service.
|
||||||
// the primary domain _must_ point to the service (otherwise HTTPS wouldn't work) it's
|
// 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
|
// reasonable to assume that a CNAME on any domain would suffice to point that domain to
|
||||||
// the service.
|
// the service.
|
||||||
let primary_cname = domani::service::ConfigDNSRecord::CNAME {
|
if let Some(ref interface_domain) = config.service.interface_domain {
|
||||||
name: config.service.primary_domain.clone(),
|
let interface_cname = domani::service::ConfigDNSRecord::CNAME {
|
||||||
};
|
name: interface_domain.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
let dns_records_have_primary_cname = config
|
let dns_records_have_interface_cname = config
|
||||||
.service
|
.service
|
||||||
.dns_records
|
.dns_records
|
||||||
.iter()
|
.iter()
|
||||||
.any(|r| r == &primary_cname);
|
.any(|r| r == &interface_cname);
|
||||||
|
|
||||||
if !dns_records_have_primary_cname {
|
if !dns_records_have_interface_cname {
|
||||||
log::info!(
|
log::info!("Adding 'CNAME {interface_domain}' to service.dns_records");
|
||||||
"Adding 'CNAME {}' to service.dns_records",
|
config.service.dns_records.push(interface_cname);
|
||||||
&config.service.primary_domain
|
}
|
||||||
);
|
|
||||||
config.service.dns_records.push(primary_cname);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config
|
config
|
||||||
@ -95,7 +94,6 @@ async fn main() {
|
|||||||
let domain_checker = domani::domain::checker::DNSChecker::new(
|
let domain_checker = domani::domain::checker::DNSChecker::new(
|
||||||
domani::token::MemStore::new(),
|
domani::token::MemStore::new(),
|
||||||
&config.domain.dns,
|
&config.domain.dns,
|
||||||
config.service.primary_domain.clone(),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("domain checker initialization failed");
|
.expect("domain checker initialization failed");
|
||||||
|
@ -2,8 +2,8 @@ use crate::{domain, service};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{net, str::FromStr};
|
use std::{net, str::FromStr};
|
||||||
|
|
||||||
fn default_primary_domain() -> domain::Name {
|
fn default_interface_domain() -> Option<domain::Name> {
|
||||||
domain::Name::from_str("localhost").unwrap()
|
Some(domain::Name::from_str("localhost").unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||||
@ -28,10 +28,13 @@ impl From<ConfigDNSRecord> for domain::checker::DNSRecord {
|
|||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub passphrase: String,
|
pub passphrase: String,
|
||||||
pub dns_records: Vec<ConfigDNSRecord>,
|
pub dns_records: Vec<ConfigDNSRecord>,
|
||||||
#[serde(default = "default_primary_domain")]
|
|
||||||
pub primary_domain: domain::Name,
|
#[serde(default = "default_interface_domain")]
|
||||||
|
pub interface_domain: Option<domain::Name>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub http: service::http::Config,
|
pub http: service::http::Config,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub gemini: service::gemini::Config,
|
pub gemini: service::gemini::Config,
|
||||||
}
|
}
|
||||||
|
@ -196,11 +196,7 @@ async fn listen(
|
|||||||
.with_cert_resolver(service.cert_resolver.clone()),
|
.with_cert_resolver(service.cert_resolver.clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
log::info!(
|
log::info!("Listening on gemini://{}", addr);
|
||||||
"Listening on gemini://{}:{}",
|
|
||||||
&service.config.primary_domain.clone(),
|
|
||||||
addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(addr)
|
let listener = tokio::net::TcpListener::bind(addr)
|
||||||
.await
|
.await
|
||||||
|
@ -10,7 +10,6 @@ use http::request::Parts;
|
|||||||
use hyper::{Body, Method, Request, Response};
|
use hyper::{Body, Method, Request, Response};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::{future, net, sync};
|
use std::{future, net, sync};
|
||||||
|
|
||||||
use crate::error::unexpected;
|
use crate::error::unexpected;
|
||||||
@ -164,7 +163,7 @@ impl Service {
|
|||||||
match self.domain_manager.get_file(&domain, &path) {
|
match self.domain_manager.get_file(&domain, &path) {
|
||||||
Ok(f) => self.serve(200, &path, Body::wrap_stream(f)),
|
Ok(f) => self.serve(200, &path, Body::wrap_stream(f)),
|
||||||
Err(domain::manager::GetFileError::DomainNotFound) => {
|
Err(domain::manager::GetFileError::DomainNotFound) => {
|
||||||
return self.render_error_page(404, "Domain not found")
|
return self.render_error_page(404, "Unknown domain name")
|
||||||
}
|
}
|
||||||
Err(domain::manager::GetFileError::FileNotFound) => {
|
Err(domain::manager::GetFileError::FileNotFound) => {
|
||||||
self.render_error_page(404, "File not found")
|
self.render_error_page(404, "File not found")
|
||||||
@ -210,6 +209,8 @@ impl Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn domain(&self, args: DomainArgs) -> Response<Body> {
|
fn domain(&self, args: DomainArgs) -> Response<Body> {
|
||||||
|
use domain::store::Metadata;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Data {
|
struct Data {
|
||||||
domain: domain::Name,
|
domain: domain::Name,
|
||||||
@ -217,24 +218,42 @@ impl Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let settings = match self.domain_manager.get_settings(&args.domain) {
|
let settings = match self.domain_manager.get_settings(&args.domain) {
|
||||||
|
Ok(domain::manager::GetSettingsResult {
|
||||||
|
metadata:
|
||||||
|
Metadata {
|
||||||
|
public: false,
|
||||||
|
builtin: _,
|
||||||
|
},
|
||||||
|
..
|
||||||
|
}) => None,
|
||||||
|
|
||||||
Ok(domain::manager::GetSettingsResult {
|
Ok(domain::manager::GetSettingsResult {
|
||||||
settings,
|
settings,
|
||||||
public,
|
metadata:
|
||||||
builtin,
|
Metadata {
|
||||||
}) => match (public, builtin) {
|
public: true,
|
||||||
(false, _) => None,
|
builtin: false,
|
||||||
(true, false) => Some(settings),
|
},
|
||||||
(true, true) => {
|
}) => Some(settings),
|
||||||
return self.render_error_page(
|
|
||||||
403,
|
Ok(domain::manager::GetSettingsResult {
|
||||||
format!(
|
metadata:
|
||||||
"Settings for domain {} cannot be viewed or modified",
|
Metadata {
|
||||||
args.domain
|
public: true,
|
||||||
)
|
builtin: true,
|
||||||
.as_str(),
|
},
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
return self.render_error_page(
|
||||||
|
403,
|
||||||
|
format!(
|
||||||
|
"Settings for domain {} cannot be viewed or modified",
|
||||||
|
args.domain
|
||||||
)
|
)
|
||||||
}
|
.as_str(),
|
||||||
},
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Err(domain::manager::GetSettingsError::NotFound) => None,
|
Err(domain::manager::GetSettingsError::NotFound) => None,
|
||||||
Err(domain::manager::GetSettingsError::Unexpected(e)) => {
|
Err(domain::manager::GetSettingsError::Unexpected(e)) => {
|
||||||
return self.internal_error(
|
return self.internal_error(
|
||||||
@ -367,7 +386,8 @@ impl Service {
|
|||||||
|
|
||||||
let mut domains: Vec<String> = domains
|
let mut domains: Vec<String> = domains
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|domain| domain.as_str().to_string())
|
.filter(|d| d.metadata.public)
|
||||||
|
.map(|d| d.domain.as_str().to_string())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
domains.sort();
|
domains.sort();
|
||||||
@ -375,83 +395,7 @@ impl Service {
|
|||||||
self.render_page("/domains.html", Response { domains })
|
self.render_page("/domains.html", Response { domains })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_request(
|
async fn serve_interface(&self, req: Request<Body>) -> Response<Body> {
|
||||||
&self,
|
|
||||||
client_ip: net::IpAddr,
|
|
||||||
req: Request<Body>,
|
|
||||||
req_is_https: bool,
|
|
||||||
) -> Response<Body> {
|
|
||||||
let maybe_host = match (
|
|
||||||
req.headers()
|
|
||||||
.get("Host")
|
|
||||||
.and_then(|v| v.to_str().ok())
|
|
||||||
.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),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
.and_then(|h| domain::Name::from_str(h).ok());
|
|
||||||
|
|
||||||
{
|
|
||||||
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 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) {
|
|
||||||
return self.serve(200, "token.txt", key.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serving domani challenges similarly takes priority.
|
|
||||||
if req.method() == Method::GET && path == "/.well-known/domani-challenge" {
|
|
||||||
if let Some(ref domain) = maybe_host {
|
|
||||||
match self
|
|
||||||
.domain_manager
|
|
||||||
.get_domain_checker_challenge_token(domain)
|
|
||||||
{
|
|
||||||
Ok(Some(token)) => return self.serve(200, "token.txt", token.into()),
|
|
||||||
Ok(None) => return self.render_error_page(404, "Token not found"),
|
|
||||||
Err(e) => {
|
|
||||||
return self.internal_error(
|
|
||||||
format!("failed to get token for domain {}: {e}", domain).as_str(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a managed domain was given then serve that from its origin or a proxy.
|
|
||||||
if let Some(domain) = maybe_host {
|
|
||||||
if let Some(proxied_domain_config) = self.config.http.proxied_domains.get(&domain) {
|
|
||||||
return service::http::proxy::serve_http_request(
|
|
||||||
proxied_domain_config,
|
|
||||||
client_ip,
|
|
||||||
req,
|
|
||||||
req_is_https,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
self.internal_error(
|
|
||||||
format!(
|
|
||||||
"serving {domain} via proxy {}: {e}",
|
|
||||||
proxied_domain_config.url.as_ref()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.serve_origin(domain, req).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve main domani site
|
|
||||||
let (req, body) = req.into_parts();
|
let (req, body) = req.into_parts();
|
||||||
let path = req.uri.path();
|
let path = req.uri.path();
|
||||||
|
|
||||||
@ -488,6 +432,86 @@ impl Service {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn domain_from_req(req: &Request<Body>) -> Option<domain::Name> {
|
||||||
|
let host_header = req
|
||||||
|
.headers()
|
||||||
|
.get("Host")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.map(strip_port);
|
||||||
|
|
||||||
|
host_header
|
||||||
|
// if host_header isn't present, try the host from the URI
|
||||||
|
.or_else(|| req.uri().host().map(strip_port))
|
||||||
|
.and_then(|h| h.parse().ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_request(
|
||||||
|
&self,
|
||||||
|
client_ip: net::IpAddr,
|
||||||
|
req: Request<Body>,
|
||||||
|
req_is_https: bool,
|
||||||
|
) -> Response<Body> {
|
||||||
|
let domain = match Self::domain_from_req(&req) {
|
||||||
|
Some(domain) => domain,
|
||||||
|
None => return self.render_error_page(400, "Cannot serve page without domain"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let method = req.method();
|
||||||
|
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/") {
|
||||||
|
let token = path.trim_start_matches("/.well-known/acme-challenge/");
|
||||||
|
|
||||||
|
if let Ok(key) = self.domain_manager.get_acme_http01_challenge_key(token) {
|
||||||
|
return self.serve(200, "token.txt", key.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serving domani challenges similarly takes priority.
|
||||||
|
if method == Method::GET && path == "/.well-known/domani-challenge" {
|
||||||
|
match self
|
||||||
|
.domain_manager
|
||||||
|
.get_domain_checker_challenge_token(&domain)
|
||||||
|
{
|
||||||
|
Ok(Some(token)) => return self.serve(200, "token.txt", token.into()),
|
||||||
|
Ok(None) => return self.render_error_page(404, "Token not found"),
|
||||||
|
Err(e) => {
|
||||||
|
return self.internal_error(
|
||||||
|
format!("failed to get token for domain {}: {e}", domain).as_str(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(proxied_domain_config) = self.config.http.proxied_domains.get(&domain) {
|
||||||
|
return service::http::proxy::serve_http_request(
|
||||||
|
proxied_domain_config,
|
||||||
|
client_ip,
|
||||||
|
req,
|
||||||
|
req_is_https,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
self.internal_error(
|
||||||
|
format!(
|
||||||
|
"serving {domain} via proxy {}: {e}",
|
||||||
|
proxied_domain_config.url.as_ref()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if Some(&domain) == self.config.interface_domain.as_ref() {
|
||||||
|
return self.serve_interface(req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.serve_origin(domain, req).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strip_port(host: &str) -> &str {
|
fn strip_port(host: &str) -> &str {
|
||||||
|
@ -13,7 +13,13 @@ pub async fn listen_http(
|
|||||||
canceller: CancellationToken,
|
canceller: CancellationToken,
|
||||||
) -> Result<(), unexpected::Error> {
|
) -> Result<(), unexpected::Error> {
|
||||||
let addr = service.config.http.http_addr.clone();
|
let addr = service.config.http.http_addr.clone();
|
||||||
let primary_domain = service.config.primary_domain.clone();
|
|
||||||
|
// 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());
|
||||||
|
|
||||||
let make_service = hyper::service::make_service_fn(move |conn: &AddrStream| {
|
let make_service = hyper::service::make_service_fn(move |conn: &AddrStream| {
|
||||||
let service = service.clone();
|
let service = service.clone();
|
||||||
@ -31,11 +37,7 @@ pub async fn listen_http(
|
|||||||
async move { Ok::<_, convert::Infallible>(hyper_service) }
|
async move { Ok::<_, convert::Infallible>(hyper_service) }
|
||||||
});
|
});
|
||||||
|
|
||||||
log::info!(
|
log::info!("Listening on http://{}:{}", listen_host, addr.port(),);
|
||||||
"Listening on http://{}:{}",
|
|
||||||
primary_domain.as_str(),
|
|
||||||
addr.port(),
|
|
||||||
);
|
|
||||||
let server = hyper::Server::bind(&addr).serve(make_service);
|
let server = hyper::Server::bind(&addr).serve(make_service);
|
||||||
|
|
||||||
let graceful = server.with_graceful_shutdown(async {
|
let graceful = server.with_graceful_shutdown(async {
|
||||||
@ -51,7 +53,13 @@ pub async fn listen_https(
|
|||||||
) -> Result<(), unexpected::Error> {
|
) -> Result<(), unexpected::Error> {
|
||||||
let cert_resolver = service.cert_resolver.clone();
|
let cert_resolver = service.cert_resolver.clone();
|
||||||
let addr = service.config.http.https_addr.unwrap().clone();
|
let addr = service.config.http.https_addr.unwrap().clone();
|
||||||
let primary_domain = service.config.primary_domain.clone();
|
|
||||||
|
// 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());
|
||||||
|
|
||||||
let make_service = hyper::service::make_service_fn(move |conn: &TlsStream<AddrStream>| {
|
let make_service = hyper::service::make_service_fn(move |conn: &TlsStream<AddrStream>| {
|
||||||
let service = service.clone();
|
let service = service.clone();
|
||||||
@ -91,11 +99,7 @@ pub async fn listen_https(
|
|||||||
|
|
||||||
let incoming = hyper::server::accept::from_stream(incoming);
|
let incoming = hyper::server::accept::from_stream(incoming);
|
||||||
|
|
||||||
log::info!(
|
log::info!("Listening on https://{}:{}", listen_host, addr.port());
|
||||||
"Listening on https://{}:{}",
|
|
||||||
primary_domain.as_str(),
|
|
||||||
addr.port()
|
|
||||||
);
|
|
||||||
|
|
||||||
let server = hyper::Server::builder(incoming).serve(make_service);
|
let server = hyper::Server::builder(incoming).serve(make_service);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user