Opa!! Vamos avançar em nossa série. Hoje aprenderemos como controlar o acesso com filtro de IP e autenticação interna.
Bloqueando acessos
Você pode bloquear um IP (ou faixa de IP) utilizando a diretiva deny
. Além do IP, é possível também utilizar o CIDR para bloquear requisições.
# Syntax: deny address | CIDR | unix: | all;
# Default: —
# Context: http, server, location, limit_except
#
# Bloqueia um IP específico
deny 127.0.0.1;
# Bloqueia faixa 142.250.218.0 - 142.250.218.255
deny 142.250.218.0/24;
# Bloqueia uma faixa de IP do tipo IPV6
deny 2001:0db8::/32;
# Bloqueia todos os acessos
deny all;
Bloqueando acesso a arquivos
Com o deny
, também é possível bloquear acesso aos arquivos ou a um diretório. Basta utilizar a diretiva dentro do contexto location
, por exemplo.
location ~ \.(log|zip|tar|gz|htaccess|bkp|old|txt)$ {
deny all;
}
Permitindo acesso a determinados IPs
Além de bloquear, você pode apenas permitir determinados IPs ou faixa de ips. Isso é útil para limitar o acesso a determinada rede.
# Syntax: allow address | CIDR | unix: | all;
# Default: —
# Context: http, server, location, limit_except
#
# Bloqueia acesso local
allow 127.0.0.1;
# Permite acesso da faixa de IP 162.158.214.0 - 162.158.214.255
allow 162.158.214.0/24;
# Permite acesso a uma faixa IPV6
allow 2001:0db8::/32;
# Bloqueia todos os acessos
deny all;
Não faz sentido usar apenas
allow
, visto que — por padrão — todos os acessos são permitidos.
Resolvendo problema com CDN
Quando você utiliza um serviço como CloudFlare (ou similar), eles recebem a requisição e a repassam para seu servidor. Isso faz com que o IP recebido pelo Nginx não seja o real, o do cliente; mas sim o IP do servidor da CloudFlare. Como resolver isso?
Para resolver, você precisa configurar duas diretivas: set_real_ip_from
e real_ip_header
.
Na primeira, você definirá os endereços de IP confiável (True-Client-IP) que sabem o IP do cliente, ou seja, você configurará com os IPs da CloudFlare.
Na segunda, você informará o header que contém o endereço real do cliente. Normalmente é CF-Connecting-IP ou X-Forwarded-For.
Como esses dados são mutáveis, deixarei o link com as configurações atuais.
Auth Request
O sistema de subautorização do Nginx funciona através do módulo ngx_http_auth_request_module
. Ele permite que o desenvolvedor crie um sistema de autorização baseado em sub-requisições (requisições internas).
Essas requisições devem retornar o status code 200 (2xx), 401 ou 403. A depender do código de status retornado, o Nginx liberará (ou não) a solicitação. Caso a autorização seja negada, o Nginx retornará o erro para o usuário.
Verificar suporte
Antes de começar, verifique se o Nginx foi compilado com o suporte ao módulo.
nginx -V 2>&1 | grep --color http_auth_request_module
Se não tiver configurado, compile o Nginx com a flag --with-http_auth_request_module
Observação: É importante que você saiba compilar o código fonte do Nginx. Caso não tenha esse conhecimento, acesse a postagem Série Nginx #2: Compilando o Nginx
Configurando rota
Para configurar a autorização, utilizaremos a diretiva auth_request
.
Iremos configurar a autorização para uma rota, mas você pode configurar para um servidor virtual (no contexto
server
) ou todos os sites (no contextohttp
), beleza? 👍
server {
# Outras configurações
location /upload {
# Syntax: auth_request uri | off;
# Default: auth_request off;
# Context: http, server, location
#
auth_request /auth_upload;
proxy_pass http://upload-server;
}
}
Perfeito! Agora vamos configurar a localização de autorização.
location = /auth_upload {
proxy_pass http://auth-server;
}
Com isso já conseguimos realizar a validação. Lembrando que o status code do proxy http://auth-server
deve retornar 2xx, 401 ou 403.
Bloqueando acesso direto
Em alguns sistemas, você pode querer bloquear o acesso direto à URL ou ao servidor de autenticação ou autorização. O Nginx oferece esse suporte com a diretiva internal
, ou seja, o Nginx impedirá que o usuário acesse a URL /auth_upload
e apenas ele fará o acesso.
location = /auth_upload {
internal;
proxy_pass http://auth-server;
}
No nosso exemplo, utilizarei os dados enviados na requisição POST para realizar a verificação da autorização, por isso é necessário configurar as diretivas proxy_pass_request_body
e proxy_method
.
Caso seu sistema de autorização não faça uso dos dados enviados no corpo da requisição, ignore-a.
location = /auth_upload {
internal;
proxy_pass http://auth-server;
proxy_pass_request_body on;
proxy_method POST;
}
Pronto! Com isso, o nosso sistema deverá funcionar corretamente.
Código para verificação da autorização
O código foi feito em Node e o servidor iniciado com Docker. Não explicarei o funcionamento porque a ideia é focar apenas no servidor web.
Utilizei o padrão adotado pelo S3, da AWS, para realizar a autorização ou negação. Ele basicamente verificará o método da requisição, content type, hash do arquivo e a data de envio e depois comparará com o hash enviado no header Authorization.
const crypto = require('crypto'); | |
const express = require('express'); | |
const fileUpload = require('express-fileupload'); | |
const morgan = require('morgan'); | |
const app = express(); | |
const mockUsers = require('./users.json'); | |
app.use(fileUpload()); | |
app.use(morgan('dev')); | |
/** | |
* Valida o cabeçalho Authorization | |
*/ | |
app.use(function(req, res, next) { | |
if (req.method.toUpperCase() == 'POST' && req.path == '/auth_upload') { | |
if (!req.get('authorization')) { | |
res.status(401).json({ msg: 'Header Authorization is invalid' }).end(); | |
} | |
} | |
next(); | |
}) | |
/** | |
* Valida usuário | |
*/ | |
app.use(function(req, res, next) { | |
if (req.method == 'POST' && req.path == '/auth_upload') { | |
const [, AccessKeyId, Signature] = req.get('authorization').match(/([\w]+):([\w]+)/i) | |
const userFiltered = mockUsers.filter((item) => item.AccessKeyId == AccessKeyId); | |
if (!userFiltered || userFiltered.length == 0) { | |
res.status(401).json({ msg: 'User not found' }).end(); | |
} | |
req.user = userFiltered[0]; | |
req.signature = Signature; | |
} | |
next(); | |
}) | |
app.post('/auth_upload', (req, res, next) => { | |
if (!req.files?.file) { | |
res.status(401).end(); | |
} | |
const StringToSign = `${req.method}\n${req.files.file.md5}\n${req.files.file.mimetype}\n${req.get('date')}`; | |
const HMacHash = crypto.createHmac('sha1', req.user.YourSecretAccessKey).update(StringToSign).digest('hex'); | |
let statusCode = 401; | |
if ( req.signature === HMacHash ) { | |
statusCode = 200; | |
} | |
res.status(statusCode).end(); | |
}); | |
app.listen(80, '0.0.0.0', () => console.log(`Start server ${new Date().toLocaleString()}`)) |
Realizando Upload
token="2HmVrZRYii8ZWrfIAFHLmlCYtqqv5nSASLCzZMHUg6M5vRlarfjumNDz9ZR5NHoY"
date_req=$(date -R)
hash=$(echo -en "POST\n$(md5sum anitta.mp4 | cut -d' ' -f1)\nvideo/mp4\n$date_req" | openssl sha1 -hmac "<user-key>" -hex)
http --form POST \
php.valdeirpsr.dev/upload \
Authorization:"AWS $token:$hash" \
Date:"$date_req" \
file@anitta.mp4
Como é feito o upload
- O arquivo é enviado para a URL
/upload
; - O Nginx recebe a requisição;
- O Nginx realiza uma subrequisição para autorização em
/auth_upload
; - O servidor
http://auth-server
realiza a validação e retorna o status code 200 ou 401. - O Nginx recebe a resposta da subrequisição e verifica o código retornado;
- Se o código for 401 ou 403, o Nginx retorna um erro para o cliente; caso retorne 2xx, prossegue com a requisição de upload.
Conclusão
É isso aew, meu povo! Finalizamos mais uma etapa da série de Nginx. Caso tenham dúvidas e/ou sugestões, basta deixarem o comentário. Beleza?
Até a próxima!
Top comments (0)