Protegendo URLs com o módulo Secure Link no NGINX e no NGINX Plus

NGINX | 29 de julho de 2016

Tanto o software de código aberto NGINX quanto o NGINX Plus são muito seguros e confiáveis como servidores web, proxies reversos e caches para o seu conteúdo. Para obter proteção adicional contra o acesso de clientes não autorizados, você pode usar as diretivas do módulo Secure Link para exigir que os clientes incluam uma string de hash específica na URL do recurso solicitado.

Nesta publicação do blog, discutiremos como configurar os dois métodos implementados no módulo Secure Link. Os exemplos de trechos de configuração protegem arquivos HTML e de playlists de mídia, mas podem ser aplicados a qualquer tipo de URL HTTP. Os métodos se aplicam tanto ao NGINX quanto ao NGINX Plus, mas, para simplificar, mencionaremos apenas o NGINX Plus no restante da publicação.

Visão geral dos métodos do módulo Secure Link

O módulo Secure Link verifica a validade de um recurso solicitado comparando uma string codificada na URL da solicitação HTTP com a string que ele calcula para essa solicitação. Se um link tiver uma vida útil limitada e o prazo tiver expirado, o link será considerado desatualizado. O status dessas verificações é registrado na variável $secure_link e usado para controlar o fluxo de processamento.

Como mencionado, o módulo oferece dois métodos. Apenas um deles pode ser configurado em um determinado contexto http, server ou location.

  • O primeiro modo, que também é o mais simples, é habilitado pela diretiva secure_link_secret. A string codificada é um hash MD5 calculado a partir da concatenação de duas strings de texto: a parte final da URL e uma palavra secreta definida na configuração do NGINX Plus. (Para obter detalhes sobre a primeira string de texto, consulte Uso de URLs protegidas básicas.) Para acessar o recurso protegido, o cliente deve incluir o hash logo após o prefixo da URL, que é uma string arbitrária sem barras. Nesta URL de exemplo, o prefixo é videos e o recurso protegido é o arquivo bunny.m3u8: /videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 Um caso de uso desse método ocorre quando um usuário envia uma imagem ou um documento a um servidor para compartilhamento, mas deseja impedir que qualquer pessoa que conheça o nome do arquivo o acesse antes da publicação do link oficial.
  • O segundo método, mais flexível, é habilitado pelas diretivas secure_link e secure_link_md5. Nesse caso, a string codificada é um hash MD5 de variáveis definidas no arquivo de configuração do NGINX Plus. Normalmente, a variável $remote_addr é incluída para restringir o acesso a um endereço IP de cliente específico, mas você pode usar outros valores, como $http_user_agent, que captura o cabeçalho User-Agent e, assim, restringe o acesso a determinados navegadores. Opcionalmente, você pode especificar uma data de expiração, após a qual a URL deixa de funcionar, mesmo que o hash esteja correto. O cliente deve acrescentar o argumento md5 à URL da solicitação para especificar o hash. Se uma data de expiração estiver incluída na string usada para gerar o hash, o cliente também deverá acrescentar o argumento expires para especificar a data, como nesta URL de exemplo para solicitar o arquivo protegido pricelist.html: /files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740

O módulo Secure Link está incluído nos binários de código aberto pré-compilados do NGINX fornecidos pelo nginx.org, nos pacotes do NGINX disponibilizados por fornecedores de sistemas operacionais e no NGINX Plus. Ele não é incluído por padrão quando você compila o NGINX a partir do código-fonte. Para habilitá-lo, inclua o argumento --with-http_secure_link_module no comando configure.

Como usar URLs protegidos básicos

A maneira mais básica de proteger URLs é usar a diretiva secure_link_secret. No trecho de exemplo a seguir, protegemos um arquivo de playlist de mídia de HTTP Live Streaming (HLS) chamado /bunny.m3u8. Ele fica armazenado no diretório /opt/secure/hls, mas é disponibilizado aos clientes por meio de uma URL que começa com o prefixo 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;
    }
}

Com essa configuração, para acessar o arquivo /opt/secure/hls/bunny.m3u8 os clientes devem usar a seguinte URL:

/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8

A string com hash vem logo após o prefixo, que é uma string arbitrária sem nenhuma barra (neste caso, videos).

O hash é calculado sobre uma string de texto que concatena dois elementos:

  • A parte da URL que vem após o hash, neste caso, hls/bunny.m3u8.
  • O parâmetro da diretiva secure_link_secret, neste caso, enigma.

Se a URL da solicitação do cliente não tiver o hash correto, o NGINX Plus definirá a variável $secure_link como uma string vazia. O teste if falhará, e o NGINX Plus retornará o código de status 403 Forbidden na resposta HTTP.

Caso contrário (ou seja, se o hash estiver correto), a diretiva rewrite reescreve a URL — em nosso exemplo, para /secure/hls/bunny.m3u8 (a variável $secure_link captura a parte da URL que vem após o hash). As URLs que começam com /secure são processadas pelo segundo bloco location. A diretiva root nesse bloco define /opt como o diretório raiz dos arquivos solicitados, e a diretiva internal especifica que o bloco é usado apenas para solicitações geradas internamente.

Gerando o hash no cliente para uma URL básica protegida

Para obter o hash MD5 no formato hexadecimal que o cliente deve incluir na URL, executamos o comando openssl md5 com a opção -hex:

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

Para uma discussão sobre como gerar hashes programaticamente, consulte Gerando o hash programaticamente.

Resposta do servidor a URLs com segurança básica

Os seguintes exemplos de comandos curl mostram como o servidor responde a diferentes URLs protegidas.

Se a URL incluir o hash MD5 correto, a resposta será 200 OK:

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

Se o hash MD5 estiver incorreto, a resposta será 403 Proibido:

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

Se o hash de bunny.m3u8 for usado para outro arquivo, a resposta também será 403 Proibido:

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

Como usar URLs seguras que expiram

O método mais flexível para proteger URLs usa as diretivas secure_link e secure_link_md5. Neste exemplo, nós as usamos para permitir o acesso ao arquivo /var/www/files/pricelist.html somente por clientes com o endereço IP 192.168.33.14 e apenas até 31 de dezembro de 2016.

Nosso servidor virtual escuta na porta 80 e processa todas as solicitações HTTP protegidas no bloco location /files, em que a diretiva root define /var/www como o diretório raiz dos arquivos solicitados.

A diretiva secure_link define duas variáveis que capturam argumentos na URL da solicitação: $arg_md5 recebe o valor do argumento md5, e $arg_expires recebe o valor do argumento expires.

A diretiva secure_link_md5 define a expressão cujo hash é calculado para gerar o valor MD5 da solicitação; durante o processamento da URL, o hash é comparado ao valor de $arg_md5. A expressão de exemplo inclui o tempo de expiração informado na solicitação (capturado na variável $secure_link_expires), a URL ($uri), o endereço IP do cliente ($remote_addr) e a palavra 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; }
        }
}

Com essa configuração, para acessar /var/www/files/pricelist.html, um cliente com o endereço IP 192.168.33.14 deve enviar esta URL de solicitação antes de sábado, 31 de dezembro de 2016, às 23:59:00 UTC:

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

Se o hash na URL enviada pelo cliente (capturado na variável $arg_md5) não corresponder ao hash calculado com base na diretiva secure_link_md5, o NGINX Plus definirá a variável $secure_link como uma string vazia. O teste if falhará, e o NGINX Plus retornará o código de status 403 Forbidden na resposta HTTP.

Se os hashes corresponderem, mas o link tiver expirado, o NGINX Plus definirá a variável $secure_link como 0; novamente, o teste if falhará, mas, desta vez, o NGINX Plus retornará o código de status 410 Gone na resposta HTTP.

Como gerar o hash e o tempo de expiração em um cliente

Agora, vamos ver como um cliente calcula os argumentos md5 e expires a serem incluídos na URL.

A primeira etapa é determinar o horário Unix equivalente à data de expiração, pois esse valor é incluído na expressão com hash na forma da variável $secure_link_expires. Para obter o horário Unix – o número de segundos desde a Época Unix (1970-01-01 00:00:00 UTC) – usamos o comando date com a opção -d e o especificador de formato +%s.

Em nosso exemplo, estamos definindo o horário de expiração como sábado, 31 de dezembro de 2016, às 23:59:00 UTC. Portanto, o comando é:

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

O cliente inclui esse valor como o argumento expires=1483228740 na URL da solicitação.

Agora, processamos a string definida pela diretiva secure_link_md5 — $secure_link_expires$uri$remote_addr enigma — usando três comandos:

  • O comando openssl md5 com a opção -binary gera o hash MD5 em formato binário.
  • O comando openssl base64 aplica a codificação Base64 ao valor de hash.
  • Os comandos tr substituem o sinal de mais ( + ) pelo hífen ( - ) e a barra ( / ) pelo sublinhado ( _ ), e excluem o sinal de igual (=) do valor codificado.

Para o nosso exemplo, o comando completo é:

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

O cliente inclui esse valor como o argumento md5=AUEnXC7T-Tfv9WLsWbf-mw na URL da solicitação.

Gerando o hash programaticamente

Se o seu servidor web NGINX Plus estiver fornecendo conteúdo dinâmico de um servidor de aplicações, tanto o NGINX Plus quanto o servidor de aplicações precisarão usar a mesma URL protegida. Você pode gerar programaticamente o hash para o argumento md5 na URL. A função Node.js a seguir gera um hash correspondente ao definido no trecho de configuração do NGINX Plus acima. Ela recebe como argumentos um tempo de expiração, uma URL, o endereço IP do cliente e uma palavra secreta, e retorna o hash MD5 em formato binário codificado em 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 o hash do nosso exemplo atual, passamos estes argumentos:

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

Resposta do servidor a URLs protegidas com prazos de expiração

Os seguintes exemplos de comandos curl mostram como o servidor responde a URLs protegidas.

Se um cliente com o endereço IP 192.168.33.14 incluir o hash MD5 e o tempo de expiração corretos, a resposta será 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

Se um cliente com um endereço IP diferente enviar a mesma URL, a resposta será 403 Proibido:

# 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

Se o valor de hash do argumento md5 estiver incorreto, a resposta será 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

Se a URL tiver expirado (a data representada pelo argumento expires estiver no passado), a resposta será 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

Exemplo – Protegendo arquivos de segmento com uma data de expiração

Veja outro exemplo de uma URL protegida com data de expiração, usada para proteger tanto a playlist de um ativo de mídia quanto os arquivos de segmento.

Uma diferença em relação ao exemplo anterior é que adicionamos aqui um bloco de configuração map para remover a extensão da playlist (arquivo .m3u8) e dos segmentos HLS (arquivos .ts) ao capturarmos o nome do arquivo na variável $file_name, que é passada para a diretiva secure_link_md5. Isso permite proteger tanto as solicitações dos segmentos individuais .ts quanto as da playlist.

Outra diferença em relação ao primeiro exemplo é que incluímos a variável $http_user_agent (que captura o cabeçalho User-Agent) na diretiva secure_link_md5, para restringir o acesso a clientes que usam navegadores específicos (por exemplo, para que a URL funcione no Safari, mas não no Chrome ou 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; }
    }
}

Resumo

O módulo Secure Link do NGINX permite proteger arquivos contra acesso não autorizado adicionando dados codificados, como o hash de uma parte específica da URL. Adicionar um prazo de expiração também limita o período de validade dos links, proporcionando ainda mais segurança.

Para experimentar o NGINX Plus, comece sua avaliação gratuita de 30 dias hoje mesmo ou entre em contato conosco para conversar sobre seus casos de uso.


Compartilhar

Sobre o autor

Kunal Pariani
Kunal ParianiArquiteto de Soluções Técnicas

Mais publicações de Kunal Pariani

Publicações relacionadas no blog

Como o F5 NGINX One aprimora o gerenciamento e a observabilidade
NGINX | 10/10/2024

Como o F5 NGINX One aprimora o gerenciamento e a observabilidade

Descubra como o F5 NGINX One aprimora o gerenciamento e a observabilidade de ambientes híbridos e multicloud ao oferecer um console web centralizado baseado em SaaS, com insights em tempo real, conformidade automatizada e gerenciamento simplificado de configurações.

Do OpenAPI ao NGINX como gateway de API usando uma API declarativa
NGINX | 17/06/2024

Do OpenAPI ao NGINX como gateway de API usando uma API declarativa

Descubra como transformar uma definição de esquema OpenAPI em uma configuração NGINX totalmente funcional, operando como um API Gateway com a segurança de um Web Application Firewall e um Portal do Desenvolvedor, usando uma abordagem declarativa de API.

Como gerenciar picos repentinos de tráfego e sobrecarga do servidor
NGINX | 14/07/2021

Como gerenciar picos repentinos de tráfego e sobrecarga do servidor

Picos de tráfego e sobrecarga de servidores são preocupações sérias para varejistas online em qualquer época, mas especialmente durante a “Cyber Season” de compras de fim de ano. Detalhamos como você pode aproveitar as soluções de entrega de aplicações de nível de produção da NGINX para se preparar para aumentos repentinos de tráfego.

Distribuição segura de chaves privadas SSL com o NGINX
NGINX | 02/04/2019

Distribuição segura de chaves privadas SSL com o NGINX

Descrevemos três maneiras progressivamente mais seguras de proteger chaves privadas SSL ao configurar o NGINX para processar tráfego HTTPS: permitir acesso de leitura apenas ao usuário root, criptografar as chaves com senhas armazenadas separadamente e distribuir as senhas a partir de um repositório central.

Agrupando solicitações de API em lotes com NGINX Plus e o módulo JavaScript do NGINX
NGINX | 24/05/2018

Agrupando solicitações de API em lotes com NGINX Plus e o módulo JavaScript do NGINX

O módulo JavaScript do NGINX lançado com o NGINX Plus R15 oferece suporte a subsolicitações, possibilitando uma classe totalmente nova de casos de uso para o NGINX Plus. Este exemplo completo também usa a API do NGINX Plus e os módulos de chave-valor para converter solicitações de API dos clientes em solicitações separadas para as APIs dos microsserviços que compõem um aplicativo.

Como superar o esgotamento de portas efêmeras no NGINX e no NGINX Plus
NGINX | 19/04/2016

Como superar o esgotamento de portas efêmeras no NGINX e no NGINX Plus

Confira dicas para evitar o esgotamento de portas efêmeras com ajustes no kernel do Linux, conexões keepalive e conexões vinculadas dinamicamente a endereços IP locais.