bws_web_server/ssl/
manager.rs

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
9// Utility function to validate domain names
10fn is_valid_domain(domain: &str) -> bool {
11    // Basic domain validation
12    !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, // Check daily
50            renewal_days_before_expiry: 30,   // Renew 30 days before expiry
51        }
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 exists
66        ensure_certificate_directory(&config.cert_dir).await?;
67
68        // Initialize certificate store
69        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        // Initialize ACME client if auto_cert is enabled
74        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        // Load existing certificates
93        manager.load_certificates().await?;
94
95        Ok(manager)
96    }
97
98    /// Create SSL manager from site configuration
99    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        // Determine cert_dir first
107        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() // Use local directory for ACME auto-certificates
116                } else {
117                    "/etc/bws/certs".to_string() // Use system directory for manual certificates
118                }
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(), // Auto-agree if email is provided
133                challenge_dir: site_acme.challenge_dir.clone().unwrap_or_else(|| {
134                    // Auto-generate challenge directory based on cert_dir
135                    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        // Check if we already have a valid certificate
174        {
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        // Try to obtain certificate
185        if self.config.auto_cert {
186            self.obtain_certificate_via_acme(domain).await
187        } else {
188            // Check manual certificate configuration
189            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            // Create certificate paths
230            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            // Create certificate object
237            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, // auto_renew enabled for ACME certificates
244            )?;
245
246            // Save certificate files
247            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            // Add to store
252            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            // Update TLS config
261            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        // Validate certificate files exist and are readable
286        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        // Create certificate object
294        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        // Validate certificate files
303        if !certificate.validate_certificate_files().await? {
304            return Err(format!("Invalid certificate files for domain: {domain}").into());
305        }
306
307        // Add to store
308        {
309            let mut store = self.certificate_store.write().await;
310            store.add_certificate(certificate);
311            store.save().await?;
312        }
313
314        // Update TLS config
315        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            // Validate certificate files
340            if certificate
341                .validate_certificate_files()
342                .await
343                .unwrap_or(false)
344            {
345                // Update TLS config for valid certificates
346                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            // Update renewal check timestamp
406            {
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                    // Continue with other certificates even if one fails
419                }
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            // For ACME certificates, obtain a new certificate
429            self.obtain_certificate_via_acme(domain).await?;
430        } else {
431            // For manual certificates, reload from files (in case they were updated)
432            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            // Remove from TLS configs
457            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    /// Check if auto-cert is enabled for this SSL manager
493    pub fn is_auto_cert_enabled(&self) -> bool {
494        self.config.auto_cert
495    }
496
497    /// Check if a certificate is available for a domain
498    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    /// Get TLS configuration for a domain (Result version for dynamic TLS)
504    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    /// Get list of domains managed by this SSL manager
521    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    /// Get certificate expiry date for a domain
527    /// Get certificate expiry date for a domain
528    ///
529    /// # Errors
530    ///
531    /// Returns an error if the certificate cannot be parsed or accessed.
532    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    /// Renew certificate for a domain
542    /// Renew certificate for a specific domain (public method)
543    ///
544    /// # Errors
545    ///
546    /// Returns an error if certificate renewal fails or if ACME client is not initialized.
547    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            // Save certificate files
558            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            // Create certificate object and add to store
568            match crate::ssl::certificate::Certificate::from_files(
569                domain.to_string(),
570                cert_path,
571                key_path,
572                true, // auto_renew = true
573            )
574            .await
575            {
576                Ok(certificate) => {
577                    let mut store = self.certificate_store.write().await;
578                    store.add_certificate(certificate);
579                    // Note: Save functionality will be added later if needed
580                }
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    /// Check and renew certificate for a specific domain (public method)
595    ///
596    /// # Errors
597    ///
598    /// Returns an error if certificate renewal fails or if ACME client is not initialized.
599    pub async fn check_and_renew_certificate(
600        &self,
601        domain: &str,
602    ) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
603        // Check if certificate needs renewal
604        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
630// Configuration validation
631impl SslConfig {
632    /// Validate SSL manager configuration
633    ///
634    /// # Errors
635    ///
636    /// Returns an error if the configuration is invalid.
637    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()); // Disabled SSL should be valid
691
692        config.enabled = true;
693        config.auto_cert = true;
694        assert!(config.validate().is_err()); // Missing ACME config
695
696        config.acme = Some(AcmeConfig::default());
697        assert!(config.validate().is_err()); // Empty email
698
699        config.acme.as_mut().unwrap().contact_email = "test@example.com".to_string();
700        assert!(config.validate().is_err()); // Terms not agreed
701
702        config.acme.as_mut().unwrap().terms_agreed = true;
703        assert!(config.validate().is_ok()); // Should be valid now
704    }
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        // Add invalid domain
715        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        // Fix domain but empty cert file
726        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        // Fix cert file but empty key file
738        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        // Fix key file - should be valid
747        config.manual_certs.get_mut("example.com").unwrap().key_file = "key.pem".to_string();
748        assert!(config.validate().is_ok());
749    }
750}