Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2025-08-16 23:59:05 +02:00
padre 6133e1b3d4
commit 0c795faf47
Se han modificado 7 ficheros con 638 adiciones y 13 borrados

268
DEPLOYMENT.md Archivo normal
Ver fichero

@@ -0,0 +1,268 @@
# Deployment con Nginx - API Ping Service
Esta guía explica cómo configurar nginx como proxy reverso para el servicio de ping, asegurando que se obtenga correctamente la IP real de los clientes.
## 🔧 Configuración de Nginx
### 1. Configuración Básica
```nginx
server {
listen 80;
server_name your-domain.com;
# Configuración para obtener IP real del cliente
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# IPs confiables (ajustar según tu infraestructura)
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
location / {
proxy_pass http://127.0.0.1:3000;
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_cache_bypass $http_upgrade;
}
}
```
### 2. Rate Limiting en Nginx
```nginx
# En el bloque http {}
http {
# Zona de rate limiting para API general
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
# Zona específica para ping (más restrictiva)
limit_req_zone $binary_remote_addr zone=ping:10m rate=10r/m;
# Rate limiting por IP real (después del proxy)
limit_req_zone $realip_remote_addr zone=real_ip:10m rate=10r/m;
}
# En el bloque server {}
server {
location /api/ping {
limit_req zone=ping burst=5 nodelay;
proxy_pass http://127.0.0.1:3000;
# ... otros headers de proxy
}
}
```
### 3. SSL/HTTPS (Recomendado)
```nginx
server {
listen 443 ssl http2;
ssl_certificate /path/to/certificate.pem;
ssl_certificate_key /path/to/private.key;
# Headers de seguridad
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# ... resto de la configuración
}
```
## 🌐 Configuración para Cloudflare
Si usas Cloudflare, agrega estas IPs confiables:
```nginx
# IPs de Cloudflare (actualizar periódicamente)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
# ... (ver archivo nginx.conf completo)
# Headers específicos para Cloudflare
real_ip_header CF-Connecting-IP;
```
## 🚀 Pasos de Deployment
### 1. Preparar la Aplicación
```bash
# Compilar para producción
npm run build
# Iniciar en modo producción
npm start
# O usar PM2 para gestión de procesos
npm install -g pm2
pm2 start npm --name "api-ping" -- start
pm2 save
pm2 startup
```
### 2. Configurar Nginx
```bash
# Copiar configuración
sudo cp nginx.conf /etc/nginx/sites-available/api-ping
sudo ln -s /etc/nginx/sites-available/api-ping /etc/nginx/sites-enabled/
# Verificar configuración
sudo nginx -t
# Recargar nginx
sudo systemctl reload nginx
```
### 3. Variables de Entorno
```bash
# .env.production
NODE_ENV=production
TRUST_PROXY=true
RATE_LIMIT_MAX=10
RATE_LIMIT_WINDOW_MS=60000
```
## 🔍 Verificación del Setup
### 1. Verificar Headers de Proxy
```bash
# Verificar que nginx está pasando los headers correctos
curl -H "X-Forwarded-For: 203.0.113.1" http://your-domain.com/api/status
```
### 2. Test de Rate Limiting
```bash
# Script para probar rate limiting
for i in {1..15}; do
echo "Request $i:"
curl -s http://your-domain.com/api/ping \
-H "Content-Type: application/json" \
-d '{"target":"8.8.8.8","count":1}' \
| jq '.rateLimit // .error'
sleep 1
done
```
### 3. Verificar IP del Cliente
El endpoint `/api/status` ahora incluye información de debugging:
```json
{
"clientInfo": {
"ip": "203.0.113.1",
"isProxied": true,
"userAgent": "curl/7.68.0",
"proxyHeaders": {
"x-forwarded-for": "203.0.113.1",
"x-real-ip": "203.0.113.1"
}
}
}
```
## 🛡️ Seguridad Adicional
### 1. Fail2ban para Protección DDoS
```ini
# /etc/fail2ban/jail.local
[api-ping]
enabled = true
port = http,https
filter = api-ping
logpath = /var/log/nginx/api-ping-access.log
maxretry = 20
bantime = 3600
findtime = 300
```
### 2. Monitoreo de Logs
```bash
# Monitorear requests sospechosos
tail -f /var/log/nginx/api-ping-access.log | grep -E "(429|5[0-9]{2})"
# Analizar IPs con más requests
awk '{print $1}' /var/log/nginx/api-ping-access.log | sort | uniq -c | sort -nr | head -10
```
## 📊 Monitoreo y Métricas
### 1. Nginx Status
```nginx
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
```
### 2. Logs Estructurados
```nginx
log_format api_ping '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$http_x_forwarded_for" "$http_x_real_ip" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/api-ping-access.log api_ping;
```
## 🔧 Troubleshooting
### Problema: IP incorrecta
```bash
# Verificar headers que llegan a la aplicación
curl -H "X-Debug: true" http://your-domain.com/api/status
# Verificar configuración de nginx
sudo nginx -T | grep -A 10 -B 10 real_ip
```
### Problema: Rate limiting no funciona
```bash
# Verificar que la IP se está obteniendo correctamente
# La aplicación debe usar la IP real, no la del proxy (127.0.0.1)
```
### Problema: CORS
```bash
# Verificar headers CORS
curl -H "Origin: http://example.com" -H "Access-Control-Request-Method: POST" \
-X OPTIONS http://your-domain.com/api/ping -v
```
## 📝 Checklist de Deployment
- [ ] Aplicación compilada para producción
- [ ] PM2 o similar configurado para gestión de procesos
- [ ] Nginx configurado con proxy_pass
- [ ] Headers X-Forwarded-* configurados
- [ ] real_ip_header configurado
- [ ] IPs confiables (set_real_ip_from) configuradas
- [ ] Rate limiting configurado en nginx (opcional)
- [ ] SSL/HTTPS configurado
- [ ] Headers de seguridad configurados
- [ ] Logs configurados
- [ ] Monitoring configurado
- [ ] Backup y recovery plan preparado
Con esta configuración, el servicio obtendrá correctamente la IP real de los clientes incluso cuando esté detrás de nginx, Cloudflare u otros proxies. 🎉

Ver fichero

@@ -1,4 +1,29 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
// Configuración para proxy reverso (nginx)
async headers() {
return [
{
source: '/api/:path*',
headers: [
{
key: 'X-Forwarded-Proto',
value: 'https',
},
],
},
];
},
// Configuración experimental para mejorar el manejo de headers
experimental: {
serverComponentsExternalPackages: ['ping'],
},
// Configuración de red
env: {
TRUST_PROXY: 'true',
},
};
export default nextConfig;

167
nginx.conf Archivo normal
Ver fichero

@@ -0,0 +1,167 @@
# Configuración de nginx para API Ping Service
# Este archivo debe ser adaptado según tu configuración específica
server {
listen 80;
listen [::]:80;
server_name your-domain.com; # Cambiar por tu dominio
# Redirección HTTPS (recomendado para producción)
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your-domain.com; # Cambiar por tu dominio
# Configuración SSL (ajustar rutas según tu setup)
ssl_certificate /path/to/your/certificate.pem;
ssl_certificate_key /path/to/your/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
# Headers de seguridad
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
# Configuración para obtener la IP real del cliente
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# IPs confiables (ajustar según tu infraestructura)
# Cloudflare IPs (si usas Cloudflare)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
# IPs locales/privadas (ajustar según tu red)
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
# Proxy a Next.js
location / {
proxy_pass http://127.0.0.1:3000; # Ajustar puerto si es necesario
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_cache_bypass $http_upgrade;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Configuración específica para API endpoints
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# Rate limiting adicional en nginx (opcional)
limit_req zone=api burst=20 nodelay;
# Headers CORS para API
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
# Manejar preflight requests
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Content-Length 0;
add_header Content-Type text/plain;
return 200;
}
}
# Rate limiting para proteger la API
location /api/ping {
limit_req zone=ping burst=5 nodelay;
proxy_pass http://127.0.0.1:3000;
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_set_header X-Forwarded-Proto $scheme;
}
# Archivos estáticos con cache
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
expires 1y;
add_header Cache-Control "public, immutable";
add_header X-Cache-Status "STATIC";
}
# Logs
access_log /var/log/nginx/api-ping-access.log;
error_log /var/log/nginx/api-ping-error.log;
}
# Rate limiting zones (definir en http block)
# Agregar estas líneas al bloque http {} en nginx.conf:
#
# http {
# # Rate limiting para API general
# limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
#
# # Rate limiting específico para ping (más restrictivo)
# limit_req_zone $binary_remote_addr zone=ping:10m rate=10r/m;
#
# # Rate limiting por IP real (considerando proxy)
# limit_req_zone $realip_remote_addr zone=real_ip:10m rate=10r/m;
# }
# Configuración adicional para development/testing
# Archivo separado: nginx-dev.conf
#
# server {
# listen 80;
# server_name localhost;
#
# location / {
# proxy_pass http://127.0.0.1:3000;
# 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_set_header X-Forwarded-Proto $scheme;
# }
# }

Ver fichero

@@ -3,17 +3,25 @@ import { NextResponse } from 'next/server';
import { RateLimiter } from '../../../lib/rate-limiter';
import { validateIP } from '../../../lib/validators';
import { PING_CONFIG } from '../../../lib/config.js';
import { getClientIP, getProxyInfo } from '../../../lib/proxy-utils.js';
// Crear instancia del rate limiter
const rateLimiter = new RateLimiter();
export async function POST(request) {
try {
// Obtener IP del cliente para rate limiting
const forwarded = request.headers.get('x-forwarded-for');
const clientIP = forwarded
? forwarded.split(',')[0].trim()
: request.headers.get('x-real-ip') || 'unknown';
// Obtener IP del cliente considerando proxies (nginx, cloudflare, etc.)
const clientIP = getClientIP(request);
const proxyInfo = getProxyInfo(request);
// Log para debugging en desarrollo
if (PING_CONFIG.DEBUG.LOG_REQUESTS) {
console.log('Ping request from:', {
clientIP,
isProxied: proxyInfo.isProxied,
proxyHeaders: proxyInfo.proxyHeaders
});
}
// Verificar rate limit
const rateLimitResult = await rateLimiter.checkLimit(clientIP);

Ver fichero

@@ -1,16 +1,15 @@
import { NextResponse } from 'next/server';
import { RateLimiter } from '../../../lib/rate-limiter';
import { getClientIP, getProxyInfo } from '../../../lib/proxy-utils.js';
// Crear instancia del rate limiter
const rateLimiter = new RateLimiter();
export async function GET(request) {
try {
// Obtener IP del cliente
const forwarded = request.headers.get('x-forwarded-for');
const clientIP = forwarded
? forwarded.split(',')[0].trim()
: request.headers.get('x-real-ip') || 'unknown';
// Obtener IP del cliente considerando proxies
const clientIP = getClientIP(request);
const proxyInfo = getProxyInfo(request);
// Obtener estadísticas del rate limiter para este cliente
const rateLimitStats = rateLimiter.getStats(clientIP);
@@ -23,7 +22,12 @@ export async function GET(request) {
uptime: process.uptime(),
clientInfo: {
ip: clientIP,
rateLimit: rateLimitStats
isProxied: proxyInfo.isProxied,
userAgent: proxyInfo.userAgent,
rateLimit: rateLimitStats,
...(process.env.NODE_ENV === 'development' && {
proxyHeaders: proxyInfo.proxyHeaders
})
},
features: {
maxPingsPerRequest: 10,

129
src/lib/proxy-utils.js Archivo normal
Ver fichero

@@ -0,0 +1,129 @@
/**
* Extrae la IP real del cliente considerando proxies como nginx
* @param {Request} request - La request de Next.js
* @returns {string} La IP del cliente
*/
export function getClientIP(request) {
// Headers que pueden contener la IP real del cliente
const forwardedFor = request.headers.get('x-forwarded-for');
const realIP = request.headers.get('x-real-ip');
const cfConnectingIP = request.headers.get('cf-connecting-ip'); // Cloudflare
const trueClientIP = request.headers.get('true-client-ip'); // Cloudflare Enterprise
const xClientIP = request.headers.get('x-client-ip');
const xForwardedHost = request.headers.get('x-forwarded-host');
// Prioridad de headers (más confiables primero)
let clientIP = null;
// 1. Cloudflare headers (muy confiables)
if (trueClientIP) {
clientIP = trueClientIP.trim();
} else if (cfConnectingIP) {
clientIP = cfConnectingIP.trim();
}
// 2. X-Real-IP (nginx real_ip_module)
else if (realIP) {
clientIP = realIP.trim();
}
// 3. X-Forwarded-For (puede contener múltiples IPs)
else if (forwardedFor) {
// X-Forwarded-For puede contener múltiples IPs separadas por comas
// La primera es la IP original del cliente
const ips = forwardedFor.split(',').map(ip => ip.trim());
clientIP = ips[0];
}
// 4. Otros headers menos comunes
else if (xClientIP) {
clientIP = xClientIP.trim();
}
// Validar que la IP obtenida sea válida
if (clientIP && isValidIP(clientIP)) {
return clientIP;
}
// Fallback: usar una IP por defecto
return '127.0.0.1';
}
/**
* Valida si una cadena es una IP válida (IPv4 o IPv6)
* @param {string} ip - La IP a validar
* @returns {boolean} True si es válida
*/
function isValidIP(ip) {
// Regex básico para IPv4
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
// Regex básico para IPv6
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$/;
if (ipv4Regex.test(ip)) {
// Validar rangos de IPv4 (0-255)
const parts = ip.split('.');
return parts.every(part => {
const num = parseInt(part, 10);
return num >= 0 && num <= 255;
});
}
if (ipv6Regex.test(ip) || ip === '::1' || ip.startsWith('::ffff:')) {
return true;
}
return false;
}
/**
* Obtiene información detallada del proxy y cliente
* @param {Request} request - La request de Next.js
* @returns {object} Información del cliente y proxy
*/
export function getProxyInfo(request) {
const headers = {};
// Recopilar todos los headers relevantes para debugging
const proxyHeaders = [
'x-forwarded-for',
'x-real-ip',
'x-forwarded-proto',
'x-forwarded-host',
'x-forwarded-port',
'cf-connecting-ip',
'true-client-ip',
'x-client-ip',
'x-cluster-client-ip',
'forwarded'
];
proxyHeaders.forEach(header => {
const value = request.headers.get(header);
if (value) {
headers[header] = value;
}
});
const clientIP = getClientIP(request);
return {
clientIP,
proxyHeaders: headers,
isProxied: Object.keys(headers).length > 0,
userAgent: request.headers.get('user-agent') || 'unknown'
};
}
/**
* Middleware helper para configurar trust proxy en desarrollo
* @param {Request} request
* @returns {object} Configuración de trust
*/
export function getTrustProxyConfig(request) {
const isProduction = process.env.NODE_ENV === 'production';
const trustProxy = process.env.TRUST_PROXY === 'true';
return {
trustProxy: isProduction || trustProxy,
clientIP: getClientIP(request),
environment: process.env.NODE_ENV || 'development'
};
}

Ver fichero

@@ -1,9 +1,13 @@
import { NextResponse } from 'next/server';
import { getClientIP } from './lib/proxy-utils.js';
export function middleware(request) {
// Crear la respuesta
const response = NextResponse.next();
// Obtener IP real del cliente
const clientIP = getClientIP(request);
// Agregar headers de seguridad
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
@@ -12,16 +16,36 @@ export function middleware(request) {
// Headers específicos para API
if (request.nextUrl.pathname.startsWith('/api/')) {
// CORS headers
response.headers.set('Access-Control-Allow-Origin', '*');
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type');
// Headers para debugging de proxy (solo en desarrollo)
if (process.env.NODE_ENV === 'development') {
response.headers.set('X-Debug-Client-IP', clientIP);
response.headers.set('X-Debug-Original-IP', request.ip || 'unknown');
}
// Manejar preflight requests
if (request.method === 'OPTIONS') {
return new Response(null, { status: 200, headers: response.headers });
return new Response(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400', // 24 horas
}
});
}
}
// Headers para contenido estático
if (request.nextUrl.pathname.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg)$/)) {
response.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
}
return response;
}