added last fixes for dev branch prepartion
This commit is contained in:
182
backend/providers/acme_provider.go
Normal file
182
backend/providers/acme_provider.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ACMEProvider Interface für ACME-basierte Certificate Authorities
|
||||
type ACMEProvider interface {
|
||||
// GetName gibt den Namen des ACME-Providers zurück
|
||||
GetName() string
|
||||
// GetDisplayName gibt den Anzeigenamen zurück
|
||||
GetDisplayName() string
|
||||
// GetDescription gibt eine Beschreibung zurück
|
||||
GetDescription() string
|
||||
// GetDirectoryURL gibt die ACME Directory URL zurück
|
||||
GetDirectoryURL() string
|
||||
// GetRenewalInfoURL gibt die RenewalInfo API URL zurück (optional)
|
||||
GetRenewalInfoURL() string
|
||||
// ValidateConfig validiert die Konfiguration
|
||||
ValidateConfig(settings map[string]interface{}) error
|
||||
// TestConnection testet die Verbindung zum ACME-Server
|
||||
TestConnection(settings map[string]interface{}) error
|
||||
// GetRequiredSettings gibt die erforderlichen Einstellungen zurück
|
||||
GetRequiredSettings() []SettingField
|
||||
}
|
||||
|
||||
// ACMEProviderConfig enthält die Konfiguration eines ACME-Providers
|
||||
type ACMEProviderConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Settings map[string]interface{} `json:"settings"`
|
||||
}
|
||||
|
||||
// ACMEProviderManager verwaltet alle ACME-Provider
|
||||
type ACMEProviderManager struct {
|
||||
providers map[string]ACMEProvider
|
||||
configs map[string]*ACMEProviderConfig
|
||||
configDir string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var acmeManager *ACMEProviderManager
|
||||
var acmeOnce sync.Once
|
||||
|
||||
// GetACMEManager gibt die Singleton-Instanz des ACMEProviderManagers zurück
|
||||
func GetACMEManager() *ACMEProviderManager {
|
||||
acmeOnce.Do(func() {
|
||||
acmeManager = &ACMEProviderManager{
|
||||
providers: make(map[string]ACMEProvider),
|
||||
configs: make(map[string]*ACMEProviderConfig),
|
||||
configDir: "./config/providers",
|
||||
}
|
||||
acmeManager.loadAllConfigs()
|
||||
})
|
||||
return acmeManager
|
||||
}
|
||||
|
||||
// RegisterACMEProvider registriert einen neuen ACME-Provider
|
||||
func (pm *ACMEProviderManager) RegisterACMEProvider(provider ACMEProvider) {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
providerID := provider.GetName()
|
||||
pm.providers[providerID] = provider
|
||||
|
||||
// Lade Konfiguration falls vorhanden
|
||||
if pm.configs[providerID] == nil {
|
||||
pm.configs[providerID] = &ACMEProviderConfig{
|
||||
Enabled: false,
|
||||
Settings: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetACMEProvider gibt einen ACME-Provider zurück
|
||||
func (pm *ACMEProviderManager) GetACMEProvider(id string) (ACMEProvider, bool) {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
provider, exists := pm.providers[id]
|
||||
return provider, exists
|
||||
}
|
||||
|
||||
// GetAllACMEProviders gibt alle registrierten ACME-Provider zurück
|
||||
func (pm *ACMEProviderManager) GetAllACMEProviders() map[string]ACMEProvider {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
result := make(map[string]ACMEProvider)
|
||||
for id, provider := range pm.providers {
|
||||
result[id] = provider
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetACMEProviderConfig gibt die Konfiguration eines ACME-Providers zurück
|
||||
func (pm *ACMEProviderManager) GetACMEProviderConfig(id string) (*ACMEProviderConfig, error) {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
|
||||
config, exists := pm.configs[id]
|
||||
if !exists {
|
||||
return &ACMEProviderConfig{
|
||||
Enabled: false,
|
||||
Settings: make(map[string]interface{}),
|
||||
}, nil
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// SetACMEProviderEnabled aktiviert/deaktiviert einen ACME-Provider
|
||||
func (pm *ACMEProviderManager) SetACMEProviderEnabled(id string, enabled bool) error {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
if pm.configs[id] == nil {
|
||||
pm.configs[id] = &ACMEProviderConfig{
|
||||
Enabled: enabled,
|
||||
Settings: make(map[string]interface{}),
|
||||
}
|
||||
} else {
|
||||
pm.configs[id].Enabled = enabled
|
||||
}
|
||||
|
||||
return pm.saveConfig(id, pm.configs[id])
|
||||
}
|
||||
|
||||
// loadAllConfigs lädt alle Konfigurationsdateien
|
||||
func (pm *ACMEProviderManager) loadAllConfigs() {
|
||||
// Stelle sicher, dass das Verzeichnis existiert
|
||||
os.MkdirAll(pm.configDir, 0755)
|
||||
|
||||
// Lade alle JSON-Dateien im Konfigurationsverzeichnis
|
||||
files, err := filepath.Glob(filepath.Join(pm.configDir, "*.json"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
id := filepath.Base(file[:len(file)-5]) // Entferne .json
|
||||
// Nur ACME-Provider-Konfigurationen laden (beginnen mit "letsencrypt")
|
||||
if id == "letsencrypt-production" || id == "letsencrypt-staging" {
|
||||
config, err := pm.loadConfig(id)
|
||||
if err == nil {
|
||||
pm.configs[id] = config
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loadConfig lädt eine Konfigurationsdatei
|
||||
func (pm *ACMEProviderManager) loadConfig(id string) (*ACMEProviderConfig, error) {
|
||||
filePath := filepath.Join(pm.configDir, id+".json")
|
||||
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config ACMEProviderConfig
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// saveConfig speichert eine Konfiguration in eine Datei
|
||||
func (pm *ACMEProviderManager) saveConfig(id string, config *ACMEProviderConfig) error {
|
||||
// Stelle sicher, dass das Verzeichnis existiert
|
||||
os.MkdirAll(pm.configDir, 0755)
|
||||
|
||||
filePath := filepath.Join(pm.configDir, id+".json")
|
||||
|
||||
data, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(filePath, data, 0644)
|
||||
}
|
||||
|
||||
106
backend/providers/letsencrypt.go
Normal file
106
backend/providers/letsencrypt.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LetsEncryptProvider ist der Provider für Let's Encrypt
|
||||
type LetsEncryptProvider struct {
|
||||
environment string // "production" oder "staging"
|
||||
}
|
||||
|
||||
// NewLetsEncryptProvider erstellt einen neuen Let's Encrypt Provider
|
||||
func NewLetsEncryptProvider(environment string) *LetsEncryptProvider {
|
||||
if environment != "staging" && environment != "production" {
|
||||
environment = "production"
|
||||
}
|
||||
return &LetsEncryptProvider{
|
||||
environment: environment,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *LetsEncryptProvider) GetName() string {
|
||||
if p.environment == "staging" {
|
||||
return "letsencrypt-staging"
|
||||
}
|
||||
return "letsencrypt-production"
|
||||
}
|
||||
|
||||
func (p *LetsEncryptProvider) GetDisplayName() string {
|
||||
if p.environment == "staging" {
|
||||
return "Let's Encrypt (Staging)"
|
||||
}
|
||||
return "Let's Encrypt (Production)"
|
||||
}
|
||||
|
||||
func (p *LetsEncryptProvider) GetDescription() string {
|
||||
if p.environment == "staging" {
|
||||
return "Let's Encrypt Staging Environment für Tests"
|
||||
}
|
||||
return "Let's Encrypt Production Certificate Authority"
|
||||
}
|
||||
|
||||
func (p *LetsEncryptProvider) GetDirectoryURL() string {
|
||||
if p.environment == "staging" {
|
||||
return "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
}
|
||||
return "https://acme-v02.api.letsencrypt.org/directory"
|
||||
}
|
||||
|
||||
func (p *LetsEncryptProvider) GetRenewalInfoURL() string {
|
||||
if p.environment == "staging" {
|
||||
return "https://acme-staging-v02.api.letsencrypt.org/acme/renewal-info"
|
||||
}
|
||||
return "https://acme-v02.api.letsencrypt.org/acme/renewal-info"
|
||||
}
|
||||
|
||||
func (p *LetsEncryptProvider) ValidateConfig(settings map[string]interface{}) error {
|
||||
// Let's Encrypt benötigt keine zusätzliche Konfiguration
|
||||
// Die Directory URL wird automatisch basierend auf der Environment gesetzt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *LetsEncryptProvider) TestConnection(settings map[string]interface{}) error {
|
||||
// Teste Verbindung zum ACME Directory
|
||||
directoryURL := p.GetDirectoryURL()
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get(directoryURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ACME Directory nicht erreichbar: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("ACME Directory antwortet mit Status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// Prüfe ob es ein gültiges ACME Directory ist
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fehler beim Lesen der Directory-Response: %v", err)
|
||||
}
|
||||
|
||||
// Einfache Validierung: Prüfe ob "newAccount" oder "newNonce" im Body enthalten ist
|
||||
bodyStr := string(body)
|
||||
if !strings.Contains(bodyStr, "newAccount") && !strings.Contains(bodyStr, "newNonce") {
|
||||
return fmt.Errorf("ungültige ACME Directory Response")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *LetsEncryptProvider) GetRequiredSettings() []SettingField {
|
||||
// Let's Encrypt benötigt keine zusätzlichen Einstellungen
|
||||
// Die Directory URL wird automatisch basierend auf der Environment gesetzt
|
||||
return []SettingField{}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user