OVH DNS update support

This commit is contained in:
mdecimus 2025-07-27 17:44:28 +02:00
parent 285d1cc90e
commit 51a0a1445d
5 changed files with 82 additions and 64 deletions

View file

@ -2,12 +2,13 @@
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
## [0.13.2] - 2025-07-27 ## [0.13.2] - 2025-07-28
If you are upgrading from v0.11.x or v0.12.x, this version includes **breaking changes** to the message queue and MTA configuration. Please read the [UPGRADING.md](https://github.com/stalwartlabs/stalwart/blob/main/UPGRADING.md) file for more information on how to upgrade from previous versions. If you are upgrading from v0.11.x or v0.12.x, this version includes **breaking changes** to the message queue and MTA configuration. Please read the [UPGRADING.md](https://github.com/stalwartlabs/stalwart/blob/main/UPGRADING.md) file for more information on how to upgrade from previous versions.
## Added ## Added
- ACME: DeSEC cloud DNS provider support (contributed by @Tyr3al). - ACME: DeSEC cloud DNS provider support (contributed by @Tyr3al).
- ACME: OVH cloud DNS provider support (contributed by @srachner).
- CalDAV Scheduling: Catalan language support (contributed by @jolupa) (#1873). - CalDAV Scheduling: Catalan language support (contributed by @jolupa) (#1873).
- MTA: Allow to send e-mails as group, while member of that group (#485). - MTA: Allow to send e-mails as group, while member of that group (#485).
- OIDC: Allow local access tokens to be used with third-party OIDC backends (#1311 stalwartlabs/webadmin#52). - OIDC: Allow local access tokens to be used with third-party OIDC backends (#1311 stalwartlabs/webadmin#52).

5
Cargo.lock generated
View file

@ -2051,15 +2051,16 @@ dependencies = [
[[package]] [[package]]
name = "dns-update" name = "dns-update"
version = "0.1.4" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41dbb7b41e755c6d9f9a62d3861e84fd33860b796d93aea6cfc8ef1068f4bff6" checksum = "42788b21a1231c646c46508c406db9bf628342a781c24888bf60e1a29396deb2"
dependencies = [ dependencies = [
"hickory-client", "hickory-client",
"reqwest 0.12.15", "reqwest 0.12.15",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"sha1",
"tokio", "tokio",
] ]

View file

@ -19,7 +19,7 @@ mail-builder = { version = "0.4" }
mail-auth = { version = "0.7.1" } mail-auth = { version = "0.7.1" }
mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] }
smtp-proto = { version = "0.1", features = ["rkyv"] } smtp-proto = { version = "0.1", features = ["rkyv"] }
dns-update = { version = "0.1.4" } dns-update = { version = "0.1.5" }
calcard = { version = "0.1.3", features = ["rkyv"] } calcard = { version = "0.1.3", features = ["rkyv"] }
ahash = { version = "0.8.2", features = ["serde"] } ahash = { version = "0.8.2", features = ["serde"] }
parking_lot = "0.12.1" parking_lot = "0.12.1"

View file

@ -186,6 +186,10 @@ impl AcmeProviders {
#[allow(clippy::unnecessary_to_owned)] #[allow(clippy::unnecessary_to_owned)]
fn build_dns_updater(config: &mut Config, acme_id: &str) -> Option<DnsUpdater> { fn build_dns_updater(config: &mut Config, acme_id: &str) -> Option<DnsUpdater> {
let timeout = config
.property_or_default(("acme", acme_id, "timeout"), "30s")
.unwrap_or_else(|| Duration::from_secs(30));
match config.value_require(("acme", acme_id, "provider"))? { match config.value_require(("acme", acme_id, "provider"))? {
"rfc2136-tsig" => { "rfc2136-tsig" => {
let algorithm: TsigAlgorithm = config let algorithm: TsigAlgorithm = config
@ -231,67 +235,79 @@ fn build_dns_updater(config: &mut Config, acme_id: &str) -> Option<DnsUpdater> {
}) })
.ok() .ok()
} }
"cloudflare" => { "cloudflare" => DnsUpdater::new_cloudflare(
let timeout = config config
.property_or_default(("acme", acme_id, "timeout"), "30s") .value_require(("acme", acme_id, "secret"))?
.unwrap_or_else(|| Duration::from_secs(30)); .trim()
.to_string(),
DnsUpdater::new_cloudflare( config.value(("acme", acme_id, "user")).map(|s| s.trim()),
config timeout.into(),
.value_require(("acme", acme_id, "secret"))? )
.trim() .map_err(|err| {
.to_string(), config.new_build_error(
config.value(("acme", acme_id, "user")).map(|s| s.trim()), ("acme", acme_id, "provider"),
timeout.into(), format!("Failed to create Cloudflare DNS updater: {err}"),
) )
.map_err(|err| { })
config.new_build_error( .ok(),
("acme", acme_id, "provider"), "digitalocean" => DnsUpdater::new_digitalocean(
format!("Failed to create Cloudflare DNS updater: {err}"), config
) .value_require(("acme", acme_id, "secret"))?
}) .trim()
.ok() .to_string(),
} timeout.into(),
"digitalocean" => { )
let timeout = config .map_err(|err| {
.property_or_default(("acme", acme_id, "timeout"), "30s") config.new_build_error(
.unwrap_or_else(|| Duration::from_secs(30)); ("acme", acme_id, "provider"),
format!("Failed to create DigitalOcean DNS updater: {err}"),
DnsUpdater::new_digitalocean(
config
.value_require(("acme", acme_id, "secret"))?
.trim()
.to_string(),
timeout.into(),
) )
.map_err(|err| { })
config.new_build_error( .ok(),
("acme", acme_id, "provider"), "desec" => DnsUpdater::new_desec(
format!("Failed to create DigitalOcean DNS updater: {err}"), config
) .value_require(("acme", acme_id, "secret"))?
}) .trim()
.ok() .to_string(),
} timeout.into(),
"desec" => { )
let timeout = config .map_err(|err| {
.property_or_default(("acme", acme_id, "timeout"), "30s") config.new_build_error(
.unwrap_or_else(|| Duration::from_secs(30)); ("acme", acme_id, "provider"),
format!("Failed to create Desec DNS updater: {err}"),
DnsUpdater::new_desec(
config
.value_require(("acme", acme_id, "secret"))?
.trim()
.to_string(),
timeout.into(),
) )
.map_err(|err| { })
config.new_build_error( .ok(),
("acme", acme_id, "provider"), "ovh" => DnsUpdater::new_ovh(
format!("Failed to create Desec DNS updater: {err}"), config
) .value_require(("acme", acme_id, "key"))
}) .map(|s| s.trim())?
.ok() .to_string(),
} config
.value_require(("acme", acme_id, "secret"))?
.trim()
.to_string(),
config
.value_require(("acme", acme_id, "consumer-key"))?
.trim()
.to_string(),
config
.value_require(("acme", acme_id, "ovh-endpoint"))?
.parse()
.map_err(|_| {
config
.new_parse_error(("acme", acme_id, "ovh-endpoint"), "Invalid OVH endpoint")
})
.ok()?,
timeout.into(),
)
.map_err(|err| {
config.new_build_error(
("acme", acme_id, "provider"),
format!("Failed to create Desec DNS updater: {err}"),
)
})
.ok(),
_ => { _ => {
config.new_parse_error(("acme", acme_id, "provider"), "Unsupported provider"); config.new_parse_error(("acme", acme_id, "provider"), "Unsupported provider");
None None

View file

@ -3,7 +3,7 @@
use chrono::{DateTime, TimeZone, Utc}; use chrono::{DateTime, TimeZone, Utc};
use compact_str::CompactString; use compact_str::CompactString;
use dns_update::DnsRecord; use dns_update::{DnsRecord, DnsRecordType};
use futures::future::try_join_all; use futures::future::try_join_all;
use rcgen::{CertificateParams, DistinguishedName, PKCS_ECDSA_P256_SHA256}; use rcgen::{CertificateParams, DistinguishedName, PKCS_ECDSA_P256_SHA256};
use rustls::crypto::ring::sign::any_ecdsa_type; use rustls::crypto::ring::sign::any_ecdsa_type;
@ -252,7 +252,7 @@ impl Server {
.to_string(); .to_string();
// First try deleting the record // First try deleting the record
if let Err(err) = updater.delete(&name, &origin).await { if let Err(err) = updater.delete(&name, &origin, DnsRecordType::TXT).await {
// Errors are expected if the record does not exist // Errors are expected if the record does not exist
trc::event!( trc::event!(
Acme(AcmeEvent::DnsRecordDeletionFailed), Acme(AcmeEvent::DnsRecordDeletionFailed),