'.htmlentities(realpath($f),ENT_COMPAT,'UTF-8').'
';
    $f=file_get_contents($f);
    if ($c) {
      $f=preg_replace($c[0],$c[1],$f);
    }
    if ($n) 
      $f=implode("\n",array_map(create_function('$l','static $_nlin=1; return ($_nlin++)."   ".$l; '),explode("\n",$f)));
    if ($i)
      $f=iconv('ISO-8859-1', 'UTF-8//IGNORE', $f);
    echo htmlspecialchars($f, ENT_QUOTES, 'UTF-8').'
'; } ?> Sobre la configuración de este servidor

Nisu - UJI

Sobre la configuración de este servidor

24 November 2012

Contenido

  1. Introducción
  2. El firewall
  3. Los usuarios
  4. Los usuarios remotos
  5. El DNS
  6. El correo
  7. Apache y PHP
  8. Las copias de seguridad
    1. Las bases de datos
  9. Notas.
Este documento describe las particularidades de la configuración de este servidor. A mí me es últil, espero que también a otros.

Introducción

Este servidor no es otra cosa que un PC con dos discos duros donde tengo mis cosas y donde mis alumnos y los de otros colegas pueden desarrollar su actividad académica, especialmente proyectos web basados en PHP. Su configuración no es fruto del azar sino el resultado de 15 años de mantener servidores Linux multiusuario, pero obviamente es discutible en todos sus aspectos.

En primer lugar notar que los discos no están en RAID. Simplemente prefiero mantener copias de seguridad selectivas y coherentes que duplicar a bajo nivel. Si un disco se rompe (espero que no), me cuesta poco reconstruirlo. Poner más discos y hacer RAID es una opción, pero no con el hard que tengo ahora, pues me resultaría muy difícil mantener los discos a 30º como los tengo ahora.
Un disco contiene /home y el otro el resto del sistema. El /tmp no es un disco aparte, lo que sería deseable para ciertas prácticas que hacemos, pero es lo que hay.

El firewall

Se trata de un conjunto de reglas de iptables1 que explicaré un poco. En primer lugar se establecen unas reglas para intentar evitar el syn flood, todas las conexiones TCP con SYN se envían a la cadena SYN_FLOOD que limita a 5 por segundo. Cuando un paquete que entra en la cadena BAN, es eliminada su IP de PBAN, marcada la IP y rechazado. Cuando un paquete que entra en la cadena PBAN es marcado y admitido, slavo que haya entrado otro desde la misma IP en el último minuto, que entonces es rechazado y actualizada la hora.

Es preciso aceptar los paquetes de retorno de las conexiones TCP, identificados por el estado ESTABLISHED o RELATED. A partir de aquí hay que autorizar algunas cosas, apicar las reglas de entrada en PBN y rechazar lo demás.

En primer lugar, cualquier IP que viene al SMB, que no se usa en la máquina, es baneada, todos los Windozes que van escaneando son baneados. Para ciertas protecciones al Apache todo lo que va al puerto 8888 es baneado inmediatamente. Así, desde Apache cuando se detecta cualquier intento de ataque o similar, el servidor redirige al puerto 8888, si el cliente acepta la redirección, es baneado de inmediato.

Después, todas las IP que están ya en BAN, si hacen cualquier conexión siguen en BAN hasta que estén 2 días sin conectar. El criterio para entrar en BAN (desde PBAN) son 20 conexiones por minuto, que ningún cliente no-agresivo debería hacer en ningún caso. La entrada en PBAN se produce con 10 coenxiones por minuto y simplemente bloquea durante un minuto.

El criterio para acceder a PBAN es acceder a puertos que requieren autenticación, concretamente ssh, ftp, pop3, pop3s, imap, imaps, 998, 5900, 5500.
Por último, el cortafuegos acepta conexiones a ciertos puertos usuales y el resto las rechaza.

Los usuarios

La UJI registra cientos de incidencias anuales del tipo "no recuerdo mi contraseña". En este servidor, de uso menos frecuente, la probabilidad de olvido se dispara. Para evitarlo, la estrategia es simple: los usuarios tienen los mismos nombres que en la UJI y pueden (re)establecer su contraseña desde la página http://al.nisu.org. Esta página, después de validar que usuario existe, añade a un archivo el usuario y la contraseña elegida. Un proceso del root desde el Cron lee archivo y cambia la contraseña del sistema y de las bases de datos 2. El comando passwd para el cambio de contraseña Linux está deshabilitado para evitar incoherencias con las contraseñas de las bases de datos.
Si un usuario pierde la condición de alumno de la UJI deja de poder cambiar su contraseña. Para evitarlo, mientras se es alumno, una vez autenticado en http://al.nisu.org, hay un enlace que permite habilitar la autenticación en ésta página basada en certificado.

Los usuarios remotos

Los usuarios que tengan un directorio de nombre home_remoto pueden montarlo por NFS desde otro ordenador. Para ello es necesario crear un túnel que dé autenticidad al cliente. El tunel debe crearse mediante OpenVPN, autenticando. Una vez realizado el túnel, la IP asignada está autorizada a montar el home_remoto del usuario. Desde las aulas, de forma automatizada, puede realizarse todo ello junto con la creación del usuario, usando un script 3. Esto lo motiva el hecho de que cuando empleamos los ordenadores de los laboratorios de Informática de la ESTCE (no las aulas informáticas), accedemos con un usuario genérico, de modo que no podemos tener, por ejemplo, un Firefox configurado al gusto y lo más obvio, no podemos guardar los archivos de trabajo de forma fiable sin usar FTP o similar.

El script se ejecuta en el arranque de los equipos de algunos laboratorios y se activa condicionalmente según los siguientes criterios:

  • Si se encuentra el archivo homenisu.pem4 en el directorio raíz de un disco USB (que el usuario habrá introducido durante el arranque), se espera que contenga el certificado de la GVA (sólo ese) y la llave en formato PEM, y se utilizarán como credenciales para OpenVPN.
  • Si no, si se encuentra el archivo homenisu.p12 en dicho disco, se espera lo mismo, el certificado de la GVA.
  • Si no, si se encuentra el archivo homenisu.iden en dicho disco, se espera que contenga el nombre de usuario, produciendo la activación.
  • Si no, el script se activa manteniendo pulsada la tecla Retroceso (Backspace).
Una vez activado, el script establece un túnel OpenVPN contra el servidor, empleando las credenciales que proceda según el modo de activación. Dentro del túnel, el script obtiene los datos del usuario autenticado y crea el usuario en el ordenador local, con la misma contraseña, uid y gid que tiene en el servidor. Después, monta por NFS el directorio personal del usuario local sobre el directorio home_aulas que el usuario tiene en el servidor. El uso de NFS permite máxima compatibilidad entre sistemas, resultando transparente para el usuario. Por último el script prepara el gestor de entrada al sistema gráfico (gdm) de modo que el usuario recién creado entra sin autenticar.
Como facilidad adicional, el directorio public_html dentro de home_aulas se monta (con --bind) con el public_html del HOME, de modo que desde las aulas informáticas se puede editar la web sin acceder al servidor por SSH. Para evitar confusiones, desde el servidor, el directorio home_aulas/public_html no es accesible cuando no está montado.
Los usuarios pueden crear un script .homenisu en home_aulas con instrucciones adicionales que se ejecutarán en el momento del arranque del sistema. Para un ejemplo consultad conmigo.

El DNS

El propio servidor contiene el DNS del dominio nisu.org5. Lo relevante es que implementa dos comodines:
	al		IN	A	150.128.97.91
	*.al		IN	A	150.128.97.91
	*		IN	A	150.128.97.91
Es decir, cualquier nombre de la forma xxxx.nisu.org resuelve a una determinada IP y lo mismo para xxxx.al.nisu.org. Realmente al ser actualmente la misma IP, el comodín xxxx.al.nisu.org no es necesario, pero lo sería si el servidor de alumnos no coincidiera con el general de nisu, como sucedía en el pasado.

Además el servidor delega autoridad sobre zonas del tipo xxxx.dyn.nisu.org. Se obtiene simplemente accediendo a la URL http://gsi.nisu.org/prDNS.php?dele=xxxxxx, que realiza la delegación llamando a un script6 con el nombre solicitado y la IP desde la que se invocó.

El correo

El servidor de correo Postfix está configurado para escuchar el puerto 26 además del 25. Con esto puede recibir correo de fuera de la UJI con ayuda de un relay exterior. Como sistema de filtrado en el reparto se emplea Maildrop (de Courier). El correo se almacena en formato maildir, establecido en /etc/maildroprc con
DEFAULT="$HOME/Maildir"

Es de esperar que los alumnos no vayan a leer el correo en este servidor, por lo que cuando se crea el usuario, se le instala un .forward conteniendo:

"| /usr/local/bin/reenvia"
El script reenvia es sencillo. Compone el mensaje original como un adjunto y con un aviso lo envía a la cuenta oficial de la UJI, evitando bucles 7.

Apache y PHP

El servidor concentra en una sola IP, varios VirtualHost que a su vez se expanden en infinitos nombres que se gestionan mediante reglas de reescritura. Actualmente están en servicio el VirtualHost genérico de nisu y el de los estudiantes, al.nisu.org.

Existe un VirtualHost que recoge las peticiones por defecto, y su misión es banear a todos los que acceden directamente a la IP o generan peticiones incorrectas. Se consigue redirigiendo todos los errores a la URL relativa "/", de modo que el script index.php8 acaba recibiendo todas las peticiones incorrectas. Este script escribe la IP del cliente en un fichero pipe que es leída por un proceso del root e insertada en la cadena BAN del firewall:

while true; do read ip </var/www/def/banea ; [ "$ip" ] || continue; echo +$ip >/proc/net/xt_recent/BAN; done &

Son varios los VirtualHost que atienden las peticiones de nisu.org, siendo el más interesante el destinado a los estudiantes. Cada estudiante tiene su propia URL que es implementada a través de una reescritura, de modo que al crear un usuario o hay que alterar la configuración del servidor web, basta con que el usuario cree su directorio de web y el site pasa a funcionar directamente. Expliquemos brevemente las configuración de este VirtualHost9. Atiende todas las peticiones a los subdominios de al.nisu.org gracias a la potente directiva ServerAlias. La raíz del VirtualHost sirve para poco más que el tratamiento de las excepciones. Dentro de las reglas de reescritura, si un PATH comienza por ~usuario , que es lo típico en apache, se redirge al scritp miserv.php que simplemente informa que la URL correcta es http://usuario.al.nisu.org/. A continuación, si se cumplen las condiciones de que el Host solicitado es de la forma usuario.al.nisu.org y existe el directorio public_html del usuario se reescribe la dirección del fichero (no la URL) de modo que accede a los achivos del usuario, se acaba la reescritura y se define la variable de entorno ES_AL Si no se cumplen las condiciones anteriores se redirige a un script indormativo nopage.php.
La directiva ErrorDocument no permite redirigir a una URL global sin que aparezca en la barra de direcciones del navegador del visitante. Por ello, para disponer del mismo tratamiento de Errores para todos los usuarios, los errores se redirigen a unas URLs falsas locales, que mediante una regla de reescritura (situada al comienzo de la lista de reglas) se reescribe internamente a un fichero local.

Para dar independencia máxima a los sites de los estudiantes, es importante que desde PHP tengan la percepción de que realmente es su servidor. Para ello es necesario ejecutar PHP a través de CGI con suPHP, no puede usarse el módulo típico de apache, porque entonces los scripts de los usuarios se ejecutan con el usuario del servidor web. Gracias a suPHP, cada PHP se ejecuta con el usuario propietario del fichero, de modo que un script puede escribir en un directorio del usuario, sin obligarle a poner permisos de escritura a todo el mundo. Además los scripts no necesitan ser leídos por el servidor, sino sólo por el usuario, manteniendo los permisos a rw-------, de modo que un usuario no puede leer los scripts de los demás usuarios (y por tanto no puede acceder a las contraseñas de los motores de base de datos almacenadas en ellos). El mecanismo suPHP resta eficiencia al servidor, pero es la única forma de garantizar la independencia y la seguridad sin virtualización. PHP dispone de un modo denominado safe_mode que podría ayudar a alcanzar esa seguridad sin suPHP, pero después de probarlo unos años, llegué a la conclusión de que crea más inconvenientes que ventajas.
El mecanismo suPHP está definido con Location /, de modo que impide que se pueda impedir el uso de suPHP desde los ficheros .htaccess que el usuario está autorizado a definir.

Los logs que no son de los usuarios (que son pocos), se envían al log del sistema, pero los logs de los usuarios, identificados por la variable de entorno ES_AL, se envían a cada usuario a través de un ineficiente script10. El script genera un archivo accesos.log propiedad del administrador (para no consumir cuota de disco), que está en un directorio accesos propiedad del usuario y de sólo lectura para él.
El log de los errores es similar, desafortunadamente es mucho más difícil filtrar los errores pues no siempre contienen la identificación del usuario propietario de la página, algunos errores se pierden, pero el mecansmo funciona bastante bien para ayudar a los usuarios. Se realiza con un script11 similar.

Para evitar el crecimiento desmesurado del archivo accesos.log, que no puede ser editado por el usuario, todas las mañanas otro script12 deja el archivo en 500 líneas, pero asegurándose de que no se borren los logs del día ni del día anterior. El número de líneas puede ajustarse entre 0 y 5000 escribiendo el número deseado en el archivo accesos.auto en el mismo directorio.

El resto de las configuraciones son similares. La configuración del genérico de nisu.org13 es una versión simplificada del de los alumnos. Tiene como particularidad que se exige SSL para ciertas ubicaciones. Efectivamente, el servidor admite peticiones SSL sobre sec.nisu.org 14 por el puerto estándar (443) sin solicitud de certificado de cliente (sólo requerida en ciertas ubicaciones con renegociación) y por el puerto no estándar 444, donde se requiere siempre certificado de cliente para acceder.
La ubicación /common/auth* implementa el protocolo de autenticación cruzada que permite autenticación con certificado de cliente en servidores noSSL.

Igualmente el servidor por defecto, que atiende al nombre de la máquina en la UJI, es el que permite la autenticación usando el login único de la UJI a los usuarios que deseen implementarlo.

Comentar por último que ciertos servidores virtuales específicos, como todos los expires*.nisu.org, empleados para control de versiones en software producido por nosotros, tiene una definición propia 15, en este caso usa el módulo del servidor para atender el PHP por razones de eficiencia y realiza separación de logs (hay decenas de accesos por segundo para estos servidores).

Las copias de seguridad

Hay dos discos, las copias de uno se realizan sobre el otro. Aunque se les indica16 que utilicen SVN o que desarrollen en casa, los estudiantes desarrollan directamente sobre la máquina, con lo que los disgustos tipo "se me ha borrado el script principal de mi proyecto" no son infrecuentes. Para ello decidí montar el sistema de copias de la siguiente forma:
  • Hay una copia idéntica de directorios seleccionados.
  • En la copia se almacenan copias antiguas de los archivos. El tiempo hasta que se borran depende del tamaño del archivo.
  • La copia se hace varias veces al día.
Esto se consigue fácilmente con el comando:
  function cpsec () {
bck=${!#}
rsync -Sqaxb --delete --suffix="~~$(date +%s)" -f "P *~~*" $*
Los archivos borrados o modificados en la fuente son renombrados en la copia añadiendo la fecha actual en segundos precedida de ~~. El filtro impide que el propio rsync borre las copias antiguas. Para ir limpiando esas copias antiguas, a continuación se ejecuta:
    find "$bck" -regex ".*~~[0-9]+" criterios | xargs -d '\n' -r rm
}
En los criterios debe usarse ctime y no mtime. Para los archivos modificados sería indiferente, pero no para los borrados. El atributo ctime permite establecer el momento de la copia de seguridad (cuando el archivo es renombrado), mientras que si se usara mtime, un archivo antiguo borrado en el original, sería renombrado en la copia e inmediatamente borrado por el find.

Con esta función, todo el /home se copia en el raíz, pero evitando archivos de ciertos tipos (avi, mp3, etc.), de modo que la copia no tiene apenas impacto en la ocupación del otro disco.

Este esquema de copia tiene dos problemas, que no han tenido impacto hasta su resolución (pues los estudiantes, o no saben o son buena gente):

  • Realmente se está duplicando el HOME del usuario, pues el usuario puede escribir en la copia. En particular sería necesario poner cuotas de disco en el raíz para impedir que lo usen de almacén. Pero el usuario podría decidir borrar la copia y emplear el espacio para guardar otras cosas.
  • En el otro extremo, el usuario puede impedir indefinidamente el borrado de las copias antiguas, simplemente haciendo touch de los archivos con ~~.
Después de darle vueltas a complejas soluciones basadas en permisos, totalmente inoperativas (el usuario y sólo él debe poder leer sus copias de seguridad), ví claramente que se tataba de implementar un acceso read-only a bajo nivel sobre las copias. En principio sólo se me ocurría usar NFS (sobre el propio equipo), pero los núcleos actuales disponen de una opción de montaje que permite hacerlo de la siguiente manera:
  • Creo un directorio /wcopias con permisos 700 del root, de modo que sólo él puede siquier entrar. Dentro de él está copias, que es accesible por los usuarios, con los debidos permisos.

  • Creo un directorio /copias y ejecuto:
    mount --bind /wcopias/copias /copias
    mount -o remount -o ro /copias
    Los usuarios tienen acceso a /copias, pero el sistema de archivos es read-only. El administrador realiza las copias sobre /wcopias/copias.
Por último comentar la necesidad de usar la opción -S (sparse) en cualquier copia cuya fuente sea el usuario, pues por ejemplo, en prácticas de GSI se generan archivos gigantes dispersos, que de no copiarse igual, desbordarían la copia.
El resultado es un sistema de copias incrementales sólido y fácil de consultar.

Las bases de datos

Copiar las bases de datos es necesario, y también lo es tener una copia incremental. Lo más sencillo sería volcar las bases de datos de golpe, mediane mysqldump --all-databases o pg_dumpall. El inconveniente es que los usuarios no podrían leer la copia, pues debe guardarse en privado. La solución adoptada es crear un volcado individual de la base de datos en el directorio del usuario. Si esto se hace cada vez que se realiza una copia de seguridad, el archivo de volcado se actualiza cada vez, y por tanto hay una copia incremental (un archivo *~~*) varias veces al día. Para evitarlo, se realiza el volcado y si no es diferente de la copia actual, no se guarda. Las copias se realizan así:
	para cada base de datos mysql
	  establecer destino con creadm
	  mysqldump de la base de datos sobre un archivo temporal
	  si el archivo es diferente del último volcado, acualizarlo
Y lo mismo para cada base de datos PostgreSQL. Hecho así, directamente, tiene algunos inconvenientes. Si el archivo de volcado es propiedad del usuario, le consume cuota de disco. Si es del root, hay que protegerlo contra lectura del resto de usuarios. Además es iportante que el usuario torpe no pueda borrar el volcado, porque podría suponer también borrar los volcados incrementales.

Para resolver todo ello, la mencionada función creadm determina el usuario destino de la copia. Actualmente el método es sencillo: si existe un usuario con el mismo nombre de la base de datos, elige ese, si no, elige un usuario por defecto. Crea, si no existe, un directorio copia_bd propiedad del usuario con permisos 700 y dentro de él un directorio del root que contiene el volcado, también del root. Si el volcado no estuviera en un directorio del root, podría ser borrado, así no se puede borrar y por ello tampoco el directorio intermedio.
Una posible mejora al sistema sería determinar qué usuario debe ser el receptor del volcado en base a los usuarios del sistema de base de datos y no al nombre de la base de datos.

El sistema incremental no se aplica a todo. Por ejemplo, el directorio Maildir de los usuarios es copiado de forma diferente, simplemente acumulativa, sin borrado. Lo mismo para los logs del sistema.
Al final de todas las copias, los respaldos son copiados a su vez a otro ordenador, con mucho espacio, pero que a veces está apagado.

El volcado de PostgreSQL es bastante lento y carga la máquina, al igual que los rsync, por lo que se realiza todo mediante runload, que al no afectar a jerarquías de procesos, debe hacerse por proceso, no para todo el script.

El resultado es un script17 de copias invocado periódicamente desde el cron:
5 9,11,13,15,18,20,23,1 * * *	/usr/local/bin/cpsec.sh

Notas.

1