que paso?
This commit is contained in:
0
.claude/settings.local.json
Executable file → Normal file
0
.claude/settings.local.json
Executable file → Normal file
0
.env.example
Executable file → Normal file
0
.env.example
Executable file → Normal file
0
.gitignore
vendored
Executable file → Normal file
0
.gitignore
vendored
Executable file → Normal file
291
CLOUDFLARE.md
Normal file
291
CLOUDFLARE.md
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
# Traefik + Cloudflare Tunnel - Configuración
|
||||||
|
|
||||||
|
Esta guía explica cómo funciona Traefik detrás de Cloudflare Tunnel y cómo obtener las IPs reales de los visitantes.
|
||||||
|
|
||||||
|
## 🌐 Arquitectura
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet → Cloudflare (SSL/DDoS) → Cloudflare Tunnel → Traefik (443) → Contenedores
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📍 IPs Reales de Visitantes
|
||||||
|
|
||||||
|
### ¿Cómo funciona?
|
||||||
|
|
||||||
|
Cuando un usuario visita tu sitio:
|
||||||
|
|
||||||
|
1. **Cloudflare recibe la conexión** y conoce la IP real del visitante
|
||||||
|
2. **Cloudflare envía la IP real en headers HTTP:**
|
||||||
|
- `CF-Connecting-IP`: IP real del cliente ✅ (más confiable)
|
||||||
|
- `X-Forwarded-For`: Cadena de proxies
|
||||||
|
- `X-Real-IP`: IP real (alternativa)
|
||||||
|
3. **Traefik lee estos headers** y los pasa a tus aplicaciones
|
||||||
|
|
||||||
|
### Configuración aplicada
|
||||||
|
|
||||||
|
Ya está configurado en `traefik/traefik.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
entryPoints:
|
||||||
|
websecure:
|
||||||
|
forwardedHeaders:
|
||||||
|
trustedIPs:
|
||||||
|
- "173.245.48.0/20" # Rangos de IPs de Cloudflare
|
||||||
|
- "103.21.244.0/22"
|
||||||
|
# ... etc (23 rangos en total)
|
||||||
|
```
|
||||||
|
|
||||||
|
Esto le dice a Traefik: **"Confía en los headers de IPs que vienen de Cloudflare"**
|
||||||
|
|
||||||
|
### ✅ Verificar que funciona
|
||||||
|
|
||||||
|
1. **Inicia Traefik:**
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Verifica los logs de acceso:**
|
||||||
|
```bash
|
||||||
|
docker compose logs traefik | grep "CF-Connecting-IP"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Deberías ver la IP real en los logs:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ClientAddr": "173.245.48.5:12345", // IP de Cloudflare
|
||||||
|
"headers": {
|
||||||
|
"CF-Connecting-IP": "187.123.45.67" // ← IP REAL del visitante
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Leer IP real desde tus aplicaciones
|
||||||
|
|
||||||
|
### Node.js / Express
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
const realIP = req.headers['cf-connecting-ip']
|
||||||
|
|| req.headers['x-forwarded-for']?.split(',')[0]
|
||||||
|
|| req.connection.remoteAddress;
|
||||||
|
|
||||||
|
console.log('IP real del visitante:', realIP);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python / Flask
|
||||||
|
|
||||||
|
```python
|
||||||
|
from flask import Flask, request
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
real_ip = request.headers.get('CF-Connecting-IP') \
|
||||||
|
or request.headers.get('X-Forwarded-For', '').split(',')[0] \
|
||||||
|
or request.remote_addr
|
||||||
|
|
||||||
|
print(f'IP real del visitante: {real_ip}')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go / Gin
|
||||||
|
|
||||||
|
```go
|
||||||
|
func handler(c *gin.Context) {
|
||||||
|
realIP := c.GetHeader("CF-Connecting-IP")
|
||||||
|
if realIP == "" {
|
||||||
|
realIP = c.ClientIP()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("IP real del visitante: %s\n", realIP)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PHP
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$real_ip = $_SERVER['HTTP_CF_CONNECTING_IP']
|
||||||
|
?? explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]
|
||||||
|
?? $_SERVER['REMOTE_ADDR'];
|
||||||
|
|
||||||
|
echo "IP real del visitante: $real_ip";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nginx (dentro de contenedor)
|
||||||
|
|
||||||
|
Si tienes nginx dentro de un contenedor detrás de Traefik:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
location / {
|
||||||
|
# Traefik ya pasó los headers, solo léelos
|
||||||
|
set $real_ip $http_cf_connecting_ip;
|
||||||
|
|
||||||
|
# O usa el módulo real_ip
|
||||||
|
real_ip_header CF-Connecting-IP;
|
||||||
|
|
||||||
|
# Loguea la IP real
|
||||||
|
access_log /var/log/nginx/access.log combined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛡️ Seguridad
|
||||||
|
|
||||||
|
### ¿Por qué confiar solo en IPs de Cloudflare?
|
||||||
|
|
||||||
|
La configuración `trustedIPs` es crucial para **evitar IP spoofing**:
|
||||||
|
|
||||||
|
❌ **Sin trustedIPs:**
|
||||||
|
```
|
||||||
|
Atacante envía: X-Forwarded-For: 1.1.1.1
|
||||||
|
Traefik cree que la IP real es 1.1.1.1 (FALSO)
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Con trustedIPs (configurado):**
|
||||||
|
```
|
||||||
|
Si la petición viene de 192.168.1.1 (no es IP de Cloudflare):
|
||||||
|
→ Traefik IGNORA el header X-Forwarded-For
|
||||||
|
→ Usa la IP de conexión real
|
||||||
|
|
||||||
|
Si viene de 173.245.48.5 (IP de Cloudflare):
|
||||||
|
→ Traefik CONFÍA en CF-Connecting-IP ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
### Actualizar rangos de IPs de Cloudflare
|
||||||
|
|
||||||
|
Cloudflare publica sus IPs oficiales:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# IPv4
|
||||||
|
curl https://www.cloudflare.com/ips-v4
|
||||||
|
|
||||||
|
# IPv6
|
||||||
|
curl https://www.cloudflare.com/ips-v6
|
||||||
|
```
|
||||||
|
|
||||||
|
Si Cloudflare agrega nuevos rangos, actualiza `traefik/traefik.yml`.
|
||||||
|
|
||||||
|
## 🔐 SSL/TLS con Cloudflare
|
||||||
|
|
||||||
|
### Opciones de SSL en Cloudflare
|
||||||
|
|
||||||
|
En el dashboard de Cloudflare → SSL/TLS, tienes 3 opciones:
|
||||||
|
|
||||||
|
1. **Flexible** (❌ No recomendado)
|
||||||
|
- Cloudflare ↔ Cliente: HTTPS ✅
|
||||||
|
- Cloudflare ↔ Traefik: HTTP ❌
|
||||||
|
- Vulnerable a ataques man-in-the-middle
|
||||||
|
|
||||||
|
2. **Full** (⚠️ Aceptable)
|
||||||
|
- Cloudflare ↔ Cliente: HTTPS ✅
|
||||||
|
- Cloudflare ↔ Traefik: HTTPS ✅ (autofirmado OK)
|
||||||
|
- Más seguro
|
||||||
|
|
||||||
|
3. **Full (Strict)** (✅ Recomendado)
|
||||||
|
- Cloudflare ↔ Cliente: HTTPS ✅
|
||||||
|
- Cloudflare ↔ Traefik: HTTPS con certificado válido ✅
|
||||||
|
- Máxima seguridad
|
||||||
|
|
||||||
|
### Tu configuración actual
|
||||||
|
|
||||||
|
Con Cloudflare Tunnel apuntando a puerto **443**:
|
||||||
|
|
||||||
|
- Cloudflare → Tunnel → Traefik:443 (HTTPS)
|
||||||
|
- Traefik puede usar:
|
||||||
|
- **Certificados Let's Encrypt** (ya configurado en `traefik.yml`)
|
||||||
|
- O certificados autofirmados para el túnel
|
||||||
|
|
||||||
|
**Recomendación**: Usar **Full** o **Full (Strict)** en Cloudflare.
|
||||||
|
|
||||||
|
## 📊 Monitoreo de IPs
|
||||||
|
|
||||||
|
### Ver IPs en logs de Traefik
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ver logs en tiempo real con IPs
|
||||||
|
docker compose logs -f traefik
|
||||||
|
|
||||||
|
# Filtrar solo IPs de Cloudflare
|
||||||
|
docker compose logs traefik | grep "CF-Connecting-IP"
|
||||||
|
|
||||||
|
# Ver estadísticas de IPs únicas
|
||||||
|
docker exec traefik cat /var/log/traefik/access.log | \
|
||||||
|
jq -r '.headers["CF-Connecting-IP"]' | \
|
||||||
|
sort | uniq -c | sort -rn
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dashboard de Traefik
|
||||||
|
|
||||||
|
El dashboard muestra:
|
||||||
|
- ❌ IP de Cloudflare (173.245.x.x) en "Client Address"
|
||||||
|
- ✅ IP real disponible en logs JSON
|
||||||
|
|
||||||
|
## 🧪 Prueba rápida
|
||||||
|
|
||||||
|
Después de iniciar Traefik:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Simular petición desde "fuera" (pasando por Cloudflare)
|
||||||
|
curl -H "CF-Connecting-IP: 187.123.45.67" \
|
||||||
|
-H "Host: whoami.nucleoriofrio.com" \
|
||||||
|
http://localhost
|
||||||
|
|
||||||
|
# 2. Ver los logs y buscar la IP
|
||||||
|
docker compose logs traefik | tail -20
|
||||||
|
```
|
||||||
|
|
||||||
|
## ❓ FAQ
|
||||||
|
|
||||||
|
### ¿Necesito certificados SSL si uso Cloudflare?
|
||||||
|
|
||||||
|
**Para el tunnel NO** (Cloudflare maneja SSL público), **pero sí es recomendable** para:
|
||||||
|
- Seguridad adicional Cloudflare ↔ Traefik
|
||||||
|
- Servicios internos
|
||||||
|
- Modo Full (Strict) de Cloudflare
|
||||||
|
|
||||||
|
### ¿Puedo bloquear acceso directo al puerto 443?
|
||||||
|
|
||||||
|
**SÍ, deberías hacerlo**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Firewall: permitir solo Cloudflare IPs en 443
|
||||||
|
sudo ufw allow from 173.245.48.0/20 to any port 443
|
||||||
|
sudo ufw allow from 103.21.244.0/22 to any port 443
|
||||||
|
# ... agregar todos los rangos
|
||||||
|
|
||||||
|
# O usar iptables
|
||||||
|
sudo iptables -A INPUT -p tcp --dport 443 -s 173.245.48.0/20 -j ACCEPT
|
||||||
|
sudo iptables -A INPUT -p tcp --dport 443 -j DROP
|
||||||
|
```
|
||||||
|
|
||||||
|
Esto asegura que **solo Cloudflare** pueda llegar a Traefik.
|
||||||
|
|
||||||
|
### ¿Qué pasa si la IP en logs es de Cloudflare?
|
||||||
|
|
||||||
|
Verifica:
|
||||||
|
|
||||||
|
1. **Headers en logs:** `grep "CF-Connecting-IP" access.log`
|
||||||
|
2. **trustedIPs configurado:** Revisa `traefik.yml`
|
||||||
|
3. **Cloudflare enviando headers:** Verifica en Cloudflare dashboard
|
||||||
|
|
||||||
|
### ¿Los rangos de IP de Cloudflare cambian?
|
||||||
|
|
||||||
|
Sí, ocasionalmente. Verifica periódicamente:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://www.cloudflare.com/ips-v4
|
||||||
|
```
|
||||||
|
|
||||||
|
Y actualiza `traefik.yml` si hay cambios.
|
||||||
|
|
||||||
|
## 📚 Referencias
|
||||||
|
|
||||||
|
- [Cloudflare IP Ranges](https://www.cloudflare.com/ips/)
|
||||||
|
- [Traefik ForwardedHeaders](https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers)
|
||||||
|
- [Cloudflare Headers](https://developers.cloudflare.com/fundamentals/reference/http-request-headers/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Última actualización**: Octubre 2025
|
||||||
|
**Rangos de IP Cloudflare verificados**: Octubre 2025
|
||||||
0
docker-compose.yml
Executable file → Normal file
0
docker-compose.yml
Executable file → Normal file
0
dynamic/middlewares.yml
Executable file → Normal file
0
dynamic/middlewares.yml
Executable file → Normal file
0
examples/existing-service.yml
Executable file → Normal file
0
examples/existing-service.yml
Executable file → Normal file
0
examples/whoami-service.yml
Executable file → Normal file
0
examples/whoami-service.yml
Executable file → Normal file
0
traefik/traefik.yml
Executable file → Normal file
0
traefik/traefik.yml
Executable file → Normal file
Reference in New Issue
Block a user