Protección de URL con el módulo Secure Link en NGINX y NGINX Plus

NGINX | 29 de julio de 2016

Tanto el software de código abierto NGINX como NGINX Plus son muy seguros y fiables como servidores web, proxies inversos y cachés para su contenido. Para obtener protección adicional frente al acceso de clientes no autorizados, puede utilizar las directivas del módulo Secure Link para exigir que los clientes incluyan una cadena hash específica en la URL del recurso que solicitan.

En esta entrada del blog, explicaremos cómo configurar los dos métodos implementados en el módulo Secure Link. Los fragmentos de configuración de ejemplo protegen archivos HTML y listas de reproducción multimedia, pero pueden aplicarse a cualquier tipo de URL HTTP. Estos métodos son válidos tanto para NGINX como para NGINX Plus, pero, en aras de la brevedad, en el resto de la entrada nos referiremos únicamente a NGINX Plus.

Descripción general de los métodos del módulo Secure Link

El módulo Secure Link verifica la validez de un recurso solicitado comparando una cadena codificada en la URL de la solicitud HTTP con la cadena que calcula para dicha solicitud. Si un enlace tiene una duración limitada y esta ha expirado, el enlace se considera obsoleto. El resultado de estas comprobaciones se almacena en la variable $secure_link y se utiliza para controlar el flujo de procesamiento.

Como se ha mencionado, el módulo ofrece dos métodos. Solo se puede configurar uno de ellos en un contexto http, server o location determinado.

  • El primer modo, y el más sencillo, se habilita mediante la directiva secure_link_secret. La cadena codificada es un hash MD5 calculado a partir de la concatenación de dos cadenas de texto: la parte final de la URL y una palabra secreta definida en la configuración de NGINX Plus. (Para obtener información específica sobre la primera cadena de texto, consulte Uso de URL seguras básicas.) Para acceder al recurso protegido, el cliente debe incluir el hash justo después del prefijo de la URL, que es una cadena arbitraria sin barras. En esta URL de ejemplo, el prefijo es videos y el recurso protegido es el archivo bunny.m3u8: /videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 Un caso de uso de este método se da cuando un usuario sube una imagen o un documento a un servidor para compartirlo, pero quiere impedir que cualquier persona que conozca el nombre del archivo pueda acceder a él hasta que se publique el enlace oficial.
  • El segundo método, más flexible, se habilita mediante las directivas secure_link y secure_link_md5. En este caso, la cadena codificada es un hash MD5 de las variables definidas en el archivo de configuración de NGINX Plus. Lo más habitual es incluir la variable $remote_addr para restringir el acceso a una dirección IP de cliente concreta, pero se pueden utilizar otros valores, por ejemplo, $http_user_agent, que captura la cabecera User-Agent y, por tanto, restringe el acceso a determinados navegadores. Opcionalmente, se puede especificar una fecha de caducidad a partir de la cual la URL deja de funcionar, aunque el hash sea correcto. El cliente debe añadir el argumento md5 a la URL de la solicitud para especificar el hash. Si se incluye una fecha de caducidad en la cadena a partir de la cual se calcula el hash, el cliente también debe añadir el argumento expires para especificar la fecha, como en esta URL de ejemplo para solicitar el archivo protegido pricelist.html: /files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740

El módulo Secure Link se incluye en los binarios de código abierto precompilados de NGINX de nginx.org, en los paquetes de NGINX proporcionados por los proveedores de sistemas operativos y en NGINX Plus. No se incluye de forma predeterminada al compilar NGINX desde el código fuente; para habilitarlo, incluya el argumento --with-http_secure_link_module en el comando configure.

Uso de URL seguras básicas

La forma más básica de proteger las URL consiste en utilizar la directiva secure_link_secret. En el siguiente fragmento de ejemplo, protegemos un archivo de lista de reproducción multimedia de HTTP Live Streaming (HLS) llamado /bunny.m3u8. Está almacenado en el directorio /opt/secure/hls, pero se ofrece a los clientes mediante una URL que comienza con el prefijo videos.

server {
    listen 80;
    server_name secure-link-demo;

    location /videos {
        secure_link_secret enigma;
        if ($secure_link = "") { return 403; }

        rewrite ^ /secure/$secure_link;
    }

    location /secure {
        internal;
        root /opt;
    }
}

Con esta configuración, para acceder al archivo /opt/secure/hls/bunny.m3u8 los clientes deben utilizar la siguiente URL:

/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8

La cadena con hash aparece justo después del prefijo, que es una cadena arbitraria sin barras (en este caso, videos).

El hash se calcula a partir de una cadena de texto que concatena dos elementos:

  • La parte de la URL que aparece después del hash, en este caso, hls/bunny.m3u8.
  • El parámetro de la directiva secure_link_secret, en este caso, enigma.

Si la URL de solicitud del cliente no contiene el hash correcto, NGINX Plus establece la variable $secure_link en una cadena vacía. La prueba if falla y NGINX Plus devuelve el código de estado 403 Forbidden en la respuesta HTTP.

De lo contrario (es decir, si el hash es correcto), la directiva rewrite reescribe la URL; en nuestro ejemplo, como /secure/hls/bunny.m3u8 (la variable $secure_link captura la parte de la URL que aparece después del hash). Las URL que comienzan por /secure se procesan en el segundo bloque location. La directiva root de ese bloque establece /opt como directorio raíz de los archivos solicitados, y la directiva internal especifica que el bloque solo se utiliza para solicitudes generadas internamente.

Generación del hash en el cliente para una URL básica protegida

Para obtener el hash MD5 en formato hexadecimal que el cliente debe incluir en la URL, ejecutamos el comando openssl md5 con la opción -hex:

# echo -n 'hls/bunny.m3u8enigma' | openssl md5 -hex(stdin)= 80e2dfecb5f54513ad4e2e6217d36fd4

Para obtener información sobre cómo generar hashes mediante programación, consulte Generación del hash mediante programación.

Respuesta del servidor a URL con seguridad básica

Los siguientes comandos de ejemplo de curl muestran cómo responde el servidor a distintas URL protegidas.

Si la URL incluye el hash MD5 correcto, la respuesta es 200 OK:

# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 | head -n 1HTTP/1.1 200 OK

Si el hash MD5 es incorrecto, la respuesta es 403 Forbidden:

# curl -I http://secure-link-demo/videos/2c5e80de986b6fc80dd33e16cf824123/hls/bunny.m3u8 | head -n 1HTTP/1.1 403 Forbidden

Si el hash de bunny.m3u8 se utiliza para un archivo diferente, la respuesta también es 403 Prohibido:

# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hs/oven.m3u8 | head -n 1HTTP/1.1 403 Forbidden

Uso de URL seguras con fecha de caducidad

El método más flexible para proteger las URL utiliza las directivas secure_link y secure_link_md5. En este ejemplo, las utilizamos para permitir el acceso al archivo /var/www/files/pricelist.html únicamente a los clientes con la dirección IP 192.168.33.14 y solo hasta el 31 de diciembre de 2016.

Nuestro servidor virtual escucha en el puerto 80 y gestiona todas las solicitudes HTTP protegidas en el bloque location /files, donde la directiva root establece /var/www como directorio raíz de los archivos solicitados.

La directiva secure_link define dos variables que capturan argumentos de la URL de la solicitud: $arg_md5 se establece en el valor del argumento md5, y $arg_expires en el valor del argumento expires.

La directiva secure_link_md5 define la expresión a la que se aplica una función hash para generar el valor MD5 de la solicitud; durante el procesamiento de la URL, el hash se compara con el valor de $arg_md5. La expresión de ejemplo incluye el tiempo de expiración indicado en la solicitud (capturado en la variable $secure_link_expires), la URL ($uri), la dirección IP del cliente ($remote_addr) y la palabra enigma.

server {
    listen 80;
        server_name secure-link-demo;

        location /files {
            root /var/www;
            secure_link $arg_md5,$arg_expires;
            secure_link_md5 "$secure_link_expires$uri$remote_addr enigma";

            if ($secure_link = "") { return 403; }
            if ($secure_link = "0") { return 410; }
        }
}

Con esta configuración, para acceder a /var/www/files/pricelist.html, un cliente con la dirección IP 192.168.33.14 debe enviar esta URL de solicitud antes del sábado 31 de diciembre de 2016 a las 23:59:00 UTC:

/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740

Si el hash de la URL enviada por el cliente (capturado en la variable $arg_md5) no coincide con el hash calculado a partir de la directiva secure_link_md5, NGINX Plus establece la variable $secure_link como una cadena vacía. La prueba if falla y NGINX Plus devuelve el código de estado 403 Forbidden en la respuesta HTTP.

Si los hashes coinciden, pero el enlace ha caducado, NGINX Plus establece la variable $secure_link en 0; de nuevo, la prueba if falla, pero esta vez NGINX Plus devuelve el código de estado 410 Gone en la respuesta HTTP.

Generación del hash y del tiempo de expiración en un cliente

Veamos ahora cómo calcula un cliente los argumentos md5 y expires que se incluirán en la URL.

El primer paso consiste en determinar la hora Unix correspondiente a la fecha de caducidad, ya que ese valor se incluye en la expresión con hash mediante la variable $secure_link_expires. Para obtener la hora Unix —el número de segundos transcurridos desde el inicio de la época Unix (1970-01-01 00:00:00 UTC)—, utilizamos el comando date con la opción -d y el especificador de formato +%s.

En nuestro ejemplo, establecemos la fecha de vencimiento en el sábado 31 de diciembre de 2016 a las 23:59:00 UTC, por lo que el comando es:

# date -d "2016-12-31 23:59" +%s1483228740

El cliente incluye este valor como el argumento «expires=1483228740» en la URL de la solicitud.

Ahora procesamos la cadena definida por la directiva secure_link_md5 — $secure_link_expires$uri$remote_addr enigma — mediante tres comandos:

  • El comando openssl md5 con la opción -binary genera el hash MD5 en formato binario.
  • El comando openssl base64 aplica la codificación Base64 al valor hash.
  • Los comandos tr sustituyen el signo más ( + ) por el guion ( - ) y la barra ( / ) por el guion bajo ( _ ), y eliminan el signo igual (=) del valor codificado.

En nuestro ejemplo, el comando completo es:

# echo -n '1483228740/files/pricelist.html192.168.33.14 enigma' | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =AUEnXC7T-Tfv9WLsWbf-mw

El cliente incluye este valor como el argumento «md5=AUEnXC7T-Tfv9WLsWbf-mw» en la URL de la solicitud.

Generación programática del hash

Si su servidor web NGINX Plus sirve contenido dinámico desde un servidor de aplicaciones, tanto NGINX Plus como el servidor de aplicaciones deben utilizar la misma URL segura. Puede generar mediante programación el hash para el argumento md5 de la URL. La siguiente función de Node.js genera un hash que coincide con el definido en el fragmento de configuración de NGINX Plus anterior. Recibe como argumentos un tiempo de expiración, una URL, la dirección IP del cliente y una palabra secreta, y devuelve el hash MD5 en formato binario codificado en Base64.

var crypto = require("crypto");

function generateSecurePathHash(expires, url, client_ip, secret) {
    if (!expires || !url || !client_ip || !secret) {
        return undefined;
    }
    
    var input = expires + url + client_ip + " " + secret;
    var binaryHash = crypto.createHash("md5").update(input).digest();
    var base64Value = new Buffer(binaryHash).toString('base64');
    return base64Value.replace(/=/g, '').replace(/+/g, '-').replace(///g, '_');
}

Para calcular el hash de nuestro ejemplo actual, pasamos estos argumentos:

generateSecurePathHash(new Date('12/31/2016 23:59:00').getTime()), '/files/pricelist.html', “192.168.33.14”, "enigma");

Respuesta del servidor a URL protegidas con fecha de caducidad

Los siguientes comandos de ejemplo de curl muestran cómo responde el servidor a las URL protegidas.

Si un cliente con la dirección IP 192.168.33.14 incluye el hash MD5 y la fecha de caducidad correctos, la respuesta es 200 OK:

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1
HTTP/1.1 200 OK

Si un cliente con una dirección IP diferente envía la misma URL, la respuesta es 403 Prohibido:

# curl -I --interface "192.168.33.33" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1HTTP/1.1 403 Forbidden

Si el valor hash del argumento md5 es incorrecto, la respuesta es 403 Forbidden:

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=qeUNjiY2FTIVMaXUsxG-7w&expires=1483228740' | head -n 1HTTP/1.1 403 Forbidden

Si la URL ha caducado (la fecha indicada por el argumento expires ya ha pasado), la respuesta es 410 Gone:

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=Z2rNva2InyVcRTlhqAkT4Q&expires=1467417540' | head -n 1HTTP/1.1 410 Gone

Ejemplo: protección de archivos de segmento con fecha de caducidad

Este es otro ejemplo de una URL segura con fecha de caducidad, utilizada para proteger tanto la lista de reproducción de un recurso multimedia como los archivos de segmentos.

Una diferencia con respecto al ejemplo anterior es que aquí añadimos un bloque de configuración map para eliminar la extensión de la lista de reproducción (archivo .m3u8) y de los segmentos HLS (archivos .ts) al capturar el nombre del archivo en la variable $file_name, que se pasa a la directiva secure_link_md5. Esto permite proteger tanto las solicitudes de los segmentos .ts individuales como las de la lista de reproducción.

Otra diferencia con respecto al primer ejemplo es que incluimos la variable $http_user_agent (que captura la cabecera User-Agent) en la directiva secure_link_md5, para restringir el acceso a los clientes que utilicen determinados navegadores web (por ejemplo, para que la URL funcione en Safari, pero no en Chrome ni Firefox).

map $uri $file_name {
    default none;
    "~*/s/(?<name>.*).m3u8" $name;
    "~*/s/(?<name>.*).ts" $name;
}

server {
    listen 80;
    server_name secure-link-demo;

    location /s {
        root /opt;
        secure_link $arg_md5,$arg_expires;
        secure_link_md5 "$secure_link_expires$file_name$http_user_agent enigma";

        if ($secure_link = "") { return 403; }
        if ($secure_link = "0") { return 410; }
    }
}

Resumen

El módulo Secure Link de NGINX permite proteger los archivos frente al acceso no autorizado añadiendo datos codificados, como el hash de una parte específica de la URL. Añadir una fecha de caducidad también limita el periodo de validez de los enlaces, lo que proporciona una seguridad aún mayor.

Para probar NGINX Plus, inicie hoy mismo su prueba gratuita de 30 días o póngase en contacto con nosotros para hablar sobre sus casos de uso.


Compartir
Etiquetas: Security, F5 NGINX, Tech, 2016

Sobre el autor

Kunal Pariani
Kunal ParianiArquitecto de soluciones técnicas

Más artículos de Kunal Pariani

Artículos relacionados del blog

Únete a F5 NGINX en AppWorld 2024
NGINX | 04/01/2024

Únete a F5 NGINX en AppWorld 2024

Descubre las numerosas razones para asistir al evento insignia de F5, que incluirá un emocionante anuncio sobre la cartera de productos NGINX.

Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source
NGINX | 26/09/2023

Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source

La función de diccionario compartido permite configurar NGINX sin necesidad de reiniciarlo al renovar los certificados SSL/TLS.

Exposición de APIs en Kubernetes
NGINX | 10/03/2022

Exposición de APIs en Kubernetes

La unidad 2 de Microservices March 2022 se centra en cómo exponer tus API en Kubernetes. Aquí tienes enlaces a los recursos que necesitas: la retransmisión en directo del 14 de marzo, artículos de blog y otros recursos de preparación, y el laboratorio práctico «Protege las API de Kubernetes mediante la limitación de solicitudes».

NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto
NGINX | 23/08/2021

NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto

Hoy, en NGINX Sprint 2.0, anunciamos cómo respaldaremos nuestra visión del futuro de las aplicaciones modernas con una mayor presencia en la comunidad de código abierto, incluida la participación en el proyecto comunitario Kubernetes, nuevos proyectos de software de código abierto y la arquitectura de referencia para aplicaciones modernas.

Presentamos NGINX Plus R22
NGINX | 09/06/2020

Presentamos NGINX Plus R22

NGINX Plus R22 incorpora nuevas funcionalidades, como la compatibilidad con OCSP para verificar la revocación de certificados en TLS mutuo, el uso de varios proveedores de identidad OIDC en una configuración, el seguimiento en tiempo real de la limitación de solicitudes y conexiones en el panel de NGINX Plus y mejoras en NGINX JavaScript.

Presentamos NGINX Plus R17
NGINX | 11/12/2018

Presentamos NGINX Plus R17

NGINX Plus R17 incorpora compatibilidad con la limitación de velocidad en dos fases y con TLS 1.3, la versión más reciente del protocolo Transport Layer Security. La configuración de OpenID Connect es más sencilla y NGINX ModSecurity WAF es el doble de rápido que antes. También se ha actualizado el módulo JavaScript de NGINX.