feat(infra): Full migration to containerized NGINX with WAF and Auto-SSL

Major infrastructure upgrade implementing:
1. Architecture
   - Containerized NGINX with custom Alpine build (Brotli + Headers More)
   - ModSecurity WAF (OWASP CRS) as a sidecar/frontend service
   - Fail2ban service monitoring logs for bot/attack mitigation

2. SSL Automation
   - Integrated Certbot with custom daily validation scripts
   - Automatic 3-day expiry detection and renewal
   - Smart ACME challenge injection for all sites

3. Configuration
   - Migrated 28 site configs to modular structure (conf.d/)
   - Created reusable snippets (Rate Limiting, Security Maps, Caching)
   - Fixed deprecated HTTP/2 syntax and ModSecurity directives

4. Documentation
   - Added GEMINI.md with full architectural overview
   - Cleanup of legacy files
This commit is contained in:
João Pedro 2026-01-22 13:14:18 -03:00
parent 064983364c
commit cd1a164114
326 changed files with 1236 additions and 195 deletions

48
.dockerignore Normal file
View File

@ -0,0 +1,48 @@
# Documentation and config folders
.gemini/
.git/
.github/
.vscode/
.idea/
# Legacy files (not needed in container)
legacy/
# Logs and debug files
*.log
debug_logs*.txt
nginx_test*.log
# Environment files
.env
.env.local
# Git files
.gitignore
.gitattributes
# Documentation
README.md
*.md
!nginx.conf
# Docker files (avoid recursive includes)
docker-compose*.yml
Dockerfile*
# Temporary and backup files
*.tmp
*.bak
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
# SSL private keys (should be mounted as volume, not baked in)
ssl/*.key
# Disabled configs
*.disabled

177
.gemini/GEMINI.md Normal file
View File

@ -0,0 +1,177 @@
# NGINX Pathfinder Proxy - Documentação da Estrutura
## Visão Geral
Este projeto é um proxy reverso NGINX containerizado com suporte a:
- **HTTP/3 (QUIC)** - Protocolo moderno para melhor performance
- **Brotli** - Compressão avançada para menor uso de banda
- **High Availability** - Restart automático e cache stale
- **Validação Automática** - Verificação de DNS e SSL no startup
---
## Estrutura de Pastas
```
.
├── conf.d/ # Configurações de sites (carregadas automaticamente)
├── snippets/ # Trechos reutilizáveis de configuração
├── scripts/ # Scripts auxiliares (pre-flight.sh)
├── ssl/ # Certificados SSL/TLS
├── legacy/ # Arquivos antigos do servidor original
├── .gemini/ # Documentação do projeto
├── Dockerfile # Build customizado do NGINX
├── docker-compose.yml # Orquestração do container
├── nginx.conf # Configuração principal do NGINX
└── deploy.sh # Script de deploy automatizado
```
---
## Arquivos Principais
### `Dockerfile`
Constrói uma imagem NGINX customizada baseada em Alpine Linux com:
- Módulo Brotli (compressão)
- Módulo headers_more (manipulação de headers)
- Ferramentas de diagnóstico (dig, openssl, curl)
### `docker-compose.yml`
Orquestra o container com:
- **Portas**: 80 (HTTP), 443 (HTTPS), 443/udp (HTTP/3)
- **Volumes**: conf.d, ssl, snippets, cache, logs
- **Restart**: always (alta disponibilidade)
- **Variável**: HOST_PUBLIC_IP (injetada pelo deploy.sh)
### `nginx.conf`
Configuração principal que:
- Carrega os módulos (Brotli, headers_more)
- Inclui snippets do `/etc/nginx/snippets/`
- Inclui sites do `/etc/nginx/conf.d/`
### `deploy.sh`
Script de deploy que:
1. Detecta o IP público do servidor
2. Grava no `.env`
3. Build da imagem Docker
4. Testa configuração com `nginx -t`
5. Inicia o container
---
## Pasta `snippets/`
Contém configurações reutilizáveis incluídas no `nginx.conf`:
| Arquivo | Função |
|---------|--------|
| `rate_limit.conf` | Define zonas de rate limiting |
| `security_maps.conf` | Maps para detecção de bots e IPs internos |
| `log_formats.conf` | Formato JSON detalhado para logs |
| `cache_zones.conf` | Define todas as zonas de cache |
| `custom_errors.conf` | Páginas de erro customizadas |
---
## Pasta `conf.d/`
Contém configurações individuais de cada site/sistema. Cada arquivo `.conf` representa um virtual host.
**Padrão de nomenclatura**: `dominio.conf` (ex: `itguys.com.br.conf`)
**Estrutura típica de um site**:
```nginx
upstream nome_backend {
server IP:PORTA;
}
server {
listen 80;
server_name dominio.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name dominio.com;
ssl_certificate /etc/letsencrypt/live/dominio.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dominio.com/privkey.pem;
proxy_cache STATIC;
proxy_cache_use_stale error timeout;
location / {
proxy_pass http://nome_backend;
}
}
```
---
## Pasta `scripts/`
### `pre-flight.sh`
Script executado ANTES do NGINX iniciar. Valida:
- Se `HOST_PUBLIC_IP` está definida
- Se os domínios configurados apontam para o IP correto
- Logs de warning se houver inconsistências
---
## Pasta `legacy/`
Contém arquivos do servidor original (synced via proxy-sinc). **Não são usados diretamente** - servem como referência.
---
## Fluxo de Deploy
```mermaid
graph LR
A[deploy.sh] --> B[Detecta IP]
B --> C[Gera .env]
C --> D[Docker Build]
D --> E[nginx -t]
E --> F[Docker Up]
F --> G[pre-flight.sh]
G --> H[NGINX Start]
```
---
## Variáveis de Ambiente
| Variável | Descrição |
|----------|-----------|
| `HOST_PUBLIC_IP` | IP público do servidor (detectado automaticamente) |
---
## Comandos Úteis
```bash
# Deploy completo
./deploy.sh
# Rebuild após alterações
docker compose build && docker compose up -d
# Testar configuração
docker compose run --rm nginx-proxy nginx -t
# Ver logs
docker compose logs -f
# Recarregar configuração sem restart
docker compose exec nginx-proxy nginx -s reload
```
---
## Notas Importantes
1. **SSL**: Os certificados devem estar em `/etc/letsencrypt/` no host ou montados via volume
2. **ModSecurity**: Alguns sites usam WAF que requer módulo adicional (não incluído por padrão)
3. **HTTP/2**: O formato antigo `listen 443 ssl http2` está deprecated, usar `http2 on;` separado

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# Logs and debug files
*.log
debug_logs*.txt
nginx_test*.log
# Environment files
.env
.env.local
# Docker
docker-compose.override.yml
# SSL certificates (sensitive - should be managed separately)
ssl/*.key
ssl/*.crt
ssl/*.pem
# Editor files
.vscode/
.idea/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
# Temporary files
*.tmp
*.bak
# Disabled configs
*.disabled

18
Dockerfile Normal file
View File

@ -0,0 +1,18 @@
FROM alpine:latest
# Install NGINX and tools
RUN apk add --no-cache nginx nginx-mod-http-brotli nginx-mod-http-headers-more bind-tools openssl curl certbot
# Copy custom config
COPY nginx.conf /etc/nginx/nginx.conf
# Copy snippets
COPY snippets/ /etc/nginx/snippets/
# Copy scripts
COPY scripts/ /scripts/
RUN chmod +x /scripts/*.sh
# Entrypoint
ENTRYPOINT ["/scripts/pre-flight.sh"]
CMD ["nginx", "-g", "daemon off;"]

View File

@ -15,6 +15,7 @@
# ==============================================================================
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name anatram.com.br;

View File

@ -37,6 +37,7 @@ server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name business.itguys.com.br autolab.itguys.com.br;

View File

@ -35,6 +35,7 @@ upstream officeonline_backend {
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name cloud.grupopralog.com.br;

View File

@ -51,8 +51,10 @@
server {
# --- Bloco 1: Configurações de Escuta e Servidor Padrão ---
listen 80 default_server;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80 default_server;
listen 8080 default_server;
include /etc/nginx/snippets/acme_challenge.conf;
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;

View File

@ -15,6 +15,7 @@
# ==============================================================================
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name dns-primario.itguys.com.br;

View File

@ -26,6 +26,7 @@ server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
server_name ferreirareal.com.br www.ferreirareal.com.br;
# Permite a renovação de certificado Let's Encrypt.

View File

@ -21,6 +21,7 @@ upstream gitea_backend {
# ==============================================================================
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name git.itguys.com.br;

View File

@ -14,6 +14,7 @@ upstream integra_gpl_backend {
# Servidor HTTP (Redirecionamento HTTPS)
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name integra.grupopralog.com.br;

View File

@ -0,0 +1,12 @@
# Internal Networks Configuration
# Define internal network ranges for access control
# Allow access from internal networks
allow 10.10.0.0/16;
allow 10.11.0.0/16;
allow 10.12.0.0/16;
allow 172.16.0.0/16;
allow 127.0.0.1;
# Deny all others (uncomment if needed)
# deny all;

View File

@ -21,6 +21,7 @@ upstream itguys_backend {
# ==============================================================================
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name itguys.com.br www.itguys.com.br;

View File

@ -20,6 +20,7 @@ upstream snipeit_backend {
# ==============================================================================
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name katalog.itguys.com.br;

View File

@ -20,6 +20,7 @@ upstream zabbix_backend {
# ==============================================================================
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name mimir.itguys.com.br;

View File

@ -23,6 +23,7 @@ upstream grafana_backend {
# ------------------------------------------------------------------------------
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name monitoramento.itguys.com.br;

View File

@ -23,6 +23,7 @@ upstream technitium_backend {
# ------------------------------------------------------------------------------
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name ns1.itguys.com.br;

View File

@ -23,6 +23,7 @@ upstream technitium_backend_ns2 {
# ------------------------------------------------------------------------------
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name ns2.itguys.com.br;

View File

@ -29,6 +29,7 @@ server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name petytransportes.com.br www.petytransportes.com.br;

View File

@ -21,6 +21,7 @@ map $http_user_agent $block_bad_bots {
# --- Servidor HTTP (Porta 80) -> Redirecionamento para HTTPS (Formato Limpo) ---
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name pralog.com.br www.pralog.com.br;

View File

@ -5,6 +5,7 @@ server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
server_name proxy.itguys.com.br;
include /etc/nginx/snippets/custom_errors.conf; # Carrega as páginas de erro personalizadas

View File

@ -30,6 +30,7 @@ server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name rhema.itguys.com.br;

View File

@ -29,6 +29,7 @@ upstream solucionei_backend {
# ======================================================================
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name solucionei.itguys.com.br;

View File

@ -22,6 +22,7 @@ upstream magnusbilling_backend {
# ------------------------------------------------------------------------------
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name telefonia.itguys.com.br;
@ -131,7 +132,7 @@ server {
location ~* /mbilling/.*\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|woff2?|ttf|eot|json|xml|mp3|wav|ogg)$ {
# DESABILITA ModSecurity (Solução para 403 Forbidden)
modsecurity off;
# modsecurity off; # Directive disabled for NGINX Proxy (ModSecurity handles this externally now)
# Configurações de Cache
proxy_cache magnusbilling_cache;

View File

@ -23,6 +23,7 @@ upstream unifi_backend_inform {
# =========================================================================================
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name unifi.itguys.com.br;
@ -114,6 +115,7 @@ server {
# =========================================================================================
server {
listen 8080;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:8080;
server_name unifi.itguys.com.br;

View File

@ -24,6 +24,7 @@ server {
} # managed by Certbot
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name vcenter.itguys.com.br;

View File

@ -42,6 +42,7 @@ upstream office_online_backend {
# ----------------------------------------------------------------------
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name verbocloud.itguys.com.br;

View File

@ -25,6 +25,7 @@ server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
server_name vscode.itguys.com.br;
# Logs para o redirecionamento

View File

@ -20,6 +20,7 @@ server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name windmill.grupopralog.com.br;

View File

@ -31,6 +31,7 @@ server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name workspace.itguys.com.br;

View File

@ -33,6 +33,7 @@ upstream zammad_websocket_backend {
# ======================================================================
server {
listen 80;
include /etc/nginx/snippets/acme_challenge.conf;
listen [::]:80;
server_name zammad.itguys.com.br;

22
deploy.sh Normal file
View File

@ -0,0 +1,22 @@
#!/bin/bash
set -e
echo "Detecting Public IP..."
CURRENT_IP=$(curl -s https://ifconfig.me)
if [ -z "$CURRENT_IP" ]; then
echo "Error: Could not detect Public IP."
exit 1
fi
echo "Public IP detected: $CURRENT_IP"
echo "HOST_PUBLIC_IP=$CURRENT_IP" > .env
echo "Building and testing..."
docker compose build
docker compose run --rm nginx-proxy nginx -t
echo "Deploying..."
docker compose up -d
echo "Done! Proxy is running."

62
docker-compose.yml Normal file
View File

@ -0,0 +1,62 @@
services:
# ============================================
# ModSecurity WAF (Frente do NGINX)
# ============================================
modsecurity:
image: owasp/modsecurity-crs:nginx-alpine
container_name: modsecurity-waf
restart: always
ports:
- "80:80"
- "443:443"
environment:
- BACKEND=http://nginx-proxy:8080
- PARANOIA=1
- ANOMALY_INBOUND=5
- ANOMALY_OUTBOUND=4
volumes:
- ./ssl:/etc/nginx/ssl:ro
- modsec_logs:/var/log/modsecurity
depends_on:
- nginx-proxy
# ============================================
# NGINX Proxy (Backend do ModSecurity)
# ============================================
nginx-proxy:
build: .
container_name: nginx-proxy
restart: always
expose:
- "8080"
environment:
- HOST_PUBLIC_IP=${HOST_PUBLIC_IP}
volumes:
- ./conf.d:/etc/nginx/conf.d
- ./ssl:/etc/nginx/ssl
- ./snippets:/etc/nginx/snippets
- nginx_cache:/var/cache/nginx
- nginx_logs:/var/log/nginx
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
# ============================================
# Fail2ban (Lê logs e bane IPs)
# ============================================
fail2ban:
image: crazymax/fail2ban:latest
container_name: fail2ban
restart: always
network_mode: host
cap_add:
- NET_ADMIN
- NET_RAW
volumes:
- ./fail2ban:/data
- nginx_logs:/var/log/nginx:ro
- modsec_logs:/var/log/modsecurity:ro
volumes:
nginx_cache:
nginx_logs:
modsec_logs:

Binary file not shown.

View File

@ -0,0 +1,7 @@
[Definition]
# Detect failed login attempts and suspicious activity from NGINX logs
failregex = ^<HOST> .* "(GET|POST|HEAD).* HTTP/.*" 4[0-9]{2}
^<HOST> .* "(GET|POST|HEAD).*(\.env|\.git|wp-login|phpmyadmin).* HTTP/.*"
ignoreregex =

View File

@ -0,0 +1,8 @@
[nginx-badbots]
enabled = true
filter = nginx-badbots
logpath = /var/log/nginx/*.log
maxretry = 5
findtime = 300
bantime = 3600
action = iptables-multiport[name=nginx, port="80,443", protocol=tcp]

Some files were not shown because too many files have changed in this diff Show More