postrust_proxy/tls/
acme.rs1use crate::config::AcmeConfig;
4use crate::error::{ProxyError, ProxyResult};
5use crate::tls::CertificateStore;
6use rustls_acme::caches::DirCache;
7use rustls_acme::AcmeConfig as RustlsAcmeConfig;
8use std::path::Path;
9use std::sync::Arc;
10use tokio_rustls::rustls::ServerConfig;
11use tokio_util::sync::CancellationToken;
12use tracing::{error, info};
13
14pub struct AcmeManager {
16 config: AcmeConfig,
18 cert_store: Arc<CertificateStore>,
20 cache_dir: std::path::PathBuf,
22}
23
24impl AcmeManager {
25 pub fn new(
27 config: AcmeConfig,
28 cert_store: Arc<CertificateStore>,
29 cache_dir: impl AsRef<Path>,
30 ) -> Self {
31 Self {
32 config,
33 cert_store,
34 cache_dir: cache_dir.as_ref().to_path_buf(),
35 }
36 }
37
38 pub fn create_resolver(&self, domains: Vec<String>) -> ProxyResult<Arc<ServerConfig>> {
43 if !self.config.enabled {
44 return Err(ProxyError::Tls("ACME is not enabled".into()));
45 }
46
47 let directory = if self.config.staging {
48 "https://acme-staging-v02.api.letsencrypt.org/directory"
49 } else {
50 "https://acme-v02.api.letsencrypt.org/directory"
51 };
52
53 let contacts: Vec<String> = self
54 .config
55 .email
56 .as_ref()
57 .map(|email| vec![format!("mailto:{}", email)])
58 .unwrap_or_default();
59
60 info!("Setting up ACME for domains: {:?}", domains);
61
62 let cache_dir = self.cache_dir.clone();
64 let cache = DirCache::new(cache_dir);
65
66 let state = RustlsAcmeConfig::new(domains)
67 .contact(contacts)
68 .cache(cache)
69 .directory(directory)
70 .state();
71
72 let resolver = state.resolver();
74
75 let server_config = ServerConfig::builder()
77 .with_no_client_auth()
78 .with_cert_resolver(resolver);
79
80 Ok(Arc::new(server_config))
81 }
82
83 pub fn build_server_config_from_pem(
85 cert_pem: &[u8],
86 key_pem: &[u8],
87 ) -> ProxyResult<Arc<ServerConfig>> {
88 use rustls_pemfile::{certs, private_key};
89 use std::io::BufReader;
90
91 let certs: Vec<_> = certs(&mut BufReader::new(cert_pem))
93 .filter_map(|r| r.ok())
94 .collect();
95
96 if certs.is_empty() {
97 return Err(ProxyError::Tls("No certificates found in PEM".into()));
98 }
99
100 let key = private_key(&mut BufReader::new(key_pem))
102 .map_err(|e| ProxyError::Tls(format!("Failed to parse private key: {}", e)))?
103 .ok_or_else(|| ProxyError::Tls("No private key found in PEM".into()))?;
104
105 let config = ServerConfig::builder()
107 .with_no_client_auth()
108 .with_single_cert(certs, key)
109 .map_err(|e| ProxyError::Tls(format!("Failed to build TLS config: {}", e)))?;
110
111 Ok(Arc::new(config))
112 }
113
114 pub async fn start_renewal_task(self: Arc<Self>, cancel_token: CancellationToken) {
116 let check_interval = std::time::Duration::from_secs(86400); info!("ACME certificate renewal task started");
119
120 loop {
121 tokio::select! {
122 _ = cancel_token.cancelled() => {
123 info!("ACME renewal task stopped");
124 break;
125 }
126 _ = tokio::time::sleep(check_interval) => {
127 if let Err(e) = self.check_renewals().await {
128 error!("Certificate renewal check failed: {}", e);
129 }
130 }
131 }
132 }
133 }
134
135 async fn check_renewals(&self) -> ProxyResult<()> {
137 let domains = self.cert_store.list_domains().await?;
138 let now = chrono::Utc::now();
139 let renewal_threshold = chrono::Duration::days(30);
140
141 for domain in domains {
142 if let Some(cert) = self.cert_store.get(&domain).await {
143 if let Some(expires_at) = cert.expires_at {
144 if expires_at < now + renewal_threshold {
145 info!(
146 "Certificate for {} needs renewal (expires {})",
147 domain, expires_at
148 );
149 }
152 }
153 }
154 }
155
156 Ok(())
157 }
158
159 pub fn is_enabled(&self) -> bool {
161 self.config.enabled
162 }
163
164 pub fn domains(&self) -> &[String] {
166 &self.config.domains
167 }
168}