Use an HTTP challenge for ensuring that domains are correctly set up, rather than checking DNS records directly
This commit is contained in:
parent
28104f36e1
commit
03428cef02
130
Cargo.lock
generated
130
Cargo.lock
generated
@ -285,6 +285,16 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
@ -461,8 +471,10 @@ dependencies = [
|
|||||||
"mockall",
|
"mockall",
|
||||||
"openssl",
|
"openssl",
|
||||||
"pem",
|
"pem",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"reqwest",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"rustls 0.21.1",
|
"rustls",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
@ -475,7 +487,7 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tls-listener",
|
"tls-listener",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.0",
|
"tokio-rustls",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"trust-dns-client",
|
"trust-dns-client",
|
||||||
]
|
]
|
||||||
@ -1528,15 +1540,29 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-rustls"
|
name = "hyper-rustls"
|
||||||
version = "0.23.2"
|
version = "0.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
|
checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"hyper",
|
"hyper",
|
||||||
"rustls 0.20.8",
|
"rustls",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.23.4",
|
"tokio-rustls",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"hyper",
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1889,6 +1915,24 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "native-tls"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"openssl",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nibble_vec"
|
name = "nibble_vec"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1984,6 +2028,12 @@ dependencies = [
|
|||||||
"syn 2.0.15",
|
"syn 2.0.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-probe"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.87"
|
version = "0.9.87"
|
||||||
@ -2338,9 +2388,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.17"
|
version = "0.11.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91"
|
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.0",
|
"base64 0.21.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -2352,20 +2402,23 @@ dependencies = [
|
|||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-rustls",
|
"hyper-rustls",
|
||||||
|
"hyper-tls",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
|
"native-tls",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustls 0.20.8",
|
"rustls",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.23.4",
|
"tokio-native-tls",
|
||||||
|
"tokio-rustls",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"trust-dns-resolver",
|
"trust-dns-resolver",
|
||||||
"url",
|
"url",
|
||||||
@ -2449,18 +2502,6 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls"
|
|
||||||
version = "0.20.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"ring",
|
|
||||||
"sct",
|
|
||||||
"webpki",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.21.1"
|
version = "0.21.1"
|
||||||
@ -2507,6 +2548,15 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schannel"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -2523,6 +2573,29 @@ dependencies = [
|
|||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "2.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"core-foundation",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework-sys"
|
||||||
|
version = "2.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.162"
|
version = "1.0.162"
|
||||||
@ -2833,7 +2906,7 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.0",
|
"tokio-rustls",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2867,14 +2940,13 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-native-tls"
|
||||||
version = "0.23.4"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
|
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls 0.20.8",
|
"native-tls",
|
||||||
"tokio",
|
"tokio",
|
||||||
"webpki",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2883,7 +2955,7 @@ version = "0.24.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5"
|
checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls 0.21.1",
|
"rustls",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -42,3 +42,5 @@ tokio-rustls = "0.24.0"
|
|||||||
log = "0.4.19"
|
log = "0.4.19"
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
serde_yaml = "0.9.22"
|
serde_yaml = "0.9.22"
|
||||||
|
rand = "0.8.5"
|
||||||
|
reqwest = "0.11.18"
|
||||||
|
@ -128,9 +128,3 @@ Within the shell which opens you can do `cargo run` to start a local instance.
|
|||||||
* Alternative URLs (reverse proxy)
|
* Alternative URLs (reverse proxy)
|
||||||
* Google Drive
|
* Google Drive
|
||||||
* Dropbox
|
* Dropbox
|
||||||
|
|
||||||
* Better support for dDNS servers. If a server is behind dDNS then users can
|
|
||||||
only add it to their domain via CNAME. If they add the CNAME (or ALIAS or
|
|
||||||
whatever) to the zone apex then Domani can't see that actual CNAME record (the
|
|
||||||
DNS server will flatten it to A records). This breaks Domani.
|
|
||||||
|
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
use std::net;
|
use std::net;
|
||||||
use std::ops::DerefMut;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::domain;
|
|
||||||
use crate::error::unexpected::{self, Mappable};
|
use crate::error::unexpected::{self, Mappable};
|
||||||
|
use crate::{domain, token};
|
||||||
|
|
||||||
use trust_dns_client::client::{AsyncClient, ClientHandle};
|
use trust_dns_client::client::{AsyncClient, ClientHandle};
|
||||||
use trust_dns_client::rr::{DNSClass, Name, RData, RecordType};
|
use trust_dns_client::rr::{DNSClass, Name, RData, RecordType};
|
||||||
use trust_dns_client::udp;
|
use trust_dns_client::udp;
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum CheckDomainError {
|
pub enum CheckDomainError {
|
||||||
#[error("no service dns records set")]
|
#[error("no service dns records set")]
|
||||||
@ -27,123 +28,50 @@ pub enum DNSRecord {
|
|||||||
CNAME(domain::Name),
|
CNAME(domain::Name),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DNSRecord {
|
|
||||||
async fn check_a(
|
|
||||||
client: &mut AsyncClient,
|
|
||||||
domain: &trust_dns_client::rr::Name,
|
|
||||||
addr: &net::Ipv4Addr,
|
|
||||||
) -> Result<bool, unexpected::Error> {
|
|
||||||
let response = client
|
|
||||||
.query(domain.clone(), DNSClass::IN, RecordType::A)
|
|
||||||
.await
|
|
||||||
.or_unexpected_while("querying A record")?;
|
|
||||||
|
|
||||||
let records = response.answers();
|
|
||||||
|
|
||||||
for record in records {
|
|
||||||
if let Some(RData::A(record_addr)) = record.data() {
|
|
||||||
if record_addr == addr {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn check_aaaa(
|
|
||||||
client: &mut AsyncClient,
|
|
||||||
domain: &trust_dns_client::rr::Name,
|
|
||||||
addr: &net::Ipv6Addr,
|
|
||||||
) -> Result<bool, unexpected::Error> {
|
|
||||||
let response = client
|
|
||||||
.query(domain.clone(), DNSClass::IN, RecordType::AAAA)
|
|
||||||
.await
|
|
||||||
.or_unexpected_while("querying AAAA record")?;
|
|
||||||
|
|
||||||
let records = response.answers();
|
|
||||||
|
|
||||||
for record in records {
|
|
||||||
if let Some(RData::AAAA(record_addr)) = record.data() {
|
|
||||||
if record_addr == addr {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn check_cname(
|
|
||||||
client: &mut AsyncClient,
|
|
||||||
domain: &trust_dns_client::rr::Name,
|
|
||||||
cname: &trust_dns_client::rr::Name,
|
|
||||||
) -> Result<bool, unexpected::Error> {
|
|
||||||
let response = client
|
|
||||||
.query(domain.clone(), DNSClass::IN, RecordType::CNAME)
|
|
||||||
.await
|
|
||||||
.or_unexpected_while("querying CNAME record")?;
|
|
||||||
|
|
||||||
let records = response.answers();
|
|
||||||
|
|
||||||
for record in records {
|
|
||||||
if let Some(RData::CNAME(record_cname)) = record.data() {
|
|
||||||
if record_cname == cname {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn check(
|
|
||||||
&self,
|
|
||||||
client: &mut AsyncClient,
|
|
||||||
domain: &trust_dns_client::rr::Name,
|
|
||||||
) -> Result<bool, unexpected::Error> {
|
|
||||||
match self {
|
|
||||||
Self::A(addr) => Self::check_a(client, domain, &addr).await,
|
|
||||||
Self::AAAA(addr) => Self::check_aaaa(client, domain, &addr).await,
|
|
||||||
Self::CNAME(name) => Self::check_cname(client, domain, name.as_rr()).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DNSChecker {
|
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>,
|
||||||
service_dns_records: Vec<DNSRecord>,
|
token_store: Box<dyn token::Store + Send + Sync>,
|
||||||
|
service_primary_domain: domain::Name,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DNSChecker {
|
impl DNSChecker {
|
||||||
pub async fn new(
|
pub async fn new<TokenStore>(
|
||||||
|
token_store: TokenStore,
|
||||||
config: &domain::ConfigDNS,
|
config: &domain::ConfigDNS,
|
||||||
service_dns_records: Vec<DNSRecord>,
|
service_primary_domain: domain::Name,
|
||||||
) -> Result<Self, unexpected::Error> {
|
) -> Result<Self, unexpected::Error>
|
||||||
|
where
|
||||||
|
TokenStore: token::Store + Send + Sync + 'static,
|
||||||
|
{
|
||||||
let stream = udp::UdpClientStream::<tokio::net::UdpSocket>::new(config.resolver_addr);
|
let stream = udp::UdpClientStream::<tokio::net::UdpSocket>::new(config.resolver_addr);
|
||||||
let (client, bg) = AsyncClient::connect(stream).await.or_unexpected()?;
|
let (client, bg) = AsyncClient::connect(stream).await.or_unexpected()?;
|
||||||
tokio::spawn(bg);
|
tokio::spawn(bg);
|
||||||
// TODO there should be a mechanism to clean this up
|
// TODO there should be a mechanism to clean this up
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
token_store: Box::from(token_store),
|
||||||
client: tokio::sync::Mutex::new(client),
|
client: tokio::sync::Mutex::new(client),
|
||||||
service_dns_records,
|
service_primary_domain,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_challenge_token(&self, domain: &domain::Name) -> unexpected::Result<Option<String>> {
|
||||||
|
self.token_store.get(domain.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn check_domain(
|
pub async fn check_domain(
|
||||||
&self,
|
&self,
|
||||||
domain: &domain::Name,
|
domain: &domain::Name,
|
||||||
challenge_token: &str,
|
challenge_token: &str,
|
||||||
) -> Result<(), CheckDomainError> {
|
) -> Result<(), CheckDomainError> {
|
||||||
let domain = domain.as_rr();
|
let domain_rr = domain.as_rr();
|
||||||
|
|
||||||
// check that the TXT record with the challenge token is correctly installed on the domain
|
// check that the TXT record with the challenge token is correctly installed on the domain
|
||||||
{
|
{
|
||||||
let domain = Name::from_str("_domani_challenge")
|
let domain = Name::from_str("_domani_challenge")
|
||||||
.or_unexpected_while("parsing TXT name")?
|
.or_unexpected_while("parsing TXT name")?
|
||||||
.append_domain(domain)
|
.append_domain(domain_rr)
|
||||||
.or_unexpected_while("appending domain to TXT")?;
|
.or_unexpected_while("appending domain to TXT")?;
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
@ -166,16 +94,40 @@ impl DNSChecker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that one of the possible DNS records is installed on the domain
|
// check that DNS correctly resolves for the domain. This is done by serving an HTTP
|
||||||
for record in &self.service_dns_records {
|
// challenge on the domain, which we then query for here.
|
||||||
let mut client = self.client.lock().await;
|
//
|
||||||
match record.check(client.deref_mut(), domain).await {
|
// first store the challenge token, so that the HTTP server can find it via
|
||||||
Ok(true) => return Ok(()),
|
// get_challenge_token.
|
||||||
Ok(false) => (),
|
let token: String = rand::thread_rng()
|
||||||
Err(e) => return Err(e.into()),
|
.sample_iter(rand::distributions::Alphanumeric)
|
||||||
}
|
.take(16)
|
||||||
|
.map(char::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
self.token_store
|
||||||
|
.set(domain.as_str().to_string(), token.clone())
|
||||||
|
.or_unexpected_while("storing challenge token")?;
|
||||||
|
|
||||||
|
let body = match reqwest::get(format!(
|
||||||
|
"http://{}/.well-known/domani-challenge",
|
||||||
|
self.service_primary_domain.as_str()
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(_) => return Err(CheckDomainError::ServiceDNSRecordsNotSet),
|
||||||
|
Ok(res) => res
|
||||||
|
.error_for_status()
|
||||||
|
.or(Err(CheckDomainError::ServiceDNSRecordsNotSet))?
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
|
.or(Err(CheckDomainError::ServiceDNSRecordsNotSet))?,
|
||||||
|
};
|
||||||
|
|
||||||
|
if body != token {
|
||||||
|
return Err(CheckDomainError::ServiceDNSRecordsNotSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(CheckDomainError::ServiceDNSRecordsNotSet)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,11 @@ pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert {
|
|||||||
token: &str,
|
token: &str,
|
||||||
) -> Result<String, GetAcmeHttp01ChallengeKeyError>;
|
) -> Result<String, GetAcmeHttp01ChallengeKeyError>;
|
||||||
|
|
||||||
|
fn get_domain_checker_challenge_token(
|
||||||
|
&self,
|
||||||
|
domain: &domain::Name,
|
||||||
|
) -> unexpected::Result<Option<String>>;
|
||||||
|
|
||||||
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error>;
|
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,6 +293,13 @@ impl Manager for ManagerImpl {
|
|||||||
Err(GetAcmeHttp01ChallengeKeyError::NotFound)
|
Err(GetAcmeHttp01ChallengeKeyError::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_domain_checker_challenge_token(
|
||||||
|
&self,
|
||||||
|
domain: &domain::Name,
|
||||||
|
) -> unexpected::Result<Option<String>> {
|
||||||
|
self.domain_checker.get_challenge_token(domain)
|
||||||
|
}
|
||||||
|
|
||||||
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error> {
|
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error> {
|
||||||
self.domain_store.all_domains()
|
self.domain_store.all_domains()
|
||||||
}
|
}
|
||||||
|
15
src/main.rs
15
src/main.rs
@ -78,21 +78,16 @@ async fn main() {
|
|||||||
config
|
config
|
||||||
};
|
};
|
||||||
|
|
||||||
let token_store = domani::token::MemStore::new();
|
|
||||||
|
|
||||||
let origin_store = domani::origin::git::FSStore::new(&config.origin)
|
let origin_store = domani::origin::git::FSStore::new(&config.origin)
|
||||||
.expect("git origin store initialization failed");
|
.expect("git origin store initialization failed");
|
||||||
|
|
||||||
let domain_checker = {
|
let domain_checker = domani::domain::checker::DNSChecker::new(
|
||||||
let dns_records = config.service.dns_records.clone();
|
domani::token::MemStore::new(),
|
||||||
|
|
||||||
domani::domain::checker::DNSChecker::new(
|
|
||||||
&config.domain.dns,
|
&config.domain.dns,
|
||||||
dns_records.into_iter().map(|r| r.into()).collect(),
|
config.service.primary_domain.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("domain checker initialization failed")
|
.expect("domain checker initialization failed");
|
||||||
};
|
|
||||||
|
|
||||||
let domain_config_store =
|
let domain_config_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"))
|
||||||
@ -111,7 +106,7 @@ async fn main() {
|
|||||||
Some(
|
Some(
|
||||||
domani::domain::acme::manager::ManagerImpl::new(
|
domani::domain::acme::manager::ManagerImpl::new(
|
||||||
domain_acme_store,
|
domain_acme_store,
|
||||||
token_store,
|
domani::token::MemStore::new(),
|
||||||
&acme_config,
|
&acme_config,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -356,8 +356,9 @@ impl<'svc> Service {
|
|||||||
let method = req.method();
|
let method = req.method();
|
||||||
let path = req.uri().path();
|
let path = req.uri().path();
|
||||||
|
|
||||||
// Serving acme challenges always takes priority. We serve them from the same store no matter
|
// Serving acme challenges always takes priority. We serve them from the same store no
|
||||||
// the domain, presumably they are cryptographically random enough that it doesn't matter.
|
// 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/") {
|
if method == Method::GET && path.starts_with("/.well-known/acme-challenge/") {
|
||||||
let token = path.trim_start_matches("/.well-known/acme-challenge/");
|
let token = path.trim_start_matches("/.well-known/acme-challenge/");
|
||||||
|
|
||||||
@ -366,6 +367,24 @@ impl<'svc> Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serving domani challenges similarly takes priority.
|
||||||
|
if 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
|
// If a managed domain was given then serve that from its origin
|
||||||
if let Some(domain) = maybe_host {
|
if let Some(domain) = maybe_host {
|
||||||
return self.serve_origin(domain, req.uri().path());
|
return self.serve_origin(domain, req.uri().path());
|
||||||
|
Loading…
Reference in New Issue
Block a user