Seguridad
HTTPS y proxy, cabeceras y límites, contraseñas, MFA, políticas y LOPDP, derechos sobre los datos, datos enviados a la IA, seguridad de exámenes, secretos y datos personales en respaldos.
Transporte y proxy
| Control | Implementación |
|---|---|
| HTTPS de punta a punta para el usuario | Planificado con la publicación por dominio: Cloudflare (proxy del dominio) y el proxy de Coolify; Moodle recibe HTTP interno. Hoy la Academia se usa en la red interna por HTTP (http://192.168.0.192), así que sslproxy queda en false |
| URLs siempre HTTPS | sslproxy = true en config.php cuando MOODLE_WWWROOT empieza por https://. Así Moodle no depende de que el proxy reescriba X-Forwarded-Proto |
Sin reverseproxy | reverseproxy = false: Traefik no cambia el Host, y activarlo hace fallar a Moodle ("reverseproxyabused") |
| IP real del usuario | getremoteaddrconf = 1 (tomar X-Forwarded-For) e reverseproxyignore con 127.0.0.1, la red interna de Docker 172.16.0.0/12 y los rangos IPv4 e IPv6 publicados de Cloudflare. Sirve para registros y límites de intentos por IP |
| Login y cookies | loginhttps = 1, cookiesecure = 1, cookiehttponly = 1 |
| Sin caché del navegador | Todas las páginas se envían con Cache-Control: no-store y las páginas restauradas desde la caché del historial se recargan (OWASP WSTG-ATHN-06): tras cerrar sesión, "Atrás" no muestra páginas de la sesión |
| Carpetas de desarrollo | Apache niega behat, vendor, node_modules y .git |
| Errores de PHP | No se muestran en pantalla (display_errors=Off); van al registro |
| Rutas de programas | preventexecpath = true |
config.php | Generado en cada arranque, root:www-data, permisos 440 |
Cabeceras y límite de peticiones de Traefik
infrastructure/traefik/dynamic/moodle.yml define dos middlewares:
moodle-headers: fijaX-Forwarded-Proto: httpsen la petición (sin tocarX-Forwarded-For) y agrega en la respuestaX-Frame-Options: SAMEORIGIN,X-Content-Type-Options: nosniffyReferrer-Policy: strict-origin-when-cross-origin.moodle-ratelimit: 100 peticiones por segundo de promedio con ráfaga de 50, contadas por usuario según la cabeceraCF-Connecting-IP(detrás de Cloudflare todos llegan desde pocas IP).
El mismo archivo advierte no aplicar compresión a las respuestas JSON de la app móvil.
Estos middlewares solo se usan en el modo standalone (sin Coolify). En producción con Coolify, el servicio moodle no lleva etiquetas de Traefik y la ruta la genera Coolify, así que las cabeceras de respuesta y el límite de peticiones de este archivo no se aplican. Si existen controles equivalentes en Coolify o en Cloudflare (WAF, límites, cabeceras): Por confirmar.
Perímetro
La arquitectura prevé que, al publicar con dominio, el firewall del servidor (ufw) solo admita las IP de Cloudflare y que el host tenga un endurecimiento base CIS v8. El repositorio no incluye scripts para esto; su estado en producción es Por confirmar con TI. Solo el equipo de TI accede al servidor por VPN. Hoy los usuarios entran desde la red interna de la empresa; cuando se publique con dominio (planificado), entrarán por la web a través de Cloudflare.
Cuentas y contraseñas
| Control | Valor |
|---|---|
| Política de contraseñas | Activa: mínimo 10 caracteres, con letras, números y minúsculas (mayúscula no obligatoria) |
| Bloqueo de cuenta | 10 contraseñas fallidas en 30 minutos bloquean la cuenta 30 minutos (se desbloquea sola o la desbloquea el super administrador) |
| Sesión | Expira a las 2 horas de inactividad (sessiontimeout = 7200); hasta 10 sesiones simultáneas |
| Recordar usuario | Desactivado (rememberusername = 0) |
| Mostrar contraseña | Sin el botón de Moodle; en su lugar, un ojo de "mantener presionado" que oculta la clave al soltar |
| Invitados | Sin botón de invitado ni Área personal de invitado |
| Ingreso con correo | Permitido (authloginviaemail = 1), seguro porque no se admiten dos cuentas con el mismo correo |
| Cédula | Campo obligatorio en el formulario de usuario (parche patch-cedula-field.php), solo dígitos y máximo 10 |
| Contraseñas iniciales | Temporales, con cambio obligatorio en el primer ingreso; o enlace de un solo uso por correo |
Recuperación de contraseña
El enlace "¿Olvidó su contraseña?" apunta a la página propia local/candidate_enrollment/recover.php (ajuste forgottenpasswordurl). La búsqueda por correo de la página nativa se retiró con un parche.
- Con correo configurado: se envía un enlace de un solo uso al correo registrado de la cuenta, válido 1 hora. La respuesta es la misma exista o no la cuenta.
- Sin correo: la persona escribe su usuario (o correo) y su cédula; si coinciden, recibe una contraseña temporal que debe cambiar al entrar.
- Defensas: 6 intentos por IP y 5 por cuenta en ventanas de 30 minutos, mensajes de error genéricos, registro del evento en los registros de Moodle y aviso a los administradores.
- Las cuentas con privilegios (super administradores, perfiles de gestión o con capacidades de gestión) no se pueden recuperar por autoservicio ni desde el chat: las restablece otra persona autorizada.
Verificación en dos pasos (MFA)
Configurada con el MFA nativo de Moodle (tool_mfa), solo para los super administradores:
| Factor | Ajuste |
|---|---|
factor_admin | Activo, peso 100: quien no es administrador pasa sin segundo factor |
factor_totp | Activo, peso 100: código de una aplicación de autenticación |
factor_grace | Activo, peso 100, configuración obligatoria, 7 días de gracia |
Si se pierde el acceso a la aplicación en las dos cuentas de super administrador, el MFA se desactiva desde la consola del contenedor con php /var/www/html/admin/cli/cfg.php --component=tool_mfa --name=enabled --set=0.
Los códigos TOTP dependen de la hora. Con el reloj del servidor desfasado (ver Infraestructura) los códigos pueden ser rechazados.
Políticas, consentimiento y LOPDP
La Ley Orgánica de Protección de Datos Personales del Ecuador (LOPDP) aplica a los datos del personal, de los postulantes y de los clientes.
| Política | Mecanismo | Estado del texto |
|---|---|---|
| Aviso de Tratamiento de Datos Personales | Políticas del sitio de Moodle (tool_policy), obligatoria para todo usuario nuevo y existente antes de usar la plataforma. Queda registro de quién aceptó y cuándo. Una sola página de aceptación (parche patch-policy-consentpage.php) | El script configure-site-policy.php lo describe como texto oficial aprobado por la empresa. Se edita y se consulta en Usuarios → Aceptación de políticas |
| Condiciones de uso del Asistente de IA | Página propia (aiterms.php) antes de usar el chat; versión, resumen y acuerdo completo; aceptación guardada por persona. Incluye el aviso de que los mensajes se envían a un proveedor externo que puede procesarlos fuera del Ecuador | Debe validarlo Talento Humano y asesoría legal |
| Aviso de datos del postulante | El postulante lo acepta al registrarse con su código | Texto final con asesoría legal: Por confirmar |
Retención del registro de correos: 180 días. Política de retención del resto de datos personales: Por confirmar. El resumen de retención de datos de Moodle está oculto en el pie de página.
Derechos sobre los datos (acceso y eliminación)
Los plugins propios tienen proveedores de privacidad completos de Moodle (core_privacy):
local_deltamodulesdeclara sus tablas con datos personales (correos enviados, enlaces de contraseña, equipos, módulos desactivados, informes guardados, revisiones anuales, códigos de registro de clientes, reglas y contactos de empresas cliente), el envío de consultas al proveedor externo de IA y la preferencia de correo de ejemplo. Al eliminar, borra lo que es de la persona y desvincula (deja el autor en0) los registros de la empresa donde solo figura como autora.local_candidate_enrollmentdeclara qué cuentas usaron cada código de acceso y quién lo creó.
Así, una solicitud de acceso o de eliminación de datos se atiende con la herramienta de privacidad de Moodle, que incluye estos datos junto con los del núcleo. Procedimiento: Políticas y privacidad.
Datos que se envían al proveedor de IA
En el agente de acciones del chat, las cédulas, los RUC y los correos se reemplazan por marcadores antes de enviarse a Groq y se restauran solo dentro de la plataforma; los nombres sí se envían. El chat informativo general envía el texto tal como se escribe. Detalle en Inteligencia artificial.
Seguridad de exámenes y de la prueba técnica
| Control | Implementación |
|---|---|
| Intentos | Máximo 2 por repaso (regla general); si no aprueba, vuelve a ver el video y recibe 2 intentos nuevos. Las pruebas técnicas de selección tienen 1 intento |
| Intentos reales | Nadie previsualiza (salvo el super administrador), así todos los intentos cuentan |
| Candado | Un cuestionario no se puede iniciar sin cumplir su disponibilidad (por ejemplo, ver el video), ni siquiera los gestores (quizaccess_deltagate) |
| Presentación | Preguntas y respuestas en orden aleatorio, una por página, navegación secuencial; no se revelan las respuestas correctas |
| Control de cambio de pantalla | En la página del intento, cambiar de pestaña, ventana o programa (o tener dos pantallas) cuenta como un cambio; en celular, cambiar de app o apagar la pantalla. La cuenta vive en el servidor (local_deltamodules_exam_strike): recargar no la reinicia. Al llegar al límite (3 por defecto, configurable por módulo; 0 = sin control) el examen se envía solo |
| Sin ayudas durante el examen | En el intento y su resumen no se muestra el chat de IA (abrirlo no contaría como salir de la página), y en el intento, resumen y revisión se ocultan las herramientas de autor de las preguntas aunque el usuario tenga permisos de edición. Durante el intento solo funcionan los botones del examen |
| Safe Exam Browser | Disponible como opción por cuestionario para exámenes críticos |
Postulantes:
- Solo se registran con un código que genera Talento Humano: define el cargo, vence, tiene número de usos y se puede anular. Los códigos evitan caracteres confusos.
- Límite de 10 envíos fallidos por IP cada 30 minutos para impedir probar códigos al azar.
- El rol
delta_postulanteles impide mensajería, chat de IA, blogs, comentarios y búsqueda, aunque llamen a las funciones fuera de la pantalla. - Quedan aislados: cualquier página distinta de su prueba los devuelve a ella; al enviarla se cierra su sesión.
Secretos
- Todas las contraseñas y claves van en variables de entorno (Coolify → Environment Variables; en local,
.env, excluido de Git). Nunca en el código. - No hay contraseñas por defecto: sin
MYSQL_PASSWORDel sitio no arranca; sinMOODLE_ADMIN_PASSWORDno instala; sinMOODLE_BACKUP_ADMIN_PASSWORDno se crea la cuenta de respaldo. - Los valores se escriben en
config.phpescapados, yconfig.phpes de solo lectura. - La clave SMTP se guarda en la configuración de Moodle (
smtppass) a partir de la variable; el diagnóstico del correo nunca la muestra. - El token del servicio MCP no se imprime completo en el registro del despliegue.
- Las claves de rclone para la copia externa van solo en el servidor (
/root/.config/rclone/rclone.conf). - El código actual ya no contiene las contraseñas de administración que estuvieron publicadas ni cédulas del personal. El historial de Git sí los conserva: esas contraseñas deben considerarse comprometidas y ninguna cuenta debe seguir usándolas. Limpiar el historial exige reescribirlo y forzar el envío al repositorio remoto, una decisión del desarrollador principal (ver Pendientes operativos).
Datos personales en los respaldos
Los respaldos contienen nombres, cédulas, correos y notas:
- Se crean con permisos solo para el dueño (
umask 077) y la carpeta de respaldos tiene permisos700. .env(contraseñas en texto plano) no se incluye salvo con--with-env, que avisa.- La copia fuera del servidor debe ir cifrada con un remoto
cryptde rclone; la clave de cifrado se guarda en el gestor de contraseñas (sin ella la copia no se abre). El destino de la copia externa está pendiente. - En pruebas de recuperación, la copia de prueba no debe tener las variables
MOODLE_SMTP_*(o debe tenerMOODLE_MAIL_DIVERT_TO), para que los correos en cola no salgan dos veces a personas reales.
Avisos al super administrador
En su Área personal y en Administración del sitio, el super administrador ve un aviso cuando:
- El cron no se ejecuta hace más de 15 minutos.
- Algún paso del último despliegue falló (con fecha y lista de pasos).
- El último respaldo automático falló, no hay respaldo activo, el último tiene más de 36 horas o no hay copia externa.
- La cuenta
adminusa una contraseña por defecto que estuvo publicada. Este aviso lo encendían versiones anteriores de la plataforma; hoy se apaga solo cuando la cuentaadmincambia su contraseña. El aviso de queadmin.respaldorecibió una contraseña aleatoria solo aparece si quedó registrado por una versión anterior (dura 14 días): el código actual ya no reemplaza esa contraseña.