# HTTPS y proxy inverso

> Sirve InfraWard por HTTPS detrás de Caddy, nginx o Traefik, con lo que necesitan los eventos del servidor y la terminal web.

El contenedor `web` escucha en HTTP sin cifrar (puerto 8080 por defecto). Pon delante un proxy inverso que termine el TLS. Todo pasa por `web`: el proxy solo necesita un destino y nunca habla directamente con `api` ni con `gateway`.

## Qué tiene que hacer el proxy

| Ruta | Tráfico | Requisito del proxy |
|---|---|---|
| `/` | Interfaz estática | Nada especial. |
| `/api/` | API REST y eventos del servidor (progreso en directo de altas y trabajos) | No guardar las respuestas en búfer. Permitir conexiones inactivas de al menos 1 hora. |
| `/gateway/` | WebSocket de la terminal web | Pasar las cabeceras `Upgrade` y `Connection`. Permitir conexiones de hasta 9 horas (el gateway cierra las sesiones inactivas a los 15 minutos y cualquier sesión a las 8 horas). |

Además:

- Pon en `INFRAWARD_PUBLIC_URL` (en `.env`) la URL pública exacta, por ejemplo `https://infraward.example.com`, y ejecuta `docker compose up -d` después de cambiarla. La API compara con ella la cabecera `Origin` de las escrituras y el comando de alta se construye a partir de ella.
- Publica `web` solo en la interfaz de loopback (`INFRAWARD_HTTP_PORT=127.0.0.1:8080`) cuando el proxy esté en el mismo host, para que el puerto 8080 no sea accesible desde la red.
- Envía `X-Forwarded-For` y `X-Forwarded-Proto`, y pon en `INFRAWARD_TRUSTED_PROXY` (en `.env`) la dirección IP (o CIDR) del proxy tal como la ve el contenedor `web`. El contenedor `web` solo se cree esas cabeceras si llegan desde esa dirección, y pasa a la API y al gateway una única IP del cliente. La usan el registro de auditoría, los límites de intentos de login y las sesiones de la terminal. Mientras no la definas, registran la IP del proxy. Consulta [Averigua la dirección del proxy](#averigua-la-dirección-del-proxy).
- No escribas en el log de accesos del proxy las peticiones a `/api/enroll/`: el token de alta, de un solo uso, viaja en la ruta. El contenedor `web` ya las excluye de su propio log.
- El contenedor `web` ya envía `X-Frame-Options`, `Content-Security-Policy: frame-ancestors 'none'`, `Strict-Transport-Security`, `X-Content-Type-Options` y `Referrer-Policy`. No las vuelvas a añadir en el proxy.

## Ejemplos

**Caddy**

Caddy obtiene y renueva el certificado automáticamente y gestiona la actualización a WebSocket sin ajustes adicionales.

```text title="/etc/caddy/Caddyfile"
infraward.example.com {
	# Caddy no escribe logs de accesos salvo que actives la directiva log.
	# Si lo haces, excluye /api/enroll/*: el token de alta va en la ruta.
	reverse_proxy 127.0.0.1:8080 {
		# Eventos del servidor: envía cada evento en cuanto llega.
		flush_interval -1
	}
}
```

**nginx**

```nginx title="/etc/nginx/sites-available/infraward.conf"
server {
    listen 80;
    server_name infraward.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name infraward.example.com;

    ssl_certificate     /etc/letsencrypt/live/infraward.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/infraward.example.com/privkey.pem;

    proxy_http_version 1.1;
    proxy_set_header Host              $host;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    location / {
        proxy_pass http://127.0.0.1:8080;
    }

    # Script de alta: el token de un solo uso va en la ruta, fuera del log.
    location /api/enroll/ {
        proxy_pass http://127.0.0.1:8080;
        access_log off;
    }

    # API REST y eventos del servidor.
    location /api/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_buffering off;
        proxy_read_timeout 1h;
    }

    # Terminal web (WebSocket).
    location /gateway/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_buffering off;
        proxy_read_timeout 9h;
        proxy_send_timeout 9h;
    }
}
```

**Traefik**

Conecta `web` a la red de Traefik y declara el router con etiquetas en `compose.override.yml` (añade el fichero a `COMPOSE_FILE` en `.env`). Quita el puerto publicado para que solo Traefik llegue al contenedor.

```yaml title="compose.override.yml"
services:
  web:
    ports: !reset []
    networks: [default, traefik]
    labels:
      traefik.enable: "true"
      traefik.docker.network: traefik
      traefik.http.routers.infraward.rule: Host(`infraward.example.com`)
      traefik.http.routers.infraward.entrypoints: websecure
      traefik.http.routers.infraward.tls.certresolver: letsencrypt
      traefik.http.services.infraward.loadbalancer.server.port: "8080"

networks:
  traefik:
    external: true
```

Traefik reenvía la actualización a WebSocket y transmite las respuestas sin búfer por defecto. Si has cambiado los `respondingTimeouts` del entrypoint, comprueba que no cortan las conexiones largas.

## Averigua la dirección del proxy

El valor de `INFRAWARD_TRUSTED_PROXY` es la dirección desde la que llegan las conexiones dentro del contenedor, que no siempre es la que esperas:

- **Proxy en el mismo host** (Caddy o nginx conectando a `127.0.0.1:8080`): con Docker suele ser la puerta de enlace de la red de Compose, no `127.0.0.1`.
- **Proxy en un contenedor** (Traefik): la IP del contenedor del proxy en la red que comparte con `web`, o el CIDR de esa red si solo hay contenedores de proxy conectados a ella.

Para averiguarla, abre InfraWard a través del proxy y lee la dirección de origen de las peticiones en el log de `web`:

```bash
docker compose logs --tail 5 web
```

Defínela en `.env` y vuelve a crear `web`:

```bash title=".env"
INFRAWARD_TRUSTED_PROXY=172.18.0.1
```

```bash
docker compose up -d web
```

A partir de ahí, el mismo log muestra las IP reales de los clientes. El valor por defecto (`127.0.0.1`) no confía en ningún proxy. No uses nunca un rango desde el que los clientes puedan conectarse directamente: cualquiera dentro de él podría elegir la IP que aparece en la auditoría.

## Comprueba el proxy

1. Abre `https://infraward.example.com` y entra. Si la página de login carga pero al entrar vuelves a ella, el navegador está descartando la cookie de sesión: comprueba que el sitio se sirve por HTTPS.
2. Da de alta un servidor o lanza un escaneo y mira la página de progreso. Si los pasos solo se actualizan al recargar, el proxy está guardando `/api/` en búfer.
3. Abre una terminal en un servidor con acceso de administración. Si muestra enseguida *Se ha perdido la conexión con el gateway*, el proxy no está reenviando la actualización a WebSocket en `/gateway/`.
4. Busca tu acceso en **Ajustes → Auditoría**. Si muestra la IP del proxy en lugar de la tuya, `INFRAWARD_TRUSTED_PROXY` no está definida o no coincide con el proxy.

> **Autoridad de certificación propia**
>
> Si OpenBao o el proveedor OIDC usan certificados firmados por tu propia CA, monta el fichero de la CA en `api` y `worker` y define `NODE_EXTRA_CA_CERTS=/ruta/a/ca.pem` en su entorno.

---

Página HTML: https://infraward.net/es/docs/reverse-proxy/
