Securización de un sistema Ubuntu (GNU/Linux)

Últimamente he escrito varios posts sobre estándares y aspectos más estratégicos de negocio e IT, como por ejemplo:

Es habitual escuchar críticas a este tipo de enfoque más estratégico, aludiendo al hecho de que se habla y teoriza mucho pero no se concreta nada. En este nuevo artículo, en el que ha colaborado Raúl Gómez (experto en seguridad técnica), se plasman de forma técnica muchas de las ideas de buena gestión estratégica IT o de negocio.

Vamos a ver cómo podemos montar un servidor con Ubuntu Linux que cumpla mínimamente con las buenas prácticas de seguridad:

  • Efectuar una configuración segura
  • Establecer una política de contraseñas
  • Utilizar herramientas para la gestión de usuarios
  • Proteger los servicios de red
  • Garantizar la trazabilidad
  • Proporcionar herramientas de monitorización en tiempo real e histórica para facilitar proyecciones de futuras necesidades
  • Establecer política de copias de seguridad
  • Efectuar tests de stress del sistema

Y es que en definitiva el software libre, por más libre y abierto que sea, no tiene porque ser seguro a no ser que se establezca una configuración adecuada y alineada con las políticas/normativas/procedimientos de la organización.

Contenido

1. Configuración mínima
2. Securización del sistema base
    Kernel: parámetros de seguridad
    Limitando los recursos del sistema
        Memoria, número de procesos y sesiones concurrentes
        Espacio en disco
3. Interacción básica con el sistema
    Shells
    Shell Bash
    Locales: configuración del idioma
    Mensajes legales e informativos: Issue & motd
    Usuarios privilegiados: sudo
    Sistema de archivos
        Esquema de los permisos de ficheros/directorios
        Permisos por defecto: UMASK
        Permisos especiales: SUID y SGID
        Directorios con permisos de escritura global
        Archivos sin propietarios
        Detección de modificaciones de ficheros: Tripwire
4. Política de contraseñas
    Política global
    Contraseñas de un solo uso: OTP (One time password)
    Gestión del acceso
5. Gestión de usuarios
    Creación de usuarios
    Gestion de cuentas
6. Servicios básicos del sistema
    Activación en arranque
    Servicio de correo: Postfix
    Servicio web: Apache
        PHP
        Apache básico
        Módulos de Apache
        Servir por localhost las aplicaciones más criticas
    Base de datos MySQL
    Acceso remoto: SSH
7. Trazabilidad
    Fecha y hora del sistema
    Logs de usuario con Bash
    Rotación y conservación de logs
    Análisis de logs de Apache mediante Awstats
    Envio por correo de los eventos más significativos con logcheck
8. Monitorización del sistema
    Actualización automática de la lista de paquetes del sistema
    Monitorizar desde la consola
    Espacio en disco
        CPU
        Ancho de banda / Bandwidth
        Ancho de banda / Bandwidth
        Memoria RAM / Swap
    Monitorización en tiempo real: Nagios
    Ancho de banda consumido con Bandwidthd
    SNMP
        Soporte ‘Devices I/O’ en net-snmp
    Monitorización historica: Cacti
        Script Device I/O para cacti
9. Seguridad
    Cortafuegos / Firewall
        Monitorización de firewall con fwlogwatch
    Detección de intrusos / IDS: Snort
    Rootkits
    Control del ancho de banda
10. Copias de seguridad
    Utilidades rsnapshot
        Información de estado
        dpkg y mysql
        Tars semanales
        Reportes
11. Tests de stress
    Sistema
    Conexiones HTTP con httperf
    Conexiones HTTP con Apache Benchmarking tool
12. Issue tracking

1. Configuración mínima

Recomiendo echar un vistazo a la Guía de referencia rápida para la personalización de Ubuntu Edgy (6.10) GNU/Linux, aunque la versión de Ubuntu es bastante antigua, la mayoría de secciones aún son válidas. En el caso de los servidores, resultan especialmente interesante las siguientes secciones:

2. Securización del sistema base

Kernel: parámetros de seguridad

El kernel de Linux dispone de diversos parámetros que nos permiten configurar su actuación frente al tráfico de red. Al iniciar el sistema se establecen los valores de los parámetros indicados en ‘/etc/sysctl.conf’. No obstante, también es posible modificarlos mediante el comando ‘sysctl’.

Los parámetros más importantes a tener en cuenta son:

– Activar la protección contra IP spoofing:

sysctl -w net.ipv4.conf.default.rp_filter=1
sysctl -w net.ipv4.conf.all.rp_filter=1

– Activar TCP SYN Cookie Protection para minimizar la posibilidad de ataques de denegación de servicio por ataques de paquetes SYN. No obstante, actualmente ya no se recomienda esta opción dado que las capacidades de los sistemas han aumentado considerablemente y activarlo nos hace perder otras características de TCP (ver recomendación de Andi Kleen, autor de las SynCookies):

sysctl -w net.ipv4.tcp_syncookies=1

– Desactivar el reenvío de paquetes IP (solo es necesario cuando queremos que la máquina actue como una pasarela mediante NAT):

sysctl -w net.ipv4.ip_forward=0
sysctl -w net.ipv6.ip_forward=0

– Ignorar Broadcasts Request:

sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=1

– Activar la protección contra mensajes de error mal construidos:

sysctl -w net.ipv4.icmp_ignore_bogus_error_responses=1

– Desactivar ICMP Redirect Acceptance, mediante los cuales un atacante podría indicar al sistema una ruta mejor para llegar a otras redes (habitualmente utilizado por routers). Únicamente los permitiremos si proceden de nuestro gateway:

sysctl -w net/ipv4/conf/all/accept_redirects=0
sysctl -w net/ipv4/conf/all/send_redirects=0
sysctl -w net/ipv4/conf/all/secure_redirects=1

– Desactivar IP Sourcing routing mediante el cual un atacante podría especificar la ruta a seguir para ir desde una IP origen a una destino:

sysctl -w net.ipv4.conf.all.accept_source_route=0

– Logear paquetes: Spoofed Packets, Source Routed Packets, Redirect Packets

sysctl -w net.ipv4.conf.all.log_martians=1

– Ignorar ICMP Requests PINGs, personalmente prefiero dejarlo a 0 y controlar desde donde y hacia donde permito los pings mediante iptables (firewall). En cualquier caso, si queremos desactivar completamente que el sistema responda a pings:

sysctl -w net.ipv4.icmp_echo_ignore_all=1

Para consultar los valores actuales:

sysctl net.ipv4.conf.default.rp_filter
sysctl net.ipv4.conf.all.rp_filter
sysctl net.ipv4.tcp_syncookies
sysctl net.ipv4.ip_forward
sysctl net.ipv6.ip_forward
sysctl net.ipv4.icmp_echo_ignore_broadcasts
sysctl net.ipv4.icmp_ignore_bogus_error_responses
sysctl net.ipv4.conf.all.accept_redirects
sysctl net.ipv4.conf.all.send_redirects
sysctl net.ipv4.conf.all.accept_source_route
sysctl net.ipv4.conf.all.log_martians

Para establecer los nuevos valores de forma permanente para sucesivos reinicios es necesario modificar /etc/sysctl.conf.

Limitando los recursos del sistema

Se considera una buena práctica limitar los recursos del sistema utilizados por los usuarios, de forma que minimicemos las probabilidades de que se efectúen ataques de denegación de servicio contra todo el sistema.

Memoria, número de procesos y sesiones concurrentes

Para limitar la memoria, el número de procesos y las sesiones concurrentes que puede tener cada usuario debemos editar ‘/etc/security/limits.conf’. A modo de ejemplo:

# Prevents anyone from dumping core files.
*               hard    core   0

# This will prevent anyone (except root) from having more than 150 processes, and a warning will be given at 100 processes.
*          soft    nproc  100
*          hard    nproc  150

# This will prevent anyone in the 'users' group from having more than 150 processes, and a warning will be given at 100 processes.
@users          soft    nproc  100
@users          hard    nproc  150
@users          -       maxlogins 4

# Address space limit: 2GB
*           hard    as  2097152
# Maximum data size: 128MB
*           hard    data 131072
# Maximum locked-in-memory address space: 128MB
*           hard    memlock 131072
# Maximum resident set size: 1GB
*           hard    rss 1013352

En este caso estamos limitando cada usuario (excepto root) a un máximo de:

  • 150 procesos para todos los usuarios
  • 100 procesos para los usuarios del grupo ‘users’
  • 2GB de RAM
  • 4 logins concurrentes

Una vez configurado, podemos realizar una sencilla prueba para verificar que el sistema no nos permite crear más de 100 procesos:

$ for i in `seq 500`; do sleep 5 & done
Espacio en disco

Para habilitar las limitaciones de espacio debemos tener montada las particiones afectadas con los parámetros ‘usrjquota’ y ‘grpjquota’ en ‘/etc/fstab’ (journalling quotas). Por ejemplo:

/dev/sda4 / ext4 defaults,errors=remount-ro,usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0 1 1

Por otra parte, el servicio de quotas (/etc/init.d/quota) debe estar activo. Podemos comprobarlo mediante la aplicación ‘sysv-rc-conf’ (consultar sección de Servicios de esta guía).

Podemos activar inmediatamente las quotas mediante:

touch /aquota.user /aquota.group
chmod 600 /aquota.*
mount -o remount /

quotacheck -avugm
quotaon -avug

Si encontramos algun tipo de problema con quotacheck (p.ej. puede ser que entre en conflicto con gvfs), podemos probar a reiniciar el sistema.

A continuación, para modificar las quotas de un grupo de usuario determinado:

EDITOR="vi" edquota -g users

Tendremos que cambiar los números correspondientes, por ejemplo:

Disk quotas for group users (gid 100):
  Filesystem                 blocks        soft          hard              inodes     soft     hard
  /dev/sda6                  23808        1048576    1572864         37           0        0
  /dev/sda7                  10312844   47185920   52428800       8780        0        0

En este caso, el sistema realizará un aviso por mail local a los 45GB (45*1024*1024), mientras que el límite real se encuentra en los 50GB (50*1024*1024) para el grupo ‘users’ y el dispositivo sda7 (p.ej. /home/). En el caso de sda6 (p.ej. raíz /), el limite blando es 1GB (1*1024*1024) y el duro 1.5 GB (1.5*1024*1024).

Los ficheros que contabilizan dentro de la quota de grupo son aquellos que tienen como propietario el mismo grupo. Es decir, un usuario que pertenece al grupo A y B puede crear archivos asignándolos a uno u a otro hasta agotar ambas quotas. Por este motivo, establecer los límites por usuario permite un mayor control:

EDITOR="vi" edquota -u usuario

Es también interesante limitar todos aquellos grupos que corren servicios como apache2.

Podemos ver un resumen de todas las quotas aplicadas:

# repquota -s -a
*** Informe para user quotas en dispositivo /dev/mapper/isw_beahgicji_Volume07
Período de gracia de bloque: 7días; período de gracia de Inode: 7días
                        Límites de bloque límites de archivo
Usuario         usado   blando  duro  gracia   usado bland duro  gracia
----------------------------------------------------------------------
root      --   63404       0       0          18960     0     0       
usuario1    --    457G       0       0           303k     0     0       
usuario2    +-  10072M   5120M   5120M 6días    8780     0     0       
ftp       --       4       0       0              1     0     0       
freevo    --      44       0       0             18     0     0       
mythtv    --      48       0       0             15     0     0       
couchdb   --     132       0       0             33     0     0       

Un usuario puede consultar el estado de su cuota:

$ quota -s
Disk quotas for user usuario (uid 1000): 
     Filesystem  blocks   quota   limit   grace   files   quota   limit   grace
      /dev/vzfs      24      26      28               6       0       0        

Cabe destacar que el periodo de gracia (grace limit) es el tiempo que transcurre antes de que el usuario es obligado a cumplir el “soft limit”. Para modificarlo:

# edquota -t

Período de gracia antes de imponer límites blandos para users:
La unidad de tiempo puede ser: días, horas, minutos, o segundos
  Sistema de archivos Período de gracia de bloque Período de gracia de inode
  /dev/mapper/isw_beahgicji_Volume06                 7días                 7días
  /dev/mapper/isw_beahgicji_Volume07                 7días                 7días

Finalmente, en caso de agotar la cuota, el usuario recibirá el siguiente mensaje:

$ cp /usr/bin/dpkg .
cp: writing `./dpkg': Disk quota exceeded

3. Interacción básica con el sistema

Shells

La shell es la aplicación mediante la cual los usuarios pueden interactuar con el sistema. Es recomendable habilitar únicamente las shells que nos interesen editando ‘/etc/shells’:

# /etc/shells: valid login shells
#/bin/csh
/bin/sh
#/usr/bin/es
#/usr/bin/ksh
#/bin/ksh
#/usr/bin/rc
#/usr/bin/tcsh
#/bin/tcsh
#/usr/bin/esh
#/usr/bin/screen
#/bin/dash
/bin/bash
#/bin/rbash

Únicamente las shells no comentadas seran permitidas, siempre y cuando añadamos al principio de ‘/etc/pam.d/common-auth’:

# This will not allow a user to change their shell unless
# their current one is listed in /etc/shells. This keeps
# accounts with special shells from changing them.
auth       required   pam_shells.so

Shell Bash

Es interesante configurar la shell Bash para permitir el auto-completado inteligente de comandos (presionando [tab]) editando ‘/etc/bash.bashrc’ y descomentado:

# enable bash completion in interactive shells
if [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
fi

En ese mismo fichero, es recomendable añadir los siguiente alias:

alias mv='mv -i'
alias cp='cp -i'
alias rm='rm -i'
alias ln='ln -i'
export EDITOR='vim' 
export TMOUT=3600

Esto evitará borrados involuntarios de ficheros dado que cada vez que modifiquemos un fichero mediante mv, cp, rm o ln se pedirá confirmación.

Por otra parte, la variable TMOUT provocará automáticamente un logout de la terminal si no se ejecuta nada durante los segundos especificados (si se está visualizando un programa interactivo como top, no se produce timeout). De esta forma reduciremos las probabilidades de que un usuario se deje su sesión abierta y accesible por otra persona que tenga acceso físico a su ordenador.

Cabe destacar que si se ejecuta screen después de establecer la variable TMOUT, se hereda y se produce logout primero de las ventanas de screen y despues de la sesión que ejecutó screen.

Locales: configuración del idioma

Para que el sistema muestre los mensajes en un idioma determinado (p.ej. Castellano):

apt-get install language-pack-es language-pack-en

Y a continuación editamos ‘/etc/environment’ y ‘/etc/default/locale’:

#LANG="en_GB.utf8"
LANG="es_ES.UTF-8"

LANGUAGE="es_ES:en_EN"
#LANGUAGE="en_GB:en:es_ES:es"

Mensajes legales e informativos: Issue & motd

Se recomienda establecer un mensaje legal que notifique a los usuarios que el acceso al sistema es exclusivo para aquéllos que han sido autorizados. Para ello se debe editar los ficheros ‘/etc/issue’ y ‘/etc/issue.net’, por ejemplo:

***************************************************************************
                           NOTICE TO USERS

This computer system is for authorized use only. Use of this system 
constitutes consent to security monitoring and testing. All activity is 
logged with your host name and IP address.

Any or all uses of this system and all files on this system may be
intercepted, monitored, recorded, copied, audited, inspected, and disclosed 
to law enforcement personnel, as well as authorized officials of other 
agencies, both domestic and foreign.

Unauthorized or improper use of this system may result in administrative
disciplinary action and civil and criminal penalties. By continuing to use
this system you indicate your awareness of and consent to these terms and
conditions of use. LOG OFF IMMEDIATELY if you do not agree to the conditions
stated in this warning.

*****************************************************************************

Cabe destacar que si queremos que el banner se muestre a los usuarios que se conecten mediante SSH (ver sección de Servicios de esta guía), debemos editar ‘/etc/ssh/sshd_config’ y descomentar:

Banner /etc/issue.net

Por otra parte, una configuración segura implica proporcionar la mínima información sobre el sistema al usuario en cuanto a las características del mismo (p.ej. distribución, kernel, etc). Para minimizar ese aspecto podemos poner en blanco el mensaje de bienvenida (o incluir el mensaje genérico que nos interese):

echo > /etc/motd
echo > /etc/motd.tail

Si lo dejamos en blanco, debemos asegurarnos que el script de inicio ‘/etc/init.d/bootmisc.sh’ no lo reconstruye. Para ello lo editamos y comentamos las siguientes lineas:

    # Update motd
    #uname -snrvm > /var/run/motd
    [ -f /etc/motd.tail ] && cat /etc/motd.tail >> /var/run/motd

Y por otra parte, editamos ‘/etc/cron.d/update-motd’ y comentamos:

#*/10 * * * *    root    [ -x /usr/sbin/update-motd ] && /usr/sbin/update-motd 2>/dev/null

Usuarios privilegiados: sudo

Con el objetivo de garantizar la máxima trazabilidad, se requiere que el usuario root no sea utilizado directamente. Para ello podemos configurar el sistema para que el grupo de usuarios que nos interese puedan adquirir privilegios de root editando ‘/etc/group’ y añadiendo:

admin:x:114:miusuario

Ademas debemos tener en '/etc/sudoers' la siguiente línea:

# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL

Para editar sudoers siempre debemos utilizar el comando ‘visudo’. Con esta configuración, el usuario o usuarios del grupo seleccionado (‘admin’ en este caso) podrán acceder al sistema normalmente y una vez dentro ejecutar ‘sudo comando’ o ‘sudo -s’ para adquirir privilegios de root.

Cabe destacar que Ubuntu suele venir configurada así por defecto.

Sistema de archivos

Esquema de los permisos de ficheros/directorios



Permisos por defecto: UMASK

Es posible establecer los permisos con los que se crean los nuevos ficheros/directorios, para ello tendremos que editar varios ficheros. El primero es ‘/etc/login.defs’:

UMASK      022

El segundo ‘/etc/profile’:

umask 022

Y finalmente, editamos ‘/etc/pam.d/common-session’ y añadimos al final:

session optional pam_umask.so umask=0022

Son necesarios tantos cambios porque en función de como nos conectemos a la máquina (físicamente, remotamente por ssh, X Window, etc.) se utilizaran unos scripts u otros.

En cuanto a la mascara, nos podría interesar establecer umask como 027 para que todos los ficheros/directorios creados no tengan permisos de acceso para otros usuarios que no sean los del grupo del propietario.

Ejemplo de funcionamiento de umask:

$ umask 000
$ touch file1
$ ls -l file1
-rw-rw-rw-  1 oracle oinstall 0 Dec 26 19:24 file1
$ umask 002
$ touch file2
$ ls -l file2
-rw-rw-r--  1 oracle oinstall 0 Dec 26 19:24 file2
$ umask 022
$ touch file3
$ ls -l file3
-rw-r--r--  1 oracle oinstall 0 Dec 26 19:25 file3
Permisos especiales: SUID y SGID

Cuando el bit SUID (set user ID) o SGID (set group ID) está activo en un ejecutable, esto implica que ese archivo se ejecutará con los permisos del propietario o grupo propietario respectivamente. Por ejemplo:

# ls -la /bin/ping
-rwsr-xr-x 1 root root 30856 2007-12-10 18:33 /bin/ping

Ping tiene activado el bit SUID y su propietario es root, por tanto cuando sea ejecutado por un usuario, el programa adquirirá los privilegios de root. En ocasiones se requiere este comportamiento, pero en otras puede ser innecesario y únicamente contribuye a debilitar la seguridad del sistema (p.ej. se descubre una vulnerabilidad/overflow en ping que permite al usuario obtener una shell, esta shell seria con permisos de root).

Para identificar todos los ficheros SUID o GUID podemos utilizar:

find / -path /proc -prune -o -type f -perm +6000 -ls

Solo SUID:

find / -path /proc -prune -o -type f -perm +2000 -ls

Solo GUID:

find / -path /proc -prune -o -type f -perm +4000 -ls

Una vez identificados, podemos proceder a seleccionar cuales queremos dejar con el bit SUID o SGID activado.

Para activar/desactivar SUID:

chmod u+s fichero
chmod u-s fichero

Y para activar/desactivar SGID:

chmod g+s fichero
chmod g-s fichero
Directorios con permisos de escritura global

Para identificar directorios con permisos de escritura global ejecutaremos:

find / -path /proc -prune -o -perm -2 ! -type l -ls

En ocasiones estos son necesarios, como por ejemplo ‘/tmp’. Pero en otras puede suponer un peligro para el sistema. Está en nuestras manos decidir cuales dejar con dichos permisos.

A modo de ejemplo, con el usuario user01 creamos un directorio con permisos de escritura universales:

$ mkdir test
$ chmod 777 test
$ sudo chown -R root:root test
$ cd test
$ touch hola
$ ls -la
total 8
drwxrwxrwx   2 root   root   4096 2008-08-22 13:56 .
drwxr-x--- 126 root   root   4096 2008-08-22 13:55 ..
-rw-r--r--   1 user01 group1    0 2008-08-22 13:56 hola

En el ejemplo anterior, como el directorio tiene permisos de escritura para todo el mundo, el usuario user02 podría borrar/modificar el archivo ‘hola’ a pesar de que este pertenece a user01 y sus permisos sean -rw-r–r–. Por ejemplo, con el usuario user02 ejecutamos el borrado:

$ rm hola
rm: ¿borrar el archivo regular vacío «hola»  protegido contra escritura? (s/n) y

Por otra parte, los directorios con permisos de escritura global que tienen el Sticky bit activado como ‘/tmp’:

$ ls -lad /tmp/
drwxrwxrwt 13 root root 49152 2008-08-22 13:54 /tmp/

Este comportamiento no tiene lugar, los ficheros creados únicamente pueden ser modificados por sus propietarios o por el propietario del directorio. Por ejemplo, con el usuario user02:

$ rm hola
rm: ¿borrar el archivo regular vacío «hola»  protegido contra escritura? (s/n) y
rm: no se puede borrar «hola»: Operación no permitida

Consecuentemente, del listado de directorios con permisos globales de escritura, los que tienen el bit Sticky activado son casos especiales que debemos tener en cuenta.

Para activar/desactivar el bit sticky en un directorio:

chmod +t directorio/
chmod -t directorio/
Archivos sin propietarios

Para identificar archivos sin propietarios:


find / -path /proc -prune -o -nouser -o -nogroup

Si en el futuro se crease un usuario con el mismo ID, este adquiriría permisos automáticamente sobre estos ficheros y podría suponer un problema de seguridad.

Detección de modificaciones de ficheros: Tripwire

Podríamos utilizar tripwire para detectar modificaciones en ficheros del sistema. Tripwire construye una BBDD con los MD5 de los ficheros:

apt-get install tripwire

4. Política de contraseñas

Política global

Todas las contraseñas deben ser cifradas y por tanto, contenidas en /etc/shadow. Para validar que passwd no contiene passwords:

$ grep -v ':x:' /etc/passwd

En ‘/etc/passwd’ todos los usuarios deben tener el campo password una x.

Se recomienda forzar a los usuarios a cambiar sus contraseñas de forma periódica (p.ej. 60 días) y que no puedan ser cambiadas más de una vez en un día (p.ej. en caso de que tengamos configurado el sistema para mantener un histórico de contraseñas irrepetibles, el usuario no podrá cambiar su contraseña N veces en el mismo día hasta volver a tener la misma). Para ello editamos ‘/etc/login.defs’ y establecemos:

# Maximum number of days a password is valid. 
PASS_MAX_DAYS   60
# Minimum number of days before a user can change the password since the last change.
PASS_MIN_DAYS   1
#Number of days when the password change reminder starts.
PASS_WARN_AGE   15

Adicionalmente, si un usuario no cambia su contraseña 2 semanas después de haberse caducado, se recomienda bloquear la cuenta. De esta forma el sistema desactivará las cuentas de usuario que no estén siendo utilizadas y minimizaremos el riesgo de accesos indebidos. Debemos editar ‘/etc/default/useradd’:

# Number of days after password expiration that account is disabled. 
INACTIVE=14 

Por otra parte, es recomendable que el sistema lleve un control de contraseñas históricas para evitar que el usuario reutilice los últimos N passwords. Además, es interesante que el sistema también valide la robustez de las nuevas contraseñas. Con este objetivo, debemos instalar cracklib:

apt-get install libpam-cracklib
update-cracklib

A continuación editamos ‘/etc/pam.d/common-password’ y lo dejamos así:

password required pam_cracklib.so retry=3 minlen=8 difok=1 lcredit=0 ucredit=1 dcredit=1 ocredit=2
password requisite pam_unix.so use_authtok obscure md5 remember=12

En este ejemplo hemos hecho que:

  • El usuario tiene que repetir 3 veces la nueva contraseña para realizar el cambio.
  • Longitud mínima de 8 caracteres, no obstante puede ser inferior si se utilizan caracteres que valen más: Mayúsculas, minúsculas, dígitos, etc…
  • Al menos 1 caracteres deben ser diferentes al password anterior.
  • Según el manual, el parámetro ‘obscure’ provoca que se realicen las siguientes validaciones:
    • Palindrome: Verifies that the new password is not a palindrome of (i.e., the reverse of) the previous one.
    • Case Change Only: Verifies that the new password isn´t the same as the old one with a change of case.
    • Similar: Verifies that the new password isn´t too much like the previous one
    • Simple: Is the new password too simple? This is based on the length of the password and the number of different types of characters (alpha, numeric, etc.) used.
    • Rotated: Is the new password a rotated version of the old password? (E.g., “billy” and “illyb”)
  • Por su parte, cracklib realiza estas otras comprobaciones:
    • Que los cambios de passwords no se simplemente invertir la palabra o cambiar mayúsculas por minúsculas.
    • Comprobaciones contra un diccionario interno, construido a partir de datos que hay en los ficheros del sistema (update-cracklib)
  • Finalmente ‘remember=12’ hace que el sistema recuerde las últimas 12 contraseñas y no deja que el usuario las repita. Para que funcione correctamente vamos a necesitar ejecutar también:
    touch /etc/security/opasswd
    chown root:root /etc/security/opasswd
    chmod 600 /etc/security/opasswd
    

Existen alternativas a cracklib como por ejemplo libpam-passwdqc, el cual es más configurable aunque hoy por hoy menos popular.

Finalmente, validamos el estado actual de algún usuario del sistema:

# chage -l miusuario
Last password change                                    : Aug 05, 2008
Password expires                                        : Oct 04, 2008
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change          : 1
Maximum number of days between password change          : 60
Number of days of warning before password expires       : 15

Cabe destacar que para usuarios ya creados podemos modificar sus opciones mediante chage (ver man).

Contraseñas de un solo uso: OTP (One time password)

Podemos configurar el sistema para que en el momento del acceso, si introducimos incorrectamente o en blanco nuestro password, el sistema nos ofrezca la alternativa de logearnos con un password de un solo uso. Esto puede ser de gran utilidad si nos conectamos a la máquina desde un ordenador que no podemos confiar (p.ej. un cibercafé).

Para esto necesitamos instalar opie:

apt-get install opie-server

A continuación, como usuario establecemos una contraseña y el programa nos proporciona un challenge:

opiepasswd -c -f

  ID marble OTP key is 499 vp5957
  CAB TILE YOU NINA BRED ELLA

Podemos obtener un listado de los 10 futuros password de un solo uso que vamos a poder usar mediante el comando siguiente:

$ opiekey -n 10 499 vp5957
490: WARD PIE ROSE SKY SKI SHAY
491: I MITE VET AN IQ DENY
492: BED BIEN TWIN WACK GELD SENT
493: NECK MASH FLIT RICE GALE BY
494: FLUB GET NOB MOOD TUM CITE
495: WOO SNOB THEY SARA JILT GUSH
496: VALE HOWE GAIT EEL HUED ODE
497: I WON FOOL TOAD FEE HOFF
498: FULL SOWN EGAN RICH WEAN HUGE
499: CAB TILE YOU NINA BRED ELLA

No obstante, resulta interesante utilizar algún programa escrito en J2ME para móviles como vejotp, con el cual podremos generar el password de un solo uso que necesitemos indicando el challenge correspondiente que nos indique el servidor al conectarnos.

Por último, falta configurar el sistema para que acepte estas contraseñas. Editamos ‘/etc/pam.d/common-auth’:

auth sufficient pam_unix.so nullok_secure
auth sufficient pam_opie.so
auth required pam_deny.so
#auth   requisite   pam_unix.so nullok_secure
#auth   optional    pam_smbpass.so migrate missingok

Y en el caso de que usemos SSH, editamos ‘/etc/ssh/sshd_config’:

ChallengeResponseAuthentication yes

Existen alternativas a OPIE como por ejemplo libpam-otpw, el cual utiliza la típica tarjeta de coordenadas en lugar de passwords de un solo uso.

Gestión del acceso

Después de 3 intentos fallidos de un usuario, es recomendable bloquear su cuenta para los siguientes 30 segundos por ejemplo (durante los cuales será denegado su acceso aunque ponga correctamente sus credenciales). A partir de ese punto, cada nuevo intento fallido representará otros 30 segundos de bloqueo. Si el usuario indica su contraseña correcta pasado ese tiempo, el contador de intentos fallidos se resetea a 0.

Para ello utilizaremos ‘pam_tally’ editando ‘/etc/pam.d/common-auth’:

# 3 auth attempts, after that user will be locked for 30 seconds for each new failed attempt
auth required pam_tally.so onerr=succeed deny=3 unlock_time=30 per_user
auth sufficient pam_unix.so
auth sufficient pam_opie.so
auth required pam_deny.so

# Reset user lock if auth suceeds
account required pam_tally.so onerr=succeed

El parámetro ‘per_user’ hará que tally valide si se ha establecido algún valor individual (diferente de 0) al usuario mediante faillog. Por ejemplo, para limita el número de reintentos fallidos y establecer el tiempo de bloqueo para un usuario concreto:

faillog --maximum 5 --lock-time 120 -u miusuario

Otro ejemplo, no permitir que un usuario se bloquee aunque sobrepase los valores por defecto de intentos fallidos de autenticación:

faillog --maximum -1 -u miusuario

Para ver el estado de los usuarios podemos utilizar:

pam_tally
pam_tally --user misusuario
faillog -a
faillog -u miusuario

Para desbloquear:

pam_tally --user miusuario --reset
faillog -u miusuario -r

Es importante eliminar todos los parámetros ‘nullok’ o ‘nullok_secure’ del modulo pam_unix.o dado que permiten passwords en blanco (tanto en common-password como en common-auth).

Ver más en opciones (bloqueos permanentes, etc.) en el siguiente manual.

5. Gestión de usuarios

Creación de usuarios

Para crear un usuario y asignarle un password:

useradd -d /home/miusuario -m -G admin,users -K PASS_MAX_DAYS=60,PASS_MIN_DAYS=1,PASS_WARN_AGE=15,UMASK=0022 -s /bin/bash miusuario
passwd miusuario

Estos comando crean su directorio de usuario con el skeleton básico (-m), crea un grupo con el mismo nombre que el usuario y será su grupo primario (-n) y además pertenecerá al grupo admin. Por otra parte, también se especifica la política de contraseñas específica.

Para establecer contraseñas aleatorios podemos ayudarnos del siguiente programa:

apt-get install makepasswd
makepasswd --chars=8

Gestión de cuentas

Tomando como ejemplo un usuario que dispone de la siguiente entrada en ‘/etc/shadow’:

usuario:$1$ZyihUAz0$RqBWU1OINGOsUILv3g2Kh/:14111:0:99999:7:::

Veamos diversas acciones de utilidad….

  • Expira la contraseña de un usuario para obligarle a cambiarla:
    	passwd -e usuario
    	

    Establece el 3er campo de /etc/shadow a 0:

    	usuario:$1$ZyihUAz0$RqBWU1OINGOsUILv3g2Kh/:0:0:99999:7:::
    	
  • Bloquea o desbloquea (recuperando password anterior) un usuario:
    	passwd -l usuario
    	

    Añade una exclamación delante del hash de la contraseña en /etc/shadow y en el octavo campo añade un 1 (cuenta caducada el 2 de Enero de 1970):

    	usuario:!$1$ZyihUAz0$RqBWU1OINGOsUILv3g2Kh/:14111:0:99999:7::1:
    	

    Para deshacer los cambios:

    	passwd -u usuario
    	
  • Desactiva un usuario (sin posibilidad de recuperar el password):
    	passwd -d usuario
    	

    Se asigna un hash en blanco y en el octavo campo añade un 1 (cuenta caducada el 2 de Enero de 1970):

    	usuario::14111:0:99999:7::1:
    	
  • Activar un usuario que ha sido desactivado: eliminar la expiración y asignar una nueva contraseña
    	chage -E -1 aurora
    
    	passwd aurora
    	
  • Estado:
    	passwd -S usuario
    	chage -l usuario
    	
  • Si no queremos que root pueda logearse directamente al sistema y que el acceso a la cuenta de administración se realice mediante sudo. Aparte de configurar correctamente sudo, debemos bloquear y desactivar el usuario root:
    	passwd -l -d root
    	

    Quedando algo parecido a:

    	root:!:13449:0:99999:7::1:
    	

En resumen, campos del /etc/shadow:

[username]:[password]:[date]:PASS_MIN_DAYS:PASS_MAX_DAYS:PASS_WARN_AGE:INACTIVE:EXPIRE:

Según el manual:

  ·   login name
  ·   encrypted password
  ·   days since Jan 1, 1970 that password was last changed
  ·   days before password may be changed
  ·   days after which password must be changed
  ·   days before password is to expire that user is warned
  ·   days after password expires that account is disabled
  ·   days since Jan 1, 1970 that account is disabled

Otras funcionalidades útiles:

  • Listar usuarios no bloqueados:
    	egrep -v '.*:*|:!' /etc/shadow | awk -F: '{print $1}'
    	
  • Listar archivos de un usuario concreto (nos puede servir para identificar usuarios que no disponen de información en el sistema):
    	find / -path /proc -prune -o -user usuario -ls
    	
  • Comprobamos que el fichero de grupos es correcto:
    	# grpck -r
    	'www-data' is a member of the 'adm' group in /etc/group but not in /etc/gshadow
    	'miusuario' is a member of the 'users' group in /etc/group but not in /etc/gshadow
    	no matching group file entry in /etc/gshadow
    	add group 'admin' in /etc/gshadow ?No
    	group marble: no user 0
    	delete member '0'? No
    	no matching group file entry in /etc/gshadow
    	add group 'marble' in /etc/gshadow ?No
    	'miusuario' is a member of the 'adm' group in /etc/gshadow but not in /etc/group
    	grpck: no changes
    	

6. Servicios básicos del sistema

Activación en arranque

Para la activación/desactivación de los servicios que queremos en arranque podemos utilizar la herramienta de consola ‘sysv-rc-conf’:

apt-get install sysv-rc-conf

Ejecutando ‘sysv-rc-conf’ podremos marcar los servicios/scripts/demonios que queremos arrancar al iniciar el sistema. La aplicación aplica los cambios de forma instantanea al situarnos sobre alguna de las X y pulsar ‘espacio’. Para salir pulsamos la ‘q’.

Si queremos saber en que runlevel arranca el sistema por defecto:

$ grep ':initdefault' /etc/inittab
id:2:initdefault:

En caso de que no exista el fichero, es porque probablemente tengamos el sistema de arranque Upstart, el runlevel por defecto suele ser el 2 (es posible verificarlo en ‘/etc/event.d/rc-default’).

Suele ser recomendable desactivar la posibilidad de reiniciar el ordenador mediante CTRL+ALT+SUPR para evitar reinicios involuntarios (p.ej. la señora de la limpieza).

En caso de que utilicemos el tradicional sistema de arranque de Unix/Linux (System V init), bastará con comentar (/etc/inittab):

# What to do when CTRL-ALT-DEL is pressed.
#ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now

Y aplicar los cambios:

# init q

O si como mecanismo de arranque usamos Upstart (como es el caso de Ubuntu), editamos ‘/etc/event.d/control-alt-delete’ y comentamos:

#exec /sbin/shutdown -r now "Control-Alt-Delete pressed"

Servicio de correo: Postfix

El sistema requerirá un servicio de correo aunque únicamente sea para uso local, dado que varias tareas automáticas reportaran su estado mediante correo.

En caso de que nuestro sistema venga configurado con sendmail, es recomendable desinstalarlo e instalar algún otro sistema más robusto como postfix (historicamente sendmail ha tenido muchos fallos de seguridad):

apt-get purge sendmail sendmail-base sendmail-bin sendmail-cf sendmail-doc
apt-get install procmail
apt-get install postfix

Para una configuración básica y segura, primero definiremos que usuario del sistema recibirá los correos destinados a root (p.ej. muchas tareas automáticas del sistema envían correo a root). Editamos ‘/etc/aliases’ y añadimos:

root: miusuario

Y ejecutamos:

newaliases

A continuación, securizaremos la instalación de postfix de forma que muestre la información mínima necesaria. Editamos ‘/etc/postfix/main.cf’:

#smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
smtpd_banner = Welcome
biff = no

# Rewrite "user" to "user@$myorigin" with locally submitted mail

# Or "user" to "user@$remote_header_rewrite_domain" with remote connection
# only if $remote_header_rewrite_domain is not empty (by default it is empty)
#
# By default: myorigin = $mydomain = /etc/mailname
append_at_myorigin = yes

Asi ocultamos la versión y obligamos a que todo mail enviado por los procesos automáticos del sistema tengan como dominio el especificado en ‘/etc/mailname’ (si se envian a usuario@localhost se reescribira como usuario@dominio).

Editamos ‘/etc/mailname’ para establecer el dominio que queremos, por ejemplo:

un.ejemplo.com

Si no vamos a utilizar Postfix como un servicio de correo remoto y únicamente lo queremos usar de forma local, editamos ‘/etc/postfix/master.cf’ para asegurarnos que únicamente se aceptan conexiones localhost:

127.0.0.1:smtp      inet  n       -       -       -       -       smtpd

Con esto ya tenemos postfix preparado:

/etc/init.d/postfix restart

Finalmente, si nos interesa que el correo local que reciba un usuario sea reenviado a otra dirección, en el directorio personal del usuario creamos ‘.procmailrc’:

:0 c
/var/mail/miusuario

# Copia del correo personal a gmail
:0
!miotrousuario@gmail.com

Para realizar configuraciones más complejas de Postfix podemos consultar esta otra guía.

Servicio web: Apache

Una vez instalado apache (habitualmente con soporte PHP), podemos proceder a realizar una securización de su configuración.

PHP

En primer lugar, nos aseguramos que PHP no informe de su presencia editando ‘/etc/php5/apache2/php.ini’ y modificando:

; Misc
;
; Decides whether PHP may expose the fact that it is installed on the server
; (e.g. by adding its signature to the Web server header).  It is no security
; threat in any way, but it makes it possible to determine whether you use PHP
; on your server or not.
expose_php = On

por:

expose_php = Off 

Por otra parte, también se recomienda establecer límites a los recursos que puede utilizar una web PHP:

;;;;;;;;;;;;;;;;;;;
; Resource Limits ;
;;;;;;;;;;;;;;;;;;;

max_execution_time = 30     ; Maximum execution time of each script, in seconds
max_input_time = 60 ; Maximum amount of time each script may spend parsing request data
;max_input_nesting_level = 64 ; Maximum input variable nesting level
memory_limit = 16M      ; Maximum amount of memory a script may consume (16MB)
Apache básico

Veamos como mejorar la configuración de apache. Por un lado editamos ‘/etc/apache2/apache2.conf’ y cambiamos los siguientes parámetros para que Apache no muestre información innecesaria:

#ServerTokens Full
ServerTokens Prod

#ServerSignature On
ServerSignature Off

También debemos aseguramos que no se cambian estos parámetros en los sites activos (‘/etc/apache2/sites-enabled/*).

En general los comando HTTP TRACE y TRACK no son utilizados para servir páginas y por tanto, podemos desactivarlos para evitar que un usuario malintencionado los utilice para intentar atacar. Para cada uno de los sites-enabled, lo editamos y añadimos dentro del VirtualHost:

    RewriteEngine on
    RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
    RewriteRule .* - [F]

Activamos el modulo ‘rewrite’ y reiniciamos el servicio:

a2enmod rewrite
/etc/init.d/apache2 restart

A continuación podemos validar que realmente ha surgido efecto el bloqueo de los comandos TRACE y TRACK:

$ telnet 127.0.0.1 80
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.


TRACE / HTTP/1.0
A: b
C: d
Host: foo

HTTP/1.1 403 Forbidden
Date: Tue, 05 Aug 2008 17:09:34 GMT
Server: Apache
Content-Length: 255
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /
on this server.</p>
<hr>
<address>Apache Server at foo Port 80</address>
</body></html>
Connection closed by foreign host.

Otra medida de seguridad consiste en desactivar el uso de FollowSymLinks para no permitir que un usuario pueda crear enlaces simbólicos hacia directorios que no deseamos que estén expuestos. Para ello, en las opciones de los sites-enabled debemos indicar:

  Options None

Finalmente, es conveniente que le indiquemos a Apache exactamente en que IPs queremos que escuche. De esta forma podemos limitarlo por ejemplo únicamente para uso local. Para ello editamos ‘/etc/apache2/ports.conf’ y cambiamos Listen 80 por Listen ip_maquina:80.

Otro aspecto importante es proteger el servicio de ataques de denegación de servicio (DoS). En el fichero ‘/etc/apache2/apache2.conf’ podemos realizar algunos cambios:

  • Si establecemos un timeout bajo, minimizamos el impacto de los ataques pero puede que algunos CGIs que requieran más tiempo dejen de funcionar (p.ej. 300 segundos):
      Timeout 300
    
  • Si tenemos activado el HTTP KeepAlive, definir un timeout bajo minimiza el impacto de ataques (p.ej. 15 segundos):
      KeepAliveTimeout 15
      MaxKeepAliveRequests 100
    
  • Habitualmente, aunque apache2-mpm-worker, se utiliza apache2-mpm-prefork como versión de apache por defecto dado que esta es compatible con PHP. Prefork puede ser customizado, pero estos son los valores recomendados por defecto:
    <IfModule mpm_prefork_module>
        # Number of child server processes created on startup
        StartServers          5
        # Sets the desired minimum number of idle child server processes
        MinSpareServers       5
        # Sets the desired maximum number of idle child server processes
        MaxSpareServers      10
        # Sets the limit on the number of simultaneous requests that can be supported;
        MaxClients          150
        # Sets the limit on the number of requests that an individual child server process will handle
        MaxRequestsPerChild   0
    </IfModule>
    

    De todos ellos, los únicos parámetros con los que se suele jugar son MaxClient y MaxRquestPerChild. Con este último, indicar un valor diferente de cero nos permitiría limitar la memoria utilizada por procesos que tienen memory leakages.

  • Limitar el tamaño del BODY de las peticiones puede ser muy útil por ejemplo para evitar que los usuarios suban ficheros excesivamente grandes (p.ej. 500 KB = 1024*500 bytes):
      LimitRequestBody 512000
    
  • Limitamos el número de campos que puede tener una petición en el HEADER (de media suelen tener 20) y su tamaño (7KB = 8190 bytes)
      LimitRequestFields 50
      LimitRequestFieldSize 8190
      LimitRequestLine 8190
    
Módulos de Apache

Apache se encuentra programado con funciones residentes en módulos independientes al core de la aplicación, eso nos permite activar únicamente aquellas características que vamos a utilizar para minimizar riesgos. Desactivación de módulos:

a2dismod alias
a2dismod auth_basic
a2dismod authn_file
a2dismod authz_default
a2dismod authz_groupfile
a2dismod authz_host
a2dismod authz_user
a2dismod autoindex
a2dismod cgi
a2dismod dir
a2dismod env
a2dismod mime
a2dismod negotiation
a2dismod php5
a2dismod rewrite
a2dismod setenvif
a2dismod ssl
a2dismod status

Activación de modulos mínimos:

# Default page (e.g. index.html)
a2enmod dir
a2enmod mime
# Language preference
a2enmod negotiation
a2enmod php5
a2enmod rewrite
# Workarounds for certain browser bugs
a2enmod setenvif
# Allows access rules based on hosts (allow from...)
a2enmod authz_host
Servir por localhost las aplicaciones más criticas

Finalmente, si vamos a utilizar Apache como servidor web remoto pero queremos también instalar aplicaciones de gestión como phpmyadmin, cacti, nagios, etc… Es recomendable que estas se encuentren en una instancia del servidor que solo sea accesible desde local para los usuarios de la máquina, pero no para el resto del mundo. Es decir, vamos a evitar que cualquier persona pueda acceder libremente por ejemplo a ‘http://www.midominio.com/phpmyadmin’, pero si vamos a permitir que un usuario con cuenta en el sistema pueda acceder mediante SSH realizando Port Forwarding:

ssh -L 8080:localhost:80 miusuario@IP_DEL_SERVIDOR

Con el anterior comando, ssh abrirá el puerto 8080 en la máquina del cliente y redireccionará todo lo que le llegue al puerto 80 de la interfaz localhost del servidor. Por tanto, el cliente (una vez se ha autenticado con su usuario por SSH) podrá acceder a phpmyadmin mediante ‘http://localhost:8080/phpmyadmin’. Adicionalmente, todo el tráfico viajará cifrado sin necesidad de configurar el SSL de Apache.

Con este objetivo, vamos a crear ‘/etc/apache2/sites-available/localhost’:

NameVirtualHost 127.0.0.1:80
<VirtualHost 127.0.0.1:80>
    ServerAdmin admin@admin.com
    ServerName localhost
    ServerAlias localhost.localdomain

    DocumentRoot /var/www/localhost
    <Directory /var/www/localhost>
        AllowOverride All
        Options FollowSymLinks
        Order allow,deny
        allow from all
    </Directory>

    # Possible values include: debug, info, notice, warn, error, crit,
    # alert, emerg.
    LogLevel warn


    ErrorLog /var/log/apache2/localhost/error.log
    CustomLog /var/log/apache2/localhost/access.log combined
    ServerSignature Off



    Include /etc/apache2/conf.d/

</VirtualHost>

Activamos el site:

mkdir /var/www/localhost
mkdir /var/log/apache2/localhost
a2ensite localhost
/etc/init.d/apache2 restart

Cuando instalamos phpmyadmin (u otras aplicaciones web similares) mediante el sistema de paquetes de Ubuntu, se generan ficheros de configuración en ‘/etc/apache2/conf.d/’ para que apache pueda encontrar la aplicación web (p.ej. mediante alias). Esto nos resta control y por defecto hace que la web sea accesible remotamente. Para minimizar riesgos, comentamos la línea de ‘/etc/apache2/apache2.conf’ que carga estas configuraciones estándar:

# Include generic snippets of statements
#Include /etc/apache2/conf.d/

Y en contrapartida, tendremos que copiar/enlazar todo aquello que queramos que sea accesible únicamente por localhost en el directorio ‘/var/www/localhost’.

Base de datos MySQL

Securizamos la instalación básica ejecutando ‘mysql_secure_installation’. Según el manual, este comando permite:

  • You can set a password for root accounts.
  • You can remove root accounts that are accessible from outside the local host.
  • You can remove anonymous-user accounts.
  • You can remove the test database, which by default can be accessed by anonymous users.

Se recomienda aplicar las 4 acciones. Por otra parte, en la configuración de ‘/etc/mysql/my.cnf’:

# Both location gets rotated by the cronjob.
# Be aware that this log type is a performance killer.
# log     = /var/log/mysql/mysql.log
#
# Error logging goes to syslog. This is a Debian improvement :)
#
# Here you can see queries with especially long duration
log_slow_queries    = /var/log/mysql/mysql-slow.log
long_query_time = 2

Logeamos las queries que tardan más de 2 segundos en ejecutarse.

/etc/init.d/mysql restart

Finalmente, para crear un usuario con su propia base de datos:

CREATE USER 'miusuario'@'localhost' IDENTIFIED BY 'YB9gYu6I';

GRANT USAGE ON *.* TO 'miusuario'@'localhost' IDENTIFIED BY 'YB9gYu6I' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0 ;

CREATE DATABASE IF NOT EXISTS miusuario;

GRANT ALL PRIVILEGES ON miusuario.* TO 'miusuario'@'localhost';

FLUSH PRIVILEGES;

Acceso remoto: SSH

Para la securización de SSH, vamos a hacer que únicamente un grupo de usuario pueda conectarse. Creamos el grupo ‘sshlogin’ y añadimos usuarios al mismo:

sudo groupadd sshlogin
sudo adduser miusuario sshlogin

Importante: Si utilizamos FreeNX o NX (artículo sobre FreeNX) para el acceso remoto al escritorio, también tendremos que añadir al grupo ‘sshlogin’ el usuario ‘nx’.

Editamos ‘/etc/ssh/sshd_config’ para impedir que root pueda acceder directamente y no se forwardeen las X:

PermitRootLogin no
X11Forwarding no

AllowGroups sshlogin

Reiniciamos el servicio:

sudo /etc/init.d/ssh restart

En caso de que desde nuestra máquina tengamos que acceder a diversos servicios SSH y no queremos tener que recordar las diferentes contraseñas, podemos generar una pareja de claves RSA (pública y privada) en la máquina cliente (se recomienda que asignar password para proteger la clave privada):

$ ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/user/.ssh/id_rsa.
Your public key has been saved in /home/user/.ssh/id_rsa.pub.
The key fingerprint is:
18:0f:fe:29:b2:17:c2:a0:8d:6e:11:80:de:01:21:be user@localhost

Con ssh-keygen hemos creado una clave privada ‘~/.ssh/id_rsa’ que no debemos compartir con nadie y una clave pública ‘~/.ssh/id_rsa.pub’ que hay que copiar en el directorio ‘~/.ssh/authorized_keys’ de los servidores a los que queramos conectar con SSH:

scp ~/.ssh/id_rsa.pub usuario@servidor:/home/user/

Y desde cada servidor:

$ cat id_rsa.pub >> .ssh/authorized_keys
$ rm id_rsa.pub

Podemos poner determinadas condiciones bajo las cuales se utilizaran estas claves, por ejemplo:

from="81.11.105.10",command="/bin/ls",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAABdZjH4WE5y5EH/O9nHoplj8sZz0iIb1XPU5e5ZTxd7tWhMf9p391yAfM6ybjabmFXZZuFm9q/AjD53qjX+q7YhQUXC0I5JhFU0CAHxKuvuguogWio39x36Ag/zgJ5IJXcrCNsuoDw== root@servidor.com

Al inicio de la clave hemos especificado:

  • from=”81.11.105.10″: únicamente puede ser utilizada desde un cliente con esa IP.
  • command=”/bin/ls”: únicamente se permitirá la ejecución de ese fichero.
  • no-port-forwarding: no permite redirigir puertos.
  • no-X11-forwarding: no permite la redirección gráfica de programas (X Window)
  • no-agent-forwarding: no permite redirigir peticiones entre servidores a agentes ssh de autenticación.
  • no-pty: el cliente no obtendrá una terminal con shell, solo podra ejecutar comando (p.ej. ‘ssh usuario@servidor /bin/ls’)

A partir de este momento, cada vez que queramos conectarnos a uno de estos servidores SSH, ssh nos preguntará por la contraseña que le hemos puesto a nuestra clave privada y por tanto, solo hace falta recordar 1 contraseña. No obstante, aun podemos simplificar más la gestión:

  • En la generación de las claves RSA no asignamos contraseña y por tanto esta no será requerida para conectarnos a los servidores por SSH (en caso de que ya la hayamos asignado, podemos cambiarla mediante ‘ssh-keygen -p’). Este punto tiene el inconveniente de que en caso de que alguien se haga con el fichero que contiene la clave privada, tendrá acceso completo a los servidores configurados.
  • Uso de keychain (ssh-agent) para que únicamente tengamos que teclear la contraseña que protege la clave privada una vez.

Veamos como configurar el segundo punto:

$ sudo apt-get install keychain

Editamos el fichero ‘~/.bashrc’ y añadimos al final:

## SSH Agent
# Do not execute if I am root (sudo -s)
if [ "$(id -u)" != "0" ]; then
    keychain id_rsa --nocolor --quiet
    . ~/.keychain/`uname -n`-sh
fi

Salimos del sistema y volvemos a entrar para que sean efectivos los cambios de bashrc. La primera vez que se ejecute ‘keychain’ nos preguntará la contraseña que protege a la clave privada:

Enter passphrase for /home/user/.ssh/id_rsa: 
Identity added: /home/user/.ssh/id_rsa (/home/user/.ssh/id_rsa)

Mientras permanezca la máquina en marcha podremos acceder a los servidores mediante SSH sin que nos solicite de nuevo la contraseña.

En el siguiente enlace podéis encontrar una descripción detallada de cómo funciona SSH.

7. Trazabilidad

Fecha y hora del sistema

Para que los logs resulten de utilidad y tengamos una buena trazabilidad de todo lo que ocurre en el sistema, es necesario que la máquina disponga de la fecha/hora correcta. Para ello podemos configurar la sincronización automática con otros servidores de Internet via NTP:

apt-get install ntpdate

Creamos ‘/etc/cron.daily/ntpdate’:

#! /bin/bash
/usr/sbin/ntpdate -u ntp.ubuntu.com

Y damos permisos de ejecución:

chmod 755 /etc/cron.daily/ntpdate

Por defecto el sistema utilizará los servidores de Ubuntu (ntp.ubuntu.com) para la sincronización.

En caso de que fuese necesario, también conviene asegurarnos que el sistema tiene configurada la zona horaria que nos interese (p.ej. Europa/Madrid):

# date
Wed Aug  6 07:14:36 EDT 2008
# cd /etc
# mv timezone timezone.old
# echo "Europe/Madrid" > timezone
# mv localtime localtime.old
# ln -s /usr/share/zoneinfo/Europe/Madrid localtime
# date
Wed Aug  6 13:16:08 CEST 2008

Logs de usuario con Bash

En ‘/etc/profile’ podemos añadir las siguientes variables de entorno (personalmente únicamente añado la que permite tener histórico con timestamp, visualizable con el comando history de cada usuario):

########
## declare -r: Variables de entorno que no pueden ser modificadas
## readonly -p: Lista variables que no pueden ser modificadas

# Nombre del archivo de historial (default '~/.bash_history')
export HISTFILE='~/.bash_history'
#declare -r HISTFILE='~/.bash_history'

# nº máximo de comandos que puede contener el archivo (default 500).
export HISTFILESIZE='500'

# nº de comandos por los que podrá navegar el usuario con las teclas de cursor (default 500)
export HISTSIZE='500'

# Ignorar comandos duplicados y espacios en blanco delante (default blank)
export HISTCONTROL=ignorespace:ignoredups

# Save history with timestamp (default blank)
export HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S "

# No guardar en el historial los siguientes comandos (por defecto en blanco)
export HISTIGNORE=”:ls:cd ..:cd /:”

Esto mismo también lo podemos establecer en ‘/etc/bash.bashrc’ si queremos que no solo afecte a las login shells.

Rotación y conservación de logs

En el sistema debemos tener instalado anacron o cron, si tenemos ambos simultaneamente (aunque uno de los dos demonios esté parado) no se ejecutaran las tareas de /etc/cron.daily, weekly, etc.. (debido a como viene por defecto /etc/crontab). En nuestro caso, al ser un servidor y estar siempre en marcha usaremos cron:

apt-get purge anacron
apt-get install cron

La política de gestión de logs será:

  • Rotación mensual
  • Mantenimiento de las últimas 24 copias (2 años de log)
  • Incorporar la fecha de creación al nombre del fichero

Preparamos las tareas de rotacion de log a ejecutar por cron de forma mensual:

chmod 644 /etc/cron.daily/sysklogd
chmod 644 /etc/cron.weekly/sysklogd
cp /etc/cron.weekly/sysklogd /etc/cron.monthly/
chmod 755 /etc/cron.monthly/sysklogd

Editamos ‘/etc/cron.monthly/sysklogd’ y modificamos la linea que llama a save_log para incluir en la rotación todos los ficheros log gestionados por syslogd:

logs=$(syslogd-listfiles --weekly)

por:

logs=$(syslogd-listfiles --all)

También modificaremos:

 savelog -g adm -m 640 -u ${USER} -c 4 $LOG >/dev/null

por:

      # Rotate monthly (script placed at /etc/cron.monthly/)
      # Keep 24 copies: -c 24
      # Clean older copies than 24: -C
      # Add date instead of .0 .1: -d
      # Create a new file owned by adm and syslog with permission 640
      savelog -g adm -m 640 -u ${USER} -c 24 -d -C $LOG >/dev/null

De esta forma, syslogd cumplirá con la política de gestión de logs que hemos definido.

Mediante ‘syslogd-listfiles –all’ podemos ver todos los ficheros que serán rotados, por ejemplo:

/var/log/mail.warn
/var/log/uucp.log
/var/log/user.log
/var/log/daemon.log
/var/log/messages
/var/log/debug
/var/log/auth.log
/var/log/mail.err
/var/log/syslog
/var/log/mail.log
/var/log/mail.info
/var/log/kern.log
/var/log/lpr.log

El resto de logs que no son generados por syslogd, los gestionaremos a través de logrotate.

Editamos /etc/logrotate.conf y establecemos la política general de logs:

# rotate log monthly
monthly
# keep 24 months of backlog
rotate 24

# uncomment this if you want your log files compressed
compress

# Postpone compression of the previous log file to the next rotation cycle
delaycompress






# create new (empty) log files after rotating old ones
create

# uncomment this if you want your log files compressed
compress

# Postpone compression of the previous log file to the next rotation cycle
delaycompress

# Archive old versions of log files adding a daily extension like YYYYMMDD instead of simply adding a number.
dateext

# Rotate the log file even if it is empty
ifempty

# packages drop log rotation information into this directory
include /etc/logrotate.d

# no packages own wtmp, or btmp -- we'll rotate them here
/var/log/wtmp {
    missingok
#    monthly
    create 0664 root utmp
#    rotate 1
}

/var/log/btmp {
    missingok
#    monthly
    create 0664 root utmp
#    rotate 1
}

En ‘/etc/logrotate.d’ tenemos ficheros con los logs a rotar y en algunos casos, con políticas concretas diferentes a la global. Para nos aseguramos que no haya políticas diferentes a la global para logs específicos podemos ejecutar:

find /etc/logrotate.d/ -type f -exec sed -i 's/daily/#daily/g' {} ;
find /etc/logrotate.d/ -type f -exec sed -i 's/weekly/#weekly/g' {} ;
find /etc/logrotate.d/ -type f -exec sed -i 's/monthly/#monthly/g' {} ;
find /etc/logrotate.d/ -type f -exec sed -i 's/rotate/#rotate/g' {} ;

find /etc/logrotate.d/ -type f -exec sed -i 's/post#rotate/postrotate/g' {} ;
find /etc/logrotate.d/ -type f -exec sed -i 's/pre#rotate/prerotate/g' {} ;

find /etc/logrotate.d/ -type f -exec sed -i 's/notifempty/#notifempty/g' {} ;
find /etc/logrotate.d/ -type f -exec sed -i 's/size/#size/g' {} ;

Cabe destacar que es necesario revisar la configuración existente en ‘/etc/logrotate.d’ por si faltase realizar alguna modificación extra (p.ej. logs extra de apache como ‘/var/log/apache2/localhost/*.log’).

Del resto de logs del sistema que queramos rotar y no estén ya incluidos, tendremos que crear un fichero nuevo en ‘/etc/logrotate.d’ indicándolo (en general, todo paquete de Ubuntu ya viene preparado para que sus logs sean gestionados automáticamente por syslogd o logrotate). Es importante tener en cuenta que no debemos rotar:

  • Binarios como /var/log/faillog o /var/log/lastlog
  • Logs de nagios dado que romperíamos el historial interno. Mejor que lo gestione la propia aplicación directamente.
  • Logs generados directamente por syslogd (como hemos visto antes). Visualizar con syslogd-listfiles –all
Análisis de logs de Apache mediante Awstats

Con awstats podremos ver las visitas, tráfico y otros aspectos relacionados con cada uno de los dominios virtuales que tengamos configurados en Apache. Para su instalación presuponemos que ya tenemos configurado Apache y que awstats va ser accesible únicamente de forma local:

apt-get install awstats
mkdir /var/lib/awstats/localhost
chown www-data:www-data /var/lib/awstats/localhost

Creamos ‘/etc/awstats/awstats.localhost.conf’ apuntando al log que queramos analizar:

LogFile="/var/log/apache2/localhost/access.log"
LogFormat=1
DNSLookup=1
DirData="/var/lib/awstats/localhost"
DirCgi="/cgi-bin"
DirIcons="/icon"
SiteDomain="localhost"
AllowToUpdateStatsFromBrowser=1
AllowFullYearView=3

Copiamos iconos básicos:

cp -r /usr/share/awstats/icon /var/www/localhost/

Actualizamos output HTML:

/usr/lib/cgi-bin/awstats.pl -config=localhost -update

Creamos el fichero ‘/etc/apache2/conf.d/awstats.conf’ con el siguiente contenido:

ScriptAlias /cgi-bin/awstats.pl /usr/lib/cgi-bin/awstats.pl

Reiniciamos apache:

/etc/init.d/apache2 restart

Y accedemos a la web mediante ‘http://localhost:8080/cgi-bin/awstats.pl?config=localhost’.

Para que se actualice periódicamente el análisis de Awstats, editamos /etc/cron.d/awstats (cada noche):

0 1 * * * www-data /usr/lib/cgi-bin/awstats.pl -config=localhost -update >/dev/null
Envío por correo de los eventos más significativos con logcheck

Con logcheck podremos resumir los eventos del sistema registrados en los logs y enviarlos a nuestro correo de forma diaria:

apt-get install logcheck logcheck-database

Editamos ‘/etc/logcheck/logcheck.logfiles’:

# Estos 2 ficheros incluyen todos los mensajes enviados a syslog  ()/etc/syslog.conf)
/var/log/syslog
/var/log/auth.log
# Apache
/var/log/apache2/error.log
/var/log/apache2/localhost/error.log
# MySQL
#/var/log/mysql/mysql.log # En debian, si descomentas la linea de log de /etc/mysql/my.cnf, el log va por syslog
#/var/log/mysql/mysql-slow.log

La tarea cron que se encarga de la ejecución diaria es ‘/etc/cron.d/logcheck’.

Por otra parte, la configuración de logcheck se encuentra en ‘/etc/logcheck/logcheck.conf’:

# Controls the address mail goes to:
# *NOTE* the script does not set a default value for this variable!
# Should be set to an offsite "emailaddress@some.domain.tld"

SENDMAILTO="miusuario"

# Send the results as attachment or not.
# 0=not as attachment; 1=as attachment
# Default ist 0
MAILASATTACH=1

Una vez configurado, podemos probar su funcionamiento ejecutando:

sudo -u logcheck logcheck

Logcheck incorpora por defecto toda una serie de reglas para la selección o el descarte de las lineas del log a enviar en el resumen. Es posible que nos interese refinar estas reglas, haciendo que sean descartadas cadenas que no nos aporten nada. Por ejemplo, veamos como ignorar las lineas generadas por el firewall o nagios (ver secciones correspondientes), dado que esa información ya la analizamos con otras herramientas.

Creamos ‘/etc/logcheck/ignore.d.server/000-custom’ y añadimos:

^w{3} [ :0-9]{11} [._[:alnum:]-]+ kernel: [FW:[ ._[:alnum:]-]+]
^w{3} [ :0-9]{11} [._[:alnum:]-]+ nagios2: CURRENT SERVICE STATE:
^w{3} [ :0-9]{11} [._[:alnum:]-]+ nagios2: CURRENT HOST STATE:
^w{3} [ :0-9]{11} [._[:alnum:]-]+ nagios2: Warning: A system time change of [0-9]+ seconds (backwards in time) has been detected.  Compensating...

Si queremos incluir más reglas, podemos validarlas contra el log que nos interese:

sed -e 's/[[:space:]]*$//' /var/log/ulog/syslogemu.log | egrep '^w{3} [ :0-9]{11} [._[:alnum:]-]+ kernel: [FW:[ ._[:alnum:]-]+]'
sed -e 's/[[:space:]]*$//' /var/log/syslog | egrep '^w{3} [ :0-9]{11} [._[:alnum:]-]+ nagios2: CURRENT SERVICE STATE:'
sed -e 's/[[:space:]]*$//' /var/log/syslog | egrep '^w{3} [ :0-9]{11} [._[:alnum:]-]+ nagios2: CURRENT HOST STATE:'
sed -e 's/[[:space:]]*$//' /var/log/syslog | egrep '^w{3} [ :0-9]{11} [._[:alnum:]-]+ nagios2: Warning: A system time change of [0-9]+ seconds (backwards in time) has been detected.  Compensating...'

Si la regla funciona, aparecerán los mensajes que queremos eliminar.

8. Monitorización del sistema

Actualización automática de la lista de paquetes del sistema

Para comprobar que nuestro sistema se encuentra actualizado, primero necesitamos que se actualice la lista de paquetes automáticamente. Creamos ‘/etc/apt/apt.conf.d/10periodic’:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "0";
APT::Periodic::AutocleanInterval "0";

De esta forma se actualizará el listado de paquetes (apt-get update) cada día mediante el fichero ‘/etc/cron.daily/apt’.

Si hemos instalado nagios (ver Sección correspondiente), este nos informará de cuando hay paquetes pendientes de ser actualizados.

Aviso de actualizaciones pendientes

Con apticron podremos hacer que el sistema nos envíe un correo cada vez que se disponga de paquetes pendientes de ser actualizados (p.ej. parches de seguridad):

apt-get install apticron 

Para su uso necesitamos tener configurado un servidor de correo (SMTP), podemos configurar un postfix (ver sección anterior de servicios) o utilizar ssmtp para conectar contra un servidor de correo ya configurado.

En el segundo caso, instalamos mediante ‘apt-get install ssmtp’ y editamos ‘/etc/ssmtp/ssmtp.conf’:

# Config file for sSMTP sendmail
#
# The person who gets all mail for userids < 1000
# Make this empty to disable rewriting.
root=miusuario

# The place where the mail goes. The actual machine name is required no
# MX records are consulted. Commonly mailhosts are named mail.domain.com
mailhub=servidor.correo.com:25

# Where will the mail seem to come from?
#rewriteDomain=nonsense.non

# The full hostname
hostname=el.dominio.de.esta.maquina.com

# Are users allowed to set their own From: address?
# YES - Allow the user to specify their own From: address
# NO - Use the system generated From: address
FromLineOverride=NO

En el fichero anterior indicamos a quien redirigimos los correos de root, el servidor de correo que utilizaremos y el dominio con el que saldrán los correos de la máquina actual.

Tanto si ya tenemos un servidor postfix configurado como si hemos optado por SSMTP, debemos probar que el comando 'mail' nos permite enviar correos:

echo "Prueba" | mail -s "Prueba" miusuario@otrolugar.com

Una vez validado, podemos continuar con la configuración de apticron editando ‘/etc/apticron/apticron.conf’ y estableciendo el correo al que queremos recibir las alertas:

EMAIL="miusuario@otrolugar.com"

Esto es todo. Cron ejecutará diariamente el script ‘/etc/cron.daily/apticron’, el cual comprobará las actualizaciones pendientes y enviará un correo en caso de que sea necesario.

Cabe destacar que para que funcione, el sistema debe estar configurado para que actualice automáticamente el listado de paquetes (ver sección anterior).

Monitorizar desde la consola

Espacio en disco

Para la monitorización del espacio en disco podemo usar los comandos:

df -h
du -ms

El primero muestra el estado de todas las particiones, el segundo presenta los megas que ocupa el directorio actual.

Puede que nos interese ordenar los directorios en función de su tamaño, para ello podemos descargar un script específico:

cd /usr/local/bin
wget http://www.pixelbeat.org/scripts/dutop
chmod 755 /usr/local/bin/dutop

Que al ejecutar facilita la siguiente información:

# dutop
27%    183.8M    ./Fotos
26%    180.5M    ./Fotos.zip 
19%    135.0M    ./Documentos
 5%     37.4M    ./Trabajo
 5%     35.6M    ./Musica 
CPU

La monitorización de la CPU (más otros parámetros) podemos llevarla a cabo mediante ‘top’ o ‘htop’:

apt-get install htop
Ancho de banda / Bandwidth

Aplicaciones recomendadas:

apt-get install bwm-ng

Y también:

apt-get install jnettop

Para este último debemos crear el fichero ‘~/.jnettop’:

#interface "eth0"
# Show IP and Ports per separate
local_aggregation     none
# I don't care about remote port
remote_aggregation    port
# Do not generate more traffic resolving every IP
resolve               off

Al ejecutar jnettop podemos interactuar con la aplicación para cambiar los parámetros establecidos. Por ejemplo:

  • ‘l’: cambia la agregación local
  • ‘r’: cambia la agregación remota
Conexiones de red

Conexiones de la máquina:

# netstat -atunp

Si la máquina se encuentra haciendo NAT en una red, podemos ver también las conexiones “NATeadas”:

# apt-get install netstat-nat
# netstat-nat -N

Si hemos configurado el firewall con iptables (ver sección correspondiente), podremos ver las conexiones controladas por el módulo CONNTRACK:

# apt-get install iptstate
# iptstate
Memoria RAM / Swap

Para obtener un listado de los procesos del sistema ordenados por la memoria consumida:


ps -A --sort=-rsz -o pid,vsz,rssize,pmem,comm

Cada parámetro indica:

  • pid: Process ID
  • vsz: Virtual Memory Size (includes RAM & Swap)
  • rssize: Resident Memory Size (only RAM)
  • pmem: Ratio of the process’s resident set size to the physical memory on the machine

Para ver la memoria total utilizada tenemos dos opciones:

# vmstat -S M 
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 0  0     34      6     12    134    0    0   348    60  556  521 10  1 75 14
# free -m
             total       used       free     shared    buffers     cached
Mem:           502        494          8          0         12        132
-/+ buffers/cache:        349        153
Swap:          972         34        938

Cada columna significa:

  • Cache: Bloques de información leídos del disco duro
  • Buffer: Memoria temporal utilizada por el kernel (p.ej. comunicaciones con dispositivos, envio/recepción de información por red, etc.)
  • Swap: Cuando la memoria RAM escasea, el kernel transfiere partes de la memoria RAM a la memoria SWAP, la cual realmente se encuentra en el disco duro y por tanto es mucho más lenta (pero nos permite cargar más aplicaciones de las que podriamos con nuestra RAM actual).

Cuando se requiere más memoria, el kernel debe decidir si reducir la cache/buffers o enviar bloques de memoria a la Swap. Esta decisión se encuentra parametrizada mediante el valor swappines:

cat /proc/sys/vm/swappiness

Habitualmente su valor es de 60, pero puede situarse entre 0 (poco probable que se realice swap) y 100 (máxima prioridad a realizar swap). Según las aplicaciones de nuestro sistema quizás nos interese cambiarlo, p.ej. en un desktop, cuando se ejecutan procesos cron que consumen mucho disco duro (por ejemplo slocate), la cache crecerá y puede que se envíen aplicaciones a la swap (swap-in) y el usuario notará una ralentización notable del sistema en el momento que quiera acceder a ellas (swap-out). Para cambiar el parámetro:

# echo "10" > /proc/sys/vm/swappiness
# cat /proc/sys/vm/swappiness

Este cambio se perderá en el siguiente reinicio, para que sea permanente editamos /etc/sysctl.conf y añadimos la linea:

vm.swappiness=10

‘ps_mem’ es un script (python) que imprime el total de memoria RAM sumarizada por proceso padre (p.ej. si apache realiza varios forks, al mostrar la información los unificará):

cd /usr/local/bin
wget http://www.pixelbeat.org/scripts/ps_mem.py
chmod 755 /usr/local/bin/ps_mem.py

Resultado de ejemplo:

# ps_mem.py |less
Warning: Shared memory is slightly over-estimated by this system
for each program, so totals are not reported.
 Private  +   Shared  =  RAM used       Program 

 68.0 KiB + 320.0 KiB = 388.0 KiB       klogd
116.0 KiB + 288.0 KiB = 404.0 KiB       atd
 80.0 KiB + 416.0 KiB = 496.0 KiB       mysqld_safe
 88.0 KiB + 416.0 KiB = 504.0 KiB       dd
 68.0 KiB + 436.0 KiB = 504.0 KiB       logger
152.0 KiB + 520.0 KiB = 672.0 KiB       syslogd
 96.0 KiB + 584.0 KiB = 680.0 KiB       init
152.0 KiB + 720.0 KiB = 872.0 KiB       less
208.0 KiB + 740.0 KiB = 948.0 KiB       cron
308.0 KiB +   1.3 MiB =   1.6 MiB       pickup
332.0 KiB +   1.3 MiB =   1.7 MiB       master
352.0 KiB +   1.4 MiB =   1.7 MiB       qmgr
  1.2 MiB + 856.0 KiB =   2.0 MiB       screen (2)
780.0 KiB +   2.3 MiB =   3.0 MiB       sshd (3)
  1.2 MiB +   1.9 MiB =   3.1 MiB       mutt
  1.8 MiB +   2.3 MiB =   4.1 MiB       snmpd
840.0 KiB +   7.0 MiB =   7.8 MiB       apache2 (6)
  9.0 MiB +   1.4 MiB =  10.4 MiB       bash (6)
 15.4 MiB +   5.1 MiB =  20.6 MiB       mysqld

Monitorización en tiempo real: Nagios

Con Nagios podemos monitorizar diversos sistemas en tiempo real, generando alertas cuando se incumpla alguno de los parámetros especificados. También vale la pena considerar la opción de utilizar Monit en su lugar.

# apt-get install nagios2 nagios-images nagios-plugins

Configuramos un usuario para nagios:

# sudo htpasswd -c /etc/nagios2/htpasswd.users nagiosadmin
New password: 
Re-type new password: 
Adding password for user nagiosadmin

Preparamos el sistema:

# chown nagios:nagios /var/log/nagios2/
# chown -R nagios:www-data /var/cache/nagios2/

# cd /bin/
# ln -s /usr/bin/mail mail

Creamos nuestra configuración específica:

mkdir /etc/nagios2/mysite
cd /etc/nagios2/mysite
cp 
cp ../conf.d/contacts_nagios2.cfg  contacts.cfg
cp ../conf.d/timeperiods_nagios2.cfg timeperiods.cfg
cp ../conf.d/localhost_nagios2.cfg localhost.cfg
cp ../conf.d/generic-service_nagios2.cfg generic-service.cfg
cp ../conf.d/generic-host_nagios2.cfg generic-host.cfg

En estos ficheros de configuración especificaremos los servidores y servicios que queremos monitorizar. Para una guía más detallada consultar el wiki de Ubuntu sobre Nagios para así configurar Nagios según nuestras necesidades..

Hay que tener en cuenta que si alguno de los servicios a comprobar es una mysql, debemos asegurarnos que nagios tiene su propio usuario con acceso:

mysql -u root -p

CREATE USER 'nagios'@'localhost';
FLUSH PRIVILEGES;

Nagios depende de apache, dado que su interfaz principal es via web. Para ello apache va a requerir algunos módulos extra:

a2enmod auth_basic
a2enmod authn_file
a2enmod authz_default
a2enmod authz_groupfile
a2enmod authz_host
a2enmod authz_user
a2enmod cgi
a2enmod alias

/etc/init.d/apache2 restart

Una vez configurado, la puesta en marcha:

# /etc/init.d/nagios2 stop
# /etc/init.d/nagios2 start

Cabe destacar que los plugins de nagios se encuentran en ‘/usr/lib/nagios/plugins’, y puede que nos interese tener soporte SNMP (ver sección SNMP):

apt-get install nagios-snmp-plugins
Ancho de banda consumido con Bandwidthd

Bandwidthd dibuja gráficas y tablas HTML con el tráfico generado, así podemos controlar lo que sucede en el servidor:

apt-get install bandwidthd
cd /var/www
ln -s /var/lib/bandwidthd/htdocs/ bandwidthd

Configuración en /etc/bandwidthd/bandwidthd.conf:

subnet 0.0.0.0/0

Cabe destacar que bandwithd consume bastante memoria RAM (200 MB aprox. de media) y quizás con otras herramientas como Cacti ya tenemos información suficiente sobre el ancho de banda.

SNMP

SNMP es un protocolo que permite monitorizar servidores y dispositivos de red de forma remota. Nos puede resultar útil activarlo en nuestra máquina de forma local para que sea utilizado en conjunto con Nagios y/o Cacti.

# apt-get install snmpd snmp

Nos aseguramos que solo sea accesible por localhost /etc/default/snmpd y desactivamos los módulos smux, ipv6 :


# snmpd options (use syslog, close stdin/out/err).
# Log by Syslog only: warning messages, errors, alerts and critical.
#   ingore: notice info and debug
SNMPDOPTS='-Ls 01234 -Lf /dev/null -A -u snmp -I -smux,ipv6 -p /var/run/snmpd.pid 127.0.0.1'

En la configuración ‘/etc/snmp/snmpd.conf’ indicamos::

##       sec.name     source           community
##       ========     ======           =========
#com2sec  local       localhost        public
#com2sec  network_1   172.16.1.0/24    public
#com2sec  network_2   192.168.2.0/24   public

##       Access.group.name   sec.model        sec.name
##       =================  =========         ========
#group    MyROGroup_1        v1                local
#group    MyROGroup_1        v1                network_1
#group    MyROGroup_2        v2c               network_2

##   MIB.view.name     incl/excl  MIB.subtree  mask
##   ==============   =========  ===========  ====
#view all-mibs         included   .1           80

##      MIB                
##      group.name   context sec.model sec.level prefix read     write  notif
##      ==========   ======= ========= ========= ====== ====     =====  =====

#access  MyROGroup_1  ""       v1       noauth    exact  all-mibs none   none
#access  MyROGroup_2  ""       v2c      noauth    exact  all-mibs none   none
#

rocommunity public

syslocation NewYork
syscontact admin@localhost


#ignoredisk /dev/rdsk/c0t2d0
disk /
#disk /usr
#disk /var

proc apache2
proc mysqld

La configuración anterior hace que SNMP sea accesible en modo lectura para cualquier usuario, además monitorizará el disco principal “/” y los procesos apache2 y mysqld.

Reiniciamos el servicio:

/etc/init.d/snmpd restart

Si queremos lanzar consultas SNMP desde la terminal:

snmpwalk -v 1 -c public localhost interface|less
snmpwalk -v 1 -c public localhost system|less
snmpget -v 1 -c const1payted localhost ifPhysAddress.2

Para una explicación más detallada vale la pena leer el siguiente tutorial: Monitoring Server Performance.

Soporte ‘Devices I/O’ en net-snmp

En Ubuntu, net-snmp no viene compilado con soporte para proporcionar información de I/O de los dispositivos del sistema (disco duro). Tenemos la opción de recompilar activando la funcionalidad, o delegar esta tarea a un script que consultará /proc/diskstats. Optamos por la segunda por su sencillez:

Editamos/etc/snmp/snmpd.conf y añadimos al final:

pass .1.3.6.1.4.1.2021.13.15 /usr/local/bin/snmp-diskio-collector

Creamos el script /usr/local/bin/snmp-diskio-collector:

#!/bin/sh

# by Jamie Wilkinson jamie @anchor.net.au
# this code is in the public domain
# based on passtest from the net-snmp distribution examples
# WARNING there is shitloads of IO required to get this information :)

debug_flag=0

debug () {
    if [ $debug_flag -eq 1 ]; then
        echo $* >> /tmp/snmp-diskio-collector-debug.log
    fi
}

PLACE=".1.3.6.1.4.1.2021.13.15"
REQ="$2"

debug
debug "new run" 
debug "args $*" 
if [ "$1" = "-s" ]; then
  exit 0
fi

# the 'tail' of the oid, everything after $PLACE
oidtail=`echo $REQ | sed "s/^$PLACE//"`
debug "oidtail=$oidtail" 

# number of devices we can export
devcount=`wc -l /proc/diskstats | cut -f1 -d' '`
debug "devcount=$devcount" 

item=`echo $oidtail | cut -f4 -d.`
index=`echo $oidtail | cut -f5 -d.`
debug "oidtail=$oidtail, item=$item, index=$index" 

if [ "$1" = "-n" ]; then
    if [ -z "$item" ]; then
        item=1
        index=1
    elif [ -z "$index" ]; then
        index=1
    else
        index=`expr $index + 1`
        if [ "$index" -gt "$devcount" ]; then
            index=1
            item=`expr $item + 1`
            if [ "$item" -gt 6 ]; then
                # break out of the loop
                exit 0;
            fi
        fi
    fi
    RET=$PLACE.1.1.$item.$index
else
    case "$REQ" in
        $PLACE) exit 0;;
        *) RET=$REQ ;;
    esac
fi

debug "after -n, item=$item, index=$index" 
debug "RET is now $RET" 

echo "$RET"

debug "oidtail=$oidtail, item=$item, index=$index" 

# awk uses this variable in the environment below
export index
# see linux kernel Documentation/iostats.txt for format
if [ -n "$index" ]; then
    case "$item" in
        1)
            # diskIOIndex
            debug "result: diskIOIndex $index" 
            echo "integer"
            echo $index
            exit 0
            ;;
        2)
            # diskIODevice
            debug "result: diskIODevice $index" 
            echo "string"
            awk 'FNR == ENVIRON["index"] { print $3 }' /proc/diskstats
            exit 0
            ;;
        3)
            # diskIONRead
            debug "result: diskIONRead $index" 
            echo "counter"
            awk 'FNR == ENVIRON["index"] { print $6 }' /proc/diskstats
            exit 0
            ;;
        4)
            # diskIONWritten
            debug "result: diskIONWritten $index" 
            echo "counter"
            awk 'FNR == ENVIRON["index"] { print $10 }' /proc/diskstats
            exit 0
            ;;
        5)

            # diskIOReads
            debug "result: diskIOReads $index" 
            echo "counter"
            awk 'FNR == ENVIRON["index"] { print $4 }' /proc/diskstats
            exit 0
            ;;
        6)
            # diskIOWrites
            debug "result: diskIOWrites $index" 
            echo "counter"
            awk 'FNR == ENVIRON["index"] { print $8 }' /proc/diskstats
            exit 0
            ;;
        *) exit 0; #echo "string"; echo "debug... $RET $REQ"; exit 0 ;;
    esac
else
    exit 0
fi

Damos permisos de ejecución y reiniciamos SNMP:

chmod 755 /usr/local/bin/snmp-diskio-collector
/etc/init.d/snmpd restart

Para probar que funciona correctamente:

snmpwalk -m ALL -v1 -c public localhost diskIOTable

Monitorización historica: Cacti

Cacti es una herramienta que almacena un histórico del estado de diferentes aspecto del sistema local (p.ej. CPU, Memoria, ancho de banda, etc.) o remoto via SNMP. Vale la pena considerar también muni como alternativa.

Cacti requerirá Apache y SNMP para funcionar (ver secciones correspondientes).

apt-get install cacti
ln -s /usr/bin/php5 /usr/bin/php

Una vez instalado, podemos acceder vía web por defecto en ‘http://localhost/cacti’ (aunque puede variar depende de como configuremos apache).

A través de la web, crearemos un “dispositivo SNMP” que realmente será nuestro sistema y añadiremos unas gráficas estándar:

  • Crear dispositivo “ucd/net SNMP Host” apuntando a localhost.
  • Añadir graph templates:
    1) ucd/net – CPU Usage
    2) ucd/net – Load Average
    3) ucd/net – Memory Usage
    4) Unix – Logged in Users
    5) Unix – Ping Latency
    6) Unix – Processes
  • Añadir data queries:
    1) SNMP – Get Mounted Partitions
    2) SNMP – Get Processor Information
    3) SNMP – Interface Statistics
    4) ucd/net – Get Monitored Partitions
  • Añadir gráficas desde “New graphs”
  • Desde “Graph Management”, seleccionar las nuevas gráficas y añadirlas al al árbol (Place on a Tree)

Más información en la web de cacti.

Script Device I/O para cacti

Si hemos activado el soporte I/O para dispositivos (disco duro) en el servicio SNMP, podemos hacer que cacti monitorice también esa información.

Lo mejor es seguir las instrucciones del foro de cacti, pero a grandes rasgos los pasos a seguir son los siguientes:

# cd /usr/share/cacti/resource/snmp_queries/
# wget http://forums.cacti.net/download.php?id=2635
# mv download.php?id=2635 Cacti_Net-SNMP_DevIO_v2.zip
# unzip Cacti_Net-SNMP_DevIO_v2.zip
# rm -f *TMPL.xml Cacti_Net-SNMP_DevIO_v2.zip

Desde un PC cliente, descargamos el mismo fichero e importamos via web los ficheros:

  • ‘net-snmp_devIO-BytesRW_graphTMPL.xml’:
    Cacti has imported the following items:
    
    GPRINT Preset
    [success] Normal [update]
    
    Data Input Method
    [success] Get SNMP Data (Indexed) [update]
    
    Data Template
    [success] ucd/net - Device I/O [new]
    
    Graph Template
    [success] ucd/net - Device I/O - Bytes Read/Written [new]
    
  • ‘net-snmp_devIO-ReadsWrites_graphTMPL.xml’
    Cacti has imported the following items:
    
    GPRINT Preset
    [success] Exact Numbers [update]
    
    Data Input Method
    [success] Get SNMP Data (Indexed) [update]
    
    Data Template
    [success] ucd/net - Device I/O [update]
    
    Graph Template
    [success] ucd/net - Device I/O - Reads/Writes [new]
    
  • ‘net-snmp_devIO-LoadAVG_graphTMPL’: solo es compatible con sistemas BSD. No lo cargamos en Linux.

Creamos el Data Query mediante “Data Queries – Add”:

Name: ucd/net - Get Device I/O
Description:
XML Path: <path_cacti>/resource/snmp_queries/net-snmp_devio.xml
Data Input Method: Get SNMP Data (indexed)

Pulsamos “Create”.

Ahora es el momento de añadir las gráficas asociadas. En el recuadro associate graph, pulsamos sobre Add y rellenamos el formulario:

Name: Device I/O - Bytes Read/Written
Graph template: ucd/net Device I/O - Bytes Read/Written

Pulsamos Create.

A continuación, asociamos las siguientes fuentes seleccionando del desplegable la fuente que concuerde según la descripción y marcando el checkout de la derecha:

Data Source: Bytes_Read
Data Source: Bytes_Written 

Volvemos a añadir una nueva gráfica al Data Query:

Name: Device I/O - Reads/Writes
Graph template: ucd/net Device I/O - Reads/Writes

Asociamos las siguientes fuentes:

Data Source: Device_Reads
Data Source: Device_Writes

Finalmente al Host template “ucd/net SNMP Host” asociamos la Data Query “ucd/net – Get Device I/O “. A partir de aquí, todos los nuevos hosts que se asignen la plantilla “ucd/net SNMP Host”, podran crear gráficas de lectura/escritura al disco duro.

9. Seguridad

Cortafuegos / Firewall

Antes de configurar el cortafuegos, vamos a preparar el sistema ULOG que nos permitirá registrar eventos procedentes de iptables:

apt-get install ulogd ulogd-mysql

Por defecto ULOG viene configurado para que el fichero de log sea ‘/var/log/ulog/syslogemu.log’. No obstante, ULOG también nos permite almacenar la información en una MySQL. Para activarlo descomentamos de ‘/etc/ulogd.conf’:

plugin="/usr/lib/ulogd/ulogd_MYSQL.so"

Y, en el mismo fichero, configuramos el acceso a la MySQL:

[MYSQL]
table="ulog"
pass="secret"
user="ulog"
db="ulog"
host="localhost"

Finalmente, en la MySQL creamos el usuario y la BBDD con su correspondiente estructura:

CREATE USER 'ulog'@'localhost' IDENTIFIED BY 'secret';

GRANT USAGE ON * . * TO 'ulog'@'localhost' IDENTIFIED BY 'secret' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0 ;

CREATE DATABASE IF NOT EXISTS `ulog` ;

GRANT ALL PRIVILEGES ON `ulog` . * TO 'ulog'@'localhost';

FLUSH PRIVILEGES;

USE ulog;

source /usr/share/doc/ulogd-mysql/mysql.table;

Para conocer las bases de “iptables” podemos ver este otro artículo con más explicaciones sobre iptables, veamos un ejemplo más avanzado que en general establece las siguientes reglas:

  • Por defecto denegamos todo el tráfico.
  • Limitamos los rangos de IP reservados para redes privadas (excepto si estamos en una de ellas)
  • Bloqueamos ataques de SYN/RST flood.
  • Bloqueamos paquetes malformados.
  • Mantenemos una lista negra temporal de IPs que han intentado realizar un ataque de fuerza bruta: han intentado realizar más de 2 conexiones en 20 segundos al puerto de SSH. Las IPs se mantienen en la lista negra durante 10 minutos.
  • Dejamos el puerto SSH de entrada abierto.
  • De salida permitimos:
    • DNS
    • SMTP + IMAP SSL
    • SSH
    • HTTP/HTTPS
    • NTP de Ubuntu
  • Respecto al protocolo ICMP, permitimos:
    • Echo request hacia Internet
    • Echo reply desde Internet
    • Destination Unrecheable desde Internet
    • Time exceed desde Internet
  • Registramos el tráfico que no ha sido aceptado por las reglas anteriores (no obstante limitamos el número de registros por segundo para no desbordar el sistema):
    • Entrada
    • Salida
    • Redirecciones: en nuestro ejemplo no aplica.

Podemos guardar el script en ‘/usr/local/bin/firewall’:

#!/bin/bash
#
# Copyright (C) 2008 http://www.marblestation.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

######### CONFIGURABLE #########
IPTABLES="/sbin/iptables";
ECHO="/bin/echo";

#LAN_INTERFACE_GETIP="eth0:0"

LAN_INTERFACE="eth0"
LOOPBACK_INTERFACE="lo"

#LAN_IP1="71.11.10.19"
LAN_IP1=`ifconfig $LAN_INTERFACE|grep inet|grep -v inet6|cut -d " " -f 12|cut -d ":" -f 2`
LAN_IP2=""
LAN_IP3=""
DNS_IP="213.186.33.99" # NS1 NS2 of ISP

#BROADCAST_IP1=71.11.10.255"
BROADCAST_IP1=`ifconfig $LAN_INTERFACE|grep inet|grep -v inet6|cut -d " " -f 14|cut -d ":" -f 2`

#LAN_NET=$LAN_IP/`ifconfig $LAN_INTERFACE|grep inet|grep -v inet6|cut -d " " -f 16|cut -d ":" -f 2`

BANNED_IPs=""

NTP_UBUNTU="91.189.94.4"
####### FIN CONFIGURABLE #######

##
# Required kernel modules: nf_conntrack, nf_nat, nf_nat_ftp, nf_conntrack_ftp
#
#  * Non-standard ports:
#      modprobe ip_conntrack_ftp ports=21,2121
#      modprobe ip_nat_ftp ports=21,2121
#
# Current tracked connections:
#  cat /proc/net/ip_conntrack | wc -l
# 
# Max allowed tracked connections:
#  sysctl -a net.ipv4.netfilter.ip_conntrack_max
#
# Possible modifications to /etc/sysctl.conf:
#
## Time limit to consider a connection active: 432000 seconds (432000 / (60*60*24) = 8 days)
#  net.ipv4.netfilter.ip_conntrack_tcp_timeout_established = 28800
#
## Max tracked connections (65536 connections * 350 bytes per connection) / (1024 * 1024) = 21,87 MBytes
#  net.ipv4.netfilter.ip_conntrack_max = 65536
#
## Time limit to close a connection (60 seconds)
#  net.ipv4.netfilter.ip_conntrack_tcp_timeout_close_wait = 60
#
# Reload sysctl: sysctl -p
##



# Reset tablas de filtrado
$IPTABLES -t filter -Z
$IPTABLES -t filter -F
$IPTABLES -t filter -X

# Reset tablas de nuevas conexiones
$IPTABLES -t nat -Z
$IPTABLES -t nat -F
$IPTABLES -t nat -X

# Reset tablas de modificación de paquetes
$IPTABLES -t mangle -Z
$IPTABLES -t mangle -F
$IPTABLES -t mangle -X


echo "[start] Default policy"
# Politica general de tráfico entrante/saliente y redirigido
$IPTABLES -t filter -P INPUT DROP
$IPTABLES -t filter -P OUTPUT DROP
$IPTABLES -t filter -P FORWARD DROP

# Politica de las cadenas extendidas (donde se pueden realizar alteraciones de paquetes)
$IPTABLES -t mangle -P INPUT ACCEPT
$IPTABLES -t mangle -P OUTPUT ACCEPT
$IPTABLES -t mangle -P PREROUTING ACCEPT
$IPTABLES -t mangle -P POSTROUTING ACCEPT

# Paquetes que crean nuevas conexiones y deben ser alterados por NAT
$IPTABLES -t nat -P OUTPUT ACCEPT
$IPTABLES -t nat -P PREROUTING ACCEPT
$IPTABLES -t nat -P POSTROUTING ACCEPT
echo "[end] Default policy"



echo "[start] Local users control"
#--uid-owner {USERNAME} : Matches if the packet was created by a process with the given effective USERNAME. 
#--gid-owner {groupid}: Matches if the packet was created by a process with the given effective group id. 
#--pid-owner {processed}: Matches if the packet was created by a process with the given process id. 

# Allow any TCP connection originated by myuser
#$IPTABLES -A OUTPUT -o $LAN_INTERFACE -p tcp --sport 1024:65535 --dport 1:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner myuser -j ACCEPT
#$IPTABLES -A INPUT -i $LAN_INTERFACE -p tcp --sport 1:65535 --dport 1024:65535 -m state --state RELATED,ESTABLISHED -j ACCEPT

echo "[end] Local users control"

echo "[start] Loopback"
# Permitir trafico a la interfaz loopback
$IPTABLES -A INPUT  -i $LOOPBACK_INTERFACE -s 127.0.0.1 -j ACCEPT
$IPTABLES -A OUTPUT -o $LOOPBACK_INTERFACE -d 127.0.0.1 -j ACCEPT

# Permitir trafico a la interfaz loopback con IPs internas
SPACE=" "
INTERNAL_IPs=$LAN_IP1$SPACE$LAN_IP2$SPACE$LAN_IP3
for internal_ip in $INTERNAL_IPs
do
	$IPTABLES -A INPUT  -i $LOOPBACK_INTERFACE -s $internal_ip -j ACCEPT
	$IPTABLES -A OUTPUT -o $LOOPBACK_INTERFACE -d $internal_ip -j ACCEPT
done
echo "[end] Loopback"


echo "[start] Banned IPs"
##################### IPs Baneadas
for banned_ip in $BANNED_IPs
do
	$IPTABLES -A INPUT -s $banned_ip -j DROP
	$IPTABLES -A OUTPUT -s $banned_ip -j DROP
	$IPTABLES -A FORWARD -s $banned_ip -j DROP
done
##################### Fin IPs Baneadas
echo "[end] Banned IPs"


echo "[start] Reserved IPs blocks"
##################### Prevent spoofing
# New chain
$IPTABLES -N reserved_blocks
$IPTABLES -F reserved_blocks

# Redirect all traffic to new chain
$IPTABLES -A INPUT -j reserved_blocks
$IPTABLES -A OUTPUT -j reserved_blocks

### [start] private networks
# 10.0.0.0/8 Private network RFC 1918 
$IPTABLES -A reserved_blocks -s 10.0.0.0/8 -j DROP
$IPTABLES -A reserved_blocks -d 10.0.0.0/8 -j DROP

# 172.16.0.0/12 Private network RFC 1918 
$IPTABLES -A reserved_blocks -s 172.16.0.0/12 -j DROP
$IPTABLES -A reserved_blocks -d 172.16.0.0/12 -j DROP

# 169.254.0.0/16 Link-Local RFC 3927 
$IPTABLES -A reserved_blocks -s 169.254.0.0/16 -j DROP
$IPTABLES -A reserved_blocks -d 169.254.0.0/16 -j DROP

# 192.168.0.0/16 Private network RFC 1918 
# !! Limited to LAN_INTERFACE, because in other interface we have 192.168.0.0 networks
#$IPTABLES -A reserved_blocks -i $LAN_INTERFACE -s 192.168.0.0/16 -j DROP
#$IPTABLES -A reserved_blocks -o $LAN_INTERFACE -d 192.168.0.0/16 -j DROP
### [end] private networks


### [start] Other reserved blocks
# 0.0.0.0/8 Current network (only valid as source address) RFC 1700 
$IPTABLES -A reserved_blocks -s 0.0.0.0/8 -j DROP
$IPTABLES -A reserved_blocks -d 0.0.0.0/8 -j DROP

# 14.0.0.0/8 Public data networks (per 2008-02-10, available for use[1]) RFC 1700 
# Addresses within this block are assigned to users and should be treated as such
# Do not drop

# 127.0.0.0/8 Loopback RFC 3330 
$IPTABLES -A reserved_blocks -s 127.0.0.0/8 -j DROP
$IPTABLES -A reserved_blocks -d 127.0.0.0/8 -j DROP

# 128.0.0.0/16 Reserved (IANA) RFC 3330 
# Addresses in this block are subject to future allocation to a Regional Internet
# Registry for assignment in the normal manner.
## Do not drop

# 191.255.0.0/16 Reserved (IANA) RFC 3330 
# Addresses in this block are subject to future allocation to a Regional Internet
# Registry for assignment in the normal manner.
## Do not drop

# 192.0.2.0/24 Documentation and example code RFC 3330 (Test-Net [RFC3330])
$IPTABLES -A reserved_blocks -s 192.0.2.0/24      -j DROP
$IPTABLES -A reserved_blocks -d 192.0.2.0/24      -j DROP

# 192.88.99.0/24 IPv6 to IPv4 relay RFC 3068 
$IPTABLES -A reserved_blocks -s 192.88.99.0/24      -j DROP
$IPTABLES -A reserved_blocks -d 192.88.99.0/24      -j DROP

# 198.18.0.0/15 Network benchmark tests RFC 2544 
$IPTABLES -A reserved_blocks -s 198.18.0.0/15      -j DROP
$IPTABLES -A reserved_blocks -d 198.18.0.0/15      -j DROP

# 223.255.255.0/24 Reserved (IANA) RFC 3330 
$IPTABLES -A reserved_blocks -s 223.255.255.0/24      -j DROP
$IPTABLES -A reserved_blocks -d 223.255.255.0/24      -j DROP

# 224.0.0.0/3 includes:
#   - Reserved for Multicast (former Class D network) RFC 3171 (224.0.0.0/4): From 224/8 to 239/8
#   - Reserved for Future use (former Class E network) RFC 1700 (240.0.0.0/4): From 240/8 to 255/8
#   - Broadcast: 255.255.255.255
$IPTABLES -A reserved_blocks -s 224.0.0.0/3 -j DROP
$IPTABLES -A reserved_blocks -d 224.0.0.0/3 -j DROP
### [end] Other reserved blocks

##################### Fin Direcciones privadas
echo "[end] Reserved IPs blocks"

echo "[start] Attack protection"
##################### Ataques

echo "* SYN/RST flood"
# Stop  flood

# New chain
$IPTABLES -N flood
$IPTABLES -F flood

# Redirect all eth0 traffic to new chain
$IPTABLES -A INPUT -i $LAN_INTERFACE -p tcp --syn -j flood
$IPTABLES -A OUTPUT -o $LAN_INTERFACE -p tcp --syn -j flood
$IPTABLES -A FORWARD -i $LAN_INTERFACE -p tcp --syn -j flood

# Chain rules
# Drop excessive RST packets to avoid TCP reset attacks, by given the
# next real data packet in the sequence a better chance to arrive first.
$IPTABLES -A flood -p tcp -m tcp --tcp-flags RST RST -m limit --limit 2/second --limit-burst 2 -j ACCEPT
	
# Protect against SYN floods by rate limiting the number of new
# connections from any host to 5 per second.
$IPTABLES -A flood -m state --state NEW -p tcp -m tcp --syn -m recent --name synflood --set
$IPTABLES -A flood -m state --state NEW -p tcp -m tcp --syn -m recent --name synflood --update --seconds 1 --hitcount 5 -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:SYN Packets flood] "
$IPTABLES -A flood -m state --state NEW -p tcp -m tcp --syn -m recent --name synflood --update --seconds 1 --hitcount 5 -j DROP

echo "* Malformed packets"
# Create new chain
$IPTABLES -N malformed_packets
$IPTABLES -F malformed_packets

# Redirect all traffic to new chain
$IPTABLES -A INPUT -j malformed_packets
$IPTABLES -A OUTPUT -j malformed_packets
$IPTABLES -A FORWARD -j malformed_packets

# Log & Drop all incoming fragments
$IPTABLES -A malformed_packets -f -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:Fragments Packets] "
$IPTABLES -A malformed_packets -f -j DROP

# Log & Drop new connections with SYN flag not set
#$IPTABLES -A malformed_packets -p tcp ! --syn -m state --state NEW -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:New without SYN] "
$IPTABLES -A malformed_packets -p tcp ! --syn -m state --state NEW -j DROP 

# Log & Drop all incoming malformed NULL packets (Block null scans)
$IPTABLES  -A malformed_packets -p tcp --tcp-flags ALL NONE -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:NULL Packets] "
$IPTABLES  -A malformed_packets -p tcp --tcp-flags ALL NONE -j DROP # NULL packets 

# Drop all incoming malformed XMAS packets (Block xmas scans)
$IPTABLES  -A malformed_packets -p tcp --tcp-flags ALL ALL -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:XMAS Packets] "
$IPTABLES  -A malformed_packets -p tcp --tcp-flags ALL ALL -j DROP #XMAS

# Drop all incoming malformed FIN packets (Block FIN scans)
$IPTABLES  -A malformed_packets -p tcp --tcp-flags FIN,ACK FIN -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:Fin Packets Scan] "
$IPTABLES  -A malformed_packets -p tcp --tcp-flags FIN,ACK FIN -j DROP # FIN packet scans

# Drop bogus TCP packets
$IPTABLES -A malformed_packets -p tcp -m tcp --tcp-flags SYN,FIN SYN,FIN -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:Bogus Packets] "
$IPTABLES -A malformed_packets -p tcp -m tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
$IPTABLES -A malformed_packets -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:Bogus Packets] "
$IPTABLES -A malformed_packets -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP

# Log & Drop all incoming invalid packets
#$IPTABLES -A malformed_packets -m state --state INVALID -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:Invalid Packets] "
$IPTABLES -A malformed_packets -m state --state INVALID -j DROP

echo "  - Bruce force attack prevention"
##### [start] Prevent Brute force attack
$IPTABLES -N brute_force
$IPTABLES -N brute_force_blacklist

$IPTABLES -A brute_force_blacklist -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:Bruteforce] "
$IPTABLES -A brute_force_blacklist -m recent --name brute_force_blacklist --set
$IPTABLES -A brute_force_blacklist -j DROP

# Incoming connections from blacklisted hosts are dropped for 10 minutes (every droped connection, restarts the 10 minutes period).
$IPTABLES -A brute_force -m recent --update --name brute_force_blacklist --seconds   600 --hitcount   1 -j DROP

# These rules are just for counting of incoming connections. 
$IPTABLES -A brute_force -m recent --set    --name counting1
$IPTABLES -A brute_force -m recent --set    --name counting2
$IPTABLES -A brute_force -m recent --set    --name counting3
$IPTABLES -A brute_force -m recent --set    --name counting4

# A host is blacklisted if it exceeds:
#   - 2 connection attempts in 20 seconds
#   - 14 in 200 seconds
#   - 79 in 2000 seconds
#   - 399 attempts in 20000 seconds. 
$IPTABLES -A brute_force -m recent --update --name counting1 --seconds    20 --hitcount   3 -j brute_force_blacklist
$IPTABLES -A brute_force -m recent --update --name counting2 --seconds   200 --hitcount  15 -j brute_force_blacklist
$IPTABLES -A brute_force -m recent --update --name counting3 --seconds  2000 --hitcount  80 -j brute_force_blacklist
$IPTABLES -A brute_force -m recent --update --name counting4 --seconds 20000 --hitcount 400 -j brute_force_blacklist

$IPTABLES -A brute_force -j ACCEPT

# Control SSH service: only new conections
BRUTEFORCE_EXCEPTION_IP="132.106.3.12"
#$IPTABLES -A INPUT -p TCP -s ! $BRUTEFORCE_EXCEPTION_IP --dport 22 -m state --state NEW -j brute_force
$IPTABLES -A INPUT -p TCP --dport 22 -m state --state NEW -j brute_force
##### [end] Prevent Brute force attack


##################### Fin Ataques
echo "[end] Attack protection"


echo "[start] Internet < -> Host"
echo "* Incoming traffic"
##################### Puertos de entrada abiertos
echo "  - SSH"
# Aceptamos SSH
$IPTABLES -A INPUT -i $LAN_INTERFACE -p tcp -s 0/0 --sport 1024:65535 -d $LAN_IP1 --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A OUTPUT -o $LAN_INTERFACE -p tcp -s $LAN_IP1 --sport 22 -m state --state RELATED,ESTABLISHED -j ACCEPT

##################### Fin Puertos de entrada abiertos

echo "* Outgoing traffic"
##################### Puertos de salida abiertos
echo "  - DNS"
# Outgoing DNS
# udp first
for dns_server_ip in $DNS_IP
do
  $IPTABLES -A OUTPUT -p udp -s $LAN_IP1 --sport 1024:65535 -d $dns_server_ip --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
  $IPTABLES -A INPUT -p udp -s $dns_server_ip --sport 53 -d $LAN_IP1 -m state --state RELATED,ESTABLISHED -j ACCEPT
  # tcp next
  $IPTABLES -A OUTPUT -p tcp -s $LAN_IP1 --sport 1024:65535 -d $dns_server_ip --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
  $IPTABLES -A INPUT -p tcp -s $dns_server_ip --sport 53 -d $LAN_IP1 -m state --state RELATED,ESTABLISHED -j ACCEPT
done

echo "  - SMTP"
#outgoing SMTP
$IPTABLES -A OUTPUT -p tcp -s $LAN_IP1 --sport 1024:65535 -d 0/0 --dport 25 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -p tcp -s 0/0 --sport 25 -d $LAN_IP1 -m state --state RELATED,ESTABLISHED -j ACCEPT

echo "  - IMAP SSL"
#outgoing IMAP SSL
$IPTABLES -A OUTPUT -p tcp -s $LAN_IP1 --sport 1024:65535 -d 0/0 --dport 993 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -p tcp -s 0/0 --sport 993 -d $LAN_IP1 -m state --state RELATED,ESTABLISHED -j ACCEPT

echo "  - SSH"
#outgoing SSH
$IPTABLES -A OUTPUT -p tcp -s $LAN_IP1  --sport 1024:65535 -d 0/0 --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -p tcp -s 0/0 --sport 22 -d $LAN_IP1 -m state --state RELATED,ESTABLISHED -j ACCEPT

echo "  - HTTP & HTTPS"
#outgoing http and https
# for apt-get and other stuff
$IPTABLES -A OUTPUT -p tcp -s $LAN_IP1  --sport 1024:65535 -d 0/0 --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -p tcp -s 0/0 --sport 80 -d $LAN_IP1 -m state --state RELATED,ESTABLISHED -j ACCEPT
$IPTABLES -A OUTPUT -p tcp -s $LAN_IP1  --sport 1024:65535 -d 0/0 --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -p tcp -s 0/0 --sport 443 -d $LAN_IP1 -m state --state RELATED,ESTABLISHED -j ACCEPT

echo " - NTP"
#outgoing NTP to ubuntu servers
$IPTABLES -A OUTPUT -p udp -s $LAN_IP1  --sport 1024:65535 -d $NTP_UBUNTU --dport 123 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -p udp -s $NTP_UBUNTU --sport 123 -d $LAN_IP1 -m state --state RELATED,ESTABLISHED -j ACCEPT

##################### Fin Puertos de salida abiertos


echo "* ICMP"
##################### ICMP
### Drop icmp, but only after letting certain types through.
# Echo Reply
#$IPTABLES -A INPUT -p icmp --icmp-type 0 -j ACCEPT
# Destination Unreachable
#$IPTABLES -A INPUT -p icmp --icmp-type 3 -j ACCEPT
# Tiempo Excedido
#$IPTABLES -A INPUT -p icmp --icmp-type 11 -j ACCEPT
# Echo Request
#$IPTABLES -A INPUT -p icmp --icmp-type 8 -m limit --limit 30/second -j ACCEPT

## Allow outgoing ping
# Echo Request to Internet
$IPTABLES -A OUTPUT -o $LAN_INTERFACE -p icmp --icmp-type 8 -d 0/0 -s $LAN_IP1 -m limit --limit 30/second -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
# Echo Reply from Internet
$IPTABLES -A INPUT -i $LAN_INTERFACE -p icmp --icmp-type 0 -d $LAN_IP1 -s 0/0 -m state --state ESTABLISHED,RELATED -j ACCEPT
# Destination Unreachable from Internet
$IPTABLES -A INPUT -i $LAN_INTERFACE -p icmp --icmp-type 3 -d $LAN_IP1 -s 0/0 -m state --state ESTABLISHED,RELATED -j ACCEPT
# Time Exceeded from Internet
$IPTABLES -A INPUT -i $LAN_INTERFACE -p icmp --icmp-type 11 -d $LAN_IP1 -s 0/0 -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow ping between internal IPs
SPACE=" "
INTERNAL_IPs=$LAN_IP1$SPACE$LAN_IP2$SPACE$LAN_IP3
for internal_ip1 in $INTERNAL_IPs
do
    for internal_ip2 in $INTERNAL_IPs
    do
        # Receive Echo Reply
        $IPTABLES -A INPUT  -p icmp --icmp-type 0 -d $internal_ip1 -s $internal_ip2 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

        # Receive Echo Request
        $IPTABLES -A INPUT  -p icmp --icmp-type 8 -d $internal_ip1 -s $internal_ip2 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

        # Send Echo Request
        $IPTABLES -A OUTPUT -p icmp --icmp-type 8 -d $internal_ip1 -s $internal_ip2 -m limit --limit 30/second -m state --state ESTABLISHED,RELATED -j ACCEPT

        # Receive Echo Reply
        $IPTABLES -A OUTPUT -p icmp --icmp-type 0 -d $internal_ip1 -s $internal_ip2 -m limit --limit 30/second -m state --state ESTABLISHED,RELATED -j ACCEPT
    done
done


##################### Fin ICMP


# Use "-m state" (ip_conntrack module) in order to allow legitim traffic:
#
#  - NEW: A packet which creates a new connection.
#  - ESTABLISHED: A packet which belongs to an existing connection (i.e., a reply packet, or outgoing packet on a
#    connection which has seen replies).
#  - RELATED: A packet which is related to, but not part of, an existing connection, such as an ICMP error, or (with
#    the FTP module inserted), a packet establishing an ftp data connection.
#  - INVALID: A packet which could not be identified for some reason: this includes running out of memory and ICMP
#    errors which don't correspond to any known connection. Generally these packets should be dropped.


### Log

echo "[start] Logging"

echo "* Logging exceptions"
## [start] Do not log
# netbios UDP
$IPTABLES -A INPUT -p udp -d $BROADCAST_IP1 --dport 137:138 -j DROP
## [end] Do not log

echo "* Logging rules"
# Limit: The first 3 packets that match this rule will be logged. After this, it will be 30 seconds until one of the
# burst will be regained; if no packets hit the rule for 90 seconds, the burst will be fully recharged.
$IPTABLES -A INPUT -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:INPUT Drop] "
$IPTABLES -A OUTPUT -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:OUTPUT Drop] "
$IPTABLES -A FORWARD -m limit --limit 30/s --limit-burst 3 -j ULOG --ulog-nlgroup 1 --ulog-prefix "[FW:FORWARD Drop] "

echo "[end] Logging"

# Disable firewall in 30 seconds...
# ...so we can test it
#SAFETY=`sleep 30;/usr/local/bin/firewall-off` &

Creamos el script ‘/usr/local/bin/firewall-off’:

#!/bin/bash

######### CONFIGURABLE #########
IPTABLES="/sbin/iptables";
####### FIN CONFIGURABLE #######

# Reset
$IPTABLES -Z
$IPTABLES -F
$IPTABLES -t nat -F
$IPTABLES -t nat -X
$IPTABLES -t mangle -F
$IPTABLES -t mangle -X

# Politica general
$IPTABLES -P INPUT ACCEPT
$IPTABLES -P OUTPUT ACCEPT
$IPTABLES -P FORWARD ACCEPT

Es importante que establezcamos unos permisos seguros a los scripts:

# chmod 750 /usr/local/firewall
# chmod 750 /usr/local/firewall-off
# chown root:root /usr/local/firewall
# chown root:root /usr/local/firewall-off

A partir de estos ejemplos, probablemente será necesario adaptarlos a nuestras necesidades. Cuando cubra nuestros requerimientos, podemos hacer que se ejecute automáticamente al iniciar el sistema si lo añadimos al fichero ‘/etc/rc.local’ (antes del exit 0).

Monitorización de firewall con fwlogwatch

fwlogwatch permite enviar resumenes diarios por correo o generar output en HTML sobre el estado del firewall:

# apt-get install fwlogwatch 
# dpkg-reconfigure fwlogwatch 

Depues del reconfigure, /etc/default/fwlogwatch quedara parecido a lo siguiente:

START_DAEMON='true'
PARAMS='-A '
MODE=''
EMAIL='root@localhost'
CRON_EMAIL='root@localhost'
CRON_PARAMS='-l 1h -t -p -d -n -N -O Sapata'
# 1 day: -l 1d 
# show time: -t
# show protocol: -p
# differenciate destination port: -d
# reverse dns: -n
# service (not only port): -N
# sort by destination IP (ascending), protocol (asc) and time (asc): -O Sapata

Editamos /etc/fwlogwatch/fwlogwatch.config para indicarle el log:

input = /var/log/ulog/syslogemu.log

# Use 'alert_threshold' to define how many connections must happen (within
# the 'forget' time range) to activate an alert/response.
# Command line option: -a [count]
#
alert_threshold = 10

Editamos /etc/group:

adm:x:4:logcheck,www-data

Para el reporte HTML, presuponemos que tenemos configurado Apache. Copiamos el CGI correspondiente:

cp /usr/share/doc/fwlogwatch/examples/fwlogsummary.cgi /usr/lib/cgi-bin/

Editamos /usr/lib/cgi-bin/fwlogsummary.cgi:

# 1 day info
RECENT="-l 1d"
WEBDIR="/var/www/localhost/fwlogwatch"
...
MESSAGES="/var/log/ulog/syslogemu.log"
...
#if [ -z $1 ]
#then
  MESSAGES="/var/log/ulog/syslogemu.log"
#else
#  MESSAGES="$1"
#fi

Esta última parte la comento pq no me parece seguro permitir abrir un fichero del sistema indicado por parámetro.

Suponiendo que en /var/www/localhost tenemos un host virtual:

mkdir /var/www/localhost/fwlogwatch
chown -R www-data:www-data /var/www/localhost/fwlogwatch/

Creamos el fichero /etc/apache2/conf.d/fwlogwatch.conf con el siguiente contenido:

ScriptAlias /cgi-bin/fwlogsummary.cgi /usr/lib/cgi-bin/fwlogsummary.cgi

Reiniciamos apache:

/etc/init.d/apache2 restart

Y accedemos a la versión web via http://localhost/cgi-bin/fwlogsummary.cgi.

En caso de que queramos recibir alertas por correo en tiempo real, activaremos el demonio en el fichero /etc/default/fwlogwatch mediante START_DAEMON=’true’ y iniciaremos el servicio:

/etc/init.d/fwlogwatch restart

Detección de intrusos / IDS: Snort

Podríamos instalar Snort como sistema de detección de intrusos (IDS) de nuestra red:

apt-get install snort

También disponemos de Acidbase que utiliza snort para facilitar el análisis y detección de intrusiones mediante una interfaz web:

apt-get install snort-mysql acidbase

Rootkits

Validamos que el sistema no dispone de ningún rootkit instalado:

apt-get install chkrootkit rkhunter
sudo rkhunter --update
sudo rkhunter --check
sudo chkrootkit

Las desinstalo pq no me interesa que se ejecuten periodicamente:

apt-get purge chkrootkit rkhunter

Control del ancho de banda

Es recomendable establecer políticas para controlar el tráfico que recibimos y enviamos, o incluso dar más prioridad a determinados paquetes (p.ej. conexiones SSH). Para ello tenemos más información en otros tutoriales como Enrutamiento avanzado y control de tráfico en Linux.

Añado varios scripts de ejemplo que pueden ser de utilidad que establecen los siguientes parámetros utilizando Hierarchy Token Bucket (HTB):

  • Limita la velocidad de bajada a 95 megabits
  • Se crea una clase raiz “1:1” que limita la subida a 95 Megabits (para evitar la saturación del enlace) y de la cual cuelgan 3 colas de diferentes prioridades:
    • “1:10” Cola de prioridad alta (ideal para tráfico interactivo como SSH).
    • “1:20” Cola de prioridad media (por defecto)
    • “1:30” Cola de prioridad baja que limita la velocidad de subida al 70% de los 95 Megabits (perfecto para hosts o servicios que no queremos que nos saturen la conexión)

Entre las 3 colas el sistema cogerá más paquetes de aquellas que tienen mayor prioridad, y dentro de las colas se utiliza un reparto uniforme entre las diferentes conexiones para que una no se haga con todo el caudal (Stochastic Fairness Queueing – SFQ).

Una vez establecidas las colas, por defecto todo el tráfico va a la cola “1:20” y podemos marcar mediante iptables aquellas que queremos que vayan a una cola diferente. Por ejemplo:

$IPTABLES -t mangle -I OUTPUT -p tcp --sport 22 -s $LAN_IP1 -m mark --mark 0 -j MARK --set-mark 10

En el ejemplo anterior, todo el tráfico que surjan del puerto 22 (servicio SSH) es etiquetado con la marca “10” que hará que el paquete vaya a la cola “1:10” (la más prioritaria).

Veamos el script completo ‘/usr/local/bin/traffic-control’:

#!/bin/bash

## http://www.docum.org/docum.org/monitor/
## http://crysol.inf-cr.uclm.es/node/692

# In megabits
DOWNLINK=95
UPLINK=95
DEV=eth0
LAN_IP1="71.11.5.10"
IPTABLES="/sbin/iptables";
#LIMIT_IP1="192.168.22.3"


# Clean existing down/up-link qdiscs
tc qdisc del dev $DEV root    2> /dev/null > /dev/null
tc qdisc del dev $DEV ingress 2> /dev/null > /dev/null

###### uplink

#### QDISC
# Hierarchy Token Bucket which (HBT) pointing by default to class 1:20 (HBT is the most easy one)
tc qdisc add dev $DEV root handle 1: htb default 20

#### Classes
# Root class that shape everything at $UPLINK speed preventing huge queues which destroy latency
tc class add dev $DEV parent 1: classid 1:1 htb rate ${UPLINK}mbit burst 6k

# High priority class (1:10)
tc class add dev $DEV parent 1:1 classid 1:10 htb rate ${UPLINK}mbit burst 6k prio 1

# Medium priority class (1:20)
tc class add dev $DEV parent 1:1 classid 1:20 htb rate ${UPLINK}mbit burst 6k prio 2

# Low priority class (1:20) with speed reduced to 70%
tc class add dev $DEV parent 1:1 classid 1:30 htb rate $[7*$UPLINK/10]mbit burst 6k prio 3

#### Queueing
# Stochastic Fairness Queueing: avoid that one connection consumes all the bandwidth
tc qdisc add dev $DEV parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev $DEV parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev $DEV parent 1:30 handle 30: sfq perturb 10


#### Marking
### Mark traffic with 10 (highest), 20 or 30 (lowest)
$IPTABLES -F -t mangle
# Prioritize local SSH
$IPTABLES -t mangle -I OUTPUT -p tcp --sport 22 -s $LAN_IP1 -m mark --mark 0 -j MARK --set-mark 10
# Limit host
#$IPTABLES -t mangle -I FORWARD -s $LIMIT_IP1 -m mark --mark 0 -j MARK --set-mark 30

#### Classify
# Traffic with MARK 10 to the highest prioritized class (1:10)
tc filter add dev eth0 parent 1:0 prio 0 protocol ip handle 10 fw flowid 1:10
tc filter add dev eth0 parent 1:0 prio 0 protocol ip handle 20 fw flowid 1:20
tc filter add dev eth0 parent 1:0 prio 0 protocol ip handle 30 fw flowid 1:30
# By default, the rest ends up in 1:20


########## downlink
# Slow downloads down to somewhat less than the real speed to prevent queuing the ISP.
#
# Attach ingress policer:
tc qdisc add dev $DEV handle ffff: ingress

# Filter *everything* to it (0.0.0.0/0), drop everything that's coming in too fast:
tc filter add dev $DEV parent ffff: protocol ip prio 50 u32 match ip src 
   0.0.0.0/0 police rate ${DOWNLINK}mbit burst 10k drop flowid :1

Por otra parte, podemos tener un script para listar las colas ‘/usr/local/bin/traffic-control-list’:

#!/bin/bash
tc -s qdisc ls dev eth0

Y finalmente, para desactivar las colas ‘/usr/local/bin/traffic-control-list-off’:

#!/bin/bash
DEV=eth0
TC=/sbin/tc

# clean existing down- and uplink qdiscs, hide errors
$TC qdisc del dev $DEV root    2> /dev/null > /dev/null
$TC qdisc del dev $DEV ingress 2> /dev/null > /dev/null

Para ver en tiempo real el efecto de las colas establecidas, es recomendable utilizar el script ‘monitor_tc_top.pl‘ de la siguiente web:

# cd /usr/local/bin/
# wget -c http://www.docum.org/docum.org/monitor/download/monitor_tc_top.pl
# chmod 755 monitor_tc_top.pl
# monitor_tc_top.pl

Ejemplo:

 20:37:11 up 18 days,  7:30,  2 users,  load average: 0.18, 0.05, 0.01
                                          Interval    Cumulated Total
Dev  Classid   Tokens   Ctokens Rate      Speed       Send      Send
-------------------------------------------------------------------------
eth0 1:1       485      4033    2.42KB    624.32KB/s  6.91MB    8.47MB
eth0 1:10      485      4033    6.67KB    1.37KB/s    18.52KB   23.42KB
eth0 1:20      500      4048    18.41KB   558B/s      2.39KB    87.90KB
eth0 1:30      4        3388    2.39KB    622.40KB/s  6.89MB    8.36MB

10. Copias de seguridad

Con rsnapshot podremos hacer copias de seguridad automáticas y periódicas, optimizando al máximo el espacio ocupado mediante el uso de enlaces simbólicos:

apt-get install rsnapshot

Para configurar las copias editamos /etc/rsnapshot.conf para indicar donde se guardaran los scripts:

snapshot_root   /var/cache/rsnapshot/

Además, en este mismo fichero vamos a establecer nuestra política de backups: Se almacenaran las últimas 6 horas, los últimos 7 días, las últimas 4 semanas y los últimos 6 meses. Por otra parte, también indicaremos que directorios queremos copiar y que scripts ejecutar:

...
#rsync_short_args   -a
rsync_long_args --delete --numeric-ids --relative --delete-excluded --stats --timeout=10
...

#########################################
#           BACKUP INTERVALS            #
# Must be unique and in ascending order #
# i.e. hourly, daily, weekly, etc.      #
#########################################

## Tendremos backup de las últimas 6 horas, los últimos 7 días, las últimas 4 semanas y los últimos 6 meses:
interval    hourly  6
interval    daily   7
interval    weekly  4
interval    monthly 6

...
# Excluimos algunos ficheros para hacer más pequeños los backups
exclude access.log
exclude access.log.*
exclude access_log
exclude error.log
exclude error.log.*
exclude .bash_history
...

###############################
### BACKUP POINTS / SCRIPTS ###
###############################

# LOCALHOST
backup  /etc/       localhost/
backup	/home/	localhost
backup	/var/www/	localhost
backup	/usr/local/	localhost
backup	/var/lib/awstats/	localhost
backup	/var/lib/bandwidthd/	localhost
backup	/var/lib/cacti/	localhost
backup	/var/cache/nagios2/	localhost
backup	/var/lib/nagios2/	localho
backup_script	/usr/local/bin/backup_dpkg.sh	localhost/dpkg/
backup_script	/usr/local/bin/backup_mysql.sh	localhost/mysql/
backup_script	/usr/local/bin/backup_info.sh	localhost/info/

backup  rsnapshot@webserver:/etc/   webserver/  ssh_args=-p 22
backup  rsnapshot@webserver:/usr/local/ webserver/  ssh_args=-p 22
backup  rsnapshot@webserver:/var/www/   webserver/  ssh_args=-p 22
backup_script   /usr/bin/ssh -i /root/.ssh/id_rsa rsnapshot@webserver /home/rsnapshot/.ssh/backup_mysql_all.sh > db.sql webserver/mysql/all/

En este ejemplo estamos haciendo backup tanto de directorios locales (p.ej. /etc) como remotos del servidor ‘webserver’.

Es importante que los parámetros de este fichero de configuración se encuentren separados por tabulaciones y no por espacios, de lo contrario rsnapshot no podrá procesarlo correctamente. Comprobamos que el archivo de configuración sea correcto:

# rsnapshot configtest
Syntax OK

Simula la creación de un snapshot:

# rsnapshot -t hourly

El snapshot más reciente siempre se encontrará en el número más bajo, en el caso de los snapshots realizados cada hora: /var/cache/rsnapshot/hourly.0/

Es recomendable evitar que slocate indexe el backup, editamos /etc/updatedb.conf:

FINDOPTIONS='-ignore_readdir_race'
export FINDOPTIONS
PRUNEFS="NFS nfs nfs4 afs binfmt_misc proc smbfs autofs iso9660 ncpfs coda devpts ftpfs devfs mfs shfs sysfs cifs lustre_lite tmpfs usbfs udf"
export PRUNEFS
PRUNEPATHS="/tmp /usr/tmp /var/tmp /var/spool /media /var/cache/rsnapshot /var/cache/rsnapshot-tar"
export PRUNEPATHS
NETPATHS=""
export NETPATHS
LOCALUSER="nobody"
export LOCALUSER
NICE=10
export NICE
IONICE_CLASS=2
export IONICE_CLASS
IONICE_PRIORITY=7
export IONICE_PRIORITY

Utilidades rsnapshot

A la hora de hacer backup, además de especificar directorios concretos podemos hacer que se ejecuten scripts (p.ej. que realicen volcados de las BBDD) para que también se efectue una copia de su resultado (en el fichero de configuración de ejemplo anterior, se puede observar como hay varios scripts especificados).

Información de estado

A modo de ejemplo, podemos crear un script a medida /usr/local/bin/backup_info.sh que genere varios ficheros con el estado de la máquina en el momento de realizar la copia:

#!/bin/bash
# Date
/bin/date -R > date.out
# Host name and kernel
/bin/uname -a > uname.out
# Interfaces
/sbin/ifconfig -a > ifconfig.out
# Disk space
/bin/df -h > df.out
# Processes
/bin/ps aux > ps.out
# Open ports & conections
/bin/netstat -atunp > netstat.out
/usr/bin/lsof -i > lsof-i.out
# Processes with lots of files open
/usr/bin/lsof +c 15 | awk '{printf("%15s  (%s)n", $1, $2)}' | sort | uniq -c | sort -rn | head > losf-processes-with-lots-of-open-files.out
# Mounted filesystems
/bin/mount > mount.out
# Connected users
/usr/bin/who > who.out
# Firewall rules
/sbin/iptables -L > iptables.out
# Daemons & init scripts
/usr/sbin/sysv-rc-conf --list > sysv-rc-conf.out
# Files with special permissions
#/usr/bin/find / -path /proc -prune -o -perm -2 ! -type l -ls > find-world-writable.out
#/usr/bin/find / -path /proc -prune -o -nouser -o -nogroup > find-files-without-owner.out
#/usr/bin/find / -path /proc -prune -o -type f -perm +6000 -ls > find-suid-guid.out
dpkg y mysql

Scripts que copian el listado de paquetes instalados en Ubuntu y un volcado de la MySQL:

cp /usr/share/doc/rsnapshot/examples/utils/backup_dpkg.sh /usr/local/bin/
cp /usr/share/doc/rsnapshot/examples/utils/backup_mysql.sh  /usr/local/bin/

Editamos ‘/root/.my.cnf’ para establecer el password de root y así poder realizar volcados sin necesidad de interaccionar con la máquina:

[client]
  user = root
  password = secret
  host = localhost

Ejecutamos:

chmod 600 /root/.my.cnf

En el fichero de configuración especificaremos los scripts a ejecutar y el destino de su resultado:

backup_script	/usr/local/bin/backup_dpkg.sh	localhost/dpkg/
backup_script	/usr/local/bin/backup_mysql.sh	localhost/mysql/
Backups remotos

Si queremos hacer backup de directorios remotos o incluso ejecutar scripts remotos (p.ej. volcado mysql), en el servidor donde tenemos rsnapshot debemos generar una clave SSH con ssh-keygen (ver sección correspondiente) para el usuario root (/root/.ssh/).

En la máquina a la que queremos conectarnos para hacer las copias, crearemos un usuario bloqueado:

useradd -d /home/rsnapshot -m -K PASS_MAX_DAYS=-1,PASS_MIN_DAYS=0,PASS_WARN_AGE=0,UMASK=0022 -s /bin/bash rsnapshot
passwd -l rsnapshot
mkdir -p /home/rsnapshot/.ssh

Además, permitiremos que el usuario ejecute ‘rsync’ como root mediante ‘sudo’ sin necesidad de contraseña. Para ello ejecutamos ‘visudo’ y añadimos al final:

rsnapshot ALL=NOPASSWD: /usr/bin/rsync

Para que rsnapshot funcione se conecte remotamente sin intervención, necesitamos copiar la clave pública a ‘/home/rsnapshot/.ssh/authorized_keys’ y conviene establecer todas las limitaciones posibles (ver sección SSH):

from="71.21.5.19",command="/home/rsnapshot/.ssh/validate-ssh.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAUTQEbt7GOcR4jZ2bTgzxpgABIwAAAQEAwAQKRBv+ABDyOgvHfAxSVcWOEzhieC7rwrBjquLJw0W+U9vD7QgmffmuKDZksI7bAAB3NzaC1yc2EAAAdbHBOGg/zgXcrCNsuJ5IJoDw== root@server.com

En este ejemplo, el usuario solo podrá ser utilizado desde una IP concreta, siempre que tenga la clave privada y únicamente podrá ejecutar el script ‘/home/rsnapshot/.ssh/validate-ssh.sh’:

#!/bin/sh

case "$SSH_ORIGINAL_COMMAND" in
*&*)
echo "Rejected"
;;
*(*)
echo "Rejected"
;;
*{*)
echo "Rejected"
;;
*;*)
echo "Rejected"
;;
*< *)
echo "Rejected"
;;
*`*)
echo "Rejected"
;;
rsync --server*)
# the calling user HAST TO BE in the sudoers list for executing rsync!
sudo $SSH_ORIGINAL_COMMAND
;;
mysqldump *)
$SSH_ORIGINAL_COMMAND
;;
/home/rsnapshot/.ssh/backup_mysql_all.sh)
/home/rsnapshot/.ssh/backup_mysql_all.sh
;;
*)
echo "Rejected $SSH_ORIGINAL_COMMAND"
;;
esac 

Este script permitirá al usuario ejecutar únicamente 'rsync' (con permisos de root mediante 'sudo'), 'mysqldump' y el script '/home/rsnapshot/.ssh/backup_mysql_all.sh':

#!/bin/bash
/usr/bin/mysqldump --all-databases --add-drop-table --password=secret -u miusuario

El script anterior realiza un volcado de todas las BBDD de MySQL utilizando un usuario/password determinado. Dado que contiene información sensible, el script debe tener permisos restrictivos:

chown -R rsnapshot:rsnapshot /home/rshapshot
chmod 700 /home/rshapshot/.ssh/*.sh

Con esto ya tenemos preparado todo el montaje. Rsnapshot podrá conectarse al servidor (ver fichero de configuración de ejemplo anterior, donde se definían copias del servidor ‘webserver’) sin contraseña y hacer backups de directorios y MySQL. Adicionalmente, hemos establecido controles adicionales para minimizar la posibilidad de que un atacante pueda intentar aprovecharse de la configuración (cifrado SSH, autenticación por clave asimétrica limitada a una IP y a unos comandos concretos).

Tars semanales

De forma semanal, haremos que se cree un tar con la última copia para poder enviarlo cómodamente a otro ordenador:

cp /usr/share/doc/rsnapshot/examples/utils/rsnaptar /usr/local/bin/
mkdir /var/cache/rsnapshot-tar/

Editamos /usr/local/bin/rsnaptar para especificar el lugar donde guardaremos los tar, comentamos GPG y añadimos info al mail que se genera automaticamente.

# DIRECTORIES
TAR_DIR="/var/cache/rsnapshot-tar"
SNAPSHOT_DIR="/var/cache/rsnapshot/daily.0/"
...
#GPG="/usr/bin/gpg"
...
${CAT} << EOF | ${SENDMAIL}
To: ${TO_EMAIL}
Subject: [rsnapshot] Tar backup job complete - ${HOSTNAME}

Now is the time to backup the latest files from rsnapshot on ${HOSTNAME}

Tar files created on ${TAR_DIR}/${DATE}/
EOF

Editamos ‘/etc/cron.d/rsnapshot’ para que se ejecute cada semana:

# Weekly
# At 5:00 every monday
0  5      * * 1       root    /usr/local/bin/rsnaptar marble@localhost
Reportes

Si queremos recibir un reporte del backup cada cierto tiempo como medida para saber que se están efectuando:

cp /usr/share/doc/rsnapshot/examples/utils/rsnapreport.pl.gz /usr/local/bin/
gzip -d /usr/local/bin/rsnapreport.pl.gz
chmod 755 /usr/local/bin/rsnapreport.pl

Establecemos en /etc/rsnapshot.conf el nivel de verbose a 3 y añadimos –stats al comando rsync_long_args:

# Verbose level, 1 through 5.
# 1     Quiet           Print fatal errors only
# 2     Default         Print errors and warnings only
# 3     Verbose         Show equivalent shell commands being executed
# 4     Extra Verbose   Show extra verbose information
# 5     Debug mode      Everything
#
verbose     3

...

# Default rsync args. All rsync commands have at least these options set.
#
#rsync_short_args   -a
rsync_long_args --delete --numeric-ids --relative --delete-excluded --stats --timeout=10


# this script prints a pretty report from rsnapshot output
# in the rsnapshot.conf you must set
# verbose >= 3
# and add --stats to rsync_long_args
# then setup crontab 'rsnapshot daily 2>&1 | rsnapreport.pl | mail -s"SUBJECT" backupadm@adm.com
# don't forget the 2>&1 or your errors will be lost to stderr

Editamos /etc/cron.d/rsnapshot para hacer que el reporte se genere una vez a la semana:

# 4 times per day
0 */4     * * *       root    /usr/bin/rsnapshot hourly > /dev/null
# At 3:30 every day
30 3      * * *       root    /usr/bin/rsnapshot daily > /dev/null
# At 3:00 every monday
0  3      * * 1       root    /usr/bin/rsnapshot weekly > /dev/null
# At 2:30 every 1st of month
30 2      1 * *       root    /usr/bin/rsnapshot monthly > /dev/null

# Run rsnapshot once a week as it were hourly but with rsnapreport (send report by mail)

# I use it only to validate that backups are being done.
# When we execute rsnapshot daily/weekly/... rsync is not called, only a move is done and rsnapreport can not generate a report with that info.
# At 1:00 every monday
0  1      * * 1       root    /usr/bin/rsnapshot hourly 2>&1 | /usr/local/bin/rsnapreport.pl | mail -s"[rsnapshot] Weekly report" marble@localhost

11. Tests de stress

Sistema

Para poner a prueba la resistencia del sistema, podemos utilizar la herramienta ‘stress’ para consumir memoria RAM, CPU o disco duro de forma masiva y forzada:

apt-get install stress

Opciones:

 -t, --timeout N    timeout after N seconds
 -c, --cpu N        spawn N workers spinning on sqrt()
 -i, --io N         spawn N workers spinning on sync()
 -m, --vm N         spawn N workers spinning on malloc()/free()
     --vm-bytes B   malloc B bytes per vm worker (default is 256MB)
     --vm-hang N    sleep N secs before free (default is none, 0 is inf)
     --vm-keep      redirty memory instead of freeing and reallocating
 -d, --hdd N        spawn N workers spinning on write()/unlink()
     --hdd-bytes B  write B bytes per hdd worker (default is 1GB)
     --hdd-noclean  do not unlink files created by hdd workers

Ejemplo de consumo de memoria y CPU:

$ stress --cpu 8 --io 4 --vm 2 --vm-bytes 128M --vm-keep --timeout 10s
stress: info: [13247] dispatching hogs: 8 cpu, 4 io, 2 vm, 0 hdd
stress: info: [13247] successful run completed in 10s

Únicamente consumo de memoria:

stress --vm 2 --vm-bytes 128M --vm-keep --timeout 10s

Conexiones HTTP con httperf

Podemos realizar pruebas contra un servicio web para ver su capacidad de respuesta:

apt-get install httperf

Por ejemplo, crear 100 conexiones con un ratio de 10 por segundo:

httperf --hog --server www --num-conn 100 --rate 10 --timeout 5

Crear 10 sesiones a un ratio de 1 por segundo, cada sesion consisten en 5 llamadas espaciadas por 2 segundos:

httperf --hog --server=www --wsess=10,5,2 --rate 1 --timeout 5

Ejemplo de ejecución:

$ httperf --hog --server=localhost --port 3000 --wsess=10,5,2 --rate 1 --timeout 5
httperf --hog --timeout=5 --client=0/1 --server=localhost --port=3000 --uri=/ --rate=1 --send-buffer=4096 --recv-buffer=16384 --wsess=10,5,2.000
Maximum connect burst length: 1

Total: connections 50 requests 90 replies 50 test-duration 30.088 s

Connection rate: 1.7 conn/s (601.8 ms/conn, < =11 concurrent connections)
Connection time [ms]: min 308.3 avg 4186.5 max 6065.9 median 4252.5 stddev 1489.6
Connection time [ms]: connect 0.1
Connection length [replies/conn]: 1.000

Request rate: 3.0 req/s (334.3 ms/req)
Request size [B]: 86.0

Reply rate [replies/s]: min 1.2 avg 1.6 max 1.8 stddev 0.2 (6 samples)
Reply time [ms]: response 2530.6 transfer 52.8
Reply size [B]: header 414.0 content 12642.0 footer 0.0 (total 13056.0)
Reply status: 1xx=0 2xx=0 3xx=0 4xx=0 5xx=50

CPU time [s]: user 2.70 system 12.57 (user 9.0% system 41.8% total 50.8%)
Net I/O: 21.4 KB/s (0.2*10^6 bps)

Errors: total 40 client-timo 0 socket-timo 0 connrefused 0 connreset 40
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

Session rate [sess/s]: min 0.00 avg 0.33 max 1.00 stddev 0.39 (10/10)
Session: avg 5.00 connections/session
Session lifetime [s]: 20.9
Session failtime [s]: 0.0
Session length histogram: 0 0 0 0 0 10

Conexiones HTTP con Apache Benchmarking tool

Otra herramienta para testear la capacidad de un servicio web:

apt-get install apache2-utils

Por ejemplo, enviar 1000 request mediante 5 procesos concurrentes:

$ ab -n 1000 -c 5 http://localhost/
Benchmarking localhost (be patient)


Server Software:        Apache
Server Hostname:        localhost
Server Port:            80

Document Path:          /
Document Length:        612 bytes

Concurrency Level:      5
Time taken for tests:   0.382452 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      852852 bytes
HTML transferred:       612612 bytes
Requests per second:    2614.71 [#/sec] (mean)
Time per request:       1.912 [ms] (mean)
Time per request:       0.382 [ms] (mean, across all concurrent requests)
Transfer rate:          2175.44 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       1
Processing:     0    1   8.6      0      98
Waiting:        0    0   0.3      0       1
Total:          0    1   8.6      0      98

Percentage of the requests served within a certain time (ms)
  50%      0
  66%      1
  75%      1
  80%      1
  90%      1
  95%      1
  98%      1
  99%     74
 100%     98 (longest request)

Resulta interesante hacer la prueba con URLs que hagan diferentes tipos de procesado:

  • Páginas estáticas
  • Páginas dinámicas PHP
  • CGIs

Si consultamos el manual (man ab), es posible indicar a ab que utilice cookies (-C cookie-name=value) o HTTP authentication (-P proxy-auth-username:password).
Otro ejemplo para realizar conexiones con HTTP KeepAlive, se abriran 10 conexiones y durante 30 segundos se utilizaran para enviar peticiones al servidor:

# ab -kc 10 -t 30 http://localhost/

Benchmarking localhost (be patient)

Server Software:        Apache
Server Hostname:        localhost
Server Port:            80

Document Path:          /
Document Length:        612 bytes

Concurrency Level:      10
Time taken for tests:   5.824757 seconds
Complete requests:      50000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    49511
Total transferred:      44432403 bytes
HTML transferred:       30603060 bytes
Requests per second:    8584.05 [#/sec] (mean)
Time per request:       1.165 [ms] (mean)
Time per request:       0.116 [ms] (mean, across all concurrent requests)
Transfer rate:          7449.41 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       1
Processing:     0    0   2.6      0     127
Waiting:        0    0   2.6      0     127
Total:          0    0   2.6      0     127

Percentage of the requests served within a certain time (ms)
  50%      0
  66%      1
  75%      1
  80%      1
  90%      2
  95%      2
  98%      4
  99%      4
 100%    127 (longest request)

12. Issue tracking

La gestión de incidencias o las solicitudes de alta/baja/modificación del sistema puede ser efectuada mediante trac.

16 thoughts on “Securización de un sistema Ubuntu (GNU/Linux)

  1. Tengo un problema con el quotacheck estoy usando Ubuntu 10.04, el problema es q aunq reinicie el sistema, siempre entra en conflicto con el famoso directorio .gvfs, alguna sugerencia please?

    1. Una opción es habilitar el usuario root. Para ello tienes que ponerle una contraseña con passwd. Luego accedes al sistema con root y lo intentas de nuevo. Ya no debería de darte más problemas.

Leave a Reply to Gestión centralizada de usuarios con OpenLDAP | Marble Station Cancel reply

Your email address will not be published. Required fields are marked *