package ssl import ( "context" "crypto/x509" "fmt" "sync" "time" "go.uber.org/zap" ) // CertificateManager gère les certificats SSL automatiquement type CertificateManager struct { logger *zap.Logger config CertificateConfig certStore map[string]*Certificate providers map[string]CertificateProvider monitor *CertificateMonitor scheduler *RenewalScheduler mu sync.RWMutex isRunning bool } // CertificateConfig configuration des certificats type CertificateConfig struct { Enabled bool `yaml:"enabled"` AutoRenewal bool `yaml:"auto_renewal"` RenewalThreshold time.Duration `yaml:"renewal_threshold"` // 30 jours par défaut CheckInterval time.Duration `yaml:"check_interval"` // 6 heures par défaut Provider string `yaml:"provider"` // "letsencrypt", "self-signed", "manual" EmailNotifications bool `yaml:"email_notifications"` SlackNotifications bool `yaml:"slack_notifications"` StoreType string `yaml:"store_type"` // "filesystem", "kubernetes", "vault" StorePath string `yaml:"store_path"` BackupEnabled bool `yaml:"backup_enabled"` BackupPath string `yaml:"backup_path"` Domains []DomainConfig `yaml:"domains"` } // DomainConfig configuration par domaine type DomainConfig struct { Domain string `yaml:"domain"` Aliases []string `yaml:"aliases"` CertificatePath string `yaml:"certificate_path"` PrivateKeyPath string `yaml:"private_key_path"` AutoRenew bool `yaml:"auto_renew"` Provider string `yaml:"provider"` Contact string `yaml:"contact"` } // Certificate représente un certificat SSL type Certificate struct { ID string `json:"id"` Domain string `json:"domain"` Aliases []string `json:"aliases"` Provider string `json:"provider"` Certificate *x509.Certificate `json:"-"` PrivateKey interface{} `json:"-"` PEMData []byte `json:"-"` KeyData []byte `json:"-"` Status CertificateStatus `json:"status"` IssuedAt time.Time `json:"issued_at"` ExpiresAt time.Time `json:"expires_at"` LastChecked time.Time `json:"last_checked"` AutoRenew bool `json:"auto_renew"` Contact string `json:"contact"` Metadata map[string]interface{} `json:"metadata"` } // CertificateStatus statut du certificat type CertificateStatus string const ( CertStatusValid CertificateStatus = "valid" CertStatusExpiring CertificateStatus = "expiring" CertStatusExpired CertificateStatus = "expired" CertStatusRevoking CertificateStatus = "revoking" CertStatusRevoked CertificateStatus = "revoked" CertStatusRenewing CertificateStatus = "renewing" CertStatusError CertificateStatus = "error" ) // CertificateProvider interface pour les fournisseurs de certificats type CertificateProvider interface { GenerateCertificate(ctx context.Context, domain string, aliases []string, contact string) (*Certificate, error) RenewCertificate(ctx context.Context, cert *Certificate) (*Certificate, error) RevokeCertificate(ctx context.Context, cert *Certificate) error ValidateCertificate(ctx context.Context, cert *Certificate) error GetCertificateInfo(ctx context.Context, domain string) (*Certificate, error) } // CertificateMonitor surveille l'état des certificats type CertificateMonitor struct { logger *zap.Logger manager *CertificateManager isRunning bool mu sync.RWMutex } // RenewalScheduler planifie les renouvellements type RenewalScheduler struct { logger *zap.Logger manager *CertificateManager renewalQueue chan *Certificate isRunning bool mu sync.RWMutex } // NewCertificateManager crée un nouveau gestionnaire de certificats func NewCertificateManager(config CertificateConfig, logger *zap.Logger) *CertificateManager { cm := &CertificateManager{ logger: logger, config: config, certStore: make(map[string]*Certificate), providers: make(map[string]CertificateProvider), } // Initialiser le monitor cm.monitor = &CertificateMonitor{ logger: logger, manager: cm, } // Initialiser le scheduler cm.scheduler = &RenewalScheduler{ logger: logger, manager: cm, renewalQueue: make(chan *Certificate, 100), } return cm } // Initialize initialise le gestionnaire de certificats func (cm *CertificateManager) Initialize(ctx context.Context) error { cm.mu.Lock() defer cm.mu.Unlock() cm.logger.Info("Initializing Certificate Manager") // Initialiser les providers if err := cm.initializeProviders(); err != nil { return fmt.Errorf("failed to initialize providers: %w", err) } // Charger les certificats existants if err := cm.loadExistingCertificates(); err != nil { return fmt.Errorf("failed to load existing certificates: %w", err) } cm.logger.Info("Certificate Manager initialized successfully") return nil } // Start démarre le gestionnaire de certificats func (cm *CertificateManager) Start(ctx context.Context) error { cm.mu.Lock() defer cm.mu.Unlock() if cm.isRunning { return nil } cm.logger.Info("Starting Certificate Manager") // Démarrer le monitor go cm.monitor.Start(ctx) // Démarrer le scheduler go cm.scheduler.Start(ctx) // Démarrer le monitoring périodique go cm.startPeriodicChecks(ctx) cm.isRunning = true cm.logger.Info("Certificate Manager started successfully") return nil } // Stop arrête le gestionnaire de certificats func (cm *CertificateManager) Stop(ctx context.Context) error { cm.mu.Lock() defer cm.mu.Unlock() if !cm.isRunning { return nil } cm.logger.Info("Stopping Certificate Manager") // Arrêter les composants cm.monitor.Stop() cm.scheduler.Stop() cm.isRunning = false cm.logger.Info("Certificate Manager stopped") return nil } // GetCertificate récupère un certificat par domaine func (cm *CertificateManager) GetCertificate(domain string) (*Certificate, error) { cm.mu.RLock() defer cm.mu.RUnlock() cert, exists := cm.certStore[domain] if !exists { return nil, fmt.Errorf("certificate not found for domain: %s", domain) } return cert, nil } // RequestCertificate demande un nouveau certificat func (cm *CertificateManager) RequestCertificate(ctx context.Context, domain string, aliases []string, contact string) (*Certificate, error) { cm.mu.Lock() defer cm.mu.Unlock() cm.logger.Info("Requesting certificate", zap.String("domain", domain)) // Vérifier si le certificat existe déjà if cert, exists := cm.certStore[domain]; exists { if cert.Status == CertStatusValid && time.Until(cert.ExpiresAt) > cm.config.RenewalThreshold { cm.logger.Info("Certificate already exists and is valid", zap.String("domain", domain)) return cert, nil } } // Obtenir le provider provider, err := cm.getProvider(cm.config.Provider) if err != nil { return nil, fmt.Errorf("failed to get provider: %w", err) } // Générer le certificat cert, err := provider.GenerateCertificate(ctx, domain, aliases, contact) if err != nil { return nil, fmt.Errorf("failed to generate certificate: %w", err) } // Stocker le certificat cm.certStore[domain] = cert // Sauvegarder sur disque if err := cm.saveCertificate(cert); err != nil { cm.logger.Error("Failed to save certificate", zap.Error(err)) } cm.logger.Info("Certificate generated successfully", zap.String("domain", domain)) return cert, nil } // RenewCertificate renouvelle un certificat func (cm *CertificateManager) RenewCertificate(ctx context.Context, domain string) (*Certificate, error) { cm.mu.Lock() defer cm.mu.Unlock() cm.logger.Info("Renewing certificate", zap.String("domain", domain)) cert, exists := cm.certStore[domain] if !exists { return nil, fmt.Errorf("certificate not found for domain: %s", domain) } // Obtenir le provider provider, err := cm.getProvider(cert.Provider) if err != nil { return nil, fmt.Errorf("failed to get provider: %w", err) } // Marquer comme en cours de renouvellement cert.Status = CertStatusRenewing // Renouveler le certificat newCert, err := provider.RenewCertificate(ctx, cert) if err != nil { cert.Status = CertStatusError return nil, fmt.Errorf("failed to renew certificate: %w", err) } // Remplacer le certificat cm.certStore[domain] = newCert // Sauvegarder sur disque if err := cm.saveCertificate(newCert); err != nil { cm.logger.Error("Failed to save renewed certificate", zap.Error(err)) } cm.logger.Info("Certificate renewed successfully", zap.String("domain", domain)) return newCert, nil } // RevokeCertificate révoque un certificat func (cm *CertificateManager) RevokeCertificate(ctx context.Context, domain string) error { cm.mu.Lock() defer cm.mu.Unlock() cm.logger.Info("Revoking certificate", zap.String("domain", domain)) cert, exists := cm.certStore[domain] if !exists { return fmt.Errorf("certificate not found for domain: %s", domain) } // Obtenir le provider provider, err := cm.getProvider(cert.Provider) if err != nil { return fmt.Errorf("failed to get provider: %w", err) } // Révoquer le certificat if err := provider.RevokeCertificate(ctx, cert); err != nil { return fmt.Errorf("failed to revoke certificate: %w", err) } // Marquer comme révoqué cert.Status = CertStatusRevoked cm.logger.Info("Certificate revoked successfully", zap.String("domain", domain)) return nil } // ListCertificates liste tous les certificats func (cm *CertificateManager) ListCertificates() map[string]*Certificate { cm.mu.RLock() defer cm.mu.RUnlock() result := make(map[string]*Certificate) for k, v := range cm.certStore { result[k] = v } return result } // GetCertificateStatus retourne le statut d'un certificat func (cm *CertificateManager) GetCertificateStatus(domain string) (CertificateStatus, error) { cm.mu.RLock() defer cm.mu.RUnlock() cert, exists := cm.certStore[domain] if !exists { return "", fmt.Errorf("certificate not found for domain: %s", domain) } return cert.Status, nil } // CheckCertificateExpiry vérifie l'expiration des certificats func (cm *CertificateManager) CheckCertificateExpiry() ([]*Certificate, error) { cm.mu.RLock() defer cm.mu.RUnlock() var expiringCerts []*Certificate now := time.Now() for _, cert := range cm.certStore { timeUntilExpiry := cert.ExpiresAt.Sub(now) // Mettre à jour le statut if timeUntilExpiry <= 0 { cert.Status = CertStatusExpired } else if timeUntilExpiry <= cm.config.RenewalThreshold { cert.Status = CertStatusExpiring expiringCerts = append(expiringCerts, cert) } else { cert.Status = CertStatusValid } cert.LastChecked = now } return expiringCerts, nil } // Méthodes privées func (cm *CertificateManager) initializeProviders() error { // Initialiser le provider Let's Encrypt letsEncryptProvider := NewLetsEncryptProvider(cm.logger) cm.providers["letsencrypt"] = letsEncryptProvider // Initialiser le provider self-signed selfSignedProvider := NewSelfSignedProvider(cm.logger) cm.providers["self-signed"] = selfSignedProvider return nil } func (cm *CertificateManager) loadExistingCertificates() error { // Charger les certificats depuis le store configuré // Implémentation simplifiée for _, domainConfig := range cm.config.Domains { if domainConfig.CertificatePath != "" { cert, err := cm.loadCertificateFromFile(domainConfig) if err != nil { cm.logger.Warn("Failed to load certificate from file", zap.String("domain", domainConfig.Domain), zap.Error(err)) continue } cm.certStore[domainConfig.Domain] = cert } } return nil } func (cm *CertificateManager) loadCertificateFromFile(config DomainConfig) (*Certificate, error) { // Implémentation simplifiée - charger depuis fichier cert := &Certificate{ Domain: config.Domain, Aliases: config.Aliases, Provider: config.Provider, Status: CertStatusValid, IssuedAt: time.Now().AddDate(0, -1, 0), // 1 mois avant ExpiresAt: time.Now().AddDate(0, 2, 0), // 2 mois après LastChecked: time.Now(), AutoRenew: config.AutoRenew, Contact: config.Contact, Metadata: make(map[string]interface{}), } return cert, nil } func (cm *CertificateManager) saveCertificate(cert *Certificate) error { // Sauvegarder le certificat selon la configuration // Implémentation simplifiée cm.logger.Info("Certificate saved", zap.String("domain", cert.Domain)) return nil } func (cm *CertificateManager) getProvider(providerName string) (CertificateProvider, error) { provider, exists := cm.providers[providerName] if !exists { return nil, fmt.Errorf("provider not found: %s", providerName) } return provider, nil } func (cm *CertificateManager) startPeriodicChecks(ctx context.Context) { ticker := time.NewTicker(cm.config.CheckInterval) defer ticker.Stop() for { select { case <-ticker.C: cm.performPeriodicCheck() case <-ctx.Done(): return } } } func (cm *CertificateManager) performPeriodicCheck() { cm.logger.Debug("Performing periodic certificate check") // Vérifier l'expiration des certificats expiringCerts, err := cm.CheckCertificateExpiry() if err != nil { cm.logger.Error("Failed to check certificate expiry", zap.Error(err)) return } // Planifier le renouvellement des certificats expirants for _, cert := range expiringCerts { if cert.AutoRenew { cm.scheduler.ScheduleRenewal(cert) } } } // CertificateMonitor methods func (monitor *CertificateMonitor) Start(ctx context.Context) { monitor.mu.Lock() defer monitor.mu.Unlock() if monitor.isRunning { return } monitor.logger.Info("Starting Certificate Monitor") monitor.isRunning = true go monitor.monitorCertificates(ctx) } func (monitor *CertificateMonitor) Stop() { monitor.mu.Lock() defer monitor.mu.Unlock() monitor.isRunning = false monitor.logger.Info("Certificate Monitor stopped") } func (monitor *CertificateMonitor) monitorCertificates(ctx context.Context) { ticker := time.NewTicker(time.Hour) defer ticker.Stop() for { select { case <-ticker.C: monitor.checkCertificateHealth() case <-ctx.Done(): return } } } func (monitor *CertificateMonitor) checkCertificateHealth() { monitor.logger.Debug("Checking certificate health") certs := monitor.manager.ListCertificates() for domain, cert := range certs { // Vérifier la validité du certificat if err := monitor.validateCertificate(cert); err != nil { monitor.logger.Error("Certificate validation failed", zap.String("domain", domain), zap.Error(err)) cert.Status = CertStatusError } } } func (monitor *CertificateMonitor) validateCertificate(cert *Certificate) error { // Validation basique du certificat if cert.Certificate != nil { now := time.Now() if now.Before(cert.Certificate.NotBefore) || now.After(cert.Certificate.NotAfter) { return fmt.Errorf("certificate is not valid for current time") } } return nil } // RenewalScheduler methods func (scheduler *RenewalScheduler) Start(ctx context.Context) { scheduler.mu.Lock() defer scheduler.mu.Unlock() if scheduler.isRunning { return } scheduler.logger.Info("Starting Renewal Scheduler") scheduler.isRunning = true go scheduler.processRenewals(ctx) } func (scheduler *RenewalScheduler) Stop() { scheduler.mu.Lock() defer scheduler.mu.Unlock() scheduler.isRunning = false close(scheduler.renewalQueue) scheduler.logger.Info("Renewal Scheduler stopped") } func (scheduler *RenewalScheduler) ScheduleRenewal(cert *Certificate) { if !scheduler.isRunning { return } select { case scheduler.renewalQueue <- cert: scheduler.logger.Info("Certificate renewal scheduled", zap.String("domain", cert.Domain)) default: scheduler.logger.Warn("Renewal queue is full", zap.String("domain", cert.Domain)) } } func (scheduler *RenewalScheduler) processRenewals(ctx context.Context) { for { select { case cert := <-scheduler.renewalQueue: if cert != nil { scheduler.renewCertificate(ctx, cert) } case <-ctx.Done(): return } } } func (scheduler *RenewalScheduler) renewCertificate(ctx context.Context, cert *Certificate) { scheduler.logger.Info("Processing certificate renewal", zap.String("domain", cert.Domain)) _, err := scheduler.manager.RenewCertificate(ctx, cert.Domain) if err != nil { scheduler.logger.Error("Failed to renew certificate", zap.String("domain", cert.Domain), zap.Error(err)) } else { scheduler.logger.Info("Certificate renewed successfully", zap.String("domain", cert.Domain)) } }