Actualizar Traefik.md: limpiar duplicados y añadir serversTransport insecure@file
481
Traefik.md
481
Traefik.md
@@ -60,6 +60,7 @@ services:
|
||||
# Service backend
|
||||
- "traefik.http.services.portainer.loadbalancer.server.port=9443"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.scheme=https"
|
||||
- "traefik.http.services.portainer.loadbalancer.serversTransport=insecure@file"
|
||||
|
||||
# Middlewares de seguridad (opcional)
|
||||
- "traefik.http.routers.portainer.middlewares=security-headers@file"
|
||||
@@ -180,9 +181,19 @@ El middleware `ip-allowlist@file` ya incluye rangos privados (10.0.0.0/8, 172.16
|
||||
email: tu-email@tudominio.com # Debe estar configurado
|
||||
```
|
||||
|
||||
### Certificado Autofirmado en Logs
|
||||
### Certificado Autofirmado (Error 500)
|
||||
|
||||
Es normal. Traefik se comunica con Portainer por HTTPS (cert autofirmado interno), pero el usuario final ve el certificado de Let's Encrypt.
|
||||
Portainer usa certificado SSL autofirmado. Traefik necesita el `serversTransport` para aceptarlo:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.services.portainer.loadbalancer.server.scheme=https"
|
||||
- "traefik.http.services.portainer.loadbalancer.serversTransport=insecure@file"
|
||||
```
|
||||
|
||||
El transport `insecure@file` está definido en `dynamic/config.yml` del repositorio de Traefik.
|
||||
|
||||
**Nota**: El usuario final ve el certificado de Let's Encrypt, no el autofirmado de Portainer.
|
||||
|
||||
## Ejemplo Completo
|
||||
|
||||
@@ -200,6 +211,7 @@ services:
|
||||
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.port=9443"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.scheme=https"
|
||||
- "traefik.http.services.portainer.loadbalancer.serversTransport=insecure@file"
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
@@ -208,6 +220,26 @@ networks:
|
||||
|
||||
### docker-compose.override.yaml con Seguridad
|
||||
|
||||
```yaml
|
||||
services:
|
||||
portainer:
|
||||
networks:
|
||||
- proxy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.portainer.rule=Host(`portainer.tudominio.com`)"
|
||||
- "traefik.http.routers.portainer.entrypoints=websecure"
|
||||
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.port=9443"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.scheme=https"
|
||||
- "traefik.http.services.portainer.loadbalancer.serversTransport=insecure@file"
|
||||
- "traefik.http.routers.portainer.middlewares=security-headers@file,rate-limit@file"
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
```
|
||||
|
||||
```yaml
|
||||
services:
|
||||
portainer:
|
||||
@@ -234,448 +266,3 @@ networks:
|
||||
- [Wiki Traefik](https://git.ictiberia.com/groales/traefik/wiki)
|
||||
|
||||
**Volver a**: [Página Principal](Home)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
portainer:
|
||||
networks:
|
||||
- traefik_network
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.portainer-http.rule=Host(`portainer.tudominio.com`)"
|
||||
- "traefik.http.routers.portainer-http.entrypoints=web"
|
||||
- "traefik.http.routers.portainer-http.middlewares=redirect-to-https"
|
||||
- "traefik.http.routers.portainer.rule=Host(`portainer.tudominio.com`)"
|
||||
- "traefik.http.routers.portainer.entrypoints=websecure"
|
||||
- "traefik.http.routers.portainer.tls=true"
|
||||
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.port=9443"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.scheme=https"
|
||||
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
|
||||
- "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true"
|
||||
|
||||
networks:
|
||||
traefik_network:
|
||||
external: true
|
||||
```
|
||||
|
||||
**Ventaja**: El `docker-compose.yaml` base permanece sin cambios, y los overrides se aplican automáticamente.
|
||||
|
||||
### 4. Reiniciar Portainer
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 5. Verificar
|
||||
|
||||
```bash
|
||||
# Ver logs de Traefik
|
||||
docker logs traefik
|
||||
|
||||
# Verificar que Portainer está en la red de Traefik
|
||||
docker network inspect traefik_network
|
||||
```
|
||||
|
||||
Acceder a: `https://portainer.tudominio.com`
|
||||
|
||||
## Configuración de Traefik
|
||||
|
||||
### traefik.yml Mínimo
|
||||
|
||||
Si aún no tienes Traefik configurado, aquí un ejemplo básico:
|
||||
|
||||
```yaml
|
||||
# traefik.yml
|
||||
api:
|
||||
dashboard: true
|
||||
insecure: false
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
http:
|
||||
redirections:
|
||||
entryPoint:
|
||||
to: websecure
|
||||
scheme: https
|
||||
websecure:
|
||||
address: ":443"
|
||||
|
||||
providers:
|
||||
docker:
|
||||
endpoint: "unix:///var/run/docker.sock"
|
||||
exposedByDefault: false
|
||||
network: traefik_network
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: admin@tudominio.com
|
||||
storage: /letsencrypt/acme.json
|
||||
httpChallenge:
|
||||
entryPoint: web
|
||||
```
|
||||
|
||||
### docker-compose.yml de Traefik
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:latest
|
||||
container_name: traefik
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "8080:8080" # Dashboard (proteger en producción)
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./traefik.yml:/traefik.yml:ro
|
||||
- ./letsencrypt:/letsencrypt
|
||||
networks:
|
||||
- traefik_network
|
||||
labels:
|
||||
# Dashboard (opcional, configurar autenticación)
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.traefik.rule=Host(`traefik.tudominio.com`)"
|
||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
||||
- "traefik.http.routers.traefik.tls=true"
|
||||
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.traefik.service=api@internal"
|
||||
|
||||
networks:
|
||||
traefik_network:
|
||||
external: true
|
||||
```
|
||||
|
||||
Iniciar Traefik:
|
||||
|
||||
```bash
|
||||
docker network create traefik_network
|
||||
mkdir letsencrypt
|
||||
touch letsencrypt/acme.json
|
||||
chmod 600 letsencrypt/acme.json
|
||||
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Configuración Avanzada
|
||||
|
||||
### Autenticación Básica (Opcional)
|
||||
|
||||
Proteger Portainer con autenticación HTTP básica:
|
||||
|
||||
```bash
|
||||
# Generar password hash
|
||||
echo $(htpasswd -nb admin tu_password) | sed -e s/\\$/\\$\\$/g
|
||||
# Resultado: admin:$$apr1$$...
|
||||
```
|
||||
|
||||
Añadir labels:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
# ... labels existentes ...
|
||||
- "traefik.http.routers.portainer.middlewares=portainer-auth"
|
||||
- "traefik.http.middlewares.portainer-auth.basicauth.users=admin:$$apr1$$..."
|
||||
```
|
||||
|
||||
### Headers de Seguridad
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
# ... labels existentes ...
|
||||
- "traefik.http.routers.portainer.middlewares=security-headers"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsPreload=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.forceSTSHeader=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.frameDeny=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.browserXssFilter=true"
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
Limitar peticiones para prevenir abusos:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
# ... labels existentes ...
|
||||
- "traefik.http.routers.portainer.middlewares=rate-limit"
|
||||
- "traefik.http.middlewares.rate-limit.ratelimit.average=100"
|
||||
- "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
|
||||
```
|
||||
|
||||
### IP Whitelist
|
||||
|
||||
Restringir acceso solo desde IPs específicas:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
# ... labels existentes ...
|
||||
- "traefik.http.routers.portainer.middlewares=ip-whitelist"
|
||||
- "traefik.http.middlewares.ip-whitelist.ipwhitelist.sourcerange=192.168.1.0/24,10.0.0.0/8"
|
||||
```
|
||||
|
||||
### Múltiples Dominios
|
||||
|
||||
Acceder a Portainer desde varios dominios:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.portainer.rule=Host(`portainer.tudominio.com`) || Host(`docker.tudominio.com`)"
|
||||
```
|
||||
|
||||
### Configuración con Subdirectorio
|
||||
|
||||
Acceder en `https://tudominio.com/portainer`:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.portainer.rule=Host(`tudominio.com`) && PathPrefix(`/portainer`)"
|
||||
- "traefik.http.routers.portainer.middlewares=portainer-stripprefix"
|
||||
- "traefik.http.middlewares.portainer-stripprefix.stripprefix.prefixes=/portainer"
|
||||
```
|
||||
|
||||
⚠️ **Nota**: Portainer puede tener problemas con subdirectorios. Recomendamos usar subdominios.
|
||||
|
||||
## Wildcard Certificates
|
||||
|
||||
Para certificados wildcard con DNS challenge:
|
||||
|
||||
```yaml
|
||||
# traefik.yml
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: admin@tudominio.com
|
||||
storage: /letsencrypt/acme.json
|
||||
dnsChallenge:
|
||||
provider: cloudflare # O tu proveedor DNS
|
||||
delayBeforeCheck: 30
|
||||
```
|
||||
|
||||
Variables de entorno para Cloudflare:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml de Traefik
|
||||
services:
|
||||
traefik:
|
||||
environment:
|
||||
- CF_API_EMAIL=tu@email.com
|
||||
- CF_API_KEY=tu_api_key
|
||||
```
|
||||
|
||||
Labels en Portainer:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.portainer.tls.domains[0].main=tudominio.com"
|
||||
- "traefik.http.routers.portainer.tls.domains[0].sans=*.tudominio.com"
|
||||
```
|
||||
|
||||
## Docker Compose Completo
|
||||
|
||||
### Portainer con Traefik
|
||||
|
||||
Archivo completo `docker-compose.override.yaml`:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
portainer:
|
||||
networks:
|
||||
- traefik_network
|
||||
labels:
|
||||
# Habilitar Traefik
|
||||
- "traefik.enable=true"
|
||||
|
||||
# Dominio
|
||||
- "traefik.http.routers.portainer.rule=Host(`portainer.tudominio.com`)"
|
||||
|
||||
# Entrypoints
|
||||
- "traefik.http.routers.portainer.entrypoints=websecure"
|
||||
|
||||
# TLS
|
||||
- "traefik.http.routers.portainer.tls=true"
|
||||
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
|
||||
|
||||
# Service backend
|
||||
- "traefik.http.services.portainer.loadbalancer.server.port=9443"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.scheme=https"
|
||||
|
||||
# Middlewares (opcional)
|
||||
- "traefik.http.routers.portainer.middlewares=security-headers"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.frameDeny=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.browserXssFilter=true"
|
||||
|
||||
networks:
|
||||
traefik_network:
|
||||
external: true
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Certificado No Se Genera
|
||||
|
||||
1. **Verificar logs de Traefik**:
|
||||
```bash
|
||||
docker logs traefik | grep -i error
|
||||
```
|
||||
|
||||
2. **Revisar permisos de acme.json**:
|
||||
```bash
|
||||
chmod 600 letsencrypt/acme.json
|
||||
```
|
||||
|
||||
3. **Verificar DNS**:
|
||||
```bash
|
||||
nslookup portainer.tudominio.com
|
||||
dig portainer.tudominio.com
|
||||
```
|
||||
|
||||
4. **Rate limits de Let's Encrypt**:
|
||||
- Máximo 5 certificados por semana por dominio
|
||||
- Usar staging durante pruebas:
|
||||
```yaml
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
caServer: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
```
|
||||
|
||||
### Error 502 Bad Gateway
|
||||
|
||||
1. **Verificar que Portainer está corriendo**:
|
||||
```bash
|
||||
docker ps | grep portainer
|
||||
```
|
||||
|
||||
2. **Verificar red compartida**:
|
||||
```bash
|
||||
docker network inspect traefik_network | grep portainer
|
||||
```
|
||||
|
||||
3. **Revisar puerto del servicio**:
|
||||
```yaml
|
||||
# Asegurarse de usar el puerto interno correcto
|
||||
- "traefik.http.services.portainer.loadbalancer.server.port=9443"
|
||||
```
|
||||
|
||||
4. **Verificar scheme HTTPS**:
|
||||
```yaml
|
||||
- "traefik.http.services.portainer.loadbalancer.server.scheme=https"
|
||||
```
|
||||
|
||||
### Redirección Infinita
|
||||
|
||||
Si hay loop de redirecciones:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
# Remover redirección HTTP si Traefik ya lo maneja globalmente
|
||||
# Comentar estas líneas:
|
||||
# - "traefik.http.routers.portainer-http.rule=..."
|
||||
# - "traefik.http.routers.portainer-http.middlewares=redirect-to-https"
|
||||
```
|
||||
|
||||
### Certificado Autofirmado Aún Se Muestra
|
||||
|
||||
Portainer usa su propio certificado SSL. Traefik termina SSL antes de llegar a Portainer, por lo que:
|
||||
|
||||
1. **Es normal** que Portainer siga usando su cert autofirmado internamente
|
||||
2. El usuario final ve el certificado de Let's Encrypt de Traefik
|
||||
3. Traefik se comunica con Portainer por HTTPS (con cert autofirmado)
|
||||
|
||||
Si quieres evitar warnings en logs de Traefik:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.services.portainer.loadbalancer.server.scheme=https"
|
||||
- "traefik.http.services.portainer.loadbalancer.serversTransport=ignorecert"
|
||||
|
||||
# En traefik.yml:
|
||||
# http:
|
||||
# serversTransports:
|
||||
# ignorecert:
|
||||
# insecureSkipVerify: true
|
||||
```
|
||||
|
||||
## Monitorización
|
||||
|
||||
### Logs de Traefik
|
||||
|
||||
```bash
|
||||
# Ver requests a Portainer
|
||||
docker logs -f traefik | grep portainer
|
||||
|
||||
# Ver errores TLS
|
||||
docker logs traefik | grep -i "tls\|certificate"
|
||||
```
|
||||
|
||||
### Verificar Certificado SSL
|
||||
|
||||
```bash
|
||||
# Ver información del certificado
|
||||
echo | openssl s_client -servername portainer.tudominio.com -connect localhost:443 2>/dev/null | openssl x509 -noout -dates
|
||||
|
||||
# Verificar desde exterior
|
||||
curl -vI https://portainer.tudominio.com 2>&1 | grep -i "SSL\|certificate"
|
||||
```
|
||||
|
||||
## Seguridad Adicional
|
||||
|
||||
### 1. Fail2ban con Traefik
|
||||
|
||||
Proteger contra brute-force:
|
||||
|
||||
```bash
|
||||
# /etc/fail2ban/filter.d/traefik-auth.conf
|
||||
[Definition]
|
||||
failregex = ^<HOST> - - .* "(GET|POST|HEAD).*HTTP.*" 401 .*$
|
||||
ignoreregex =
|
||||
```
|
||||
|
||||
```ini
|
||||
# /etc/fail2ban/jail.local
|
||||
[traefik-auth]
|
||||
enabled = true
|
||||
port = http,https
|
||||
logpath = /var/log/traefik/access.log
|
||||
maxretry = 5
|
||||
bantime = 3600
|
||||
```
|
||||
|
||||
### 2. Autenticación de Dos Factores
|
||||
|
||||
Portainer tiene 2FA integrado (no depende de Traefik):
|
||||
|
||||
1. **Settings** → **Authentication**
|
||||
2. Habilitar **OAuth** o **LDAP** con 2FA
|
||||
3. O usar middleware de Traefik con OAuth2 Proxy
|
||||
|
||||
### 3. VPN como Alternativa
|
||||
|
||||
Para máxima seguridad, no exponer Portainer a Internet:
|
||||
|
||||
1. Acceso solo via VPN (WireGuard, OpenVPN)
|
||||
2. Traefik escucha solo en IP privada
|
||||
3. Usuarios conectan primero a VPN
|
||||
|
||||
---
|
||||
|
||||
**Recursos adicionales**:
|
||||
- [Documentación Traefik](https://doc.traefik.io/traefik/)
|
||||
- [Let's Encrypt Rate Limits](https://letsencrypt.org/docs/rate-limits/)
|
||||
- [Traefik Middlewares](https://doc.traefik.io/traefik/middlewares/overview/)
|
||||
|
||||
**Volver a**: [Página Principal](Home)
|
||||
|
||||
Reference in New Issue
Block a user