Além de servir como um servidor web leve e versátil o Nginx é muito poderoso como proxy-reverso. Também podemos utilizá-lo como um sistema de balanceamento de carga de excelente performance.
Neste post mostrarei como criar um ambiente não só de balanceamento de carga com failover, como também irei incrementá-lo com uma camada de segurança usando o WAF Naxsi.
Instalação
aptitude install nginx-naxsi
Habilitar o balanceamento de carga é muito simples bastando para isso incluir a diretiva upstream no vhost do site. Ele possibilita diversos tipos de configurações:
Esse é caso mais simples onde os backends serão tratados como um único host.
upstream acme {
server 192.168.0.2;
server 192.168.0.3;
server 192.168.0.4;
}
A entidade acme agora será tratada como upstream servindo de referência para o parâmetro proxy_pass como mostra o exemplo abaixo:
server {
listen acme:80;
access_log /var/log/nginx/nginx.log;
location / {
proxy_pass http://acme;
}
}
Para o cliente sempre manter a conexão no mesmo backend utiliza-se o parâmetro hash_ip.
upstream acme {
hash_ip;
server 192.168.0.2;
server 192.168.0.3;
server 192.168.0.4;
}
Prioridade e Failover
Para definir a prioridade de cada backend utiliza-se o parâmetro weight o valor padrão é 1. No exemplo abaixo as 3 primeiras requisições serão enviadas para o servidor 192.168.0.2, a 4a e 5a para o 192.168.0.3 e a 6a. para o 192.168.0.4.
upstream acme {
hash_ip;
server 192.168.0.2 weight=3;
server 192.168.0.3 weight=2;
server 192.168.0.4;
}
O failover é habilitando usando os parâmetros max_fails define o total de falhas na requisição e fail_timeout define o intervalo de tempo entre as falhas a partir dai a requisição é enviada para o próximo backend.
upstream acme {
hash_ip;
server 192.168.0.2 max_fails=3 fail_timeout=30s;
server 192.168.0.3;
server 192.168.0.4;
server 192.168.0.5 down;
}
O parâmetro down é utilizado quando um backend está inacessível.
Naxsi
O Naxsi é um Web Application Firewall para o Nginx criado pelo Thibault Koechlin. Uma das grandes vantagens é que ele segue o modelo positivo de segurança aprendendo como a aplicação funciona e criando regras baseadas no comportamento (whitelist-based), ele não utiliza assinatura dos ataques (blacklist-based) como um AV por exemplo.
Habilitar esse WAF é muito simples bastando apenas adicionar a seguinte linha no arquivo nginx.conf:
include /etc/nginx/naxsi_core.rules;
E no arquivo do vhost
location / {
include /etc/nginx/naxsi.rules;
proxy_pass http://acme/;
...
}
#Naxsi Learning Mode
location /RequestDenied {
proxy_pass http://127.0.0.1:4242;
}
A opção Learning Mode permite ao Naxsi aprender o funcionamento da aplicação criando novas regras de whitelist.
Outra ferramenta bastante útil é o nx_util, ele é um parser que lê o arquivo de erro dos sites, atualiza uma base de dados sqlite, gera novas regras de whitelist e exporta os dados para a tela ou para um arquivo html muito bacana.
wget https://naxsi.googlecode.com/files/nx_util-0.3.tgz
tail /var/log/nginx/acme.error.log | nx_util.py -d naxsi_ui -l -i -o -H /var/www/acme.html
Visualizando os dados na console
tail /var/log/nginx/naxsi.error.log | nx_util.py -d naxsi_ui -l -i -o
Using stdin.
Committing to db ...
########### Optimized Rules Suggestion ##################
# total_count:1 (50.0%), peer_count:1 (100.0%) | simple quote
BasicRule wl:1306 "mz:$URL:/vulnerabilities/sqli/|$ARGS_VAR:id";
# total_count:1 (50.0%), peer_count:1 (100.0%) | simple quote
BasicRule wl:1013 "mz:$URL:/vulnerabilities/sqli/|$ARGS_VAR:id";
OBS1: Para acessar o html utilizei o lighttpd configurando-o na porta 8080.
OBS2: Para evitar problemas com o WordPress existem regras específicas que podem ser encontradas neste blog
Abaixo seguem os arquivos de configuração completos:
Nginx
/etc/nginx/nginx.conf
user nginx;
worker_processes 4;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nodelay on;
gzip on;
gzip_disable "MSIE [1-6].(?!.*SV1)";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
# Protecao contra DoS
client_body_buffer_size 1K;
client_header_buffer_size 1k;
client_max_body_size 2M;
large_client_header_buffers 2 1k;
client_body_timeout 10;
client_header_timeout 10;
keepalive_timeout 5 5;
send_timeout 10;
server_tokens off;
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_max_temp_file_size 0;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
proxy_cache_methods GET HEAD POST;
# Naxsi WAF
include /etc/nginx/naxsi_core.rules;
}
Vhost
/etc/nginx/sites-available/acme
upstream acme {
hash_ip;
server 192.168.0.2 weight=5;
server 192.168.0.3 max_fails=3 fail_timeout=30s;
server 192.168.0.4;
}
server {
listen 80;
server_name acme;
access_log /var/log/nginx/acme.access.log main;
error_log /var/log/nginx/acme.error.log;
location / {
include /etc/nginx/naxsi.rules;
proxy_pass http://acme/;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_redirect off;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
#Naxsi Learning Mode
location /RequestDenied {
proxy_pass http://127.0.0.1:4242;
}
}