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:

  1. Identifica al usuario autenticado.

  2. Busca su membresía activa en TenantUser.

  3. Inyecta el tenant activo en el objeto request.

  4. Si TENANT_ISOLATION_STRICT=True y 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_domains

  • quota_storage_gb, quota_ftp_users, quota_mailboxes

  • quota_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_KEY de 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: Campo admin o client que 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 has_admin_access().

Client

Acceso limitado a módulos habilitados por can_access_*. Solo ve datos de su propio tenant.

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 CsrfViewMiddleware de Django valida el token CSRF en todas las peticiones POST, PUT, PATCH y DELETE.

  • CSRF_COOKIE_HTTPONLY=False es 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-internal aislada).

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 nginx y hosting-core al 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.

  • XFrameOptionsMiddleware está activo en la cadena de middleware.

Content-Type Sniffing

  • SECURE_CONTENT_TYPE_NOSNIFF = True envía el header X-Content-Type-Options: nosniff.

CORS

  • CorsMiddleware está configurado con orígenes de confianza explícitos (CORS_ALLOWED_ORIGINS).

  • CORS_ALLOW_CREDENTIALS = True permite 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

SECURE_HSTS_SECONDS=31536000

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

TENANT_ISOLATION_STRICT

Aislamiento red

Docker networks

Solo nginx/hosting-core expuestos

Clickjacking

DENY

X_FRAME_OPTIONS

XSS Filter

Habilitado

SECURE_BROWSER_XSS_FILTER

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:

  1. Nginx — Rate limiting por zona (login: 5r/min, API: 30r/s, general: 60r/s).

  2. Middleware Django (IPSecurityMiddleware) — Bloqueo a nivel de aplicación antes del procesamiento de sesiones.

  3. 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:

  1. Si la ruta es /health/ — siempre permitir (health checks).

  2. Si la IP está en SECURITY_AUTO_WHITELIST — siempre permitir.

  3. Comprobar caché Redis (security_wl:{ip}, security_block:{ip}).

  4. Si no hay entrada en caché, consultar la base de datos y cachear el resultado.

  5. Si la IP está bloqueada y el bloqueo no ha expirado → HTTP 403.

  6. Si el bloqueo ha expirado → desactivar automáticamente.

Tracking de Logins Fallidos

Cada intento de login fallido en CustomLoginView.form_invalid():

  1. Crea un SecurityEvent con tipo failed_login.

  2. Incrementa un contador en Redis (security_fail:{ip}) con TTL configurable.

  3. 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:

  1. Se crea/actualiza un registro BlockedIP con is_active=True.

  2. Se cachea en Redis para respuesta inmediata del middleware.

  3. Se crea un SecurityEvent con tipo ip_blocked.

  4. 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:

  1. Login fallido → se resuelve el tenant del username vía TenantUser.

  2. Se consulta TenantSecurityConfig del tenant.

  3. Si alert_on_failed_login=True: - Telegram: envía al bot/chat del tenant (si configurado). - Email: envía al contact_email del tenant.

  4. Si se produce auto-bloqueo y alert_on_ip_blocked=True: se repite.

  5. 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: Anula SECURITY_MAX_FAILURES global.

  • block_duration_override: Anula SECURITY_BLOCK_DURATION global.

  • Si están vacíos, se usan los valores globales de settings.py.

Claves Redis Utilizadas

Clave

Uso

security_block:{ip}

IP bloqueada (TTL 5 min, se renueva en cada petición)

security_wl:{ip}

IP en whitelist (TTL 5 min)

security_fail:{ip}

Contador de fallos (TTL = SECURITY_FAILURE_WINDOW)

Tareas Celery

Tarea

Frecuencia

Descripción

security.expire_blocked_ips

Cada 5 min

Desactiva bloqueos expirados

security.cleanup_old_security_events

Diaria

Elimina eventos y bloqueos inactivos > 90 días