TrafficServerLocal/docs/http-https.md
merios 33c368936e feat: infraestructura base de Traefik + generador de proyectos
- core/: reverse proxy Traefik v3 (HTTP 80 / HTTPS 443)
  - traefik.yml con provider Docker (exposedByDefault: false) y provider file
  - dynamic_tls.yml sanitizado con certificados .local comentados
  - certs/README.md con procedimiento de certificados auto-firmados
- init_project.sh: generador de proyectos base (SRV_DIR configurable)
  - plantilla compose con redireccion HTTP->HTTPS + Let's Encrypt
  - bloque alternativo comentado para dominios .local
- README.md: documentacion integral (puesta en marcha, HTTP/HTTPS,
  troubleshooting y seguridad)
- docs/arquitectura.md: guia de instalacion y gestion de URLs
- docs/http-https.md: referencia de etiquetas Traefik, TLS y certresolver
- .gitignore: excluye .env, acme.json, certificados, datos y respaldos
2026-10-15 11:34:39 -03:00

248 lines
8.0 KiB
Markdown

# HTTP / HTTPS en Traefik — referencia de etiquetas
Todos los ejemplos son etiquetas de Docker Compose aplicables al servicio `web`
del proyecto. Traefik las lee por el socket de Docker y construye la cadena
**Router → Middleware → Service**.
---
## 1. Anatomía de una etiqueta
```text
traefik.http.<scope>.<component>.<atributo>=<valor>
│ │ │
│ │ └─ propiedad (rule, entrypoints, tls, port…)
│ └─────────── tipo (routers, services, middlewares)
└──────────────────── ámbito/grupo (ej. "miapp-https")
```
Componentes usados:
| Componente | Función |
|---|---|
| `routers` | Decide **qué** petición llega (Host, Path) y **a qué entrypoint** responde |
| `middlewares` | Transformaciones (redirecciones, auth, headers) |
| `services` | Traduce a **con qué puerto interno** del contenedor |
| `tls` | Habilita TLS y define de dónde sale el certificado |
---
## 2. Exponer un proyecto (mínimo funcional)
```yaml
networks:
default:
driver: bridge
proxy_network:
external: true
services:
web:
build: { context: ./app, dockerfile: Dockerfile }
container_name: miapp-web
restart: unless-stopped
networks: [ default, proxy_network ] # ← ambas redes
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy_network"
- "traefik.http.routers.miapp.rule=Host(`miapp.local`)"
- "traefik.http.routers.miapp.entrypoints=websecure"
- "traefik.http.services.miapp.loadbalancer.server.port=80"
```
Notas:
- `traefik.enable=true` es **obligatorio** (`exposedByDefault: false`).
- `traefik.docker.network=proxy_network` fuerza a leer las etiquetas desde esa
red cuando el contenedor está en varias.
- El puerto del service es el **puerto interno**, no el publicado.
---
## 3. Modo A — Dominio público con Let's Encrypt
```yaml
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy_network"
# 1. Middleware de redirección
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true"
# 2. Router HTTP (:80) → redirige
- "traefik.http.routers.miapp-http.rule=Host(`app.ejemplo.com`)"
- "traefik.http.routers.miapp-http.entrypoints=web"
- "traefik.http.routers.miapp-http.middlewares=redirect-to-https"
# 3. Router HTTPS (:443) → sirve el contenido
- "traefik.http.routers.miapp-https.rule=Host(`app.ejemplo.com`)"
- "traefik.http.routers.miapp-https.entrypoints=websecure"
- "traefik.http.routers.miapp-https.tls=true"
- "traefik.http.routers.miapp-https.tls.certresolver=letsencrypt"
- "traefik.http.routers.miapp-https.service=miapp-svc"
# 4. Servicio interno
- "traefik.http.services.miapp-svc.loadbalancer.server.port=80"
```
Requisitos:
1. DNS `A/AAAA` apuntando a la IP del servidor.
2. Puertos **80 y 443** abiertos (el challenge HTTP-01 de Let's Encrypt usa el 80).
3. `~/srv/core/data/acme.json` existente y con permisos `600`.
> Traefik emite el certificado la primera vez que recibe tráfico HTTPS por ese
> Host. Para forzarlo: `docker logs -f core-proxy` y hacer un `curl` al dominio.
### Mejorar el router HTTP con challenge sin romper la redirección
```yaml
- "traefik.http.routers.miapp-http.tls=true"
- "traefik.http.routers.miapp-http.tls.certresolver=letsencrypt"
- "traefik.http.routers.miapp-http.middlewares=redirect-to-https"
```
Esto permite que Let's Encrypt valide por el puerto 80 aunque todo el tráfico
se redirija a HTTPS.
---
## 4. Modo B — Dominio local `.local` con certificado auto-firmado
### 4.1 Generar el certificado
```bash
cd ~/srv/core/data/certs
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout miapp.local.key -out miapp.local.crt \
-subj "/CN=miapp.local" \
-addext "subjectAltName=DNS:miapp.local"
chmod 600 miapp.local.key
```
Certificado multi-dominio:
```bash
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout intranet.local.key -out intranet.local.crt \
-subj "/CN=intranet.local" \
-addext "subjectAltName=DNS:intranet.local,DNS:app.intranet.local,DNS:api.intranet.local"
```
### 4.2 Declararlo en el provider `file`
`~/srv/core/data/dynamic_tls.yml` (montado en `/etc/traefik/dynamic_tls.yml`):
```yaml
tls:
certificates:
- certFile: /etc/traefik/certs/miapp.local.crt
keyFile: /etc/traefik/certs/miapp.local.key
```
> La ruta es **la de dentro del contenedor**, porque el directorio se monta en
> `/etc/traefik/certs`.
### 4.3 Etiquetar el proyecto (sin certresolver)
```yaml
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy_network"
- "traefik.http.routers.miapp.rule=Host(`miapp.local`)"
- "traefik.http.routers.miapp.entrypoints=websecure"
- "traefik.http.routers.miapp.tls=true"
- "traefik.http.services.miapp.loadbalancer.server.port=80"
```
Recargar:
```bash
cd ~/srv/core && docker compose restart traefik
```
### 4.4 Resolver el nombre en el cliente
```bash
echo "192.168.1.50 miapp.local" | sudo tee -a /etc/hosts
```
---
## 5. Modo C — Solo HTTP (entornos internos)
```yaml
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy_network"
- "traefik.http.routers.miapp.rule=Host(`miapp.local`)"
- "traefik.http.routers.miapp.entrypoints=web"
- "traefik.http.services.miapp.loadbalancer.server.port=80"
```
---
## 6. Varios dominios en un mismo proyecto
Dos approaches:
**a) Múltiples reglas en un router** (OR lógico):
```yaml
- "traefik.http.routers.miapp.rule=Host(`app.local`) || Host(`www.app.local`)"
```
**b) Un router por dominio**, apuntando al mismo service:
```yaml
- "traefik.http.routers.www.rule=Host(`www.app.local`)"
- "traefik.http.routers.www.entrypoints=websecure"
- "traefik.http.routers.www.tls=true"
- "traefik.http.routers.www.service=miapp-svc"
```
Ambos necesitan los certificados correspondientes en `dynamic_tls.yml` o en
Let's Encrypt (agregando los dominios a la regla del router con certresolver).
---
## 7. Errores frecuentes
| Error | Causa | Solución |
|---|---|---|
| `404` en todos los dominios | `traefik.enable` ausente | Agregar la etiqueta |
| `404` solo en un dominio | Regla `Host()` mal escrita | Verificar comillas invertidas: `` Host(`dominio`) `` |
| `502 Bad Gateway` | Puerto interno incorrecto | Confirmar `EXPOSE` del Dockerfile y el puerto real |
| `too many redirects` | Router HTTP con `redirectscheme` sin HTTPS válido | Verificar que exista el router `websecure` |
| `TLS handshake error` / `no certificate` | `dynamic_tls.yml` mal cargado o certificado expirado | Rutas internas correctas + `docker compose restart traefik` |
| `cannot generate ACME certificate` | DNS mal, puertos cerrados o `acme.json` sin permisos | `dig`, revisar firewall y `chmod 600 acme.json` |
| El router no cambia al editar el compose | Etiquetas cacheadas | `docker compose up -d --force-recreate` |
Verificación:
```bash
curl -sI http://miapp.local # 301 → https://miapp.local
curl -sk https://miapp.local -I # respuesta del backend (-k ignora el cert)
```
---
## 8. Cheatsheet de etiquetas
| Etiqueta | Efecto |
|---|---|
| `traefik.enable=true` | Publica el contenedor |
| `traefik.docker.network=<red>` | Red desde la que leer etiquetas |
| `...routers.X.rule=Host(\`d\`)` | Enruta por dominio |
| `...routers.X.rule=PathPrefix(\`/api\`)` | Enruta por ruta |
| `...routers.X.entrypoints=web\|websecure` | Puerto de escucha (80 / 443) |
| `...routers.X.tls=true` | Habilita TLS en ese router |
| `...routers.X.tls.certresolver=letsencrypt` | Emite/obtiene el certificado |
| `...routers.X.middlewares=mw1,mw2` | Aplica middlewares (orden izquierda→derecha) |
| `...routers.X.service=svc` | Fuerza el service (si hay más de uno) |
| `...services.X.loadbalancer.server.port=N` | Puerto interno del contenedor |
| `...middlewares.X.redirectscheme.scheme=https` | Redirige HTTP → HTTPS |
| `...middlewares.X.redirectscheme.permanent=true` | 301 en vez de 302 |
| `...middlewares.X.basicauth.users=file` | Protección básica |