# SSL/TLS Certificate Setup with Let's Encrypt Automatische SSL/TLS-Zertifikat-Verwaltung mit Let's Encrypt und Certbot für Production Deployment. ## Übersicht Das Framework nutzt **Let's Encrypt** für kostenlose, automatisch erneuernde SSL/TLS-Zertifikate via Certbot im Docker-Container. **Features**: - Automatische Zertifikat-Ausstellung - Automatische Erneuerung alle 12 Stunden - Wildcard-Zertifikate möglich (DNS-01 Challenge) - Zero-Downtime Erneuerung - Nginx-Integration via Shared Volumes ## Architektur ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Certbot │───▶│ Let's Encrypt │◀───│ ACME Server │ │ Container │ │ Certificates │ │ (HTTP-01) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ ▼ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Shared Volumes: /etc/letsencrypt, /var/www/certbot │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────┐ │ Nginx Web │ │ Container │ └─────────────────┘ ``` ## Voraussetzungen ### DNS-Konfiguration **Bevor SSL-Zertifikate ausgestellt werden können**: 1. **A Record** muss auf Server-IP zeigen: ```bash # Prüfen dig +short example.com # Erwartetes Ergebnis: Ihre Server-IP (z.B. 203.0.113.42) ``` 2. **AAAA Record** für IPv6 (optional): ```bash dig +short example.com AAAA ``` 3. **CAA Record** für Let's Encrypt (empfohlen): ```dns example.com. CAA 0 issue "letsencrypt.org" example.com. CAA 0 issuewild "letsencrypt.org" ``` ### Firewall-Konfiguration Port 80 und 443 müssen offen sein: ```bash # UFW sudo ufw allow 80/tcp sudo ufw allow 443/tcp # Iptables sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT ``` ## Erste Zertifikat-Ausstellung ### Schritt 1: Nginx-Konfiguration für ACME Challenge Erstelle temporäre Nginx-Config für initiale Zertifikat-Ausstellung: **`docker/nginx/conf.d/certbot-challenge.conf`**: ```nginx server { listen 80; listen [::]:80; server_name example.com www.example.com; # ACME Challenge für Let's Encrypt location /.well-known/acme-challenge/ { root /var/www/certbot; try_files $uri =404; } # Redirect alle anderen Requests zu HTTPS (nach Zertifikat-Ausstellung) location / { return 301 https://$host$request_uri; } } ``` ### Schritt 2: Initiales Certbot-Setup Starte nur Web und Certbot Container für initiale Zertifikat-Ausstellung: ```bash # 1. Erstelle Certbot-Verzeichnisse mkdir -p ./certbot/{conf,www,logs} # 2. Starte nur Nginx für ACME Challenge docker-compose up -d web # 3. Teste ACME Challenge Endpoint curl -I http://example.com/.well-known/acme-challenge/test # Sollte 404 zurückgeben (Endpoint erreichbar) # 4. Initiale Zertifikat-Ausstellung (dry-run) docker run --rm \ -v $(pwd)/certbot/conf:/etc/letsencrypt \ -v $(pwd)/certbot/www:/var/www/certbot \ -v $(pwd)/certbot/logs:/var/log/letsencrypt \ certbot/certbot:latest certonly \ --webroot \ --webroot-path=/var/www/certbot \ --email admin@example.com \ --agree-tos \ --no-eff-email \ --dry-run \ -d example.com \ -d www.example.com # 5. Echte Zertifikat-Ausstellung (ohne --dry-run) docker run --rm \ -v $(pwd)/certbot/conf:/etc/letsencrypt \ -v $(pwd)/certbot/www:/var/www/certbot \ -v $(pwd)/certbot/logs:/var/log/letsencrypt \ certbot/certbot:latest certonly \ --webroot \ --webroot-path=/var/www/certbot \ --email admin@example.com \ --agree-tos \ --no-eff-email \ -d example.com \ -d www.example.com ``` **Output bei Erfolg**: ``` Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/example.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/example.com/privkey.pem Your cert will expire on 2025-04-15. ``` ### Schritt 3: Nginx HTTPS-Konfiguration Nach erfolgreicher Zertifikat-Ausstellung, aktiviere HTTPS: **`docker/nginx/conf.d/default.conf`** (Production): ```nginx # HTTP Server - Redirect zu HTTPS server { listen 80; listen [::]:80; server_name example.com www.example.com; # ACME Challenge für Zertifikat-Erneuerung location /.well-known/acme-challenge/ { root /var/www/certbot; try_files $uri =404; } # Redirect zu HTTPS location / { return 301 https://$host$request_uri; } } # HTTPS Server server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com www.example.com; # SSL Zertifikate ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # SSL Konfiguration (Mozilla Modern) ssl_protocols TLSv1.3; ssl_prefer_server_ciphers off; ssl_session_timeout 1d; ssl_session_cache shared:SSL:10m; ssl_session_tickets off; # OCSP Stapling ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem; # Security Headers add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; # Application root /var/www/html/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass php:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } ``` ### Schritt 4: Starte Production Stack ```bash # Mit Certbot für automatische Erneuerung docker-compose -f docker-compose.yml -f docker-compose.production.yml up -d # Prüfe Logs docker-compose logs -f certbot # Teste HTTPS curl -I https://example.com ``` ## Automatische Erneuerung Der Certbot-Container erneuert Zertifikate automatisch alle 12 Stunden: **`docker-compose.production.yml`**: ```yaml certbot: image: certbot/certbot:latest entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot --quiet; sleep 12h & wait $${!}; done;'" volumes: - certbot-conf:/etc/letsencrypt - certbot-www:/var/www/certbot - certbot-logs:/var/log/letsencrypt ``` **Manuelle Erneuerung**: ```bash # Test Renewal (dry-run) docker-compose exec certbot certbot renew --dry-run # Force Renewal (vor Ablauf) docker-compose exec certbot certbot renew --force-renewal # Nginx Reload nach Erneuerung docker-compose exec web nginx -s reload ``` ## Wildcard-Zertifikate (DNS-01 Challenge) Für Wildcard-Zertifikate (*.example.com) ist DNS-01 Challenge erforderlich: ### Voraussetzungen - DNS-Provider API-Zugang (Cloudflare, Route53, etc.) - Certbot DNS Plugin installiert ### Beispiel: Cloudflare **`docker-compose.production.yml`** (erweitert): ```yaml certbot: image: certbot/dns-cloudflare:latest environment: - CLOUDFLARE_EMAIL=admin@example.com - CLOUDFLARE_API_KEY=${CLOUDFLARE_API_KEY} volumes: - certbot-conf:/etc/letsencrypt - ./certbot/cloudflare.ini:/cloudflare.ini:ro command: certonly --dns-cloudflare --dns-cloudflare-credentials /cloudflare.ini -d example.com -d *.example.com ``` **`certbot/cloudflare.ini`**: ```ini dns_cloudflare_email = admin@example.com dns_cloudflare_api_key = your_cloudflare_api_key ``` **Ausstellung**: ```bash docker run --rm \ -v $(pwd)/certbot/conf:/etc/letsencrypt \ -v $(pwd)/certbot/cloudflare.ini:/cloudflare.ini:ro \ certbot/dns-cloudflare:latest certonly \ --dns-cloudflare \ --dns-cloudflare-credentials /cloudflare.ini \ --email admin@example.com \ --agree-tos \ --no-eff-email \ -d example.com \ -d *.example.com ``` ## Zertifikat-Monitoring ### Ablaufdatum prüfen ```bash # Via OpenSSL echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates # Via Certbot docker-compose exec certbot certbot certificates # Output: # Certificate Name: example.com # Expiry Date: 2025-04-15 12:34:56+00:00 (VALID: 89 days) ``` ### Automatisches Monitoring **Nagios/Icinga Check**: ```bash #!/bin/bash DAYS_LEFT=$(echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | \ openssl x509 -noout -checkend $((86400 * 30))) if [ $? -eq 0 ]; then echo "OK - Certificate valid for more than 30 days" exit 0 else echo "CRITICAL - Certificate expires in less than 30 days" exit 2 fi ``` ## Troubleshooting ### Problem: ACME Challenge fehlgeschlagen **Symptom**: ``` Challenge failed for domain example.com ``` **Lösung**: ```bash # 1. Prüfe DNS dig +short example.com # Muss auf Server-IP zeigen # 2. Prüfe Port 80 erreichbar curl -I http://example.com/.well-known/acme-challenge/test # 3. Prüfe Nginx Logs docker-compose logs web # 4. Prüfe Certbot Logs docker-compose logs certbot cat certbot/logs/letsencrypt.log ``` ### Problem: Rate Limit erreicht Let's Encrypt hat Rate Limits: - 50 Zertifikate pro Domain pro Woche - 5 fehlgeschlagene Validierungen pro Stunde **Lösung**: ```bash # Nutze Staging-Umgebung für Tests docker run --rm \ -v $(pwd)/certbot/conf:/etc/letsencrypt \ -v $(pwd)/certbot/www:/var/www/certbot \ certbot/certbot:latest certonly \ --staging \ --webroot -w /var/www/certbot \ -d example.com # Warte 1 Stunde bei fehlgeschlagenen Validierungen ``` ### Problem: Zertifikat-Erneuerung schlägt fehl **Symptom**: ``` Failed to renew certificate example.com ``` **Lösung**: ```bash # 1. Manuelle Erneuerung mit Debug docker-compose exec certbot certbot renew --force-renewal --debug # 2. Prüfe Webroot-Pfad docker-compose exec web ls -la /var/www/certbot/.well-known/acme-challenge/ # 3. Prüfe Nginx Config docker-compose exec web nginx -t # 4. Reload Nginx nach Config-Änderung docker-compose exec web nginx -s reload ``` ### Problem: Mixed Content Warnings Nach HTTPS-Umstellung erscheinen Mixed Content Warnings. **Lösung**: ```nginx # Content Security Policy Header add_header Content-Security-Policy "upgrade-insecure-requests" always; # In Application # Verwende relative URLs oder HTTPS: # Relativ - empfohlen # Absolut HTTPS ``` ## Security Best Practices ### 1. SSL-Konfiguration Härten **Mozilla SSL Configuration Generator**: https://ssl-config.mozilla.org/ ```nginx # Modern Configuration (nur TLS 1.3) ssl_protocols TLSv1.3; # Intermediate Configuration (TLS 1.2 + 1.3) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...'; ssl_prefer_server_ciphers off; ``` ### 2. HSTS Preload Nach erfolgreicher HTTPS-Umstellung: 1. **HSTS Header** mit Preload: ```nginx add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; ``` 2. **Submit to Preload List**: https://hstspreload.org/ ### 3. OCSP Stapling Verbessert SSL-Handshake-Performance: ```nginx ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s; ``` ### 4. Certificate Transparency Monitoring Monitor für Certificate Transparency Logs: - https://crt.sh/?q=example.com - https://transparencyreport.google.com/https/certificates ## Testing & Validation ### SSL Labs Test Teste SSL-Konfiguration: ```bash # Online https://www.ssllabs.com/ssltest/analyze.html?d=example.com # CLI via testssl.sh docker run --rm -ti drwetter/testssl.sh:latest example.com ``` **Ziel: A+ Rating** ### Security Headers Check ```bash curl -I https://example.com | grep -i "strict-transport-security\|x-frame-options\|x-content-type-options" ``` ### Certificate Chain Validation ```bash openssl s_client -connect example.com:443 -showcerts ``` ## Backup & Recovery ### Backup Zertifikate ```bash # Backup /etc/letsencrypt docker run --rm \ -v certbot-conf:/etc/letsencrypt \ -v $(pwd)/backups:/backups \ alpine tar czf /backups/letsencrypt-$(date +%Y%m%d).tar.gz -C / etc/letsencrypt # Verschlüsselt gpg --symmetric --cipher-algo AES256 backups/letsencrypt-$(date +%Y%m%d).tar.gz ``` ### Restore Zertifikate ```bash # Entschlüsseln gpg --decrypt backups/letsencrypt-20250115.tar.gz.gpg > letsencrypt-restore.tar.gz # Restore docker run --rm \ -v certbot-conf:/etc/letsencrypt \ -v $(pwd):/backups \ alpine tar xzf /backups/letsencrypt-restore.tar.gz -C / # Nginx Reload docker-compose exec web nginx -s reload ``` ## Automation Scripts Siehe: - `scripts/ssl-setup.sh` - Initiale SSL-Einrichtung - `scripts/ssl-renew.sh` - Manuelle Erneuerung - `scripts/ssl-check.sh` - Status-Check ## Next Steps Nach SSL-Setup: 1. **Teste HTTPS**: https://example.com 2. **SSL Labs Test**: A+ Rating verifizieren 3. **Monitor Ablaufdatum**: Automatisches Monitoring einrichten 4. **HSTS Preload**: Nach Stabilisierung eintragen 5. **Firewall**: Port 80 nur für ACME Challenge offen lassen