Seguridad
Este documento describe las medidas de seguridad implementadas en NoxPanel, cubriendo aislamiento multi-tenant, autenticación, autorización, cifrado y protección contra ataques comunes.
Aislamiento Multi-Tenant
El aislamiento de datos entre tenants es la piedra angular de seguridad de NoxPanel. Se implementa en múltiples capas:
Middleware de Aislamiento
TenantIsolationMiddleware (hosting/middleware.py) se ejecuta después del
middleware de autenticación de Django en cada petición:
Identifica al usuario autenticado.
Busca su membresía activa en
TenantUser.Inyecta el tenant activo en el objeto
request.Si
TENANT_ISOLATION_STRICT=Truey el usuario no tiene tenant asignado, restringe el acceso a recursos de hosting.
Note
Los superusuarios y usuarios con rol admin siempre tienen acceso a
todos los tenants, lo que permite gestión administrativa global.
Filtrado de QuerySets
TenantQuerySetMixin (hosting/mixins.py) se aplica a todos los ViewSets
de hosting, billing y ticketing:
class TenantQuerySetMixin:
tenant_field = 'tenant'
def get_queryset(self):
qs = super().get_queryset()
user = self.request.user
if is_admin_user(user):
return qs
tenant = get_current_tenant(self.request)
if tenant:
return qs.filter(**{self.tenant_field: tenant})
return qs.none() # Sin tenant = sin datos
Este mixin garantiza que:
Los administradores ven todos los registros.
Los usuarios regulares solo ven datos de su propio tenant.
Si no hay tenant activo, se retorna un queryset vacío (
qs.none()).
Validación en Escritura
TenantCreateMixin: Auto-asigna el tenant del usuario al crear recursos.SoftDeleteMixin: Verifica que el recurso pertenece al tenant del usuario antes de eliminarlo. Registra la operación en los logs.
Cuotas por Tenant
Cada HostingTenant tiene cuotas independientes que limitan la cantidad de
recursos que puede crear:
quota_sites,quota_databases,quota_domainsquota_storage_gb,quota_ftp_users,quota_mailboxesquota_mail_aliases,quota_shell_users
Los métodos can_create_*() del modelo HostingTenant validan las cuotas
antes de permitir la creación de nuevos recursos. Esto previene abuso de
recursos y garantiza un uso equitativo entre tenants.
Autenticación
NoxPanel implementa un sistema de autenticación dual que soporta tanto sesiones tradicionales como tokens JWT.
JWT (JSON Web Tokens)
Configurado mediante djangorestframework-simplejwt:
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'UPDATE_LAST_LOGIN': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'AUTH_HEADER_TYPES': ('Bearer',),
}
Características de seguridad:
Access token: Vida corta de 60 minutos para minimizar el impacto de tokens comprometidos.
Refresh token: 7 días con rotación automática (cada refresh genera un nuevo par de tokens).
Blacklist: Los refresh tokens usados se invalidan automáticamente (
BLACKLIST_AFTER_ROTATION=True).Firma HS256: Los tokens se firman con la
SECRET_KEYde Django usando HMAC-SHA256.
Sesiones Django
Para la interfaz web (dashboard, admin), se utilizan sesiones de Django:
SESSION_COOKIE_AGE = 86400 # 24 horas
SESSION_COOKIE_SECURE = not DEBUG # Solo HTTPS en producción
SESSION_COOKIE_HTTPONLY = True # No accesible desde JavaScript
SESSION_COOKIE_SAMESITE = 'Lax' # Protección CSRF
SESSION_SAVE_EVERY_REQUEST = True # Renueva sesión en cada petición
El backend de sesiones es django.contrib.sessions.backends.db, lo que
almacena las sesiones en PostgreSQL para persistencia y escalabilidad.
Modelo de Usuario Personalizado
accounts.User extiende AbstractUser con:
role: Campoadminoclientque determina el nivel de acceso base.is_verified: Flag de verificación de email.can_access_*: Permisos granulares por módulo (VPS, hosting, billing, ticketing).has_module_access(module_name): Método genérico de verificación que los superusuarios siempre pasan.
Sistema de Permisos
Roles a Nivel de Plataforma
Rol |
Permisos |
|---|---|
Superuser |
Acceso total a todos los módulos, tenants y recursos. Puede gestionar usuarios y configuración global. |
Admin |
Acceso administrativo a módulos habilitados. Puede ver datos de todos
los tenants. Verificado con |
Client |
Acceso limitado a módulos habilitados por |
Roles a Nivel de Tenant
Dentro de cada tenant (TenantUser.role):
Rol |
Permisos |
|---|---|
admin |
Gestión completa de recursos del tenant (crear, editar, eliminar). |
member |
Gestión de recursos existentes (crear y editar). |
viewer |
Solo lectura. No puede modificar recursos. |
La verificación se realiza con can_manage_resources() (admin + member) y
can_view_only() (viewer).
Control de Acceso por Módulo
Cada módulo del panel está protegido individualmente:
# En el modelo User
def has_module_access(self, module_name):
if self.is_superuser:
return True
mapping = {
'vps': self.can_access_vps,
'hosting': self.can_access_hosting,
'billing': self.can_access_billing,
'ticketing': self.can_access_ticketing,
}
return mapping.get(module_name, False)
Esto permite activar o desactivar módulos por usuario desde el panel de administración sin tocar código.
Protección CSRF
NoxPanel implementa protección CSRF completa para prevenir ataques de falsificación de peticiones:
CSRF_COOKIE_SECURE = not DEBUG # Solo HTTPS en producción
CSRF_COOKIE_HTTPONLY = False # JS necesita leer el token para API calls
CSRF_COOKIE_SAMESITE = 'Lax' # Protección contra cross-site
CSRF_TRUSTED_ORIGINS = [...] # Orígenes de confianza configurables
El middleware
CsrfViewMiddlewarede Django valida el token CSRF en todas las peticiones POST, PUT, PATCH y DELETE.CSRF_COOKIE_HTTPONLY=Falsees necesario porque las llamadas AJAX del frontend JavaScript leen el token CSRF desde las cookies.Las API REST autenticadas con JWT están exentas de CSRF ya que usan el header
Authorization: Bearer.
SSL/TLS y HTTPS
Configuración en Producción
# En producción (DEBUG=False)
SECURE_SSL_REDIRECT = True # Redireccionar HTTP → HTTPS
SECURE_HSTS_SECONDS = 31536000 # HSTS por 1 año
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True # Prevenir MIME sniffing
SECURE_BROWSER_XSS_FILTER = True # Filtro XSS del navegador
X_FRAME_OPTIONS = 'DENY' # Prevenir clickjacking
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
Traefik como Terminador SSL
Traefik actúa como reverse proxy y terminador SSL:
Obtiene certificados Let’s Encrypt automáticamente.
Configura HTTPS para todos los servicios expuestos.
Las comunicaciones internas entre contenedores Docker son en texto plano (red
noxpanel-internalaislada).
Certificados SSL de Hosting
Para los sitios de hosting gestionados, NoxPanel soporta:
Let’s Encrypt (automático): Mediante
request_letsencrypt_certificate()que activa SSL vía el motor de hosting.Certificados personalizados: Subida de certificado PEM, clave privada y CA bundle.
Auto-firmados: Para entornos de desarrollo y prueba.
Cifrado de Credenciales (Fernet)
Las credenciales sensibles del sistema de ticketing se cifran usando Fernet (AES-128-CBC con HMAC-SHA256):
Ubicación: ticketing/crypto.py
def _get_fernet_key():
"""Derivar clave Fernet desde SECRET_KEY de Django."""
key = hashlib.sha256(settings.SECRET_KEY.encode()).digest()
return base64.urlsafe_b64encode(key)
def encrypt_value(plaintext: str) -> str:
"""Cifrar un valor con Fernet."""
f = Fernet(_get_fernet_key())
return f.encrypt(plaintext.encode()).decode()
def decrypt_value(ciphertext: str) -> str:
"""Descifrar un valor con Fernet."""
f = Fernet(_get_fernet_key())
return f.decrypt(ciphertext.encode()).decode()
Campos cifrados con Fernet:
MailAccount.password_encrypted: Contraseñas de cuentas IMAP/POP3.MailAccount.oauth2_client_secret_encrypted: Secret de aplicaciones OAuth2.MailAccount.oauth2_access_token_encrypted: Access tokens OAuth2.MailAccount.oauth2_refresh_token_encrypted: Refresh tokens OAuth2.
La clave de cifrado se deriva de la SECRET_KEY de Django mediante SHA-256.
Si la biblioteca cryptography no está instalada, se usa Base64 como
fallback (solo para desarrollo).
Warning
La SECRET_KEY de Django es crítica para la seguridad del cifrado Fernet.
Debe ser una cadena larga, aleatoria y nunca debe exponerse. En producción
se configura vía variable de entorno.
Almacenamiento de Contraseñas
Contraseñas de Usuario
Django utiliza PBKDF2 con SHA-256 por defecto para el hashing de contraseñas de usuario. NoxPanel configura los siguientes validadores:
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {'min_length': 8}},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
Estos validadores garantizan que las contraseñas:
No sean similares a los datos del usuario (nombre, email).
Tengan al menos 8 caracteres.
No sean contraseñas comunes (diccionario de 20,000+ contraseñas).
No sean puramente numéricas.
Contraseñas del Motor de Hosting
La contraseña del admin del motor de hosting se comparte entre contenedores mediante
un archivo en un volumen Docker compartido. El
servicio web lee este archivo al iniciar si no encuentra la variable de
entorno ISPCONFIG_PASSWORD.
Las contraseñas de clientes del motor de hosting (ISPConfigClient.ispconfig_password)
se generan automáticamente con secrets.choice() (20 caracteres,
alfanuméricos + símbolos) y se almacenan en la base de datos.
Contraseñas de Bases de Datos de Hosting
Las contraseñas de HostingDatabase.db_password y
HostingFTPAccount.password se generan con el mismo generador seguro y
se almacenan para referencia del usuario (visibles en el panel del tenant).
Rate Limiting
La API REST implementa throttling para prevenir abuso:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '120/min', # 120 peticiones/min para anónimos
'user': '1200/min', # 1200 peticiones/min para autenticados
},
}
Protección del Panel Admin
El middleware AdminPanelProtectionMiddleware (admin_panel/middleware.py)
añade una capa adicional de protección al panel de administración de Django:
Verifica que el usuario autenticado tiene permisos de staff.
Aplica reglas de acceso adicionales según la configuración.
Seguridad de Red Docker
La arquitectura de red de Docker proporciona aislamiento a nivel de red:
traefik-public: Red externa que solo expone
nginxyhosting-coreal reverse proxy Traefik.noxpanel-internal: Red bridge interna donde se comunican todos los servicios. No es accesible directamente desde Internet.
Esto significa que servicios como db (PostgreSQL), redis,
celery_worker, celery_beat, roundcube y phpmyadmin no tienen
puertos expuestos directamente al exterior.
Protecciones Adicionales
Clickjacking
X_FRAME_OPTIONS = 'DENY'impide que la aplicación se cargue en iframes.XFrameOptionsMiddlewareestá activo en la cadena de middleware.
Content-Type Sniffing
SECURE_CONTENT_TYPE_NOSNIFF = Trueenvía el headerX-Content-Type-Options: nosniff.
CORS
CorsMiddlewareestá configurado con orígenes de confianza explícitos (CORS_ALLOWED_ORIGINS).CORS_ALLOW_CREDENTIALS = Truepermite envío de cookies (necesario para sesiones).
Monitorización
django-prometheus: Exporta métricas de Django (peticiones, respuestas, latencia) para monitorización con Prometheus/Grafana.
Sentry (opcional): Captura errores y excepciones en tiempo real con integración Django y trazas de rendimiento configurables.
Resumen de Configuración de Seguridad
Medida |
Estado |
Detalles |
|---|---|---|
HTTPS / SSL |
Habilitado (producción) |
Traefik + Let’s Encrypt |
HSTS |
1 año + preload |
|
CSRF |
Habilitado |
Cookie SameSite=Lax |
JWT tokens |
60 min / 7 días |
Rotación + blacklist |
Hashing passwords |
PBKDF2-SHA256 |
Min 8 chars + validadores |
Cifrado credenciales |
Fernet (AES-128) |
Derivado de SECRET_KEY |
Rate limiting |
120/min anon, 1200/min auth |
DRF Throttling |
Aislamiento tenant |
Middleware + QuerySet filter |
|
Aislamiento red |
Docker networks |
Solo nginx/hosting-core expuestos |
Clickjacking |
DENY |
|
XSS Filter |
Habilitado |
|
Anti-DDoS / IP Block |
Habilitado |
Middleware + Redis cache + auto-block |
Protección Anti-DDoS y Bloqueo de IPs
NoxPanel incluye un módulo de seguridad IP (security) que protege contra
ataques de fuerza bruta, escaneos automatizados y abuso de rate limit.
Arquitectura
El sistema opera en tres capas:
Nginx — Rate limiting por zona (login: 5r/min, API: 30r/s, general: 60r/s).
Middleware Django (
IPSecurityMiddleware) — Bloqueo a nivel de aplicación antes del procesamiento de sesiones.Lógica de negocio (
SecurityService) — Tracking de intentos fallidos y auto-bloqueo basado en umbrales configurables.
Middleware de Seguridad IP
security.middleware.IPSecurityMiddleware se ejecuta justo después de
SecurityMiddleware y antes de SessionMiddleware:
SecurityMiddleware → IPSecurityMiddleware → SessionMiddleware → ...
Flujo por cada petición:
Si la ruta es
/health/— siempre permitir (health checks).Si la IP está en
SECURITY_AUTO_WHITELIST— siempre permitir.Comprobar caché Redis (
security_wl:{ip},security_block:{ip}).Si no hay entrada en caché, consultar la base de datos y cachear el resultado.
Si la IP está bloqueada y el bloqueo no ha expirado → HTTP 403.
Si el bloqueo ha expirado → desactivar automáticamente.
Tracking de Logins Fallidos
Cada intento de login fallido en CustomLoginView.form_invalid():
Crea un
SecurityEventcon tipofailed_login.Incrementa un contador en Redis (
security_fail:{ip}) con TTL configurable.Si el contador alcanza
SECURITY_MAX_FAILURES→ auto-bloqueo.
Tras un login exitoso, el contador de fallos se resetea.
Auto-Bloqueo
Cuando una IP supera el umbral de intentos fallidos:
Se crea/actualiza un registro
BlockedIPconis_active=True.Se cachea en Redis para respuesta inmediata del middleware.
Se crea un
SecurityEventcon tipoip_blocked.Se notifica a todos los superusuarios via
NotificationService.notify_admins().
Configuración
# En settings.py
SECURITY_MAX_FAILURES = 5 # Intentos antes de bloqueo
SECURITY_FAILURE_WINDOW = 600 # Ventana de 10 minutos
SECURITY_BLOCK_DURATION = 1800 # Bloqueo de 30 minutos
SECURITY_AUTO_WHITELIST = ['127.0.0.1', '::1']
Dashboard de Seguridad
Accesible en /security/ por superusuarios y usuarios con tenant asignado.
Vista superusuario (global):
Ve IPs bloqueadas, whitelist y eventos de todos los tenants.
Columna «Tenant» extra en todas las tablas.
Acceso a la configuración de alertas de cualquier tenant.
Vista tenant (scoped):
Solo ve IPs, whitelist y eventos de su propio tenant.
Puede bloquear/desbloquear IPs y gestionar su whitelist.
Configura sus propias alertas (Telegram + Email).
Funcionalidades del dashboard:
Ver IPs bloqueadas activas con motivo, duración y acciones.
Ver IPs en whitelist con descripción.
Bloquear/desbloquear IPs manualmente.
Añadir/eliminar IPs de la whitelist.
Consultar eventos de seguridad recientes con filtro por IP.
Ver los «top offenders» (IPs con más intentos fallidos en 24h).
Alertas Per-Tenant (Telegram + Email)
Cada tenant puede configurar sus propias alertas de seguridad
independientes a través de /security/config/.
Modelo: TenantSecurityConfig (OneToOne con HostingTenant):
class TenantSecurityConfig(models.Model):
tenant = OneToOneField(HostingTenant)
telegram_bot_token = CharField(max_length=100)
telegram_chat_id = CharField(max_length=100)
telegram_alerts_enabled = BooleanField(default=False)
email_alerts_enabled = BooleanField(default=True)
alert_on_failed_login = BooleanField(default=True)
alert_on_ip_blocked = BooleanField(default=True)
max_failures_override = PositiveIntegerField(null=True)
block_duration_override = PositiveIntegerField(null=True)
Flujo de alertas:
Login fallido → se resuelve el tenant del username vía
TenantUser.Se consulta
TenantSecurityConfigdel tenant.Si
alert_on_failed_login=True: - Telegram: envía al bot/chat del tenant (si configurado). - Email: envía alcontact_emaildel tenant.Si se produce auto-bloqueo y
alert_on_ip_blocked=True: se repite.EngineRIT (superadmin) siempre recibe todas las alertas a través de los canales globales (
TELEGRAM_BOT_TOKEN+support@enginerit.com).
Umbrales personalizables por tenant:
max_failures_override: AnulaSECURITY_MAX_FAILURESglobal.block_duration_override: AnulaSECURITY_BLOCK_DURATIONglobal.Si están vacíos, se usan los valores globales de
settings.py.
Claves Redis Utilizadas
Clave |
Uso |
|---|---|
|
IP bloqueada (TTL 5 min, se renueva en cada petición) |
|
IP en whitelist (TTL 5 min) |
|
Contador de fallos (TTL = SECURITY_FAILURE_WINDOW) |
Tareas Celery
Tarea |
Frecuencia |
Descripción |
|---|---|---|
|
Cada 5 min |
Desactiva bloqueos expirados |
|
Diaria |
Elimina eventos y bloqueos inactivos > 90 días |