Files
Stalwart/tests/src/automation/dns.rs
Maurus Decimus 3ad4aa82cb v0.16.0
2026-04-20 17:50:58 +02:00

201 lines
7.7 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::utils::server::TestServer;
use common::network::dns::update::DNS_RECORDS;
use dns_update::{CAARecord, bind::BindSerializer};
use registry::{
schema::{
enums::{AcmeChallengeType, DnsRecordType},
prelude::{ObjectType, Property},
structs::{
AcmeProvider, CertificateManagement, CertificateManagementProperties, DkimManagement,
DkimManagementProperties, DnsManagement, DnsManagementProperties, DnsServer,
DnsServerCloudflare, SecretKey, SecretKeyValue,
},
},
types::map::Map,
};
use serde_json::json;
const EXPECTED_ZONE: &str = r#"example.org. IN CAA 0 iodef "mailto:postmaster@example.org"
example.org. IN CAA 0 issue "pebble.letsencrypt.org"
autoconfig.example.org. IN CNAME mail.example.org.
autodiscover.example.org. IN CNAME mail.example.org.
mta-sts.example.org. IN CNAME mail.example.org.
ua-auto-config.example.org. IN CNAME mail.example.org.
example.org. IN MX 10 mx1.example.org.
example.org. IN MX 20 mx2.example.org.
_caldavs._tcp.example.org. IN SRV 0 1 443 mail.example.org.
_carddavs._tcp.example.org. IN SRV 0 1 443 mail.example.org.
_imaps._tcp.example.org. IN SRV 0 1 993 imap.example.org.
_jmap._tcp.example.org. IN SRV 0 1 443 mail.example.org.
_pop3s._tcp.example.org. IN SRV 0 1 995 pop3.example.org.
_submissions._tcp.example.org. IN SRV 0 1 465 smtp.example.org.
_25._tcp.mx1.example.org. IN TLSA 3 1 1
_25._tcp.mx1.example.org. IN TLSA 2 1 1
_25._tcp.mx2.example.org. IN TLSA 3 1 1
_25._tcp.mx2.example.org. IN TLSA 2 1 1
_443._tcp.autoconfig.example.org. IN TLSA 3 1 1
_443._tcp.autoconfig.example.org. IN TLSA 2 1 1
_443._tcp.autodiscover.example.org. IN TLSA 3 1 1
_443._tcp.autodiscover.example.org. IN TLSA 2 1 1
_443._tcp.mail.example.org. IN TLSA 3 1 1
_443._tcp.mail.example.org. IN TLSA 2 1 1
_443._tcp.mta-sts.example.org. IN TLSA 3 1 1
_443._tcp.mta-sts.example.org. IN TLSA 2 1 1
_443._tcp.ua-auto-config.example.org. IN TLSA 3 1 1
_443._tcp.ua-auto-config.example.org. IN TLSA 2 1 1
_465._tcp.smtp.example.org. IN TLSA 3 1 1
_465._tcp.smtp.example.org. IN TLSA 2 1 1
_993._tcp.imap.example.org. IN TLSA 3 1 1
_993._tcp.imap.example.org. IN TLSA 2 1 1
_995._tcp.pop3.example.org. IN TLSA 3 1 1
_995._tcp.pop3.example.org. IN TLSA 2 1 1
_dmarc.example.org. IN TXT "v=DMARC1; p=reject; rua=mailto:postmaster@example.org"
_mta-sts.example.org. IN TXT "v=STSv1; id=12942536112359691423"
_smtp._tls.example.org. IN TXT "v=TLSRPTv1; rua=mailto:postmaster@example.org"
_ua-auto-config.example.org. IN TXT "v=UAAC1; a=sha256; d=JJ/h5yIbCwPiASVbENZzCyyzeV/AiFCoBbiH2iTISWs="
_validation-persist.example.org. IN TXT "pebble.letsencrypt.org; accounturi=REDACTED"
dummy-v1-ed25519._domainkey.example.org. IN TXT "v=DKIM1; k=ed25519; h=sha256; p=REDACTED"
dummy-v1-rsa._domainkey.example.org. IN TXT "v=DKIM1; k=rsa; h=sha256; p=REDACTED"
example.org. IN TXT "v=spf1 mx -all"
mx1.example.org. IN TXT "v=spf1 a -all"
mx2.example.org. IN TXT "v=spf1 a -all"
"#;
pub async fn test(test: &TestServer) {
println!("Running DNS Management tests...");
let account = test.account("admin@example.org");
DNS_RECORDS.lock().unwrap().clear();
let domain_id = account.find_or_create_domain("example.org").await;
account
.registry_update_object(
ObjectType::Domain,
domain_id,
json!({
Property::CertificateManagement: CertificateManagement::Manual,
Property::DnsManagement: DnsManagement::Manual,
Property::DkimManagement: DkimManagement::Manual,
}),
)
.await;
// Create test In Memory DNS servers
let dns_server_id = account
.registry_create_object(DnsServer::Cloudflare(DnsServerCloudflare {
email: "test@memory.org".to_string().into(),
secret: SecretKey::Value(SecretKeyValue {
secret: "secret".into(),
}),
description: "In-memory DNS server".to_string(),
..Default::default()
}))
.await;
let acme_provider_id = account
.registry_create_object(AcmeProvider {
directory: "https://localhost:14000/dir".to_string(),
contact: Map::new(vec!["mailto:hello@example.org".to_string()]),
challenge_type: AcmeChallengeType::TlsAlpn01,
..Default::default()
})
.await;
let cert = CertificateManagement::Automatic(CertificateManagementProperties {
acme_provider_id,
subject_alternative_names: Default::default(),
});
let dns = DnsManagement::Automatic(DnsManagementProperties {
dns_server_id,
publish_records: Map::new(vec![
DnsRecordType::Dkim,
DnsRecordType::Tlsa,
DnsRecordType::Spf,
DnsRecordType::Mx,
DnsRecordType::Dmarc,
DnsRecordType::Srv,
DnsRecordType::MtaSts,
DnsRecordType::TlsRpt,
DnsRecordType::Caa,
DnsRecordType::AutoConfig,
DnsRecordType::AutoConfigLegacy,
DnsRecordType::AutoDiscover,
]),
..Default::default()
});
let dkim = DkimManagement::Automatic(DkimManagementProperties {
selector_template: "dummy-v{version}-{algorithm}-{epoch}".to_string(),
..Default::default()
});
account
.registry_update_object(
ObjectType::Domain,
domain_id,
json!({
Property::CertificateManagement: cert,
Property::DnsManagement: dns,
Property::DkimManagement: dkim,
}),
)
.await;
test.wait_for_tasks_skip_not_due().await;
let mut records = DNS_RECORDS
.lock()
.unwrap()
.iter()
.map(|r| {
let mut r = r.clone();
if r.name.starts_with("dummy-") {
let (selector, domain) = r.name.split_once("._domainkey.").unwrap();
let selector = selector.rsplit_once('-').unwrap().0;
r.name = format!("{selector}._domainkey.{domain}");
}
match &mut r.record {
dns_update::DnsRecord::TXT(r) => {
if r.starts_with("v=DKIM1;") {
*r = r.split_once("; p=").unwrap().0.to_string() + "; p=REDACTED";
} else if r.contains("letsencrypt.org") {
*r = r.split_once("; accounturi=").unwrap().0.to_string()
+ "; accounturi=REDACTED";
}
}
dns_update::DnsRecord::TLSA(r) => {
r.cert_data.clear();
}
dns_update::DnsRecord::CAA(CAARecord::Issue { options, .. }) => {
options.clear();
}
_ => (),
}
r
})
.collect::<Vec<_>>();
records.sort_unstable_by_key(|r| format!("{:?}-{}-{:?}", r.record.as_type(), r.name, r.record));
assert_eq!(BindSerializer::serialize(&records), EXPECTED_ZONE);
// Cleanup
account
.registry_update_object(
ObjectType::Domain,
domain_id,
json!({
Property::CertificateManagement: CertificateManagement::Manual,
Property::DnsManagement: DnsManagement::Manual,
Property::DkimManagement: DkimManagement::Manual,
}),
)
.await;
account.registry_destroy_all(ObjectType::DnsServer).await;
account.registry_destroy_all(ObjectType::AcmeProvider).await;
account.registry_destroy_all(ObjectType::Certificate).await;
account.registry_destroy_all(ObjectType::Task).await;
}