Files
certigo/backend/providers/certigo-acmeproxy.go

235 lines
6.5 KiB
Go

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, &registerResponse); err != nil {
return nil, fmt.Errorf("fehler beim Parsen der Response: %v", err)
}
return &registerResponse, 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.")
}