Let's Encrypt Challenges per SCP lösen für eingeschränktes Webhosting
Das Problem: Eingeschränktes Domain-Hosting ohne API-Zugang
Wer schon einmal versucht hat, SSL-Zertifikate mit Let’s Encrypt bei einem Domain-Hosting-Anbieter ohne API-Zugang einzurichten, kennt das Problem. Meine Situation war besonders herausfordernd:
- Meine Domain wird bei einem Anbieter gehostet, der keinerlei API für automatisierte Zertifikatsverwaltung anbietet
- Mein Webspace ist begrenzt, was es schwierig macht, einen vollständigen ACME-Client direkt auf dem Server auszuführen
- Ich musste den Zertifikatserneuerungsprozess automatisieren, um alle 90 Tage manuelle Eingriffe zu vermeiden
Der Standardansatz für die Let’s Encrypt-Validierung besteht darin, eine bestimmte Challenge-Datei im Verzeichnis .well-known/acme-challenge/ der Website abzulegen. Aber wie macht man das, wenn man keine Software direkt beim Hosting-Anbieter ausführen kann?
Die Lösung: Lego-SCP-Solver
Um dieses Problem zu lösen, habe ich ein Go-basiertes Tool entwickelt, das die hervorragende lego ACME Client Library mit einem eigenen Challenge-Solver kombiniert, der SCP (Secure Copy Protocol) nutzt, um die Challenge-Dateien auf meinen Webserver hochzuladen.
Das Tool funktioniert folgendermaßen:
- Verbindung zum Webserver über SSH herstellen
- Das erforderliche Verzeichnis
.well-known/acme-challenge/anlegen - Die Challenge-Token-Datei per SCP hochladen
- Überprüfen, ob die Datei per HTTP erreichbar ist
- Nach Abschluss der Challenge aufräumen
Dieser Ansatz ermöglicht es mir, den Zertifikatsausstellungsprozess von meinem lokalen Rechner oder einer CI/CD-Pipeline aus zu starten, ohne etwas auf dem Webserver selbst installieren zu müssen.
So funktioniert es
Das Herzstück der Lösung ist ein eigener HTTP-01 Challenge-Provider, der das Challenge-Interface von lego implementiert. Hier eine vereinfachte Version der Funktionsweise:
// Present implements the challenge.Provider interface
func (s *SCPSolver) Present(domain, token, keyAuth string) error {
// Connect to SSH
if err := s.connect(); err != nil {
return fmt.Errorf("SSH connection failed: %w", err)
}
defer s.sshClient.Close()
// Create remote directory
remotePath := s.webrootPath + "/.well-known/acme-challenge"
if err := s.createRemoteDir(remotePath); err != nil {
return fmt.Errorf("failed to create remote directory: %w", err)
}
// Upload challenge file
remoteFile := remotePath + "/" + token
if err := s.uploadFile(remoteFile, keyAuth); err != nil {
return fmt.Errorf("failed to upload challenge file: %w", err)
}
// Set proper permissions for web server access
if err := s.setPermissions(remotePath, remoteFile); err != nil {
log.Printf("Warning: Failed to set permissions: %v", err)
}
return nil
}
Wenn Let’s Encrypt die Domain-Inhaberschaft validieren muss, macht dieser Code Folgendes:
- Eine SSH-Verbindung zum Webserver aufbauen
- Das Challenge-Verzeichnis anlegen, falls es noch nicht existiert
- Den Challenge-Token mit dem korrekten Inhalt hochladen
- Die entsprechenden Berechtigungen setzen, damit der Webserver die Datei ausliefern kann
Nach der Validierung entfernt eine entsprechende Aufräumfunktion die Challenge-Datei.
Einrichtung und Verwendung des Tools
Die Verwendung des Tools ist unkompliziert. Man kann entweder Umgebungsvariablen setzen:
export LEGO_SCP_HOST="your-server.com"
export LEGO_SCP_USER="your-username"
export LEGO_SCP_KEY_PATH="/path/to/your/ssh/private/key"
export LEGO_SCP_WEBROOT_PATH="/var/www/html"
export LEGO_SCP_EMAIL="your-email@example.com"
export LEGO_SCP_DOMAINS="example.com,www.example.com"
export LEGO_SCP_ACCOUNT_KEY="/path/to/account.key"
export LEGO_SCP_CERT_PATH="/path/to/certificates"
Oder Kommandozeilenparameter verwenden:
lego-scp-solver -e your-email@example.com -d example.com,www.example.com \
--scp-host your-server.com --scp-user username --scp-key ~/.ssh/id_rsa \
--scp-webroot /var/www/html --cert-path ./certificates
Automatisierung mit GitHub Actions
Um den Zertifikatserneuerungsprozess vollständig zu automatisieren, habe ich einen GitHub Actions Workflow eingerichtet, der das Tool nach einem Zeitplan ausführt:
name: Renew SSL Certificates
on:
schedule:
- cron: "0 0 1 * *" # Run on the 1st of every month
workflow_dispatch: # Allow manual triggering
jobs:
renew:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.21"
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
- name: Run certificate renewal
env:
LEGO_SCP_HOST: ${{ secrets.SCP_HOST }}
LEGO_SCP_USER: ${{ secrets.SCP_USER }}
LEGO_SCP_KEY_PATH: ~/.ssh/id_rsa
LEGO_SCP_WEBROOT_PATH: ${{ secrets.WEBROOT_PATH }}
LEGO_SCP_EMAIL: ${{ secrets.ACME_EMAIL }}
LEGO_SCP_DOMAINS: ${{ secrets.DOMAINS }}
LEGO_SCP_ACCOUNT_KEY: account.key
run: |
go run .
- name: Upload certificates
uses: actions/upload-artifact@v3
with:
name: certificates
path: ./*.crt
Dieser Workflow speichert alle sensiblen Informationen sicher in GitHub Secrets und führt den Zertifikatserneuerungsprozess automatisch aus.
Vorteile dieses Ansatzes
- Keine serverseitige Installation erforderlich: Funktioniert mit jedem Hosting-Anbieter, der SSH/SCP-Zugang bietet
- Vollständig automatisiert: Einmal einrichten und vergessen Zertifikate werden automatisch erneuert
- Sicher: Nutzt SSH-Key-Authentifizierung für sichere Dateiübertragungen
- Flexibel: Funktioniert mit mehreren Domains und Subdomains
- Leichtgewichtig: Minimale Abhängigkeiten und Ressourcennutzung
Die Lego ACME Library: Ein leistungsstarkes Fundament
Lego ist ein Let’s Encrypt Client und eine ACME-Bibliothek, geschrieben in Go. Sie bietet eine vollständige Lösung zum Beziehen, Erneuern und Widerrufen von SSL-Zertifikaten von Let’s Encrypt und anderen ACME-kompatiblen Zertifizierungsstellen.
Was Lego besonders leistungsfähig macht, ist seine Erweiterbarkeit. Es unterstützt mehrere Challenge-Typen (HTTP-01, DNS-01, TLS-ALPN-01) und bringt integrierte Provider für viele beliebte DNS-Dienste und Hosting-Plattformen mit. Seine wahre Stärke liegt jedoch in der Möglichkeit, eigene Challenge-Solver zu entwickeln.
Die einfachere Alternative: Unterstützte DNS-Provider verwenden
Obwohl meine SCP-Lösung für meine spezifischen Einschränkungen gut funktioniert, sollte ich erwähnen, dass es einen einfacheren Weg gibt, wenn man bei der Wahl des Domain-Registrars oder DNS-Providers flexibel ist. Lego hat eine integrierte Unterstützung für über 150 DNS-Provider, was die Zertifikatsausstellung erheblich vereinfacht, wenn man einen dieser Dienste nutzt.
Mit DNS-01-Validierung über einen unterstützten Provider kann man:
- Wildcard-Zertifikate ausstellen (was mit HTTP-Validierung nicht möglich ist)
- Domains validieren, ohne den Webserver dem Internet aussetzen zu müssen
- Den gesamten Prozess ohne eigenen Code automatisieren
- Zertifikate auch dann ausstellen, wenn Port 80 blockiert ist
Bei der DNS-01-Validierung erstellt Lego einen TXT-Eintrag unter _acme-challenge.yourdomain.com, den Let’s Encrypt überprüft. Man kann diesen Eintrag selbst mit nslookup prüfen:
$ nslookup -q=TXT _acme-challenge.yourdomain.com 8.8.8.8
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
_acme-challenge.yourdomain.com text = "IyxgKAO2vD-GRuMQgJfDKI8zcJRZwjTkYOv_xgAQmq4"
Authoritative answers can be found from:
yourdomain.com nameserver = ns1.example-dns.com.
yourdomain.com nameserver = ns2.example-dns.com.
Dieser TXT-Eintrag enthält den Validierungstoken, der beweist, dass man die Kontrolle über die Domain hat.
Wer ein neues Projekt startet oder seinen DNS migrieren kann, sollte einen dieser unterstützten Provider in Betracht ziehen:
- AWS Route 53
- Cloudflare
- DigitalOcean
- Google Cloud DNS
- Azure DNS
- OVH