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: .. code-block:: python 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``: .. code-block:: python 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: .. code-block:: python 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 20 80 * - 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``): .. list-table:: :header-rows: 1 :widths: 20 80 * - 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: .. code-block:: python # 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: .. code-block:: python 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python # 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`` .. code-block:: python 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: .. code-block:: python 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: .. code-block:: python 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 --------------------------------------- .. list-table:: :header-rows: 1 :widths: 35 25 40 * - 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``: .. code-block:: text 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 ~~~~~~~~~~~~~~ .. code-block:: python # 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``): .. code-block:: python 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 ~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 40 60 * - 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 ~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 40 20 40 * - 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