Configuraciones de proxy inverso¶
Evey sirve la API y la interfaz web juntas en el puerto 9091, en HTTP
simple; un proxy inverso solo tiene que encargarse de TLS y reenviarlo todo a
evey:9091. Elige tu proxy más abajo (el Caddy incluido, Nginx, Traefik,
Nginx Proxy Manager, o uno que ya tengas funcionando), y lee después las dos
secciones del final si ajustas el streaming o pones tus propias cabeceras de
seguridad.
Decirle a Evey que hay un proxy¶
Sea cual sea el proxy, Evey necesita su dirección pública y saber en qué
proxy confiar. Las dos cosas se ponen en Administración → Acceso remoto:
una URL externa en https:// y, cuando el proxy es un contenedor, su
red en Proxies de confianza. Las dos se aplican sin reiniciar. Una
dirección https:// es lo que le dice a Evey que delante de él hay un proxy
que se encarga de TLS: las cookies llevan la marca Secure, se envía HSTS y
se acepta la cabecera X-Forwarded-Proto de un proxy de confianza. El
interruptor de acceso remoto y la dirección real del cliente se explican en
Acceso remoto y HTTPS.
Las mismas respuestas pueden venir del entorno, bajo environment: en el
archivo compose, para una instalación descrita entera como código (el panel
tiene prioridad cuando están las dos):
services:
evey:
environment:
EVEY_SERVER_BASE_URL: https://media.example.com
EVEY_TRUSTED_PROXIES: 172.16.0.0/12 # solo hace falta si el proxy es un contenedor; loopback siempre es de confianza
La antigua EVEY_FORWARDED_PROTO todavía se acepta para forzar el valor,
pero está obsoleta: basta con el esquema de la dirección.
Todos los ejemplos incluidos leen el dominio de EVEY_DOMAIN, con un valor
de ejemplo en el archivo que copias: cámbialo ahí por tu dominio, en la línea
EVEY_DOMAIN: del servicio del proxy (Caddy, Nginx) o en la regla
Host(...) (Traefik).
Caddy (incluido)¶
Archivos¶
docker/Caddyfile, convertido en imagen por
docker/Dockerfile.caddy; el servicio caddy
del docker-compose.yml del repositorio.
Configurarlo¶
- Escribe tu dominio en la línea
EVEY_DOMAIN:del serviciocaddyy comprueba que los puertos 80 y 443 de la máquina llegan al contenedor de Caddy. - Añade a tu archivo compose el servicio
caddydel archivo compose del repositorio y ejecutadocker compose up -d. Caddy obtiene por sí solo un certificado de Let's Encrypt paraEVEY_DOMAIN. - En Administración → Acceso remoto, pon la URL externa en
https://y172.16.0.0/12en Proxies de confianza.
Lo que ya resuelve¶
Lo reenvía todo a evey:9091, con las rutas de streaming enviadas sin búfer
y sin tiempo límite. Pone las cabeceras de seguridad y la CSP de referencia
de la sección de seguridad,
y quita la CSP propia de la aplicación para que llegue exactamente una al
navegador; las dos páginas que llevan su propia política la conservan.
Nginx¶
Archivos¶
examples/nginx/evey.conf.template
y una capa compose lista para usar,
examples/nginx/docker-compose.nginx.yml.
Configurarlo¶
- Escribe tu dominio en la línea
EVEY_DOMAIN:de la capa. La imagen de nginx genera la configuración a partir de la plantilla al arrancar, sustituyendoEVEY_DOMAIN. - Pon el certificado y la clave donde los espera la plantilla, con los
nombres de archivo que corresponden (
/etc/ssl/certs/<domain>.pem,/etc/ssl/private/<domain>.keyen el volumennginx-certs): esta capa no obtiene certificados por ti. docker compose -f docker-compose.yml -f examples/nginx/docker-compose.nginx.yml up -d.- En Administración → Acceso remoto, pon la URL externa en
https://y172.16.0.0/12en Proxies de confianza.
Lo que ya resuelve¶
Reenvía a evey:9091 con el búfer y los tiempos límite desactivados en las
rutas de streaming, el WebSocket de SyncPlay actualizado, X-Forwarded-For y
X-Forwarded-Proto transmitidos, y client_max_body_size 2G para subir
plugins y restaurar copias de seguridad. Deja pasar las cabeceras de
seguridad y la CSP propias de la aplicación.
Traefik¶
Archivos¶
examples/traefik/docker-compose.traefik.yml.
Configurarlo¶
- Ejecuta Traefik con un entrypoint
websecure, un resolvedor de certificadosletsencrypty una red Docker externa llamadaproxy; las etiquetas de la capa dan por hecho esos nombres. -
Escribe tu dominio en la regla
Host(...)de la capa y después: -
En Administración → Acceso remoto, pon la URL externa en
https://y el rango de la redproxyen Proxies de confianza (172.16.0.0/12cubre las redes por defecto de Docker).
Lo que ya resuelve¶
La capa solo añade las etiquetas del router y se une a la red proxy.
Traefik, con sus valores por defecto, no guarda las respuestas en búfer ni
limita su duración, así que no hace falta ajustar nada para el streaming;
transmite X-Forwarded-For y X-Forwarded-Proto por sí solo y deja pasar
las cabeceras de seguridad de la aplicación.
Nginx Proxy Manager¶
Archivos¶
Ninguno: todo se configura en la interfaz de NPM. El contenedor de evey tiene que estar en la red que usa NPM, ver Un proxy que ya tienes funcionando.
Configurarlo¶
En Edit Proxy Host:
- Details: Scheme
http, Forward Hostnameevey-server, Forward Port9091. Websockets Support: on (SyncPlay). Cache Assets: off - si no, guardaría en caché los segmentos de vídeo y las listas de reproducción con una caducidad fija. Block Common Exploits puede quedarse activado (desactívalo si alguna vez una búsqueda o la subida de un plugin responde 403 sin motivo). - SSL: tu certificado, Force SSL, HTTP/2. Aquí HSTS es opcional - la aplicación ya lo envía.
- En Administración → Acceso remoto, pon la URL externa en
https://y el rango de la red de NPM en Proxies de confianza.
Lo que ya resuelve¶
NPM transmite X-Forwarded-For, X-Forwarded-Proto y la cabecera Host por
sí solo, y sus tiempos límite por defecto nunca saltan con los flujos de
Evey. No hace falta nada más.
Opcional - evitar que NPM guarde en búfer, en su propio disco, los
archivos en reproducción directa: Advanced → Custom Nginx Configuration
(la pestaña Custom Locations no admite la expresión regular):
# Subida de plugins (hasta 100 MB) y restauración de copias de seguridad (hasta 2 GB).
client_max_body_size 2G;
# Streaming - sin búfer, sin tiempo límite.
location ~ ^/api/v1/media/.+/(hls|stream) {
proxy_pass http://evey-server:9091;
proxy_buffering off;
proxy_request_buffering off;
proxy_read_timeout 0;
proxy_send_timeout 0;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Los escaneos de biblioteca envían su progreso mientras dura el escaneo.
location ~ ^/api/v1/libraries/.+/scan {
proxy_pass http://evey-server:9091;
proxy_buffering off;
proxy_read_timeout 0;
proxy_send_timeout 0;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Todas las demás rutas (la interfaz web, el resto de la API, el WebSocket de SyncPlay) pasan por el bloque de proxy propio de NPM, que se configura en la pestaña Details. Si omites este fragmento, no deja de funcionar nada - solo cambia dónde se guardan en búfer los archivos grandes.
Un proxy que ya tienes funcionando (TLS resuelto antes)¶
En un NAS que ya tiene un proxy inverso (el del fabricante, Nginx Proxy Manager, un Traefik en una red Docker compartida), no hace falta Caddy: Evey envía sus propias cabeceras de seguridad y una CSP completa, y de todos modos las reglas de streaming solo las puede aplicar el proxy que está de cara al cliente. Un segundo proxy detrás no aporta nada.
Configurarlo¶
Pon el servicio evey en la red de ese proxy y apunta a evey-server:9091
(el nombre del contenedor; el nombre del servicio evey también funciona):
services:
evey:
# sin puertos: el proxy es la única entrada
environment:
EVEY_SERVER_BASE_URL: https://media.example.com
EVEY_TRUSTED_PROXIES: 172.16.0.0/12 # redúcelo a la subred del proxy cuando la conozcas
# ...
networks:
default:
name: proxy-net
external: true
Después, en ese proxy, para la ruta hacia evey-server:9091: transmite
X-Forwarded-For y X-Forwarded-Proto, activa el soporte de WebSocket, y
aplica las reglas de streaming de abajo en las rutas de streaming - las
secciones de Nginx y de Nginx Proxy Manager de arriba muestran las líneas
exactas.
Lo que ya resuelve¶
Depende del proxy: comprueba que llega exactamente una cabecera CSP al
navegador (siguientes secciones) y ejecuta evey admin doctor una vez
puestos los proxies de confianza.
Streaming - lo que le hacen los valores por defecto de un proxy¶
Evey funciona detrás de un proxy inverso con sus valores por defecto: cada respuesta larga o bien envía datos sin parar (segmentos HLS, reproducción directa, salida de la transcodificación), o bien mantiene viva la conexión (los flujos de eventos envían una señal de actividad), así que un tiempo límite de lectura por defecto nunca salta. Aun así, conviene saber dos cosas:
- El búfer. Un proxy que guarda las respuestas en búfer (nginx lo hace
por defecto) lee un archivo en reproducción directa en sus propios búferes
y archivos temporales antes de que lo reciba el cliente - funciona, pero
una película de varios GB pasa entonces por el disco de la máquina del
proxy.
proxy_buffering offen las rutas de streaming lo evita. - Los escaneos.
/api/v1/libraries/*/scanenvía el progreso mientras dura el escaneo; un proxy que limita la duración total de una respuesta (es raro - nginx, Caddy y Traefik no lo hacen por defecto) lo cortaría antes. El escaneo sigue en el servidor de todos modos.
Las rutas afectadas:
El Caddy incluido y el ejemplo de Nginx aplican las dos cosas (sin búfer, sin tiempo límite) en estas rutas. El ejemplo de Traefik y un proxy que se maneja desde una interfaz, como Nginx Proxy Manager, mantienen sus valores por defecto, que están bien; el fragmento de NPM de arriba es opcional.
Cabeceras de seguridad y CSP - lee esto si tu proxy pone las suyas¶
Evey envía una Content-Security-Policy completa. Si tu proxy la deja pasar, no tienes que hacer nada más.
En cambio, muchas configuraciones la quitan (header_down
-Content-Security-Policy en Caddy) y ponen la suya - eso está bien, pero
entonces tu política tiene que conservar estos tokens, o algunas
funciones se rompen sin avisar:
| Token | Directiva | Qué se rompe sin él |
|---|---|---|
'self' |
frame-src |
Todas las interfaces de plugin y los scripts de tema (iframe en blanco). Se ejecutan en /plugin-frame.html, una página de Evey enmarcada por la aplicación (ver más abajo). |
'wasm-unsafe-eval' |
script-src |
La reproducción AC-3/E-AC-3 en el navegador (el decodificador WebAssembly ac3go no puede compilarse, así que la película se reproduce sin sonido). Es el token limitado que solo permite compilar Wasm, no eval() en general. |
https://www.gstatic.com |
script-src (+ frame-src) |
El SDK emisor de Google Cast no se carga nunca, así que nadie ve el botón de envío. Es la única forma de que un navegador descubra los Chromecast de la red local del usuario. |
blob: |
media-src |
La reproducción MSE (hls.js conecta el flujo mediante una URL blob). |
blob: |
worker-src |
hls.js pasa sin avisar a separar el flujo en el hilo principal (va a tirones en el móvil). |
Una política que funciona para un proxy que pone la suya, la que envía el Caddy incluido:
default-src 'self'; base-uri 'self'; object-src 'none';
img-src 'self' https: data: blob:; media-src 'self' blob:;
style-src 'self' 'unsafe-inline';
script-src 'self' 'wasm-unsafe-eval' https://www.gstatic.com;
connect-src 'self' https: wss:; worker-src 'self' blob:; font-src 'self' data:;
frame-src 'self' https://www.gstatic.com
base-uri y object-src no recurren a default-src cuando faltan, así que
una política que los omite deja sin restricción la inyección de
<base href> y la inserción con <object>/<embed>. Consérvalos si pones
tu propia política. script-src no lleva 'unsafe-inline': un script
inyectado en la página no se ejecuta. No lo vuelvas a añadir - nada en la
aplicación lo necesita.
Dos páginas llevan su propia política, enviada por la aplicación; deja la cabecera de la aplicación en esas dos rutas:
/plugin-frame.html, la página en la que se ejecutan las interfaces de plugin y los scripts de tema. Su política la aísla (incluso abierta fuera de un iframe), limita su red a los servidores que declara el manifiesto del plugin, y solo deja que la enmarque la aplicación, así que también envíaX-Frame-Options: SAMEORIGIN: no lo sustituyas porDENYen esta ruta, o todas las interfaces de plugin se quedarán en blanco./cast-receiver.html, la página que carga un Chromecast cuando reproduce desde Evey. El reproductor Cast le inyecta estilos, fuentes e imágenes dewww.gstatic.com. Deja la cabecera de la aplicación en esa ruta, o aplica esta política solo a esa ruta:
default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://www.gstatic.com; style-src 'self' 'unsafe-inline' https://www.gstatic.com; img-src 'self' data: https: blob:; media-src 'self' blob: https:; worker-src 'self' blob:; font-src 'self' data: https://www.gstatic.com; connect-src 'self' https: wss:; frame-src https://www.gstatic.com; base-uri 'self'; object-src 'none'
Al navegador tiene que llegar exactamente una cabecera CSP
Con dos (la de la aplicación y la de tu proxy, o la de Caddy y el
fragmento "Advanced" de Nginx Proxy Manager), los navegadores aplican la
intersección: se impone la más estricta y el fallo no es visible. Compruébalo con
curl -sI https://your.domain | grep -ci content-security-policy, que
tiene que mostrar 1.
Dos comprobaciones más después de cualquier cambio:
- Deja la política en una sola línea. Una CSP repartida en varias líneas
en la configuración de un proxy es una corrupción silenciosa clásica: al
unir las líneas se pierden los caracteres de la unión
(
...gstatic.com; connect-srcse convirtió una vez en...gstaticonnect-src, lo que desactivó el SDK de Cast y fusionó dos directivas). - El marco de plugins conserva su propia política:
curl -sI https://your.domain/plugin-frame.html | grep -i -e content-security -e x-frametiene que mostrarsandbox allow-scriptsySAMEORIGIN.
IP reales de los clientes y el control de acceso remoto¶
En cuanto el proxy transmite la dirección real del cliente, Evey puede distinguir a un espectador en casa de uno en internet: sobre eso actúan Bitrate remoto máximo y el interruptor de acceso remoto. Dos reglas:
- Los proxies de confianza (panel o
EVEY_TRUSTED_PROXIES) tienen que cubrir el proxy:172.16.0.0/12cuando es un contenedor en la red de Docker, nada que añadir cuando funciona en la propia máquina, porque loopback siempre es de confianza y lo que pongas se suma a él. - Cada proxy de una cadena tiene que conservar
X-Forwarded-For. En Caddy esservers { trusted_proxies static private_ranges }; nginx usaproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;.
Activar esto cambia quién cuenta como remoto: un servidor con el acceso
remoto desactivado empieza a rechazar a los clientes remotos reales
(403 {"code":"remote_access_disabled"}) aunque no haya cambiado ningún
ajuste de la aplicación. Evey abre el acceso remoto por sí solo cuando hay
una dirección pública y nadie ha elegido, anota el estado en cada arranque y
cada rechazo con la dirección del cliente, y evey admin doctor falla con
"published but refusing". Todo el mecanismo se describe en
Acceso remoto y HTTPS.