use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use certon::{
AcmeIssuer, CertResolver, Config, Dns01Solver, DnsProvider, FileStorage, KeyType,
LETS_ENCRYPT_STAGING, Result, Storage,
};
use tokio::sync::RwLock;
struct ExampleDnsProvider {
records: RwLock<HashMap<(String, String), Vec<String>>>,
}
impl ExampleDnsProvider {
fn new() -> Self {
Self {
records: RwLock::new(HashMap::new()),
}
}
}
#[async_trait]
impl DnsProvider for ExampleDnsProvider {
async fn set_record(&self, zone: &str, name: &str, value: &str, ttl: u32) -> Result<()> {
println!(
"[DNS] Creating TXT record: zone={}, name={}, value={}, ttl={}",
zone, name, value, ttl
);
let mut records = self.records.write().await;
records
.entry((zone.to_string(), name.to_string()))
.or_default()
.push(value.to_string());
Ok(())
}
async fn delete_record(&self, zone: &str, name: &str, value: &str) -> Result<()> {
println!(
"[DNS] Deleting TXT record: zone={}, name={}, value={}",
zone, name, value
);
let mut records = self.records.write().await;
if let Some(values) = records.get_mut(&(zone.to_string(), name.to_string())) {
values.retain(|v| v != value);
if values.is_empty() {
records.remove(&(zone.to_string(), name.to_string()));
}
}
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let storage: Arc<dyn Storage> = Arc::new(FileStorage::new("/tmp/certon-dns01"));
let dns_provider = ExampleDnsProvider::new();
let dns_solver = Dns01Solver::with_timeouts(
Box::new(dns_provider),
Duration::from_secs(120), Duration::from_secs(4), );
let issuer = AcmeIssuer::builder()
.ca(LETS_ENCRYPT_STAGING)
.email("admin@example.com")
.agreed(true)
.storage(storage.clone())
.dns01_solver(Arc::new(dns_solver))
.disable_http_challenge(true)
.disable_tlsalpn_challenge(true)
.build();
let config = Config::builder()
.storage(storage)
.issuers(vec![Arc::new(issuer)])
.key_type(KeyType::EcdsaP256)
.build();
let domains = vec!["*.example.com".into(), "example.com".into()];
println!(
"Obtaining wildcard certificate for {:?} via DNS-01...",
domains
);
config.manage_sync(&domains).await?;
println!("Certificate obtained successfully!");
let resolver = CertResolver::new(config.cache.clone());
let _tls_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_cert_resolver(Arc::new(resolver));
println!("TLS config ready for wildcard domain");
let _maintenance = certon::start_maintenance(&config);
tokio::signal::ctrl_c().await.ok();
config.cache.stop();
Ok(())
}