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:
passphrase: foobar
dns_records:
- type: A
- kind: A
addr: 127.0.0.1
- type: AAAA
- kind: AAAA
addr: ::1

View File

@ -53,6 +53,18 @@ domain:
# renewed.
#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:
# 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
# included.
dns_records:
#- type: A
#- kind: A
# addr: 127.0.0.1
#- type: AAAA
#- kind: AAAA
# addr: ::1
# NOTE that the name given here must resolve to the Domani server.
#- type: CNAME
#- kind: CNAME
# name: domain.com
# 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:
* IPFS/IPNS
* Alternative URLs (reverse proxy)
* Small static files (e.g. for well-knowns)
* Google Drive
* Dropbox

View File

@ -1,7 +1,9 @@
use std::{net, path, str::FromStr};
use std::{collections, net, path, str::FromStr};
use serde::Deserialize;
use crate::domain;
fn default_resolver_addr() -> net::SocketAddr {
net::SocketAddr::from_str("1.1.1.1:53").unwrap()
}
@ -25,10 +27,19 @@ pub struct ConfigACME {
pub contact_email: String,
}
#[derive(Deserialize)]
pub struct BuiltinDomain {
#[serde(flatten)]
pub domain: domain::Domain,
pub public: bool,
}
#[derive(Deserialize)]
pub struct Config {
pub store_dir_path: path::PathBuf,
#[serde(default)]
pub dns: ConfigDNS,
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)]
pub enum SyncWithConfigError {
#[error("cannot call SyncWithConfig on builtin domain")]
BuiltinDomain,
#[error("invalid url")]
InvalidURL,
@ -127,6 +130,7 @@ impl From<checker::CheckDomainError> for SyncWithConfigError {
impl From<store::SetError> for SyncWithConfigError {
fn from(e: store::SetError) -> SyncWithConfigError {
match e {
store::SetError::BuiltinDomain => SyncWithConfigError::BuiltinDomain,
store::SetError::Unexpected(e) => SyncWithConfigError::Unexpected(e),
}
}

View File

@ -1,5 +1,5 @@
use std::str::FromStr;
use std::{cmp, fmt};
use std::{cmp, fmt, hash};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
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)]
/// Validated representation of a domain name
pub struct Name {
inner: trust_dns_rr::Name,
rr: trust_dns_rr::Name,
utf8_str: String,
}
@ -17,7 +17,7 @@ impl 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);
Ok(Name { inner: n, utf8_str })
Ok(Name { rr: n, utf8_str })
}
}
impl cmp::PartialEq for Name {
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::str::FromStr;
use std::{fs, io};
use std::{collections, fs, io, path, str::FromStr};
use crate::domain;
use crate::error::unexpected::{self, Intoable, Mappable};
@ -16,6 +14,9 @@ pub enum GetError {
#[derive(thiserror::Error, Debug)]
pub enum SetError {
#[error("cannot call set on builtin domain")]
BuiltinDomain,
#[error(transparent)]
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)]
mod tests {
use super::{Store, *};

View File

@ -89,10 +89,13 @@ async fn main() {
.await
.expect("domain checker initialization failed");
let domain_config_store =
let domain_store =
domani::domain::store::FSStore::new(&config.domain.store_dir_path.join("domains"))
.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 acme_config = config
.domain
@ -121,7 +124,7 @@ async fn main() {
let domain_manager = domani::domain::manager::ManagerImpl::new(
&mut task_stack,
origin_store,
domain_config_store,
domain_store,
domain_checker,
domain_acme_manager,
);

View File

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

View File

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

View File

@ -302,6 +302,7 @@ impl<'svc> Service {
let error_msg = match sync_result {
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::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()),