diff --git a/README.md b/README.md index 73a3279..ebaf98f 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,19 @@ Siga este procedimento para colocar um novo sistema no ar com segurança máxima 3. Faça o commit e push para a branch `producao`. ### 2. Sincronização no Servidor (SSH) -1. Entre no servidor e vá para `/etc/nginx`. -2. Execute `sudo git pull origin producao`. -3. Valide a sintaxe: `sudo nginx -t`. -4. Recarregue o Nginx: `sudo systemctl reload nginx`. +1. Entre no servidor e vá para o diretório de scripts: `cd /etc/nginx/scripts/`. +2. Execute o deploy seguro: `sudo python3 deploy_pathfinder.py`. + - **Nota:** Este script faz backup automático e rollback se a configuração estiver errada. + +--- + +## ⚡ Automação de Deploy (Safe-Rollback) +O Pathfinder inclui um script robusto para evitar downtime: +- `scripts/deploy_pathfinder.py`: + - Faz backup datado de `/etc/nginx` e `/etc/fail2ban`. + - Sincroniza os novos arquivos da pasta temporária. + - Valida com `nginx -t`. + - **Auto-Rollback**: Se houver erro (ex: módulo Brotli faltando), ele restaura os backups originais e reinicia os serviços em milissegundos. --- @@ -93,9 +102,9 @@ Rode os comandos abaixo (substitua o domínio): ## 🚀 Manutenção e Logs -### Recompilar WAF (Se necessário) -Se o Nginx for atualizado para uma versão superior à 1.29.5, o módulo ModSecurity precisará ser recompilado usando o script: -`sudo ./scripts/install_modsecurity.sh` +### Recompilar WAF e Performance (Se necessário) +Se o Nginx for atualizado ou se precisar habilitar o Brotli, o módulo precisará ser recompilado usando o script: +`sudo ./scripts/setup_pathfinder.sh` ### Validação Sempre teste a configuração antes do reload: diff --git a/fail2ban/data/fail2ban/action.d/nginx-pathfinder-action.conf b/fail2ban/data/fail2ban/action.d/nginx-pathfinder-action.conf new file mode 100644 index 0000000..606fcbc --- /dev/null +++ b/fail2ban/data/fail2ban/action.d/nginx-pathfinder-action.conf @@ -0,0 +1,27 @@ +# /etc/fail2ban/action.d/nginx-pathfinder-action.conf +# +# Ação Híbrida Pathfinder Proxy (2026). +# 1. Bloqueia o IP no Firewall (UFW). +# 2. Insere o IP no snippet 'blacklist.conf' do Nginx para bloqueio de aplicação. +# ***************************************************************************** + +[Definition] + +# Comando executado ao iniciar a jail +actionstart = touch /etc/nginx/snippets/blacklist.conf + +# Comando de banimento: Firewall + Nginx Blacklist +# Usamos 'prepend' no UFW para garantir que o bloqueio venha antes de qualquer permissão. +actionban = ufw prepend deny from to any + printf "deny ;\n" >> /etc/nginx/snippets/blacklist.conf + nginx -s reload + +# Comando de desbanimento: Remove do Firewall e do arquivo Nginx +# O sed remove a linha exata 'deny IP;' do snippet. +actionunban = ufw delete deny from to any + sed -i "/deny ;/d" /etc/nginx/snippets/blacklist.conf + nginx -s reload + +[Init] +# Nome da jail para logs de auditoria +name = nginx-pathfinder diff --git a/fail2ban/data/fail2ban/filter.d/nginx-pathfinder-filter.conf b/fail2ban/data/fail2ban/filter.d/nginx-pathfinder-filter.conf new file mode 100644 index 0000000..83feeea --- /dev/null +++ b/fail2ban/data/fail2ban/filter.d/nginx-pathfinder-filter.conf @@ -0,0 +1,18 @@ +# /etc/fail2ban/filter.d/nginx-pathfinder-filter.conf +# +# Filtro inteligente para o Pathfinder Proxy (2026). +# Monitora o log JSON 'detailed_proxy' e extrai falhas baseadas no Security Score. +# ***************************************************************************** + +[Definition] + +# Captura IPs com Security Score 2 (Perigo Alto) ou 3 (Ataque Crítico) +# Também valida via risk_level para redundância de segurança. +failregex = ^.*"remote_addr":"".*"security_score":"(2|3)".*$ + ^.*"remote_addr":"".*"risk_level":"(PERIGO_ALTO_RISCO_TENTATIVA_VAZAMENTO|ATAQUE_CRITICO_BLOQUEIO_DE_EXPLORACAO)".*$ + +# Ignora tráfego limpo ou suspeito de baixo nível (Score 0 e 1) +ignoreregex = ^.*"security_score":"(0|1)".*$ + +# Nota: O formato JSON do Nginx é processado aqui como uma string única por linha. +# é a tag padrão do Fail2Ban para identificar o endereço IP. diff --git a/fail2ban/data/fail2ban/jail.d/nginx-pathfinder-jail.conf b/fail2ban/data/fail2ban/jail.d/nginx-pathfinder-jail.conf new file mode 100644 index 0000000..5e04b7a --- /dev/null +++ b/fail2ban/data/fail2ban/jail.d/nginx-pathfinder-jail.conf @@ -0,0 +1,24 @@ +# /etc/fail2ban/jail.d/nginx-pathfinder-jail.conf +# +# Jail Unificada Pathfinder Proxy (2026). +# Centraliza a proteção de todos os sites monitorando o Score de Risco do Nginx. +# ***************************************************************************** + +[nginx-pathfinder-jail] +enabled = true +port = http,https +filter = nginx-pathfinder-filter +action = nginx-pathfinder-action +logpath = /var/log/nginx/access_json.log +backend = auto + +# --- Política de Tolerância Zero --- +# Se o Nginx marcou o tráfego como PERIGO ou ATAQUE (Score 2 ou 3), +# o banimento é executado na PRIMEIRA ocorrência. +maxretry = 1 + +# Tempo de observação (não muito relevante com maxretry=1, mas mantido por padrão) +findtime = 1d + +# Tempo de banimento: 7 dias (IPs maliciosos persistentes) +bantime = 7d diff --git a/fail2ban/data/fail2ban/jail.d/nginx-unified.conf b/fail2ban/data/fail2ban/jail.d/nginx-unified.conf deleted file mode 100644 index e5d13f1..0000000 --- a/fail2ban/data/fail2ban/jail.d/nginx-unified.conf +++ /dev/null @@ -1,34 +0,0 @@ -[nginx-limit-req] -enabled = true -port = http,https -filter = nginx-limit-req -logpath = /var/log/nginx/*.error.log -maxretry = 1 - -[nginx-badbots] -enabled = true -port = http,https -filter = apache-badbots -logpath = /var/log/nginx/*.access.log -maxretry = 2 - -[nginx-deny] -enabled = true -port = http,https -filter = nginx-deny -logpath = /var/log/nginx/*.error.log -maxretry = 1 - -[nginx-unauthorized] -enabled = true -port = http,https -filter = nginx-unauthorized -logpath = /var/log/nginx/*.access.log -maxretry = 3 - -[nginx-bad-request] -enabled = true -port = http,https -filter = nginx-bad-request -logpath = /var/log/nginx/*.error.log -maxretry = 1 diff --git a/nginx/conf.d/ferreirareal.com.br.conf b/nginx/conf.d/ferreirareal.com.br.conf index 4d83614..bae3422 100644 --- a/nginx/conf.d/ferreirareal.com.br.conf +++ b/nginx/conf.d/ferreirareal.com.br.conf @@ -1,5 +1,5 @@ upstream ferreirareal_backend { - server 172.112.1.2:8081; + server 172.17.0.3:8081; } server { diff --git a/nginx/nginx.conf b/nginx/nginx.conf index b406dfd..74d2643 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -2,8 +2,19 @@ # Load essential modules load_module modules/ngx_http_modsecurity_module.so; -# load_module modules/ngx_http_brotli_filter_module.so; -# load_module modules/ngx_http_brotli_static_module.so; +load_module modules/ngx_http_geoip2_module.so; +load_module modules/ngx_http_brotli_filter_module.so; +load_module modules/ngx_http_brotli_static_module.so; +load_module modules/ngx_http_cache_purge_module.so; +load_module modules/ngx_http_upstream_fair_module.so; +load_module modules/ngx_http_echo_module.so; +load_module modules/ngx_http_headers_more_filter_module.so; +load_module modules/ngx_http_subs_filter_module.so; +load_module modules/ngx_otel_module.so; +load_module modules/ngx_http_cookie_flag_filter_module.so; +load_module modules/ngx_http_lower_upper_case_module.so; +load_module modules/ngx_http_image_filter_module.so; +load_module modules/ngx_http_ssl_fingerprint_module.so; user nginx; worker_processes auto; @@ -57,6 +68,7 @@ http { include snippets/security_headers.conf; # Novos headers de 2026 include snippets/security_maps.conf; include snippets/rate_limit.conf; + include snippets/bandwidth_limit.conf; # Ativação Global da Blacklist include /etc/nginx/dynamic/blacklist.conf; diff --git a/nginx/snippets/bandwidth_limit.conf b/nginx/snippets/bandwidth_limit.conf new file mode 100644 index 0000000..ab477d5 --- /dev/null +++ b/nginx/snippets/bandwidth_limit.conf @@ -0,0 +1,10 @@ +# --- Pathfinder Bandwidth Control --- +# Evita que um único usuário sature o link de saída (Upload do servidor) + +# 1. Limite por requisição (ex: Downloads de arquivos grandes) +# Ativa o limite apenas após 10MB transferidos na conexão atual +limit_rate_after 10m; + +# 2. Velocidade máxima (ex: 1MB/s por requisição) +# Pode ser sobrescrito dentro dos blocos 'location' ou 'server' +limit_rate 1m; diff --git a/nginx/snippets/fingerprinting.conf b/nginx/snippets/fingerprinting.conf new file mode 100644 index 0000000..af014ae --- /dev/null +++ b/nginx/snippets/fingerprinting.conf @@ -0,0 +1,15 @@ +# --- Pathfinder Static Asset Fingerprinting --- +# Permite o uso de 'Cache-Control: immutable' para arquivos com hash no nome. + +# Lógica: Se o arquivo termina em .[hash].ext e não existir fisicamente, +# o Nginx tenta carregar o arquivo original (sem o hash). +# Ex: main.a1b2c3d4.js -> tenta main.js no backend/disco local. + +location ~* "^(.+)\.[0-9a-f]{8,}\.(js|css|png|jpg|jpeg|gif|ico|svg|webp|avif|woff2?|wasm)$" { + # 1. Tenta o arquivo original se o versionado não existir + try_files $uri $1.$2 =404; + + # 2. Força cache imutável (Já que o conteúdo muda se o nome mudar) + add_header Cache-Control "public, max-age=31536000, immutable"; + add_header X-Pathfinder-Cache "Immutable-Asset"; +} diff --git a/nginx/snippets/log_formats.conf b/nginx/snippets/log_formats.conf index b97507f..3ebaae3 100644 --- a/nginx/snippets/log_formats.conf +++ b/nginx/snippets/log_formats.conf @@ -3,78 +3,89 @@ log_format detailed_proxy escape=json '{' -# Timestamps e Identificadores -'"@timestamp":"$time_iso8601",' -'"time_local":"$time_local",' -'"msec":"$msec",' -'"request_id":"$request_id",' -'"hostname":"$hostname",' -'"worker_pid":$pid,' + # Timestamps e Identificadores + '"@timestamp":"$time_iso8601",' + '"time_local":"$time_local",' + '"msec":"$msec",' + '"hostname":"$hostname",' + '"worker_pid":$pid,' -# Informações de Conexão e Cliente -'"remote_addr":"$remote_addr",' -'"remote_port":$remote_port,' -'"server_addr":"$server_addr",' -'"server_port":"$server_port",' -'"real_ip":"$http_x_forwarded_for",' -'"http_x_real_ip":"$http_x_real_ip",' -'"remote_user":"$remote_user",' + # Informações de Conexão e Cliente + '"remote_addr":"$remote_addr",' + '"remote_port":$remote_port,' + '"server_addr":"$server_addr",' + '"server_port":"$server_port",' + '"real_ip":"$http_x_forwarded_for",' + '"http_x_real_ip":"$http_x_real_ip",' + '"remote_user":"$remote_user",' -# Detalhes da Requisição HTTP -'"request":"$request",' -'"request_method":"$request_method",' -'"scheme":"$scheme",' -'"server_protocol":"$server_protocol",' -'"host_header":"$host",' -'"request_uri":"$request_uri",' -'"uri":"$uri",' -'"document_uri":"$document_uri",' -'"args":"$args",' -'"query_string":"$query_string",' -'"request_length":$request_length,' + # Detalhes da Requisição HTTP + '"request":"$request",' + '"request_method":"$request_method",' + '"scheme":"$scheme",' + '"server_protocol":"$server_protocol",' + '"host_header":"$host",' + '"request_uri":"$request_uri",' + '"uri":"$uri",' + '"document_uri":"$document_uri",' + '"args":"$args",' + '"query_string":"$query_string",' + '"request_length":$request_length,' -# Headers da Requisição -'"http_referer":"$http_referer",' -'"http_user_agent":"$http_user_agent",' -'"http_accept_encoding":"$http_accept_encoding",' -'"http_accept_language":"$http_accept_language",' + # Headers da Requisição + '"http_referer":"$http_referer",' + '"http_user_agent":"$http_user_agent",' + '"http_accept_encoding":"$http_accept_encoding",' + '"http_accept_language":"$http_accept_language",' -# Detalhes da Resposta -'"status":$status,' -'"body_bytes_sent":$body_bytes_sent,' -'"bytes_sent":$bytes_sent,' -'"sent_http_content_type":"$sent_http_content_type",' -'"sent_http_cache_control":"$sent_http_cache_control",' + # Detalhes da Resposta + '"status":$status,' + '"body_bytes_sent":$body_bytes_sent,' + '"bytes_sent":$bytes_sent,' + '"sent_http_content_type":"$sent_http_content_type",' + '"sent_http_cache_control":"$sent_http_cache_control",' -# Performance e Conexão -'"request_time":$request_time,' -'"connection":"$connection",' -'"connection_requests":$connection_requests,' + # Performance e Conexão + '"request_time":$request_time,' + '"connection":"$connection",' + '"connection_requests":$connection_requests,' -# SSL/TLS -'"ssl_protocol":"$ssl_protocol",' -'"ssl_cipher":"$ssl_cipher",' -'"ssl_session_reused":"$ssl_session_reused",' + # SSL/TLS + '"ssl_protocol":"$ssl_protocol",' + '"ssl_cipher":"$ssl_cipher",' + '"ssl_session_reused":"$ssl_session_reused",' + '"ssl_early_data":"$ssl_early_data",' -# Upstream -'"upstream_addr":"$upstream_addr",' -'"upstream_status":"$upstream_status",' -'"upstream_connect_time":"$upstream_connect_time",' -'"upstream_header_time":"$upstream_header_time",' -'"upstream_response_time":"$upstream_response_time",' -'"upstream_cache_status":"$upstream_cache_status",' + # Upstream + '"upstream_addr":"$upstream_addr",' + '"upstream_status":"$upstream_status",' + '"upstream_bytes_received":$upstream_bytes_received,' + '"upstream_bytes_sent":$upstream_bytes_sent,' + '"upstream_connect_time":"$upstream_connect_time",' + '"upstream_header_time":"$upstream_header_time",' + '"upstream_response_time":"$upstream_response_time",' + '"upstream_cache_status":"$upstream_cache_status",' -# Compressão e Performance Modernos -'"content_encoding":"$sent_http_content_encoding",' -'"compression_ratio":"$gzip_ratio",' -'"is_global_asset":"$is_global_asset",' -'"cache_key":"$pathfinder_cache_key",' + # Compressão e Performance Modernos + '"content_encoding":"$sent_http_content_encoding",' + '"compression_ratio":"$gzip_ratio",' + '"is_global_asset":"$is_global_asset",' + '"cache_key":"$pathfinder_cache_key",' -# Variáveis Customizadas de Segurança -'"is_bad_bot":"$is_bad_bot",' -'"is_suspicious_uri":"$is_suspicious_uri",' -'"block_request":"$block_request",' -'"risk_level":"$risk_level",' -'"security_score":"$security_score",' -'"is_internal_ip":"$is_internal"' + # Variáveis Customizadas de Segurança + '"is_bad_bot":"$is_bad_bot",' + '"is_suspicious_uri":"$is_suspicious_uri",' + '"block_request":"$block_request",' + '"risk_level":"$risk_level",' + '"security_score":"$security_score",' + '"is_internal_ip":"$is_internal",' + + # --- Pathfinder Extra Metadata (Seção Especial) --- + '"pathfinder_meta": {' + '"request_uuid": "$request_id",' + '"ja3_fingerprint": "$http_ssl_ja3",' + '"h2_fingerprint": "$http2_fingerprint",' + '"version": "2026.1",' + '"engine": "Pathfinder-Elite"' + '}' '}'; diff --git a/nginx/snippets/proxy_params.conf b/nginx/snippets/proxy_params.conf index cefdf61..44ed65f 100644 --- a/nginx/snippets/proxy_params.conf +++ b/nginx/snippets/proxy_params.conf @@ -14,6 +14,8 @@ proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; -# Security: Hide Backend Technology -proxy_hide_header X-Powered-By; -proxy_hide_header Server; +# --- Pathfinder Security: Ofuscação Avançada de Bordas --- +# Remove headers que revelam tecnologia +more_clear_headers 'Server' 'X-Powered-By' 'X-AspNet-Version' 'X-Runtime'; +# Define um Server personalizado (Camo) +more_set_headers 'Server: Pathfinder'; diff --git a/nginx/snippets/ssl_params.conf b/nginx/snippets/ssl_params.conf index c8e1ec1..5242814 100644 --- a/nginx/snippets/ssl_params.conf +++ b/nginx/snippets/ssl_params.conf @@ -7,6 +7,9 @@ add_header Strict-Transport-Security "max-age=63072000" always; # HTTP/3 (QUIC) Alt-Svc add_header Alt-Svc 'h3=":443"; ma=86400'; +http3 on; +quic_retry on; +# http3_max_concurrent_streams 128; # Opcional: Tuning # OCSP Stapling ssl_stapling on; diff --git a/nginx/snippets/stub_status.conf b/nginx/snippets/stub_status.conf new file mode 100644 index 0000000..5567db8 --- /dev/null +++ b/nginx/snippets/stub_status.conf @@ -0,0 +1,10 @@ +# --- Pathfinder Monitoring: Stub Status --- +# Fornece métricas básicas de conexões ativas para o Zabbix/Prometheus. + +location /nginx_status { + stub_status on; + access_log off; + allow 127.0.0.1; + allow 172.17.0.0/24; # Range interna itguys + deny all; +} diff --git a/scripts/deploy_pathfinder.py b/scripts/deploy_pathfinder.py new file mode 100644 index 0000000..36bd403 --- /dev/null +++ b/scripts/deploy_pathfinder.py @@ -0,0 +1,92 @@ +import os +import subprocess +import shutil +import sys +from datetime import datetime + +# Configurações +PASSWORD = "vR7Ag$Pk" +TMP_SYNC_BASE = "/tmp/pathfinder_sync" +TARGETS = { + "nginx": { + "src": f"{TMP_SYNC_BASE}/nginx/", + "dst": "/etc/nginx", + "bak": "/etc/nginx.bak", + "test_cmd": ["nginx", "-t"] + }, + "fail2ban": { + "src": f"{TMP_SYNC_BASE}/fail2ban/", + "dst": "/etc/fail2ban", + "bak": "/etc/fail2ban.bak", + "test_cmd": ["fail2ban-server", "-t"] # Apenas teste de config + } +} + +def run_sudo(cmd): + """Executa comando com sudo -S e retorna (rc, stdout, stderr).""" + p = subprocess.Popen(['sudo', '-S'] + cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + out, err = p.communicate(input=PASSWORD + '\n') + return p.returncode, out, err + +def log(msg): + print(f"[*] {msg}") + +def deploy(): + log("Iniciando Deploy Seguro Pathfinder...") + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + + # 1. Backups preventivos + for name, config in TARGETS.items(): + log(f"Criando backup de {name}...") + run_sudo(['cp', '-rp', config['dst'], f"{config['bak']}_{timestamp}"]) + # Mantém também um backup 'fixo' para o rollback rápido do script + run_sudo(['rm', '-rf', config['bak']]) + run_sudo(['cp', '-rp', config['dst'], config['bak']]) + + # 2. Aplicação das novas configurações + for name, config in TARGETS.items(): + if not os.path.exists(config['src']): + log(f"Aviso: Fonte {config['src']} não encontrada. Pulando {name}.") + continue + + log(f"Aplicando novas configurações em {name}...") + # Copia o conteúdo da pasta temporária para o destino + run_sudo(['cp', '-rf', os.path.join(config['src'], '.'), config['dst']]) + + # 3. Validação Crítica (Nginx) + log("Validando configuração do Nginx...") + rc, out, err = run_sudo(TARGETS['nginx']['test_cmd']) + + if rc != 0: + log("ERRO DETECTADO NA CONFIGURAÇÃO!") + print(f"\nDetalhes do Erro:\n{err}\n") + rollback() + sys.exit(1) + + log("Configuração validada com sucesso.") + + # 4. Reinicialização de Serviços + log("Reiniciando serviços...") + run_sudo(['systemctl', 'restart', 'nginx']) + run_sudo(['systemctl', 'restart', 'fail2ban']) + + log("Deploy finalizado com sucesso!") + +def rollback(): + log("EXECUTANDO ROLLBACK AUTOMÁTICO...") + for name, config in TARGETS.items(): + log(f"Restaurando {name} do backup...") + run_sudo(['rm', '-rf', config['dst']]) + run_sudo(['cp', '-rp', config['bak'], config['dst']]) + + log("Tentando reiniciar serviços após rollback...") + run_sudo(['systemctl', 'restart', 'nginx']) + run_sudo(['systemctl', 'restart', 'fail2ban']) + log("Rollback concluído. Servidor estabilizado.") + +if __name__ == "__main__": + if os.getuid() == 0: + log("Erro: Não execute como root diretamente. O script usa sudo internamente.") + sys.exit(1) + deploy()