Academia Deltamontero · Documentación
Arquitectura

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

ControlImplementación
HTTPS de punta a punta para el usuarioPlanificado 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 HTTPSsslproxy = true en config.php cuando MOODLE_WWWROOT empieza por https://. Así Moodle no depende de que el proxy reescriba X-Forwarded-Proto
Sin reverseproxyreverseproxy = false: Traefik no cambia el Host, y activarlo hace fallar a Moodle ("reverseproxyabused")
IP real del usuariogetremoteaddrconf = 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 cookiesloginhttps = 1, cookiesecure = 1, cookiehttponly = 1
Sin caché del navegadorTodas 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 desarrolloApache niega behat, vendor, node_modules y .git
Errores de PHPNo se muestran en pantalla (display_errors=Off); van al registro
Rutas de programaspreventexecpath = true
config.phpGenerado 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: fija X-Forwarded-Proto: https en la petición (sin tocar X-Forwarded-For) y agrega en la respuesta X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff y Referrer-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 cabecera CF-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

ControlValor
Política de contraseñasActiva: mínimo 10 caracteres, con letras, números y minúsculas (mayúscula no obligatoria)
Bloqueo de cuenta10 contraseñas fallidas en 30 minutos bloquean la cuenta 30 minutos (se desbloquea sola o la desbloquea el super administrador)
SesiónExpira a las 2 horas de inactividad (sessiontimeout = 7200); hasta 10 sesiones simultáneas
Recordar usuarioDesactivado (rememberusername = 0)
Mostrar contraseñaSin el botón de Moodle; en su lugar, un ojo de "mantener presionado" que oculta la clave al soltar
InvitadosSin botón de invitado ni Área personal de invitado
Ingreso con correoPermitido (authloginviaemail = 1), seguro porque no se admiten dos cuentas con el mismo correo
CédulaCampo obligatorio en el formulario de usuario (parche patch-cedula-field.php), solo dígitos y máximo 10
Contraseñas inicialesTemporales, 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:

FactorAjuste
factor_adminActivo, peso 100: quien no es administrador pasa sin segundo factor
factor_totpActivo, peso 100: código de una aplicación de autenticación
factor_graceActivo, 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íticaMecanismoEstado del texto
Aviso de Tratamiento de Datos PersonalesPolí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 IAPá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 EcuadorDebe validarlo Talento Humano y asesoría legal
Aviso de datos del postulanteEl postulante lo acepta al registrarse con su códigoTexto 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_deltamodules declara 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 en 0) los registros de la empresa donde solo figura como autora.
  • local_candidate_enrollment declara 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

ControlImplementación
IntentosMá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 realesNadie previsualiza (salvo el super administrador), así todos los intentos cuentan
CandadoUn cuestionario no se puede iniciar sin cumplir su disponibilidad (por ejemplo, ver el video), ni siquiera los gestores (quizaccess_deltagate)
PresentaciónPreguntas y respuestas en orden aleatorio, una por página, navegación secuencial; no se revelan las respuestas correctas
Control de cambio de pantallaEn 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 examenEn 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 BrowserDisponible 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_postulante les 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_PASSWORD el sitio no arranca; sin MOODLE_ADMIN_PASSWORD no instala; sin MOODLE_BACKUP_ADMIN_PASSWORD no se crea la cuenta de respaldo.
  • Los valores se escriben en config.php escapados, y config.php es 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 permisos 700.
  • .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 crypt de 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 tener MOODLE_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 admin usa una contraseña por defecto que estuvo publicada. Este aviso lo encendían versiones anteriores de la plataforma; hoy se apaga solo cuando la cuenta admin cambia su contraseña. El aviso de que admin.respaldo recibió 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.

En esta página