1use crate::ssl::{acme::*, certificate::*};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::PathBuf;
5use std::sync::Arc;
6use tokio::sync::RwLock;
7use tokio::time::{interval, Duration};
8
9fn is_valid_domain(domain: &str) -> bool {
11 !domain.is_empty()
13 && domain.len() <= 253
14 && domain
15 .chars()
16 .all(|c| c.is_alphanumeric() || c == '.' || c == '-')
17 && !domain.starts_with('-')
18 && !domain.ends_with('-')
19 && !domain.starts_with('.')
20 && !domain.ends_with('.')
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct SslConfig {
25 pub enabled: bool,
26 pub auto_cert: bool,
27 pub cert_dir: String,
28 pub acme: Option<AcmeConfig>,
29 pub manual_certs: HashMap<String, ManualCertConfig>,
30 pub renewal_check_interval_hours: u64,
31 pub renewal_days_before_expiry: i64,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ManualCertConfig {
36 pub cert_file: String,
37 pub key_file: String,
38 pub auto_renew: bool,
39}
40
41impl Default for SslConfig {
42 fn default() -> Self {
43 Self {
44 enabled: false,
45 auto_cert: false,
46 cert_dir: "/etc/bws/certs".to_string(),
47 acme: None,
48 manual_certs: HashMap::new(),
49 renewal_check_interval_hours: 24, renewal_days_before_expiry: 30, }
52 }
53}
54
55#[derive(Debug)]
56pub struct SslManager {
57 config: SslConfig,
58 certificate_store: Arc<RwLock<CertificateStore>>,
59 acme_client: Option<Arc<RwLock<AcmeClient>>>,
60 tls_configs: Arc<RwLock<HashMap<String, rustls::ServerConfig>>>,
61}
62
63impl SslManager {
64 pub async fn new(config: SslConfig) -> Result<Self, Box<dyn std::error::Error>> {
65 ensure_certificate_directory(&config.cert_dir).await?;
67
68 let store_path = PathBuf::from(&config.cert_dir).join("certificates.toml");
70 let mut certificate_store = CertificateStore::new(store_path);
71 certificate_store.load().await?;
72
73 let acme_client = if config.auto_cert {
75 if let Some(acme_config) = &config.acme {
76 let client = AcmeClient::new(acme_config.clone());
77 Some(Arc::new(RwLock::new(client)))
78 } else {
79 return Err("ACME configuration required when auto_cert is enabled".into());
80 }
81 } else {
82 None
83 };
84
85 let manager = Self {
86 config,
87 certificate_store: Arc::new(RwLock::new(certificate_store)),
88 acme_client,
89 tls_configs: Arc::new(RwLock::new(HashMap::new())),
90 };
91
92 manager.load_certificates().await?;
94
95 Ok(manager)
96 }
97
98 pub async fn from_site_config(
100 site: &crate::config::site::SiteConfig,
101 ) -> Result<Option<Self>, Box<dyn std::error::Error>> {
102 if !site.ssl.enabled {
103 return Ok(None);
104 }
105
106 let cert_dir = site
108 .ssl
109 .cert_file
110 .as_ref()
111 .and_then(|path| std::path::Path::new(path).parent())
112 .map(|p| p.to_string_lossy().to_string())
113 .unwrap_or_else(|| {
114 if site.ssl.auto_cert {
115 "./certs".to_string() } else {
117 "/etc/bws/certs".to_string() }
119 });
120
121 let ssl_config = SslConfig {
122 enabled: site.ssl.enabled,
123 auto_cert: site.ssl.auto_cert,
124 cert_dir: cert_dir.clone(),
125 acme: site.ssl.acme.as_ref().map(|site_acme| AcmeConfig {
126 directory_url: if site_acme.staging {
127 "https://acme-staging-v02.api.letsencrypt.org/directory".to_string()
128 } else {
129 "https://acme-v02.api.letsencrypt.org/directory".to_string()
130 },
131 contact_email: site_acme.email.clone(),
132 terms_agreed: !site_acme.email.is_empty(), challenge_dir: site_acme.challenge_dir.clone().unwrap_or_else(|| {
134 format!("{cert_dir}/challenges")
136 }),
137 account_key_file: format!("{cert_dir}/acme-account.key"),
138 enabled: site_acme.enabled,
139 staging: site_acme.staging,
140 }),
141 manual_certs: {
142 let mut manual_certs = HashMap::new();
143 if let (Some(cert_file), Some(key_file)) = (&site.ssl.cert_file, &site.ssl.key_file)
144 {
145 manual_certs.insert(
146 site.hostname.clone(),
147 ManualCertConfig {
148 cert_file: cert_file.clone(),
149 key_file: key_file.clone(),
150 auto_renew: false,
151 },
152 );
153 }
154 manual_certs
155 },
156 renewal_check_interval_hours: 24,
157 renewal_days_before_expiry: 30,
158 };
159
160 let manager = Self::new(ssl_config).await?;
161 Ok(Some(manager))
162 }
163
164 pub async fn get_tls_config(&self, domain: &str) -> Option<rustls::ServerConfig> {
165 let configs = self.tls_configs.read().await;
166 configs.get(domain).cloned()
167 }
168
169 pub async fn ensure_certificate(
170 &self,
171 domain: &str,
172 ) -> Result<bool, Box<dyn std::error::Error>> {
173 {
175 let store = self.certificate_store.read().await;
176 if let Some(cert) = store.get_certificate(domain) {
177 if !cert.is_expired() && cert.validate_certificate_files().await.unwrap_or(false) {
178 log::info!("Valid certificate already exists for {domain}");
179 return Ok(true);
180 }
181 }
182 }
183
184 if self.config.auto_cert {
186 self.obtain_certificate_via_acme(domain).await
187 } else {
188 if let Some(manual_config) = self.config.manual_certs.get(domain) {
190 self.load_manual_certificate(domain, manual_config).await
191 } else {
192 log::warn!("No certificate configuration found for domain: {domain}");
193 Ok(false)
194 }
195 }
196 }
197
198 async fn obtain_certificate_via_acme(
199 &self,
200 domain: &str,
201 ) -> Result<bool, Box<dyn std::error::Error>> {
202 if !is_valid_domain(domain) {
203 return Err(format!("Invalid domain name: {domain}").into());
204 }
205
206 log::info!("Obtaining certificate via ACME for domain: {domain}");
207 log::info!("Domain passed to ACME client: '{domain}'");
208
209 if let Some(acme_client) = &self.acme_client {
210 let client = acme_client.write().await;
211 log::info!(
212 "Calling ACME client.obtain_certificate with domains: {:?}",
213 &[domain.to_string()]
214 );
215 let (cert_pem, key_pem): (String, String) = client
216 .obtain_certificate(&[domain.to_string()])
217 .await
218 .map_err(|e| {
219 log::error!("ACME obtain_certificate failed: {e}");
220 Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error>
221 })?;
222
223 log::info!(
224 "ACME certificate obtained successfully, cert size: {} bytes, key size: {} bytes",
225 cert_pem.len(),
226 key_pem.len()
227 );
228
229 let cert_path = get_certificate_path(domain, &self.config.cert_dir);
231 let key_path = get_key_path(domain, &self.config.cert_dir);
232
233 log::info!("Certificate will be saved to: {cert_path:?}");
234 log::info!("Private key will be saved to: {key_path:?}");
235
236 log::info!("Creating certificate object for domain: {domain}");
238 let certificate = Certificate::from_pem_data(
239 domain.to_string(),
240 cert_path.clone(),
241 key_path.clone(),
242 &cert_pem,
243 true, )?;
245
246 log::info!("Saving certificate and private key files...");
248 certificate.save_certificate(&cert_pem, &key_pem).await?;
249 log::info!("Certificate files saved successfully");
250
251 log::info!("Adding certificate to store...");
253 {
254 let mut store = self.certificate_store.write().await;
255 store.add_certificate(certificate);
256 store.save().await?;
257 }
258 log::info!("Certificate added to store successfully");
259
260 log::info!("Updating TLS configuration...");
262 self.update_tls_config(domain).await?;
263 log::info!("TLS configuration updated successfully");
264
265 log::info!(
266 "Successfully obtained and configured certificate for {}",
267 domain
268 );
269 Ok(true)
270 } else {
271 Err("ACME client not initialized".into())
272 }
273 }
274
275 async fn load_manual_certificate(
276 &self,
277 domain: &str,
278 manual_config: &ManualCertConfig,
279 ) -> Result<bool, Box<dyn std::error::Error>> {
280 log::info!("Loading manual certificate for domain: {domain}");
281
282 let cert_path = PathBuf::from(&manual_config.cert_file);
283 let key_path = PathBuf::from(&manual_config.key_file);
284
285 if !cert_path.exists() {
287 return Err(format!("Certificate file not found: {}", cert_path.display()).into());
288 }
289 if !key_path.exists() {
290 return Err(format!("Key file not found: {}", key_path.display()).into());
291 }
292
293 let certificate = Certificate::from_files(
295 domain.to_string(),
296 cert_path,
297 key_path,
298 manual_config.auto_renew,
299 )
300 .await?;
301
302 if !certificate.validate_certificate_files().await? {
304 return Err(format!("Invalid certificate files for domain: {domain}").into());
305 }
306
307 {
309 let mut store = self.certificate_store.write().await;
310 store.add_certificate(certificate);
311 store.save().await?;
312 }
313
314 self.update_tls_config(domain).await?;
316
317 log::info!("Successfully loaded manual certificate for {domain}");
318 Ok(true)
319 }
320
321 async fn update_tls_config(&self, domain: &str) -> Result<(), Box<dyn std::error::Error>> {
322 let store = self.certificate_store.read().await;
323 if let Some(certificate) = store.get_certificate(domain) {
324 let tls_config = certificate.get_rustls_config().await?;
325 let mut configs = self.tls_configs.write().await;
326 configs.insert(domain.to_string(), tls_config);
327 log::info!("Updated TLS configuration for {domain}");
328 }
329 Ok(())
330 }
331
332 async fn load_certificates(&self) -> Result<(), Box<dyn std::error::Error>> {
333 let certificates = {
334 let store = self.certificate_store.read().await;
335 store.list_certificates().to_vec()
336 };
337
338 for certificate in certificates {
339 if certificate
341 .validate_certificate_files()
342 .await
343 .unwrap_or(false)
344 {
345 self.update_tls_config(&certificate.domain).await?;
347 } else {
348 log::warn!(
349 "Certificate files invalid for domain: {}, will attempt renewal",
350 certificate.domain
351 );
352 }
353 }
354
355 let store = self.certificate_store.read().await;
356 let cert_count = store.list_certificates().len();
357 drop(store);
358
359 log::info!("Loaded certificates for {cert_count} domains");
360 Ok(())
361 }
362
363 pub async fn start_renewal_monitor(self: Arc<Self>) {
364 let renewal_interval = Duration::from_secs(self.config.renewal_check_interval_hours * 3600);
365 let mut interval_timer = interval(renewal_interval);
366
367 log::info!(
368 "Starting certificate renewal monitor (check every {} hours)",
369 self.config.renewal_check_interval_hours
370 );
371
372 loop {
373 interval_timer.tick().await;
374 if let Err(e) = self.check_and_renew_certificates().await {
375 log::error!("Error during certificate renewal check: {e}");
376 }
377 }
378 }
379
380 async fn check_and_renew_certificates(&self) -> Result<(), Box<dyn std::error::Error>> {
381 log::info!("Checking certificates for renewal");
382
383 let certificates_to_renew = {
384 let store = self.certificate_store.read().await;
385 store
386 .get_certificates_needing_renewal(self.config.renewal_days_before_expiry)
387 .into_iter()
388 .map(|cert| cert.domain.clone())
389 .collect::<Vec<_>>()
390 };
391
392 if certificates_to_renew.is_empty() {
393 log::info!("No certificates need renewal");
394 return Ok(());
395 }
396
397 log::info!(
398 "Found {} certificates that need renewal",
399 certificates_to_renew.len()
400 );
401
402 for domain in certificates_to_renew {
403 log::info!("Renewing certificate for domain: {domain}");
404
405 {
407 let mut store = self.certificate_store.write().await;
408 store.update_renewal_check(&domain);
409 store.save().await?;
410 }
411
412 match self.renew_certificate(&domain).await {
413 Ok(()) => {
414 log::info!("Successfully renewed certificate for {domain}");
415 }
416 Err(e) => {
417 log::error!("Failed to renew certificate for {domain}: {e}");
418 }
420 }
421 }
422
423 Ok(())
424 }
425
426 async fn renew_certificate(&self, domain: &str) -> Result<(), Box<dyn std::error::Error>> {
427 if self.config.auto_cert {
428 self.obtain_certificate_via_acme(domain).await?;
430 } else {
431 if let Some(manual_config) = self.config.manual_certs.get(domain) {
433 self.load_manual_certificate(domain, manual_config).await?;
434 } else {
435 return Err(format!("No renewal method available for domain: {domain}").into());
436 }
437 }
438
439 Ok(())
440 }
441
442 pub async fn remove_certificate(
443 &self,
444 domain: &str,
445 ) -> Result<bool, Box<dyn std::error::Error>> {
446 let removed = {
447 let mut store = self.certificate_store.write().await;
448 let removed = store.remove_certificate(domain);
449 if removed {
450 store.save().await?;
451 }
452 removed
453 };
454
455 if removed {
456 let mut configs = self.tls_configs.write().await;
458 configs.remove(domain);
459 log::info!("Removed certificate for domain: {domain}");
460 }
461
462 Ok(removed)
463 }
464
465 pub async fn list_certificates(&self) -> Vec<Certificate> {
466 let store = self.certificate_store.read().await;
467 store.list_certificates().to_vec()
468 }
469
470 pub async fn get_certificate_info(&self, domain: &str) -> Option<Certificate> {
471 let store = self.certificate_store.read().await;
472 store.get_certificate(domain).cloned()
473 }
474
475 pub fn is_ssl_enabled(&self) -> bool {
476 self.config.enabled
477 }
478
479 pub fn handles_acme_challenge(&self, path: &str) -> bool {
480 path.starts_with("/.well-known/acme-challenge/")
481 }
482
483 pub async fn get_acme_challenge_response(&self, token: &str) -> Option<String> {
484 if let Some(acme_client) = &self.acme_client {
485 let client = acme_client.read().await;
486 client.get_acme_challenge_response(token).await
487 } else {
488 None
489 }
490 }
491
492 pub fn is_auto_cert_enabled(&self) -> bool {
494 self.config.auto_cert
495 }
496
497 pub async fn has_certificate(&self, domain: &str) -> bool {
499 let store = self.certificate_store.read().await;
500 store.has_certificate(domain)
501 }
502
503 pub async fn get_rustls_config(
505 &self,
506 domain: &str,
507 ) -> Result<rustls::ServerConfig, Box<dyn std::error::Error + Send + Sync>> {
508 let store = self.certificate_store.read().await;
509
510 if let Some(certificate) = store.get_certificate(domain) {
511 match certificate.get_rustls_config().await {
512 Ok(config) => Ok(config),
513 Err(e) => Err(format!("Failed to create rustls config: {e}").into()),
514 }
515 } else {
516 Err(format!("No certificate found for domain: {domain}").into())
517 }
518 }
519
520 pub async fn get_managed_domains(&self) -> Vec<String> {
522 let store = self.certificate_store.read().await;
523 store.get_all_domains()
524 }
525
526 pub async fn get_certificate_expiry(
533 &self,
534 domain: &str,
535 ) -> Result<Option<chrono::DateTime<chrono::Utc>>, Box<dyn std::error::Error + Send + Sync>>
536 {
537 let store = self.certificate_store.read().await;
538 store.get_certificate_expiry(domain)
539 }
540
541 pub async fn renew_certificate_public(
548 &self,
549 domain: &str,
550 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
551 if let Some(acme_client) = &self.acme_client {
552 let (cert_pem, key_pem) = {
553 let client = acme_client.write().await;
554 client.obtain_certificate(&[domain.to_string()]).await?
555 };
556
557 let cert_dir = std::path::Path::new(&self.config.cert_dir);
559 tokio::fs::create_dir_all(cert_dir).await?;
560
561 let cert_path = cert_dir.join(format!("{domain}.crt"));
562 let key_path = cert_dir.join(format!("{domain}.key"));
563
564 tokio::fs::write(&cert_path, &cert_pem).await?;
565 tokio::fs::write(&key_path, &key_pem).await?;
566
567 match crate::ssl::certificate::Certificate::from_files(
569 domain.to_string(),
570 cert_path,
571 key_path,
572 true, )
574 .await
575 {
576 Ok(certificate) => {
577 let mut store = self.certificate_store.write().await;
578 store.add_certificate(certificate);
579 }
581 Err(e) => {
582 log::error!("Failed to create certificate object for {domain}: {e}");
583 return Err(format!("Failed to create certificate object: {e}").into());
584 }
585 }
586
587 log::info!("Successfully renewed certificate for domain: {domain}");
588 Ok(())
589 } else {
590 Err("ACME client not initialized".into())
591 }
592 }
593
594 pub async fn check_and_renew_certificate(
600 &self,
601 domain: &str,
602 ) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
603 let needs_renewal = {
605 let store = self.certificate_store.read().await;
606 store
607 .get_certificate(domain)
608 .is_none_or(|cert| cert.needs_renewal(self.config.renewal_days_before_expiry))
609 };
610
611 if needs_renewal {
612 log::info!("Certificate for {domain} needs renewal");
613 match self.renew_certificate(domain).await {
614 Ok(()) => {
615 log::info!("Successfully renewed certificate for {domain}");
616 Ok(true)
617 }
618 Err(e) => {
619 log::error!("Failed to renew certificate for {domain}: {e}");
620 Err(format!("Certificate renewal failed: {e}").into())
621 }
622 }
623 } else {
624 log::debug!("Certificate for {domain} is still valid");
625 Ok(false)
626 }
627 }
628}
629
630impl SslConfig {
632 pub fn validate(&self) -> Result<(), Box<dyn std::error::Error>> {
638 if !self.enabled {
639 return Ok(());
640 }
641
642 if self.auto_cert {
643 if self.acme.is_none() {
644 return Err("ACME configuration required when auto_cert is enabled".into());
645 }
646
647 if let Some(acme_config) = &self.acme {
648 if acme_config.contact_email.is_empty() {
649 return Err("ACME email is required".into());
650 }
651 if !acme_config.terms_agreed {
652 return Err("ACME terms of service must be agreed to".into());
653 }
654 }
655 }
656
657 for (domain, manual_config) in &self.manual_certs {
658 if !is_valid_domain(domain) {
659 return Err(format!("Invalid domain name in manual_certs: {domain}").into());
660 }
661
662 if manual_config.cert_file.is_empty() {
663 return Err(format!("Certificate file path required for domain: {domain}").into());
664 }
665
666 if manual_config.key_file.is_empty() {
667 return Err(format!("Key file path required for domain: {domain}").into());
668 }
669 }
670
671 if self.renewal_check_interval_hours == 0 {
672 return Err("Renewal check interval must be greater than 0".into());
673 }
674
675 if self.renewal_days_before_expiry < 1 {
676 return Err("Renewal days before expiry must be at least 1".into());
677 }
678
679 Ok(())
680 }
681}
682
683#[cfg(test)]
684mod tests {
685 use super::*;
686
687 #[test]
688 fn test_ssl_config_validation() {
689 let mut config = SslConfig::default();
690 assert!(config.validate().is_ok()); config.enabled = true;
693 config.auto_cert = true;
694 assert!(config.validate().is_err()); config.acme = Some(AcmeConfig::default());
697 assert!(config.validate().is_err()); config.acme.as_mut().unwrap().contact_email = "test@example.com".to_string();
700 assert!(config.validate().is_err()); config.acme.as_mut().unwrap().terms_agreed = true;
703 assert!(config.validate().is_ok()); }
705
706 #[test]
707 fn test_manual_cert_validation() {
708 let mut config = SslConfig {
709 enabled: true,
710 auto_cert: false,
711 ..Default::default()
712 };
713
714 config.manual_certs.insert(
716 "".to_string(),
717 ManualCertConfig {
718 cert_file: "cert.pem".to_string(),
719 key_file: "key.pem".to_string(),
720 auto_renew: false,
721 },
722 );
723 assert!(config.validate().is_err());
724
725 config.manual_certs.clear();
727 config.manual_certs.insert(
728 "example.com".to_string(),
729 ManualCertConfig {
730 cert_file: "".to_string(),
731 key_file: "key.pem".to_string(),
732 auto_renew: false,
733 },
734 );
735 assert!(config.validate().is_err());
736
737 config
739 .manual_certs
740 .get_mut("example.com")
741 .unwrap()
742 .cert_file = "cert.pem".to_string();
743 config.manual_certs.get_mut("example.com").unwrap().key_file = "".to_string();
744 assert!(config.validate().is_err());
745
746 config.manual_certs.get_mut("example.com").unwrap().key_file = "key.pem".to_string();
748 assert!(config.validate().is_ok());
749 }
750}