Servidor linux para exámenes
El problema
A la hora de realizar un trabajo o examen práctico sobre un ordenador, los profesores tenemos varias alternativas:
- Los alumnos realizan el examen sobre su propio ordenador o máquina virtual. Después, cada alumno muestra el resultado al profesor. Esta solución se vuelve difícil si hay muchos alumnos, porque deben ir esperando su turno para presentar el resultado al profesor.
- Como la opción 1, pero el profesor recoge las máquinas virtuales y las estudia posteriormente. En este caso, el problema es la copia de las máquinas virtuales, cada una con varios gigabytes, en un disco duro que hay que ir acoplando a cada ordenador de los alumnos.
- Abandonar la idea de práctico, y realizar el examen sobre papel 😱
Sin embargo, hay exámenes en los que el alumno no necesita ser administrador de la máquina. En estos casos, el profesor puede montar un hosting casero y dejar que los alumnos realicen el examen en su propio ordenador o máquina virtual.
A continuación, describiré qué pasos sigo para crear un entorno Linux para mis alumnos. Este entorno lo he adaptado para exámenes de bases de datos (Oracle y MySQL), y exámenes LAMP.
Entorno Linux
Cada alumno tendrá su propia contraseña. Generar las contraseñas aleatoriamente no es una buena idea, puesto que dificulta su recuperación en caso de pérdida. Es mejor que la contraseña dependa del nombre del usuario, por ejemplo los primeros caracteres del hash del usuario.
Mi solución es utilizar el código ASCII de varios caracteres del nombre del usuario: para los alumnos es inicialmente algo imposible de descifrar, y cuando preguntan puedo contar cosas como el sistema de numeración binaria, código ASCII, cadenas de caracteres, criptoanálisis...
password_para_alumno(){ local USER=$1 local uno=${USER:0:1} local dos=${USER:1:1} local tres=${USER:2:1} LC_CTYPE=C printf '%d' "'$uno'" "'$dos'" "'$tres'" } echo La contraseña para MARIA sería $(password_para_alumno MARIA)
Los usuarios se crean la contraseña especificada por la función anterior. El directorio del alumno no es accesible por otros alumnos, y el alumno no puede cambiar los permisos de su home para dejarse copiar.
crear_usuario_linux() { local USER=$1 local PASS=$(password_para_alumno $USER) sudo useradd $USER -m -s /bin/bash echo "$USER:$PASS" | sudo chpasswd sudo chown root:$USER /home/$USER sudo chmod 770 /home/$USER }
También elimino los canales de comunicación entre alumnos más comunes: mensajería, conexiones TCP... Hay demasiadas formas como para deshabilitarlas todas. De las que no se eliminan, la más evidente son los ficheros temporales en /tmp
.
deshabilita_comunicacion(){ sudo "chmod 500 $(which wall) $(which mail) $(which write) $(which nc)" }
En el momento del examen, es necesario que los alumnos conozcan su contraseña de una forma más o menos confidencial. Para eso utilizo una tabla de org-mode, que imprimo y recorto cada fila, para repartir a cada alumno.
lista_usuarios(){ rm usuarios_linux.org local USUARIOS="$*" for USER in $USUARIOS do local PASS=$(password_para_alumno $USER) printf '| Usuario | %s | Password | %s | \n' $USER $PASS >> usuarios_linux.org done }
Con todas estas funciones, ya solo queda definir la lista de alumnos e invocarlas por cada alumno.
# LISTA DE USUARIOS: GENERALMENTE, USO LOS APELLIDOS DE LOS ALUMNOS usuarios="alumno1 alumno2 alumno3" deshabilita_comunicacion for usuario in $usuarios do crear_usuario_linux $usuario done lista_usuarios $usuarios
Oracle
Utilizo la versión Oracle XE, para evitar problemas de licencia, y porque es bastante simple de instalar a partir de paquetes RPM en un Centos/Fedora.
En los exámenes basados en Oracle es necesario crear un usuario para cada alumno. Por simplicidad, el mismo usuario y contraseña de Linux se reutilizan para la base de datos.
Al comienzo defino las variables ORACLE_HOME
y ORACLE_SID
, que son necesarias para que funcione correctamente el cliente de Oracle sqlplus
. Para no dejar la contraseña del administrador escrita en el script, utilizo la variable de entorno SYSPASS
.
export ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe export ORACLE_SID=XE export NLS_LANG=`$ORACLE_HOME/bin/nls_lang.sh` export PATH=$ORACLE_HOME/bin:$PATH if [ -z "$SYSPASS" ] then echo La variable SYSPASS debe tener la contraseña SYS de la base de datos exit fi
Para cada alumno se crea un usuario con permisos básicos (crear tablas, índices, vistas...). En algunos exámenes también necesito que los alumnos creen directory
y utilicen algunas vistas dinámicas de sesiones, usuarios y procesos.
Dentro del heredoc necesito que se utilizar las variables $user
y $pass
, así que debo permitir su expansión. Pero algunas vistas contienen el caracter $
, que se intentaría expandir. Para evitar problemas, uso una variable con el valor $=
, que defino usando comilla simple para que evitar su expansión.
Después se carga un script de SQL inicial para la creación de tablas, que depende de cada examen.
crear_usuario_oracle(){ #https://oraclespin.com/2008/12/18/how-to-grant-access-to-vsession-to-other-users/ local user=$1 local pass=$2 local S='$' sqlplus sys/$SYSPASS as sysdba <<EOF drop user $user cascade; create user $user identified by $pass; alter user $user identified by $pass; alter user $user default tablespace USERS; alter user $user quota 100M on USERS; grant resource,connect to $user; grant create view to $user; grant create any directory to $user; GRANT SELECT ON sys.v_${S}session TO $user; GRANT SELECT ON sys.v_${S}gsession TO $user; GRANT SELECT ON sys.v_${S}sqltext TO $user; GRANT SELECT ON sys.v_${S}lock TO $user; GRANT SELECT ON sys.v_${S}locked_object TO $user; GRANT SELECT ON sys.v_${S}process TO $user; GRANT SELECT ON sys.gv_${S}process TO $user; GRANT SELECT ON sys.v_${S}sess_io TO $user; GRANT SELECT ON sys.ALL_OBJECTS to $user; GRANT SELECT ON sys.DBA_WAITERS to $user; alter system set sessions=300 scope=spfile; alter system set processes=300 scope=spfile; commit; EOF sqlplus $user/$pass <<EOF @tablas-iniciales.sql EOF }
LAMP
Para los exámenes LAMP se necesita una base de datos y un sitio web por cada alumno.
crea_base_de_datos() { local USER=$1 local PASS=$(password_para_alumno $USER) mysql --user=root --password=$SYSPASS <<EOF DROP DATABASE $USER; CREATE DATABASE IF NOT EXISTS $USER; GRANT ALL ON $USER.* TO '$USER' IDENTIFIED BY '$PASS'; FLUSH PRIVILEGES; EOF }
Apache2 dispone de la directiva UserDir para crear un sitio web para cada usuario. De todas formas, para tener un control más fino sobre cada opción y directorio de alumno, he decidido crear un site por alumno.
La siguiente función crea un site para un alumno en entorno Debian/Apache2.
crea_sitio_web() { local USER=$1 if [ ! -z "$USER" ] then local APACHE=www-data local DOCUMENTROOT=/home/$USER/public_html local SITE=/etc/apache2/sites-available/alumno_$USER mkdir -p /home/$USER chown -R $USER:$USER /home/$USER mkdir -p $DOCUMENTROOT echo "Sitio de $USER, en el directorio $DOCUMENTROOT, con AllowOverride All" > $DOCUMENTROOT/index.html # AJUSTE DE PERMISOS: $HOME sigue siendo privado para otros alumnos, pero # $APACHE puede accceder a $DOCUMENTROOT setfacl -R -m u:$APACHE:rxw /home/$USER chown -R $USER:$APACHE $DOCUMENTROOT chmod -R 770 $DOCUMENTROOT chmod +s $DOCUMENTROOT cat <<EOF > $SITE <Directory "$DOCUMENTROOT"> AllowOverride All </Directory> alias /$USER $DOCUMENTROOT EOF fi }
Para evitar cientos de preguntas al inicio del examen dejo una página inicial de Apache donde explico:
- Que pueden conectarse mediante ssh y sftp
- Que tienen disponible phpMyAdmin
- Que su usuario y contraseña es la misma en todos los casos
crea_pagina_inicio() { local USERS="$1" local IPADDRESS=$(hostname -I) local IPADDRESS="${IPADDRESS#"${IPADDRESS%%[![:space:]]*}"}" local IPADDRESS="${IPADDRESS%"${IPADDRESS##*[![:space:]]}"}" local HOSTNAME=$(hostname).local #local HOSTNAME=$IPADDRESS local INDEXHTML=/var/www/html/index.html cat <<EOF > $INDEXHTML <h1>Aplicaciones Web. Examen 1 evaluacion 3</h1> <p>Conexion con ssh a la IP:<b>$IPADDRESS</b> ($HOSTNAME)</p> <table border=1> EOF for i in $USERS do cat <<EOF >> $INDEXHTML <tr> <td> <a href=$i/phpBB3>$i</a> </td> <td>Misma contraseña inicial</td> <td> <a href=sftp://$i@$HOSTNAME/home/$i>SFTP</a> </td> <td> <a href=phpMyAdmin>phpMyAdmin</a> </td> </tr> EOF done echo "</table>" >> $INDEXHTML }
En algunos exámenes, los alumnos no empiezan con un LAMP vacío, sino que instalo previamente un Joomla o Wordpress que tienen que modificar. Para ello, creo un usurio plantilla donde instalo lo necesario, y después copio la base de datos y los ficheros a cada alumno
copia_base_de_datos() { local DBEXISTENTE=$1 local DBACREAR=$2 yes | mysqladmin --user=root --password=$SYSPASS drop $DBACREAR mysqladmin --user=root --password=$SYSPASS create $DBACREAR mysqldump --user=root --password=$ROOTPASS $DBEXISTENTE | mysql --user=root --password=$ROOTPASS $DBACREAR }
copia_ficheros_sitio_web() { local PLANTILLA=$1 local USER=$2 local APACHE=www-data local PLANTILLADOCUMENTROOT=/home/$PLANTILLA/public_html local DOCUMENTROOT=/home/$USER/public_html sudo cp -R $PLANTILLADOCUMENTROOT /home/$USER/ sudo chown -R $USER:$APACHE $DOCUMENTROOT sudo chmod -R 770 $DOCUMENTROOT sudo chmod +s $DOCUMENTROOT }
Dependiendo de la aplicación web copiada, puede ser necesario realizar más ajustes. Por ejemplo, si se clona un Joomla, en su fichero de configuración hay que cambiar las apariciones de plantilla por el nombre del usuario del alumno. Además, es necesario cambiar el usuario administrador de Joomla en la base de datos:
copia_joomla() { local PLANTILLA=$1 local USER=$2 local DOCUMENTROOT=/home/$USER/public_html copia_base_de_datos $PLANTILLA $USER copia_ficheros_sitio_web $PLANTILLA $USER sed -i -- "s/$PLANTILLA/$USER/g" $DOCUMENTROOT/configuration.php rm $DOCUMENTROOT/index.html mysql --user=$USER --password=$USER -e "use $USER; update isvfo_users set username='$USER' where username='plantilla';" }
Una vez se tienen todas estas funciones, basta con iterar sobre los alumnos, y acabar habilitando todos los sitios web nuevos (uso a2ensite
de la distribución de Debian)
for user in $USERS do echo ________________________________________ NOMBRE DE USUARIO: $user crea_usuario $user crea_sitio_web $user crea_base_de_datos $user copia_joomla plantilla $user done sudo a2ensite 'alumno*' sudo service apache2 restart