235 lines
6.5 KiB
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, ®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.")
|
|
}
|
|
|