domingo, 6 de noviembre de 2011

RHEL/CentOS: SELinux for Apache virtualhosts

Sabemos que los virtualhost se utilizan para poder alojar en un mismo webserver, con una única IP, varios sitios webs. Muchos administradores de hosting que implementan esto sobre Red Hat o CentOS, al momento de la instalación desactivan SELinux para evitar conflictos de acceso a los distintos recursos del sistema.

SELinux por trabaja en dos modos, Enforcing, en el cual por default todo se deniega, y se permite acceso únicamente a lo que se explicitamente permitido, y Permissive, en lo cual por default todo se deniega, pero en vez de bloquear el acceso, simplemente notifica esto en los logs (/var/log/messages y /var/log/audit/audit.log) y debe ser utilizado solamente para testing.
Para verificar el estado de SELinux utilizamos el comando sestatus:
# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /selinux
Current mode:                   enforcing
Mode from config file:          enforcing
Policy version:                 26
Policy from config file:        targeted
O mediante el comando getenforce:
# getenforce
Enforcing
Para cambiar de Enforcing a Permisive en tiempo real podemos utilizar setenforce:
# setenforce 0
# getenforce
Permissive
# setenforce 1
# getenforce
Enforcing
Si queremos saber como esta configurado en el sistema SELinux podemos revisar el archivo /etc/sysconfig/selinux que es un link simbólico hacia /etc/selinux/config:

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - SELinux is fully disabled.
SELINUX=enforcing
# SELINUXTYPE= type of policy in use. Possible values are:
# targeted - Only targeted network daemons are protected.
# strict - Full SELinux protection.
SELINUXTYPE=targeted

Cualquier modificación que hagamos en este archivo, requiere que reiniciemos el sistema para poder aplicar los cambios.
Un administrador, puede hacer uso de los directorios homes para contener los DocumentRoot de los usuarios, en un path que no es el originalmente permitido por SELinux (/var/www/), por lo cual debemos cambiar los permisos del mismo para poder acceder, veamos como hacer esto.

Una vez que httpd se encuentre instalado en el servidor, es importante definir los virtualhost en el archivo de configuración de Apache (Al final de /etc/httpd/conf/httpd.conf o añadirlos al directorio /etc/httpd/conf.d).
En este caso vamos a definir dos virutalhost, virtualhost-1 y virtualhost-2:
<VirtualHost ip.address.of.virtualhost-1>
    ServerAdmin webmaster@virtualhost-1.com
    DocumentRoot /home/user/www/virtualhost-1
    ServerName virtualhost-1
    ErrorLog logs/virtualhost-1-error_log
    CustomLog logs/host.virtualhost-1-access_log common
</VirtuailHost> 
<VirtualHost ip.address.of.virtualhost-2>
    ServerAdmin webmaster@virtualhost-2.com
    DocumentRoot /home/user/www/virtualhost-2
    ServerName virtualhost-2
    ErrorLog logs/virtualhost-2-error_log
    CustomLog logs/host.virtualhost-2-access_log common
</VirtualHost>
Como vemos los DocumentRoot de ambos virtualhost no son los permitidos por SELinux (que intenta servir el contenido de /var/www o /srv), por lo cual al intentar acceder a los mismos vamos a obtener un error del tipo Forbidden, así que debemos permitirlos de manera explicita dentro de la policy de SELinux. Podemos verificar esto con semanage fcontext -l:
# semanage fcontext -l | grep httpd_
/var/www(/.*)?            all files   system_u:object_r:httpd_sys_content_t:s0
/var/www(/.*)?/logs(/.*)? all files   system_u:object_r:httpd_log_t:s0
... 
Por lo cual, lo que tenemos que hacer es cambiar el contexto de dichos directorios, y vamos a realizar esto de manera recursiva para que afecte a todos los archivos que se encuentran en su interior. Para esto vamos a utilizar el comando chcon.
# chcon -R -v -u system_u -r object_r -t httpd_sys_content_t /home/user/www/virtualhost-{1,2}
El parámetro -R hace esto recursivo, -v es verbose, con -u se especifica el usuario (system_u), con -r se especifica el rol, y con -t el tipo, que para que caso de httpd es httpd_sys_content_t.
Otra forma de lograr el mismo resultado es por referencia, esto es, pasandole como tal un directorio que tenga el contexto previamente configurado. Para esto utilizamos el parámetro --reference:
# chcon -R -v --reference=/var/www/html /home/user/www/virtualhost-{1,2}
Ahora, nuestra configuración persistirá a través de reboots, pero no soportaría un releabel (restorecon), por lo cual debemos commitear dichos cambios a la policy. Por lo cual, para esto, vamos a utilizar semange, dicho comando no se encuentra instalado por default en RHEL 6/CentOS 6, por lo cual debemos instalar el paquete policycoreutils-python mediante yum o rpm.
# semanage fcontext -a -t httpd_sys_content_t "/home/user/www/virtualhost-1(/.*)?"
# semanage fcontext -a -t httpd_sys_content_t "/home/user/www/virtualhost-2(/.*)?"
Ahora si hacemos un restorecon sobre los DocumentRoot de nuestros virtualhost vemos que soportan el relabel:
# restorecon -R -v /home/user/www/
# ls -Z /home/user/virtualhost-{1,2}
drwx------. root root system_u:object_r:httpd_sys_content_t:s0 virtualhost-1
drwx------. root root system_u:object_r:httpd_sys_content_t:s0 virtualhost-2
Pero esto todavía tiene una vuelta de tuerca más, para permitir el acceso de SELinux a la home, tenemos que modificar el valor de un booleano.

Para listar los booleanos, se pueden utilizar dos comandos, uno es getsebool y el otro, semanage, vamos a utilizar el segundo, ya que este nos provee además una breve descripción de lo que hace cada booleano:
# semanage boolean -l | grep httpd
httpd_can_network_relay        -> off   Allow httpd to act as a relay
httpd_can_network_connect_db   -> off   Allow HTTPD scripts and modules to connect to databases.
httpd_enable_homedirs          -> off   Allow httpd to read home directories
...
El booleano que nos interesa es httpd_enable_homedirs, como cualquier booleano, tiene dos posibles valores, true o false, en este caso vemos que esta seteado en off; Por lo cual, vamos a cambiarle su valor a on utilizando setsebool y el parámetro -P para que persista atravéz de los reboots del sistema:
# setsebool -P httpd_enable_homedirs 1
En caso de querer saber más acerca de la configuración de SELinux para cualquier deamon, es interesante ver las páginas de manual asociadas al mismo, para ello con man -k http | grep _selinux podemos listar estas:
# man -k httpd | grep _selinux 
httpd_selinux (8)    - Security Enhanced Linux Policy for the httpd daemon
Ahora si se reinicia el servicio httpd, se podrá acceder a los vitualhost creados, fuera del DocumentRoot que por default se habilita en SELinux. 

3 comentarios: