✨ Características: - 45 ejercicios universitarios (Basic → Advanced) - Renderizado LaTeX profesional - IA generativa (Z.ai/DashScope) - Docker 9 servicios - Tests 123/123 pasando - Seguridad enterprise (JWT, XSS, Rate limiting) 🐳 Infraestructura: - Next.js 14 + Node.js 20 - PostgreSQL 15 + Redis 7 - Docker Compose completo - Nginx + SSL ready 📚 Documentación: - 5 informes técnicos completos - README profesional - Scripts de deployment automatizados Estado: Producción lista ✅
171 lines
5.2 KiB
Markdown
171 lines
5.2 KiB
Markdown
# Documentación de Cambio de Seguridad - Token Blacklist
|
||
|
||
## Resumen
|
||
Se corrigió un **FAIL-OPEN CRÍTICO** en la verificación de token blacklist que permitía el bypass de autenticación cuando Redis estaba indisponible.
|
||
|
||
## Fecha del Cambio
|
||
2024-03-30
|
||
|
||
## Archivo Modificado
|
||
`backend/src/shared/database/redis.client.ts`
|
||
|
||
## Problema Original (Líneas 145-157)
|
||
|
||
```typescript
|
||
} catch (error) {
|
||
logger.error({ error }, 'Redis unavailable - cannot check token blacklist');
|
||
// Return false to allow request to proceed (fail-open for availability)
|
||
// Note: This is a security trade-off. If strict security is required,
|
||
// change to return true (fail-closed)
|
||
return false; // <-- BYPASS DE SEGURIDAD CRÍTICO
|
||
}
|
||
```
|
||
|
||
**Riesgo**: Cuando Redis fallaba, cualquier token (incluso los blacklisteados) se consideraba válido, permitiendo autenticación no autorizada.
|
||
|
||
## Solución Implementada
|
||
|
||
### 1. Comportamiento FAIL-CLOSED (Seguridad Máxima)
|
||
```typescript
|
||
} catch (error) {
|
||
logger.error({
|
||
error: (error as Error).message,
|
||
consecutiveFailures,
|
||
tokenPrefix: token.substring(0, 10),
|
||
timestamp: new Date().toISOString(),
|
||
service: 'token-blacklist',
|
||
circuitBreakerOpen: false
|
||
}, 'Redis unavailable - SECURITY: blocking token');
|
||
|
||
throw new AuthenticationError('Unable to verify token status. Service temporarily unavailable.');
|
||
}
|
||
```
|
||
|
||
### 2. Circuit Breaker con Fallback
|
||
- **Umbral**: 5 fallos consecutivos activan el circuit breaker
|
||
- **Fallback**: Cache en memoria (TTL 1 minuto) para operaciones de blacklist
|
||
- **Hashing**: SHA256 para almacenamiento seguro (nunca guarda tokens completos)
|
||
|
||
### 3. Retry con Backoff Exponencial
|
||
- **Intentos**: 3 reintentos máximo
|
||
- **Delay**: 100ms × intento (100ms, 200ms, 300ms)
|
||
|
||
### 4. Métricas de Monitoreo
|
||
```typescript
|
||
const metrics = {
|
||
redisBlacklistFailures: 0, // Fallos totales
|
||
redisBlacklistConsecutiveFailures: 0, // Fallos consecutivos actuales
|
||
redisBlacklistSuccesses: 0, // Éxitos totales
|
||
circuitBreakerOpens: 0 // Veces que se abrió el circuit breaker
|
||
};
|
||
```
|
||
|
||
### 5. Logging Estructurado
|
||
- Nunca se loguean tokens completos (solo prefijo de 10 caracteres)
|
||
- Timestamp ISO8601
|
||
- Identificación de servicio
|
||
- Contadores de fallos consecutivos
|
||
|
||
## Cambios de Comportamiento
|
||
|
||
| Escenario | Antes (FAIL-OPEN) | Después (FAIL-CLOSED) |
|
||
|-----------|-------------------|----------------------|
|
||
| Redis indisponible | Token permitido ❌ | Error 401 lanzado ✅ |
|
||
| Error temporal | Token permitido ❌ | Retry con backoff ✅ |
|
||
| 5+ fallos consecutivos | Token permitido ❌ | Circuit breaker activado ✅ |
|
||
| Verificación exitosa | Token rechazado/aceptado | Sin cambios ✅ |
|
||
|
||
## Impacto en Disponibilidad
|
||
|
||
⚠️ **IMPORTANTE**: Este cambio puede causar downtime del servicio de autenticación si Redis falla.
|
||
|
||
### Recomendaciones para Alta Disponibilidad
|
||
1. **Implementar Redis Replica**: Configurar Redis Cluster o Sentinel
|
||
2. **Monitoreo**: Alertas inmediatas cuando `metrics.redisBlacklistFailures > 0`
|
||
3. **Health Checks**: Verificar estado de Redis antes de despliegues
|
||
4. **Graceful Degradation**: Cache en memoria como último recurso
|
||
|
||
## API Changes
|
||
|
||
### Nuevas Funciones Exportadas
|
||
```typescript
|
||
// Obtener métricas actuales
|
||
export function getBlacklistMetrics(): {
|
||
redisBlacklistFailures: number;
|
||
redisBlacklistConsecutiveFailures: number;
|
||
redisBlacklistSuccesses: number;
|
||
circuitBreakerOpens: number;
|
||
}
|
||
|
||
// Resetear métricas (útil para testing)
|
||
export function resetBlacklistMetrics(): void
|
||
```
|
||
|
||
### Error Handling
|
||
```typescript
|
||
class AuthenticationError extends Error {
|
||
message: 'Unable to verify token status. Service temporarily unavailable.'
|
||
}
|
||
```
|
||
|
||
## Tests Unitarios
|
||
|
||
Archivo: `backend/tests/redis.client.test.ts`
|
||
|
||
**14 tests implementados:**
|
||
1. ✅ Verificar token blacklisteado
|
||
2. ✅ Verificar token no blacklisteado
|
||
3. ✅ FAIL-CLOSED cuando Redis falla
|
||
4. ✅ FAIL-CLOSED con múltiples fallos
|
||
5. ✅ Retry con backoff exponencial
|
||
6. ✅ Métricas de éxito
|
||
7. ✅ Blacklist exitoso en Redis
|
||
8. ✅ Fallback a cache en memoria
|
||
9. ✅ Circuit breaker se abre después de 5 fallos
|
||
10. ✅ Reset de contador tras éxito
|
||
11. ✅ Nunca permite acceso con Redis caído
|
||
12. ✅ No loguea tokens completos
|
||
13. ✅ Tracking de métricas
|
||
14. ✅ Reset de métricas
|
||
|
||
## Comandos de Verificación
|
||
|
||
```bash
|
||
# TypeScript
|
||
npm run type-check
|
||
|
||
# Tests
|
||
npm test -- tests/redis.client.test.ts
|
||
|
||
# Build
|
||
npm run build
|
||
```
|
||
|
||
## Lista de Verificación Pre-Deploy
|
||
|
||
- [ ] Redis Cluster configurado en producción
|
||
- [ ] Alertas de métricas configuradas
|
||
- [ ] Documentación de rollback lista
|
||
- [ ] Comunicación al equipo sobre cambio de comportamiento
|
||
- [ ] Monitoreo de errores 401 post-deploy
|
||
- [ ] Plan de contingencia si hay degradación de servicio
|
||
|
||
## Rollback
|
||
|
||
Si es necesario revertir (aunque NO recomendado por riesgo de seguridad):
|
||
|
||
```bash
|
||
git revert <commit-hash>
|
||
# O manualmente: restaurar return false en catch block
|
||
```
|
||
|
||
## Referencias
|
||
|
||
- Issue: Token blacklist bypass (fixear.md Issue #2)
|
||
- Archivo: `backend/src/shared/database/redis.client.ts:145-157`
|
||
- PR: N/A (cambio directo)
|
||
|
||
---
|
||
|
||
**Nota de Seguridad**: Este cambio es CRÍTICO y bloquea un vector de ataque de autenticación. No debe revertirse sin considerar las implicaciones de seguridad.
|