# 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:
<script src="/js/app.js"></script>  # Relativ - empfohlen
<script src="https://example.com/js/app.js"></script>  # 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
