Add builtin domains to configuration

This commit is contained in:
Brian Picciano 2023-07-15 19:41:11 +02:00
parent 03428cef02
commit 4483185e75
10 changed files with 104 additions and 17 deletions

View File

@ -5,7 +5,7 @@ domain:
service: service:
passphrase: foobar passphrase: foobar
dns_records: dns_records:
- type: A - kind: A
addr: 127.0.0.1 addr: 127.0.0.1
- type: AAAA - kind: AAAA
addr: ::1 addr: ::1

View File

@ -53,6 +53,18 @@ domain:
# renewed. # renewed.
#contact_email: REQUIRED if service.http.https_addr is set #contact_email: REQUIRED if service.http.https_addr is set
# 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:
# An example built-in domain backed by a git repo.
#example.com:
# kind: git
# url: "https://somewhere.com/some/repo.git"
# branch: main
# public: false
service: service:
# Passphrase which must be given by users who are configuring new domains via # Passphrase which must be given by users who are configuring new domains via
@ -66,14 +78,14 @@ service:
# A CNAME record with the primary_domain of this server is automatically # A CNAME record with the primary_domain of this server is automatically
# included. # included.
dns_records: dns_records:
#- type: A #- kind: A
# addr: 127.0.0.1 # addr: 127.0.0.1
#- type: AAAA #- kind: AAAA
# addr: ::1 # addr: ::1
# NOTE that the name given here must resolve to the Domani server. # NOTE that the name given here must resolve to the Domani server.
#- type: CNAME #- kind: CNAME
# name: domain.com # name: domain.com
# 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
@ -126,5 +138,6 @@ Within the shell which opens you can do `cargo run` to start a local instance.
* Support for more backends than just git repositories, including: * Support for more backends than just git repositories, including:
* IPFS/IPNS * IPFS/IPNS
* Alternative URLs (reverse proxy) * Alternative URLs (reverse proxy)
* Small static files (e.g. for well-knowns)
* Google Drive * Google Drive
* Dropbox * Dropbox

View File

@ -1,7 +1,9 @@
use std::{net, path, str::FromStr}; use std::{collections, net, path, str::FromStr};
use serde::Deserialize; use serde::Deserialize;
use crate::domain;
fn default_resolver_addr() -> net::SocketAddr { fn default_resolver_addr() -> net::SocketAddr {
net::SocketAddr::from_str("1.1.1.1:53").unwrap() net::SocketAddr::from_str("1.1.1.1:53").unwrap()
} }
@ -25,10 +27,19 @@ pub struct ConfigACME {
pub contact_email: String, pub contact_email: String,
} }
#[derive(Deserialize)]
pub struct BuiltinDomain {
#[serde(flatten)]
pub domain: domain::Domain,
pub public: bool,
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Config { pub struct Config {
pub store_dir_path: path::PathBuf, pub store_dir_path: path::PathBuf,
#[serde(default)] #[serde(default)]
pub dns: ConfigDNS, pub dns: ConfigDNS,
pub acme: Option<ConfigACME>, pub acme: Option<ConfigACME>,
pub builtins: collections::HashMap<domain::Name, BuiltinDomain>,
} }

View File

@ -80,6 +80,9 @@ impl From<store::GetError> for SyncError {
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum SyncWithConfigError { pub enum SyncWithConfigError {
#[error("cannot call SyncWithConfig on builtin domain")]
BuiltinDomain,
#[error("invalid url")] #[error("invalid url")]
InvalidURL, InvalidURL,
@ -127,6 +130,7 @@ impl From<checker::CheckDomainError> for SyncWithConfigError {
impl From<store::SetError> for SyncWithConfigError { impl From<store::SetError> for SyncWithConfigError {
fn from(e: store::SetError) -> SyncWithConfigError { fn from(e: store::SetError) -> SyncWithConfigError {
match e { match e {
store::SetError::BuiltinDomain => SyncWithConfigError::BuiltinDomain,
store::SetError::Unexpected(e) => SyncWithConfigError::Unexpected(e), store::SetError::Unexpected(e) => SyncWithConfigError::Unexpected(e),
} }
} }

View File

@ -1,5 +1,5 @@
use std::str::FromStr; use std::str::FromStr;
use std::{cmp, fmt}; use std::{cmp, fmt, hash};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use trust_dns_client::rr as trust_dns_rr; use trust_dns_client::rr as trust_dns_rr;
@ -7,7 +7,7 @@ use trust_dns_client::rr as trust_dns_rr;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// Validated representation of a domain name /// Validated representation of a domain name
pub struct Name { pub struct Name {
inner: trust_dns_rr::Name, rr: trust_dns_rr::Name,
utf8_str: String, utf8_str: String,
} }
@ -17,7 +17,7 @@ impl Name {
} }
pub fn as_rr(&self) -> &trust_dns_rr::Name { pub fn as_rr(&self) -> &trust_dns_rr::Name {
&self.inner &self.rr
} }
} }
@ -36,13 +36,21 @@ impl FromStr for Name {
n.set_fqdn(true); n.set_fqdn(true);
Ok(Name { inner: n, utf8_str }) Ok(Name { rr: n, utf8_str })
} }
} }
impl cmp::PartialEq for Name { impl cmp::PartialEq for Name {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.inner == other.inner self.rr == other.rr
}
}
impl cmp::Eq for Name {}
impl hash::Hash for Name {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.rr.hash(state);
} }
} }

View File

@ -1,6 +1,4 @@
use std::path; use std::{collections, fs, io, path, str::FromStr};
use std::str::FromStr;
use std::{fs, io};
use crate::domain; use crate::domain;
use crate::error::unexpected::{self, Intoable, Mappable}; use crate::error::unexpected::{self, Intoable, Mappable};
@ -16,6 +14,9 @@ pub enum GetError {
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum SetError { pub enum SetError {
#[error("cannot call set on builtin domain")]
BuiltinDomain,
#[error(transparent)] #[error(transparent)]
Unexpected(#[from] unexpected::Error), Unexpected(#[from] unexpected::Error),
} }
@ -96,6 +97,51 @@ impl Store for FSStore {
} }
} }
pub struct StoreWithBuiltin<S: Store> {
inner: S,
domains: collections::HashMap<domain::Name, domain::config::BuiltinDomain>,
}
impl<S: Store> StoreWithBuiltin<S> {
pub fn new(
inner: S,
builtin_domains: collections::HashMap<domain::Name, domain::config::BuiltinDomain>,
) -> StoreWithBuiltin<S> {
StoreWithBuiltin {
inner,
domains: builtin_domains,
}
}
}
impl<S: Store> Store for StoreWithBuiltin<S> {
fn get(&self, domain: &domain::Name) -> Result<domain::Domain, GetError> {
if let Some(domain) = self.domains.get(domain) {
return Ok(domain.domain.clone());
}
self.inner.get(domain)
}
fn set(&self, domain: &domain::Name, config: &domain::Domain) -> Result<(), SetError> {
if self.domains.get(domain).is_some() {
return Err(SetError::BuiltinDomain);
}
self.inner.set(domain, config)
}
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error> {
let inner_domains = self.inner.all_domains()?;
let mut domains: Vec<domain::Name> = self
.domains
.iter()
.filter(|(_, v)| v.public)
.map(|(k, _)| k.clone())
.collect();
domains.extend(inner_domains);
Ok(domains)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Store, *}; use super::{Store, *};

View File

@ -89,10 +89,13 @@ async fn main() {
.await .await
.expect("domain checker initialization failed"); .expect("domain checker initialization failed");
let domain_config_store = let domain_store =
domani::domain::store::FSStore::new(&config.domain.store_dir_path.join("domains")) domani::domain::store::FSStore::new(&config.domain.store_dir_path.join("domains"))
.expect("domain config store initialization failed"); .expect("domain config store initialization failed");
let domain_store =
domani::domain::store::StoreWithBuiltin::new(domain_store, config.domain.builtins);
let domain_acme_manager = if config.service.http.https_addr.is_some() { let domain_acme_manager = if config.service.http.https_addr.is_some() {
let acme_config = config let acme_config = config
.domain .domain
@ -121,7 +124,7 @@ async fn main() {
let domain_manager = domani::domain::manager::ManagerImpl::new( let domain_manager = domani::domain::manager::ManagerImpl::new(
&mut task_stack, &mut task_stack,
origin_store, origin_store,
domain_config_store, domain_store,
domain_checker, domain_checker,
domain_acme_manager, domain_acme_manager,
); );

View File

@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "kind")]
/// A unique description of an origin, from where a domain might be served. /// A unique description of an origin, from where a domain might be served.
pub enum Descr { pub enum Descr {
Git { url: String, branch_name: String }, Git { url: String, branch_name: String },

View File

@ -10,7 +10,7 @@ fn default_primary_domain() -> domain::Name {
} }
#[derive(Serialize, Deserialize, Clone, PartialEq)] #[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "type")] #[serde(tag = "kind")]
pub enum ConfigDNSRecord { pub enum ConfigDNSRecord {
A { addr: net::Ipv4Addr }, A { addr: net::Ipv4Addr },
AAAA { addr: net::Ipv6Addr }, AAAA { addr: net::Ipv6Addr },

View File

@ -302,6 +302,7 @@ impl<'svc> Service {
let error_msg = match sync_result { let error_msg = match sync_result {
Ok(_) => None, Ok(_) => None,
Err(domain::manager::SyncWithConfigError::BuiltinDomain) => Some("This domain is not able to be configured, please contact the server administrator.".to_string()),
Err(domain::manager::SyncWithConfigError::InvalidURL) => Some("Fetching the git repository failed, please double check that you input the correct URL.".to_string()), Err(domain::manager::SyncWithConfigError::InvalidURL) => Some("Fetching the git repository failed, please double check that you input the correct URL.".to_string()),
Err(domain::manager::SyncWithConfigError::InvalidBranchName) => Some("The git repository does not have a branch of the given name, please double check that you input the correct name.".to_string()), Err(domain::manager::SyncWithConfigError::InvalidBranchName) => Some("The git repository does not have a branch of the given name, please double check that you input the correct name.".to_string()),
Err(domain::manager::SyncWithConfigError::AlreadyInProgress) => Some("The configuration of your domain is still in progress, please refresh in a few minutes.".to_string()), Err(domain::manager::SyncWithConfigError::AlreadyInProgress) => Some("The configuration of your domain is still in progress, please refresh in a few minutes.".to_string()),