implemented LE and ACME and fixed some bugs
This commit is contained in:
234
backend/providers/certigo-acmeproxy.go
Normal file
234
backend/providers/certigo-acmeproxy.go
Normal file
@@ -0,0 +1,234 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CertigoACMEProxyProvider ist der Provider für certigo-acmeproxy
|
||||
type CertigoACMEProxyProvider struct {
|
||||
baseURL string
|
||||
}
|
||||
|
||||
func NewCertigoACMEProxyProvider() *CertigoACMEProxyProvider {
|
||||
return &CertigoACMEProxyProvider{}
|
||||
}
|
||||
|
||||
func (p *CertigoACMEProxyProvider) GetName() string {
|
||||
return "certigo-acmeproxy"
|
||||
}
|
||||
|
||||
func (p *CertigoACMEProxyProvider) GetDisplayName() string {
|
||||
return "Certigo ACME Proxy"
|
||||
}
|
||||
|
||||
func (p *CertigoACMEProxyProvider) GetDescription() string {
|
||||
return "ACME DNS-01 Challenge Responder für Let's Encrypt und andere ACME CAs"
|
||||
}
|
||||
|
||||
func (p *CertigoACMEProxyProvider) ValidateConfig(settings map[string]interface{}) error {
|
||||
baseURL, ok := settings["baseURL"].(string)
|
||||
if !ok || strings.TrimSpace(baseURL) == "" {
|
||||
return fmt.Errorf("baseURL ist erforderlich")
|
||||
}
|
||||
|
||||
// Entferne trailing slash falls vorhanden
|
||||
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||
|
||||
// Validiere URL-Format
|
||||
if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") {
|
||||
return fmt.Errorf("baseURL muss mit http:// oder https:// beginnen")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *CertigoACMEProxyProvider) TestConnection(settings map[string]interface{}) error {
|
||||
// Validiere zuerst die Konfiguration
|
||||
if err := p.ValidateConfig(settings); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseURL, _ := settings["baseURL"].(string)
|
||||
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||
|
||||
// Teste Verbindung über Health Check
|
||||
url := fmt.Sprintf("%s/health", baseURL)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("acme-proxy nicht erreichbar: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("acme-proxy antwortet mit Status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Prüfe Response Body
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fehler beim Lesen der Health-Check-Response: %v", err)
|
||||
}
|
||||
|
||||
var healthResponse struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &healthResponse); err != nil {
|
||||
return fmt.Errorf("ungültige Health-Check-Response: %v", err)
|
||||
}
|
||||
|
||||
if healthResponse.Status != "ok" {
|
||||
return fmt.Errorf("acme-proxy meldet Status: %s", healthResponse.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRequiredSettings gibt die erforderlichen Einstellungen zurück
|
||||
func (p *CertigoACMEProxyProvider) GetRequiredSettings() []SettingField {
|
||||
return []SettingField{
|
||||
{
|
||||
Name: "baseURL",
|
||||
Label: "Base URL",
|
||||
Type: "text",
|
||||
Required: true,
|
||||
Description: "Base URL des certigo-acmeproxy Services (z.B. http://localhost:8080)",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterChallengeDomain registriert eine neue Challenge-Domain beim ACME Proxy
|
||||
func (p *CertigoACMEProxyProvider) RegisterChallengeDomain(settings map[string]interface{}) (*ChallengeDomainResponse, error) {
|
||||
if err := p.ValidateConfig(settings); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseURL, _ := settings["baseURL"].(string)
|
||||
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||
|
||||
url := fmt.Sprintf("%s/register", baseURL)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fehler beim Erstellen des Requests: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fehler beim Senden des Requests: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fehler beim Lesen der Response: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("acme-proxy Fehler (Status %d): %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var registerResponse ChallengeDomainResponse
|
||||
if err := json.Unmarshal(body, ®isterResponse); err != nil {
|
||||
return nil, fmt.Errorf("fehler beim Parsen der Response: %v", err)
|
||||
}
|
||||
|
||||
return ®isterResponse, nil
|
||||
}
|
||||
|
||||
// UpdateChallengeToken setzt oder aktualisiert den ACME Challenge Token
|
||||
func (p *CertigoACMEProxyProvider) UpdateChallengeToken(username, password, token string, settings map[string]interface{}) error {
|
||||
if err := p.ValidateConfig(settings); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseURL, _ := settings["baseURL"].(string)
|
||||
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||
|
||||
url := fmt.Sprintf("%s/update", baseURL)
|
||||
|
||||
requestBody := map[string]string{
|
||||
"txt": token,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fehler beim Erstellen des Request-Body: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("fehler beim Erstellen des Requests: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Basic Authentication
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
|
||||
req.Header.Set("Authorization", "Basic "+auth)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fehler beim Senden des Requests: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fehler beim Lesen der Response: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusUnauthorized {
|
||||
return fmt.Errorf("ungültige Authentifizierung")
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusBadRequest {
|
||||
return fmt.Errorf("ungültige Anfrage: %s", string(body))
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("acme-proxy Fehler (Status %d): %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChallengeDomainResponse enthält die Response von /register
|
||||
type ChallengeDomainResponse struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Fulldomain string `json:"fulldomain"`
|
||||
Subdomain string `json:"subdomain"`
|
||||
}
|
||||
|
||||
// SignCSR signiert einen CSR (für ACME nicht direkt verwendet, aber Interface erfordert es)
|
||||
func (p *CertigoACMEProxyProvider) SignCSR(csrPEM string, settings map[string]interface{}) (*SignCSRResult, error) {
|
||||
return nil, fmt.Errorf("certigo-acmeproxy unterstützt keine direkte CSR-Signierung. Verwenden Sie ACME für Zertifikatsanfragen.")
|
||||
}
|
||||
|
||||
// GetCertificate ruft ein Zertifikat ab (für ACME nicht direkt verwendet, aber Interface erfordert es)
|
||||
func (p *CertigoACMEProxyProvider) GetCertificate(certificateID string, settings map[string]interface{}) (string, error) {
|
||||
return "", fmt.Errorf("certigo-acmeproxy unterstützt keinen direkten Zertifikat-Abruf. Verwenden Sie ACME für Zertifikatsanfragen.")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user