From caa047f6c25d11d46f35d517a8b901f15176ce62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D1=80=D0=B8=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=20=D0=A1?= =?UTF-8?q?=D0=B0=D1=84=D1=80=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Fri, 12 Jun 2026 16:20:55 +0000 Subject: [PATCH] Upload files to "internal/cluster" --- internal/cluster/tls_config.go | 383 +++++++++++++++++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 internal/cluster/tls_config.go diff --git a/internal/cluster/tls_config.go b/internal/cluster/tls_config.go new file mode 100644 index 0000000..3db7f2f --- /dev/null +++ b/internal/cluster/tls_config.go @@ -0,0 +1,383 @@ +/* + * Copyright 2026 Safronov Grigorii + * + * Licensed under the CDDL, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * https://opensource.org/licenses/CDDL-1.0 + */ + +// Файл: internal/cluster/tls_config.go +// Назначение: TLS для межузлового общения, валидация сертификатов, ротация ключей + +package cluster + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "os" + "sync" + "time" +) + +// TLSConfig управляет TLS для межузлового общения +type TLSConfig struct { + Enabled bool `json:"enabled"` + CertFile string `json:"cert_file"` + KeyFile string `json:"key_file"` + CAFile string `json:"ca_file"` + MinVersion string `json:"min_version"` + MutualAuth bool `json:"mutual_auth"` + KeyRotationDays int `json:"key_rotation_days"` + AutoGenerate bool `json:"auto_generate"` + CertCache *tls.Certificate + caCertPool *x509.CertPool + mu sync.RWMutex + lastRotation time.Time + logger LoggerInterface +} + +// TLSOptions содержит опции TLS +type TLSOptions struct { + CertFile string + KeyFile string + CAFile string + MinVersion uint16 + MutualAuth bool + ServerName string +} + +// NewTLSConfig создаёт новую TLS конфигурацию +func NewTLSConfig(enabled bool, certFile, keyFile, caFile, minVersion string, mutualAuth bool, keyRotationDays int, autoGenerate bool, logger LoggerInterface) *TLSConfig { + cfg := &TLSConfig{ + Enabled: enabled, + CertFile: certFile, + KeyFile: keyFile, + CAFile: caFile, + MinVersion: minVersion, + MutualAuth: mutualAuth, + KeyRotationDays: keyRotationDays, + AutoGenerate: autoGenerate, + logger: logger, + lastRotation: time.Now(), + } + + if enabled { + cfg.loadCertificates() + } + + return cfg +} + +// loadCertificates загружает сертификаты +func (tc *TLSConfig) loadCertificates() error { + tc.mu.Lock() + defer tc.mu.Unlock() + + // Если включена автогенерация и файлы не существуют + if tc.AutoGenerate { + if _, err := os.Stat(tc.CertFile); os.IsNotExist(err) { + if err := tc.generateSelfSignedCert(); err != nil { + return fmt.Errorf("failed to generate self-signed cert: %v", err) + } + } + } + + // Загружаем сертификат + cert, err := tls.LoadX509KeyPair(tc.CertFile, tc.KeyFile) + if err != nil { + return fmt.Errorf("failed to load certificate: %v", err) + } + tc.CertCache = &cert + + // Загружаем CA если указан + if tc.CAFile != "" { + caCert, err := os.ReadFile(tc.CAFile) + if err != nil { + return fmt.Errorf("failed to read CA file: %v", err) + } + + tc.caCertPool = x509.NewCertPool() + if !tc.caCertPool.AppendCertsFromPEM(caCert) { + return fmt.Errorf("failed to parse CA certificate") + } + } + + if tc.logger != nil { + tc.logger.Debug(fmt.Sprintf("TLS certificates loaded from %s", tc.CertFile)) + } + + return nil +} + +// generateSelfSignedCert генерирует самоподписанный сертификат +func (tc *TLSConfig) generateSelfSignedCert() error { + // Генерируем приватный ключ + privKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return err + } + + // Создаём шаблон сертификата + template := &x509.Certificate{ + SerialNumber: big.NewInt(time.Now().Unix()), + Subject: pkix.Name{ + Organization: []string{"Futriix Cluster"}, + CommonName: "futriix-node", + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(1, 0, 0), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + BasicConstraintsValid: true, + } + + // Создаём сертификат + certBytes, err := x509.CreateCertificate(rand.Reader, template, template, &privKey.PublicKey, privKey) + if err != nil { + return err + } + + // Сохраняем сертификат + certOut, err := os.Create(tc.CertFile) + if err != nil { + return err + } + defer certOut.Close() + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) + + // Сохраняем ключ + keyOut, err := os.Create(tc.KeyFile) + if err != nil { + return err + } + defer keyOut.Close() + pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privKey)}) + + if tc.logger != nil { + tc.logger.Info(fmt.Sprintf("Generated self-signed certificate at %s", tc.CertFile)) + } + + return nil +} + +// GetTLSConfig возвращает tls.Config для использования +func (tc *TLSConfig) GetTLSConfig() (*tls.Config, error) { + if !tc.Enabled { + return nil, nil + } + + // Проверяем необходимость ротации ключей + if time.Since(tc.lastRotation).Hours() > float64(tc.KeyRotationDays*24) { + if err := tc.rotateKeys(); err != nil { + if tc.logger != nil { + tc.logger.Warn(fmt.Sprintf("Key rotation failed: %v", err)) + } + } + } + + tc.mu.RLock() + defer tc.mu.RUnlock() + + if tc.CertCache == nil { + if err := tc.loadCertificates(); err != nil { + return nil, err + } + } + + minVersion := tc.getMinVersion() + + tlsCfg := &tls.Config{ + Certificates: []tls.Certificate{*tc.CertCache}, + MinVersion: minVersion, + CurvePreferences: []tls.CurveID{ + tls.X25519, + tls.CurveP256, + tls.CurveP384, + }, + CipherSuites: []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, + PreferServerCipherSuites: true, + SessionTicketsDisabled: false, + ClientSessionCache: tls.NewLRUClientSessionCache(100), + } + + if tc.MutualAuth && tc.caCertPool != nil { + tlsCfg.ClientAuth = tls.RequireAndVerifyClientCert + tlsCfg.ClientCAs = tc.caCertPool + } else if tc.MutualAuth { + tlsCfg.ClientAuth = tls.RequireAnyClientCert + } + + return tlsCfg, nil +} + +// getMinVersion преобразует строковую версию в uint16 +func (tc *TLSConfig) getMinVersion() uint16 { + switch tc.MinVersion { + case "1.0": + return tls.VersionTLS10 + case "1.1": + return tls.VersionTLS11 + case "1.2": + return tls.VersionTLS12 + case "1.3": + return tls.VersionTLS13 + default: + return tls.VersionTLS12 + } +} + +// rotateKeys выполняет ротацию ключей шифрования +func (tc *TLSConfig) rotateKeys() error { + if !tc.AutoGenerate { + return fmt.Errorf("auto-generation disabled, cannot rotate keys") + } + + tc.mu.Lock() + defer tc.mu.Unlock() + + // Создаём бэкап старых ключей + backupCert := tc.CertFile + ".backup" + backupKey := tc.KeyFile + ".backup" + + if _, err := os.Stat(tc.CertFile); err == nil { + os.Rename(tc.CertFile, backupCert) + os.Rename(tc.KeyFile, backupKey) + } + + // Генерируем новые ключи + if err := tc.generateSelfSignedCert(); err != nil { + // Восстанавливаем из бэкапа при ошибке + os.Rename(backupCert, tc.CertFile) + os.Rename(backupKey, tc.KeyFile) + return fmt.Errorf("key rotation failed: %v", err) + } + + // Загружаем новый сертификат + cert, err := tls.LoadX509KeyPair(tc.CertFile, tc.KeyFile) + if err != nil { + return err + } + tc.CertCache = &cert + tc.lastRotation = time.Now() + + if tc.logger != nil { + tc.logger.Info("TLS keys rotated successfully") + } + + return nil +} + +// ValidatePeerCertificate валидирует сертификат пира +func (tc *TLSConfig) ValidatePeerCertificate(certificates [][]byte) error { + if !tc.MutualAuth { + return nil + } + + if len(certificates) == 0 { + return fmt.Errorf("no client certificate provided") + } + + cert, err := x509.ParseCertificate(certificates[0]) + if err != nil { + return fmt.Errorf("failed to parse peer certificate: %v", err) + } + + if tc.caCertPool != nil { + opts := x509.VerifyOptions{ + Roots: tc.caCertPool, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + } + if _, err := cert.Verify(opts); err != nil { + return fmt.Errorf("peer certificate verification failed: %v", err) + } + } + + // Проверяем срок действия + if time.Now().After(cert.NotAfter) { + return fmt.Errorf("peer certificate expired at %v", cert.NotAfter) + } + + if time.Now().Before(cert.NotBefore) { + return fmt.Errorf("peer certificate not valid until %v", cert.NotBefore) + } + + return nil +} + +// IsExpired проверяет, истёк ли сертификат +func (tc *TLSConfig) IsExpired() bool { + tc.mu.RLock() + defer tc.mu.RUnlock() + + if tc.CertCache == nil { + return true + } + + cert, err := x509.ParseCertificate(tc.CertCache.Certificate[0]) + if err != nil { + return true + } + + return time.Now().After(cert.NotAfter) +} + +// GetExpiryDays возвращает количество дней до истечения +func (tc *TLSConfig) GetExpiryDays() int { + tc.mu.RLock() + defer tc.mu.RUnlock() + + if tc.CertCache == nil { + return 0 + } + + cert, err := x509.ParseCertificate(tc.CertCache.Certificate[0]) + if err != nil { + return 0 + } + + days := int(time.Until(cert.NotAfter).Hours() / 24) + if days < 0 { + return 0 + } + return days +} + +// GetCertFile возвращает путь к файлу сертификата +func (tc *TLSConfig) GetCertFile() string { + return tc.CertFile +} + +// GetKeyFile возвращает путь к файлу ключа +func (tc *TLSConfig) GetKeyFile() string { + return tc.KeyFile +} + +// GetCAFile возвращает путь к файлу CA +func (tc *TLSConfig) GetCAFile() string { + return tc.CAFile +} + +// IsMutualAuthEnabled возвращает статус взаимной аутентификации +func (tc *TLSConfig) IsMutualAuthEnabled() bool { + return tc.MutualAuth +} + +// IsEnabled возвращает статус TLS +func (tc *TLSConfig) IsEnabled() bool { + return tc.Enabled +}