Implement support for AAAA records

This commit is contained in:
Brian Picciano 2023-07-10 18:22:37 +02:00
parent 7d64f44dab
commit 8600c1050e
7 changed files with 90 additions and 39 deletions

View File

@ -7,3 +7,5 @@ service:
dns_records:
- type: A
addr: 127.0.0.1
- type: AAAA
addr: ::1

View File

@ -66,6 +66,9 @@ 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.
@ -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)

View File

@ -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<bool, unexpected::Error> {
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<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,
}
}
}

View File

@ -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(),

View File

@ -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<ConfigDNSRecord> for domain::checker::DNSRecord {
fn from(r: ConfigDNSRecord) -> Self {
match r {
ConfigDNSRecord::A { addr } => Self::A(addr),
ConfigDNSRecord::AAAA { addr } => Self::AAAA(addr),
}
}
}

View File

@ -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<Body> {
#[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,
},
)

View File

@ -1,23 +1,9 @@
<h2>Configure DNS</h2>
<p>Next you will need to configure your DNS server to point to Domani. There
are two entries you will need to add:</p>
<p>This step requires a passphrase that has been given to you by the
administrator of the Domani server:</p>
<ul>
<li>
A <code>A {{ data.domain }}</code> entry with the value
<code>{{ data.target_a }}</code>
</li>
<li>
A <code>TXT _domani_challenge.{{ data.domain }}</code> entry with the value
<code>{{ data.challenge_token }}</code>
</li>
</ul>
<p>Once complete, you can hit the following button to check your configuration
and set up your domain.</p>
<form method="GET" action="/domain_sync.html">
<form method="GET" action="/domain_sync.html" id="syncForm">
<input name="domain" type="hidden" value="{{ data.domain }}" />
{{ #each data.flat_config }}
<input name="{{ @key }}" type="hidden" value="{{ this }}" />
@ -25,10 +11,50 @@ and set up your domain.</p>
<fieldset>
<label>
Passphrase (required during closed-beta):
Passphrase:
<input name="passphrase" placeholder="shhhh" required />
</label>
</fieldset>
<input type="submit" value="Next" />
</form>
<p>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:</p>
<table>
<tr>
<th>Type</th>
<th>Domain</th>
<th>Value</th>
</tr>
<tr>
<td>TXT</td>
<td>_domani_challenge.{{ data.domain }}</td>
<td>{{ data.challenge_token }}</td>
</tr>
</table>
<p>The second entry ensures that other users find the Domani server when they
query for your domain name. It can be <strong>one or more of</strong>:</p>
<table>
<tr>
<th>Type</th>
<th>Domain</th>
<th>Value</th>
</tr>
{{ #each data.dns_records }}
<tr>
<td>{{ this.type }}</td>
<td>{{ lookup ../data "domain" }}</td>
<td>{{ this.addr }}</td>
</tr>
{{ /each }}
</table>
<p>Once both entries are installed, you can hit the following button to check
your configuration and set up your domain.</p>
<input type="submit" value="Next" form="syncForm" />