Saltar a contenido

El proxy inverso y TLS#

El frontal de Empowerment Center es a la vez el servidor de la aplicación web y el proxy inverso hacia el backend. Esta página explica cómo publicarlo con TLS, solo o detrás del proxy de la organización, qué cabeceras pone y quita, cómo sustituirlo por otro servidor web y cómo repartir entre varias instancias del backend.

Las variables del frontal#

Variable Por defecto Qué es
ECENTER_BACKEND_URL http://backend:8080 El backend, sin barra final.
ECENTER_RESOLVER 127.0.0.11 El DNS con que se resuelve el nombre del backend en cada petición: el de Docker o, en Kubernetes, el del clúster. Así se sigue encontrando un backend que se reinicia con otra dirección.
ECENTER_CLIENT_MAX_BODY_SIZE 100m El cuerpo más grande que se admite. Tiene que ser al menos ECENTER_MAX_UPLOAD, el fichero más grande que acepta el backend (100MB).
ECENTER_BEHIND_PROXY off on si hay otro proxy delante.
ECENTER_CSP_MAP_ORIGINS (vacía) Los orígenes del mapa base, separados por espacios (https://api.maptiler.com).
ECENTER_CSP_IDP_ORIGINS (vacía) Los orígenes del proveedor de identidad al que la salida de SAML envía un formulario, separados por espacios.

En deploy/compose.yaml, las cuatro últimas se toman de deploy/.env.

Dos formas de poner TLS#

Dos formas de poner TLS Dos variantes: con otro proxy que termina TLS, el proxy pone y sobrescribe los X-Forwarded-* y el frontal los respeta; con el frontal como entrada, el frontal termina TLS en el 8443 y calcula los X-Forwarded-* sin fiarse de los del cliente. En los dos casos quita Forwarded, X-Forwarded-Prefix y X-Forwarded-Ssl. HTTPS HTTP pone X-Forwarded-Proto, -Host, -Port; sobrescribe X-Forwarded-For; HSTS respeta los X-Forwarded-* recibidos; quita Forwarded, X-Forwarded-Prefix y X-Forwarded-Ssl HTTPS :8443 calcula los X-Forwarded-*; no se fía de los del cliente; quita Forwarded, X-Forwarded-Prefix y X-Forwarded-Ssl Otro proxy termina TLS El frontal es la entrada Cliente Proxy de la organización Frontal ECENTER_BEHIND_PROXY=on Backend Cliente Frontal con TLS ECENTER_BEHIND_PROXY=off Backend
Dos formas de poner TLS Dos variantes: con otro proxy que termina TLS, el proxy pone y sobrescribe los X-Forwarded-* y el frontal los respeta; con el frontal como entrada, el frontal termina TLS en el 8443 y calcula los X-Forwarded-* sin fiarse de los del cliente. En los dos casos quita Forwarded, X-Forwarded-Prefix y X-Forwarded-Ssl. HTTPS HTTP pone X-Forwarded-Proto, -Host, -Port; sobrescribe X-Forwarded-For; HSTS respeta los X-Forwarded-* recibidos; quita Forwarded, X-Forwarded-Prefix y X-Forwarded-Ssl HTTPS :8443 calcula los X-Forwarded-*; no se fía de los del cliente; quita Forwarded, X-Forwarded-Prefix y X-Forwarded-Ssl Otro proxy termina TLS El frontal es la entrada Cliente Proxy de la organización Frontal ECENTER_BEHIND_PROXY=on Backend Cliente Frontal con TLS ECENTER_BEHIND_PROXY=off Backend
Dos formas de poner TLS Ver a tamaño completo Ver a tamaño completo

Un balanceador, un Traefik o el proxy de la organización recibe HTTPS y manda el tráfico al frontal por HTTP.

  1. Ponga ECENTER_BEHIND_PROXY=on en deploy/.env y vuelva a crear el frontal.
  2. Configure el proxy de delante para que mande a http://<servidor>:<ECENTER_HTTP_PORT>/ todo lo de su nombre, sin cambiar la ruta.
  3. Haga que el proxy de delante ponga X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port y X-Forwarded-For, y que quite los que traiga el cliente.
  4. Haga que sobrescriba X-Forwarded-For con la dirección que ve, en lugar de añadirla a la que traiga el cliente.
  5. Ponga el HSTS (Strict-Transport-Security) en ese proxy.

Con on, el frontal respeta los X-Forwarded-* que recibe y añade su propia dirección al X-Forwarded-For. El backend toma la primera dirección de esa lista como la del cliente: si el proxy de delante no la sobrescribe, cada cliente elige con qué dirección aparece en la auditoría y en las sesiones.

El frontal recibe directamente a los usuarios, con ECENTER_BEHIND_PROXY=off (lo de por defecto). Calcula él los X-Forwarded-* y no se fía de los que traiga el cliente.

Para que termine TLS él mismo:

  1. Copie la plantilla de la imagen, /etc/nginx/templates/default.conf.template (en las fuentes, frontend/nginx/ecenter.conf.template), por ejemplo a deploy/nginx/default.conf.template.
  2. En la copia, cambie listen 8080; por estas tres líneas:

    listen 8443 ssl;
    ssl_certificate /etc/nginx/tls/cert.pem;
    ssl_certificate_key /etc/nginx/tls/key.pem;
    
  3. Monte la plantilla modificada sobre la de la imagen y el certificado en /etc/nginx/tls/, y publique el 8443 en lugar del 8080. Con Docker Compose, en un deploy/compose.override.yaml:

    services:
      frontend:
        volumes:
          - ./nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro
          - ./tls:/etc/nginx/tls:ro
        ports: !override
          - "443:8443"
    

    !override sustituye los puertos de compose.yaml en lugar de sumarlos (Docker Compose 2.24 o posterior). Con -f deploy/compose.yaml, añada también -f deploy/compose.override.yaml a cada orden.

  4. Vuelva a crear el frontal.

nginx corre sin privilegios (usuario 101): la clave del certificado tiene que poder leerla ese usuario. Mantenga en su copia todo lo demás de la plantilla: las rutas que van al backend, las cabeceras y las variables ECENTER_*, que la imagen sustituye al arrancar. Al actualizar ECenter, compare su copia con la plantilla nueva.

Qué va al backend#

El frontal manda al backend, sin tocarlas, estas rutas, y sirve la aplicación web en todas las demás:

Prefijo Qué es
/api/ La API.
/embed/ La página de la incrustación, el modo de impresión del navegador sin interfaz y el SDK de la incrustación.
/.well-known/oauth-protected-resource/ Los metadatos del servidor MCP.
/oauth2/, /login/oauth2/ La ida y la vuelta de OIDC.
/saml2/, /login/saml2/ La ida y la vuelta de SAML, y su cierre de sesión.

/ayuda/ son los manuales, que sirve el propio frontal (véase Los manuales en /ayuda/); /ayuda sin barra redirige a /ayuda/. Lo demás es la aplicación web: el fichero, si existe, y si no index.html.

Cabeceras#

Hacia el backend, el frontal pone Host y los cuatro X-Forwarded-* (los suyos, o los del proxy de delante con ECENTER_BEHIND_PROXY=on), y quita siempre tres cabeceras que el cliente podría elegir y que el backend también lee:

Cabecera que se quita Por qué
Forwarded El backend la preferiría a los X-Forwarded-* para la dirección, el nombre y el esquema. Como el frontal la quita, el proxy de delante tiene que dar los X-Forwarded-*: Forwarded no se usa.
X-Forwarded-Prefix Cambiaría la ruta de las redirecciones.
X-Forwarded-Ssl Cambiaría el esquema.

Con esos datos el backend reconstruye la petición original: las direcciones de vuelta del proveedor de identidad, las cookies seguras detrás de TLS y la dirección del cliente que anotan la auditoría y las sesiones.

Hacia el navegador, a la aplicación web el frontal le pone:

Cabecera Valor
X-Frame-Options DENY
Content-Security-Policy frame-ancestors 'none': nadie puede enmarcar la aplicación.
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
Content-Security-Policy-Report-Only La política de contenido completa, con los orígenes de ECENTER_CSP_MAP_ORIGINS y ECENTER_CSP_IDP_ORIGINS.
Permissions-Policy Apaga lo que la aplicación no usa: acelerómetro, cámara, captura de pantalla, ubicación, giroscopio, HID, magnetómetro, micrófono, MIDI, pago, puerto serie y USB. La pantalla completa y el portapapeles siguen.

Los manuales de /ayuda/ llevan las mismas cabeceras, con su propia política de contenido (también solo para informar): sus scripts en línea van por su hash, que se calcula al construir la imagen.

A lo que viene del backend no le pone ninguna: la página de la incrustación lleva su propio frame-ancestors con los orígenes de cada aplicación anfitriona, y otra cabecera lo rompería. Si pone otro proxy delante, que tampoco añada cabeceras de enmarcado a las rutas /embed/.

La política de contenido#

En la versión 8.0 la política de contenido solo informa: lo que la incumple aparece en la consola de las herramientas del navegador y no se bloquea ni se envía a ningún sitio. Compruebe la suya así:

  1. Abra la aplicación con las herramientas del navegador abiertas, en la pestaña de la consola.
  2. Recorra lo que se usa en su instalación: un mapa con su mapa base y, si hay SAML, salir de la aplicación.
  3. Si aparece un aviso «… violates the following Content Security Policy directive …», añada el origen que nombra a ECENTER_CSP_MAP_ORIGINS o a ECENTER_CSP_IDP_ORIGINS y vuelva a crear el frontal.

Caché, compresión y esperas#

  • Los ficheros con huella de la aplicación (main-….js, chunk-….js, styles-….css) se guardan un año; las fuentes, un día; index.html y lo demás se vuelven a comprobar en cada visita. Tras una actualización, los usuarios cargan la versión nueva al recargar.
  • En los manuales, los ficheros con huella se guardan un año, las fuentes un día, las capturas una hora, y las páginas y el índice de la búsqueda se vuelven a comprobar en cada visita.
  • Todo va comprimido, también el GeoJSON y las teselas de los mapas.
  • Las esperas hacia el backend son de 300 s entre dos lecturas. Una descarga larga (la exportación de la auditoría puede durar hasta 30 minutos) no se corta mientras le lleguen datos. Un proxy delante no debería cortar antes.

Otro servidor web en lugar del frontal#

Si la organización ya tiene su proxy (nginx, Apache, HAProxy o un Ingress de Kubernetes), puede servir la aplicación web con él:

  1. Copie al servidor web los ficheros de la aplicación (la carpeta dist/frontend/browser de la compilación, o el contenido de /usr/share/nginx/html de la imagen del frontal).
  2. Mande al backend las rutas de Qué va al backend, sin cambiar la ruta.
  3. Sirva index.html en cualquier otra ruta que no sea un fichero.
  4. Ponga a la aplicación web sus cabeceras y deje sin tocar las del backend.
  5. Ponga los X-Forwarded-*, sobrescriba X-Forwarded-For y quite Forwarded, X-Forwarded-Prefix y X-Forwarded-Ssl.
  6. Ajuste las esperas (300 s) y el tamaño máximo del cuerpo (al menos ECENTER_MAX_UPLOAD).
  7. Si quiere los manuales, sirva en /ayuda/ el sitio que deja manuales/construir.sh en manuales/site/ (o el contenido de /usr/share/nginx/ayuda de la imagen del frontal). python3 frontend/nginx/help-site.py manuales/site /ayuda/ help-csp.conf le quita la versión imprimible y los PDF, pone el prefijo en su página de error y escribe su política de contenido.
  8. Diga al backend dónde leer index.html para la incrustación con ECENTER_EMBEDDING_INDEX_LOCATION: una URL que lo sirva (http://frontal/index.html) o un fichero (file:/srv/ecenter/frontend/index.html).

Varias instancias del backend#

Los trabajos y sus resultados viven en la memoria de la instancia que los calcula, así que el balanceador tiene que mandar las peticiones de un mismo cliente siempre a la misma instancia. La clave de la afinidad es, por este orden:

  1. la cabecera X-ECenter-Embed-Session, que mandan un documento incrustado y el navegador sin interfaz;
  2. la cabecera Authorization, de la API de consulta y el MCP;
  3. la cookie SESSION, de la aplicación.

Con nginx, sustituyendo proxy_pass $ecenter_backend de las rutas del backend por proxy_pass http://ecenter_backend:

map "$http_x_ecenter_embed_session|$http_authorization|$cookie_SESSION" $ecenter_affinity {
    "~^(?<embed>[^|]+)\|" $embed;
    "~^\|(?<bearer>[^|]+)\|" $bearer;
    default $cookie_SESSION;
}
upstream ecenter_backend {
    hash $ecenter_affinity consistent;
    server backend-1:8080;
    server backend-2:8080;
}

Cada instancia lleva su ECENTER_INSTANCE_NAME y su propio navegador sin interfaz. Lo que se reparten las instancias y lo que no está en Arquitectura.

Problemas frecuentes#

Síntoma Causa Qué hacer
Tras entrar con el proveedor de identidad, este rechaza la dirección de vuelta, o la vuelta va a http:// en lugar de https://. El backend no recibe el esquema o el nombre originales. Con un proxy delante, ECENTER_BEHIND_PROXY=on y que ese proxy ponga X-Forwarded-Proto y X-Forwarded-Host. Sin contenedores, SERVER_FORWARD_HEADERS_STRATEGY=framework.
La auditoría y las sesiones anotan la dirección del proxy, no la del usuario. El proxy de delante no pone X-Forwarded-For, o falta ECENTER_BEHIND_PROXY=on. Configúrelo como en Dos formas de poner TLS.
Una subida de datos externos falla con 413. El fichero pasa de ECENTER_CLIENT_MAX_BODY_SIZE o de ECENTER_MAX_UPLOAD, o del límite de un proxy de delante. Suba los tres a la vez.
La incrustación no se ve en la aplicación anfitriona. Un proxy delante añade X-Frame-Options o frame-ancestors a /embed/. Quite esas cabeceras de las rutas del backend.
Con varias instancias, un cubo se queda cargando o desaparece al pasar a otra ventana. El balanceador reparte sin afinidad. Configure la afinidad de Varias instancias del backend.

Véase también#