aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2022-12-11 16:29:06 +0100
committerAlex Auvolat <alex@adnab.me>2022-12-11 16:29:06 +0100
commitb26d4d7bbad7c77f466a00f0bd21e7e4ea09c3b1 (patch)
tree0894d7de36ec28e20f36311c5144959d6f604343 /src
parent0d37299b24ec112b752b81334e35bac8c2c36167 (diff)
downloadD53-b26d4d7bbad7c77f466a00f0bd21e7e4ea09c3b1.tar.gz
D53-b26d4d7bbad7c77f466a00f0bd21e7e4ea09c3b1.zip
Change label format to be a single dns path
Diffstat (limited to 'src')
-rw-r--r--src/dns_config.rs14
-rw-r--r--src/dns_updater.rs88
-rw-r--r--src/main.rs35
-rw-r--r--src/provider/gandi.rs10
4 files changed, 106 insertions, 41 deletions
diff --git a/src/dns_config.rs b/src/dns_config.rs
index 25cc5f0..37a49fa 100644
--- a/src/dns_config.rs
+++ b/src/dns_config.rs
@@ -26,8 +26,7 @@ pub struct DnsConfig {
#[derive(Debug, Hash, PartialEq, Eq)]
pub struct DnsEntryKey {
- pub domain: String,
- pub subdomain: String,
+ pub dns_path: String,
pub record_type: DnsRecordType,
}
@@ -62,7 +61,7 @@ impl DnsConfig {
fn parse_d53_tag(tag: &str, node: &ConsulNode) -> Option<(DnsEntryKey, DnsEntryValue)> {
let splits = tag.split(' ').collect::<Vec<_>>();
- if splits.len() != 3 {
+ if splits.len() != 2 {
return None;
}
@@ -96,8 +95,7 @@ fn parse_d53_tag(tag: &str, node: &ConsulNode) -> Option<(DnsEntryKey, DnsEntryV
Some((
DnsEntryKey {
- domain: splits[1].to_string(),
- subdomain: splits[2].to_string(),
+ dns_path: splits[1].to_string(),
record_type,
},
DnsEntryValue { targets },
@@ -259,11 +257,7 @@ impl std::fmt::Display for DnsRecordType {
impl std::fmt::Display for DnsEntryKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(
- f,
- "{}.{} IN {}",
- self.subdomain, self.domain, self.record_type
- )
+ write!(f, "{} IN {}", self.dns_path, self.record_type)
}
}
diff --git a/src/dns_updater.rs b/src/dns_updater.rs
index d781671..e65689d 100644
--- a/src/dns_updater.rs
+++ b/src/dns_updater.rs
@@ -8,16 +8,28 @@ use tokio::sync::watch;
use tracing::*;
use crate::dns_config::*;
-use crate::provider::DnsProvider;
+use crate::DomainProvider;
pub async fn dns_updater_task(
mut rx_dns_config: watch::Receiver<Arc<DnsConfig>>,
- provider: Box<dyn DnsProvider>,
+ providers: Vec<DomainProvider>,
allowed_domains: Vec<String>,
mut must_exit: watch::Receiver<bool>,
) {
+ for dom in allowed_domains.iter() {
+ info!(domain = dom, "allowing subdomains of domain");
+ }
+ for prov in providers.iter() {
+ info!(
+ domain = prov.domain,
+ provider = prov.provider.provider(),
+ "got provider for domain"
+ );
+ }
+
info!("DNS updater will start in 5 seconds");
tokio::time::sleep(Duration::from_secs(5)).await;
+
info!("DNS updater starting");
let mut config = Arc::new(DnsConfig::new());
@@ -32,21 +44,37 @@ pub async fn dns_updater_task(
);
let new_config: Arc<DnsConfig> = rx_dns_config.borrow().clone();
- for (k, v) in new_config.entries.iter() {
- if config.entries.get(k) != Some(v) {
- let fulldomain = format!("{}.{}", k.subdomain, k.domain);
- if !allowed_domains.iter().any(|d| fulldomain.ends_with(d)) {
+ for (key, value) in new_config.entries.iter() {
+ // Skip entries that haven't changed
+ if config.entries.get(key) == Some(value) {
+ continue;
+ }
+
+ // Skip entries for unallowed domains
+ if !allowed_domains.iter().any(|d| key.dns_path.ends_with(d)) {
+ error!(
+ domain = key.dns_path,
+ "domain/subdomain/hostname not in allowed list",
+ );
+ continue;
+ }
+
+ let provider = providers.iter().find(|p| key.dns_path.ends_with(&p.domain));
+
+ if let Some(provider) = provider {
+ if let Err(e) = update_dns_entry(key, value, provider).await {
error!(
- "Got an entry for domain {} which is not in allowed list",
- k.domain
+ record = key.to_string(),
+ target = value.to_string(),
+ error = e.to_string(),
+ "unable to update record"
);
- continue;
- }
-
- info!("Updating {} {}", k, v);
- if let Err(e) = update_dns_entry(k, v, provider.as_ref()).await {
- error!("Unable to update entry {} {}: {}", k, v, e);
}
+ } else {
+ error!(
+ domain = key.dns_path,
+ "no provider matches this domain/subdomain/hostname"
+ );
}
}
@@ -57,8 +85,22 @@ pub async fn dns_updater_task(
async fn update_dns_entry(
key: &DnsEntryKey,
value: &DnsEntryValue,
- provider: &dyn DnsProvider,
+ provider: &DomainProvider,
) -> Result<()> {
+ let subdomain = key
+ .dns_path
+ .strip_suffix(&provider.domain)
+ .unwrap()
+ .trim_end_matches('.');
+ info!(
+ record = key.to_string(),
+ target = value.to_string(),
+ domain = provider.domain,
+ subdomain = &subdomain,
+ provider = provider.provider.provider(),
+ "updating record"
+ );
+
if value.targets.is_empty() {
bail!("zero targets (internal error)");
}
@@ -73,7 +115,8 @@ async fn update_dns_entry(
);
}
provider
- .update_a(&key.domain, &key.subdomain, &targets)
+ .provider
+ .update_a(&provider.domain, &subdomain, &targets)
.await?;
}
DnsRecordType::AAAA => {
@@ -85,17 +128,24 @@ async fn update_dns_entry(
);
}
provider
- .update_aaaa(&key.domain, &key.subdomain, &targets)
+ .provider
+ .update_aaaa(&provider.domain, &subdomain, &targets)
.await?;
}
DnsRecordType::CNAME => {
let mut targets = value.targets.iter().cloned().collect::<Vec<_>>();
if targets.len() > 1 {
targets.sort();
- warn!("Several CNAME targets for {}: {:?}. Taking first one in alphabetical order. Consider switching to a single global target instead.", key, targets);
+ warn!(
+ record = key.to_string(),
+ all_targets = value.to_string(),
+ selected_target = targets[0],
+ "Several CNAME targets, taking first one in alphabetical order. Consider switching to a single global target instead."
+ );
}
provider
- .update_cname(&key.domain, &key.subdomain, &targets[0])
+ .provider
+ .update_cname(&provider.domain, &subdomain, &targets[0])
.await?;
}
}
diff --git a/src/main.rs b/src/main.rs
index 667c058..77c8b16 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -37,8 +37,8 @@ pub struct Opt {
pub consul_client_key: Option<String>,
/// DNS provider
- #[structopt(long = "provider", env = "D53_PROVIDER")]
- pub provider: String,
+ #[structopt(long = "providers", env = "D53_PROVIDERS")]
+ pub providers: String,
/// Allowed domains
#[structopt(long = "allowed-domains", env = "D53_ALLOWED_DOMAINS")]
@@ -49,6 +49,11 @@ pub struct Opt {
pub gandi_api_key: Option<String>,
}
+pub struct DomainProvider {
+ pub domain: String,
+ pub provider: Box<dyn provider::DnsProvider>,
+}
+
#[tokio::main]
async fn main() {
if std::env::var("RUST_LOG").is_err() {
@@ -81,12 +86,24 @@ async fn main() {
let consul = df_consul::Consul::new(consul_config, "").expect("Cannot build Consul");
- let provider: Box<dyn provider::DnsProvider> = match opt.provider.as_str() {
- "gandi" => Box::new(
- provider::gandi::GandiProvider::new(&opt).expect("Cannot initialize Gandi provier"),
- ),
- p => panic!("Unsupported DNS provider: {}", p),
- };
+ let mut domain_providers = vec![];
+ for pstr in opt.providers.as_str().split(',') {
+ let (domain, provider) = pstr.split_once(':')
+ .expect("Invalid provider syntax, expected: <domain_name>:<provider>[,<domain_name>:<provider>[,...]]");
+ let provider: Box<dyn provider::DnsProvider> = match provider {
+ "gandi" => Box::new(
+ provider::gandi::GandiProvider::new(&opt).expect("Cannot initialize Gandi provier"),
+ ),
+ p => panic!("Unsupported DNS provider: {}", p),
+ };
+ domain_providers.push(DomainProvider {
+ domain: domain.to_string(),
+ provider,
+ });
+ }
+ if domain_providers.is_empty() {
+ panic!("No domain providers were specified.");
+ }
let allowed_domains = opt
.allowed_domains
@@ -98,7 +115,7 @@ async fn main() {
let updater_task = tokio::spawn(dns_updater::dns_updater_task(
rx_dns_config.clone(),
- provider,
+ domain_providers,
allowed_domains,
exit_signal.clone(),
));
diff --git a/src/provider/gandi.rs b/src/provider/gandi.rs
index 85bf570..eeff641 100644
--- a/src/provider/gandi.rs
+++ b/src/provider/gandi.rs
@@ -4,7 +4,7 @@ use anyhow::{anyhow, Result};
use async_trait::async_trait;
use reqwest::header;
use serde::Serialize;
-use tracing::{info, warn};
+use tracing::*;
use crate::provider::DnsProvider;
use crate::Opt;
@@ -34,11 +34,15 @@ impl GandiProvider {
}
async fn put_rrset(&self, url: &str, rrset: &GandiRrset) -> Result<()> {
- info!("PUT {} with {:?}", url, rrset);
+ debug!(url = url, body = format!("{:?}", rrset), "PUT");
let http = self.client.put(url).json(rrset).send().await?;
if !http.status().is_success() {
- warn!("PUT {} returned {}", url, http.status());
+ warn!(
+ url = url,
+ http_status = http.status().to_string(),
+ "PUT returned error"
+ );
}
http.error_for_status()?;