597 lines
16 KiB
Go
597 lines
16 KiB
Go
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))
|
|
}
|
|
}
|