diff --git a/.dev-config.yml b/.dev-config.yml index 407a94a..bc9fa6d 100644 --- a/.dev-config.yml +++ b/.dev-config.yml @@ -7,3 +7,5 @@ service: dns_records: - type: A addr: 127.0.0.1 + - type: AAAA + addr: ::1 diff --git a/README.md b/README.md index ed5698d..5bb1b3b 100644 --- a/README.md +++ b/README.md @@ -66,10 +66,13 @@ service: #- type: A # addr: 127.0.0.1 + #- type: AAAA + # addr: ::1 + # 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. - # primary_domain: "localhost" + #primary_domain: "localhost" #http: @@ -113,7 +116,6 @@ Within the shell which opens you can do `cargo run` to start a local instance. ## Roadmap -* Support for AAAA and CNAME records * Support for more backends than just git repositories, including: * IPFS/IPNS * Alternative URLs (reverse proxy) diff --git a/src/domain/checker.rs b/src/domain/checker.rs index a2ab465..702957f 100644 --- a/src/domain/checker.rs +++ b/src/domain/checker.rs @@ -23,9 +23,33 @@ pub enum CheckDomainError { pub enum DNSRecord { A(net::Ipv4Addr), + AAAA(net::Ipv6Addr), } impl DNSRecord { + async fn check_aaaa( + client: &mut AsyncClient, + domain: &trust_dns_client::rr::Name, + addr: &net::Ipv6Addr, + ) -> Result { + let response = client + .query(domain.clone(), DNSClass::IN, RecordType::AAAA) + .await + .or_unexpected_while("querying A record")?; + + let records = response.answers(); + + if records.len() != 1 { + return Ok(false); + } + + // if the single record isn't a AAAA, or it's not the target AAAA, then return false + match records[0].data() { + Some(RData::AAAA(remote_addr)) if remote_addr == addr => Ok(true), + _ => return Ok(false), + } + } + async fn check_a( client: &mut AsyncClient, domain: &trust_dns_client::rr::Name, @@ -42,10 +66,9 @@ impl DNSRecord { return Ok(false); } - // if the single record isn't a A, or it's not the target A, then return - // TargetANAMENotSet + // if the single record isn't a A, or it's not the target A, then return false match records[0].data() { - Some(RData::A(remote_a)) if remote_a == addr => Ok(true), + Some(RData::A(remote_addr)) if remote_addr == addr => Ok(true), _ => return Ok(false), } } @@ -57,6 +80,7 @@ impl DNSRecord { ) -> Result { match self { Self::A(addr) => Self::check_a(client, domain, &addr).await, + Self::AAAA(addr) => Self::check_aaaa(client, domain, &addr).await, } } } diff --git a/src/main.rs b/src/main.rs index a056441..1935039 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,10 @@ async fn main() { let domain_checker = { let dns_records = config.service.dns_records.clone(); + if dns_records.len() == 0 { + panic!("service.dns_records must have at least one record defined") + } + domani::domain::checker::DNSChecker::new( &config.domain.dns, dns_records.into_iter().map(|r| r.into()).collect(), diff --git a/src/service.rs b/src/service.rs index 6b96d0b..bb3c5dd 100644 --- a/src/service.rs +++ b/src/service.rs @@ -2,23 +2,25 @@ pub mod http; mod util; use crate::domain; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::{net, str::FromStr}; fn default_primary_domain() -> domain::Name { domain::Name::from_str("localhost").unwrap() } -#[derive(Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone)] #[serde(tag = "type")] pub enum ConfigDNSRecord { A { addr: net::Ipv4Addr }, + AAAA { addr: net::Ipv6Addr }, } impl From for domain::checker::DNSRecord { fn from(r: ConfigDNSRecord) -> Self { match r { ConfigDNSRecord::A { addr } => Self::A(addr), + ConfigDNSRecord::AAAA { addr } => Self::AAAA(addr), } } } diff --git a/src/service/http.rs b/src/service/http.rs index 26607b7..ada0aed 100644 --- a/src/service/http.rs +++ b/src/service/http.rs @@ -8,7 +8,7 @@ use hyper::{Body, Method, Request, Response}; use serde::{Deserialize, Serialize}; use std::str::FromStr; -use std::{future, net, sync}; +use std::{future, sync}; use crate::error::unexpected; use crate::{domain, service, util}; @@ -225,10 +225,10 @@ impl<'svc> Service { domain_config: service::util::FlatConfig, ) -> Response { #[derive(Serialize)] - struct Response { + struct Response<'a> { domain: domain::Name, flat_config: service::util::FlatConfig, - target_a: net::Ipv4Addr, + dns_records: &'a [service::ConfigDNSRecord], challenge_token: String, } @@ -249,21 +249,12 @@ impl<'svc> Service { } }; - let target_a = match self - .config - .dns_records - .get(0) - .expect("at least one target record expected") - { - service::ConfigDNSRecord::A { addr } => addr.clone(), - }; - self.render_page( "/domain_init.html", Response { domain: args.domain, flat_config: config.into(), - target_a: target_a, + dns_records: &self.config.dns_records, challenge_token: config_hash, }, ) diff --git a/src/service/http/tpl/domain_init.html b/src/service/http/tpl/domain_init.html index 7b99ff6..897d497 100644 --- a/src/service/http/tpl/domain_init.html +++ b/src/service/http/tpl/domain_init.html @@ -1,23 +1,9 @@

Configure DNS

-

Next you will need to configure your DNS server to point to Domani. There -are two entries you will need to add:

+

This step requires a passphrase that has been given to you by the +administrator of the Domani server:

-
    -
  • - A A {{ data.domain }} entry with the value - {{ data.target_a }} -
  • -
  • - A TXT _domani_challenge.{{ data.domain }} entry with the value - {{ data.challenge_token }} -
  • -
- -

Once complete, you can hit the following button to check your configuration -and set up your domain.

- -
+ {{ #each data.flat_config }} @@ -25,10 +11,50 @@ and set up your domain.

-
+ +

Next you will need to configure your DNS server to point to Domani. There are +two entries you will need to add. The first entry tells the Domani server that +it is allowed to serve this domain with your given configuration:

+ + + + + + + + + + + + +
TypeDomainValue
TXT_domani_challenge.{{ data.domain }}{{ data.challenge_token }}
+ +

The second entry ensures that other users find the Domani server when they +query for your domain name. It can be one or more of:

+ + + + + + + + + {{ #each data.dns_records }} + + + + + + {{ /each }} +
TypeDomainValue
{{ this.type }}{{ lookup ../data "domain" }}{{ this.addr }}
+ +

Once both entries are installed, you can hit the following button to check +your configuration and set up your domain.

+ +