added last fixes for dev branch prepartion

This commit is contained in:
2025-11-27 23:50:59 +01:00
parent 145dfd3d7c
commit 688b277b5d
13 changed files with 1051 additions and 158 deletions

View 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)
}

View 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{}
}