July 6, 2012

Introduction to SELinux: Modification of the Targeted Policy for Third Party Web Applications

Many of us are engaged in configuring production servers for web projects. I’m not going to explain how to set up Apache or Nginx — perhaps, you know it even better than me. However, an important aspect of creating front-end servers still remains uncovered: that is security subsystems configuration. 'Disable SELinux,' – that is a standard recommendation of the majority of amateur manuals.

I think it’s a hasty decision as the process of configuring security subsystems in the mode of ‘mild’ policy is often rather trivial.

Today I’m going to tell you about several methods of configuring the SELinux security subsystem, applied to Red Hat (CentOS) OS family. As an example, we’ll configure a set of an Apache web server + mod_wsgi + Django + ZEO on CentOS v. 5.8.

Configuring Linux security system, we are limited by Discretionary Access Control (DAC). There are three levels of standard rwx rights (User, Group and Other) and POSIX ACL. Therefore, an application with user rights has access to all resources available for the relevant user. If the application is compromised, this may lead to unfortunate results.

SELinux (Security-Enhanced Linux) is a security subsystem that provides Mandatory Access Control (MAC), operating simultaneously with traditional discretionary system. Access rights are enforced by system policies. Incorporated into the kernel, SELinux comes out of the box in the Red Hat (CentOS) family. For the simplest solution of the given issues we need the targeted policy, which defines rules for the major part of typical applications. Thus without any extra efforts we ensure basic protection of main services. The policy rules imply that all applications that are not included in it will run as part of DAC without being limited by SELinux.

'But why do we need that SELinux?' you'll ask. The answer is quite simple: in some cases the security subsystem will allow at least to log unauthorized access issues and ideally to prevent them. Anyway an attacker will have to act within the limits defined for a specific process.

We will use contexts, domains, and access vectors to customize configurations. Events, significant from the point of view of security, are captured by SELinux at the kernel level. The subsystem security mechanisms take effect after the DAC rules. SELinux provides RBAC (Role-Based Access Control), TE (Type Enforcement) and optionally MLS (Multi-Level Security). Each system object has a specific context (type). Basing on the policy rules the security subsystem either allows an operation to be performed or blocks it, and the process receives an error message. All the decisions made by SELinux are cached into Access Vector Cache (AVC).

SELinux context contains data on a user, role, type, and level. We will use the type, which is an attribute of Type Enforcement. It is defined by a domain for processes and by a type for files. Authorized interaction types are defined by SELinux rules. Access is only allowed if a specific policy rule exists for it.

I want to pay special attention to the technology of domain transitions. In SELinux, transition from one domain to another may become possible if a process in the source domain executes an application from a file with the entrypoint type of the new domain.

Contexts, domains and access rules for more than 200 applications are created and described in the targeted policy. You have an opportunity both to expand the policy and to act within the given contexts. Basic policies were developed with due consideration of almost all main use scenarios. There will be practically no need to change anything to create typical solutions.

So it seems unjustified to reject SELinux security mechanism when implementing template solutions. Some difficulties appear when additional software is installed. In our case it is the mod_wsgi and ZEO modules. We will have to change SELinux configuration to maintain its workability.

I use CentOS 5.8 (kernel 2.6.18-308.1.1.el5) with the Apache web server (httpd-2.2.3-63.el5.centos.1) in my example. Python (2.7.2), Django (1.4), mod_wsgi (3.3), and Zope (3.4.0) are additionally installed on it from the source codes. (The very simple installation process of this software does not deserve any specific description).

First of all, we will have to extend the SELinux policy for httpd. The default configuration is designed to isolate the process in case it is compromised. However, for httpd to access your project, some changes should be made. The policy authors embedded full logic of the application functioning with context restrictions. File markup in your system can be viewed with the help of a simple command:

semanage fcontext -l | grep httpd

The policy regulates access to each represented type. The full list of contexts is available on the relevant man page (man httpd_selinux). We are interested in the httpd_sys_content_t type, which allows the daemon and scripts to access files. Therefore, beside default DAC rules it is necessary to specify directory and project files contexts. It can be done simultaneously with the help of chcon command:

chcon -R -t "httpd_sys_content_t" /your/project

However, my advice is to specify the type by a rule. It will ensure subsequent automatic type assignment when new files are added.

semanage fcontext -a-t httpd_sys_content_t "/your/project(/.*)?"
restorecon -R /your/project

My demonstration project uses Django with ZoDB database. ZEO is used as a tool to communicate with the database. Due to the fact that this software is independent, it is necessary to ensure its proper functioning within SELinux. I assume, it is reasonable to launch ZEO with the apache user rights in the httpd_t domain to provide isolation. Let's specify the launch initialization script in the daemon mode. I'm not going to list the whole script because it is rather large. We'll focus on the principal issues:

/usr/local/bin/zeoctl -d -s /var/run/zeo/zsock -C /etc/zeo/zeoctl.conf start

Don't forget that it is necessary to bring your initialization script to a relevant context to avoid problems with further type transition in SELinux.

chcon –t "initrc_exec_t" /etc/init.d/your_init_script

Specify a necessary user in the configuration file.

program /usr/local/bin/runzeo -a /var/run/zeo/zeo.socket -f /var/your_db_path/db.fs
daemon True
user  apache

A socket is used as a link between ZEO and Django. Due to the fact that httpd works in the httpd_t domain, it is necessary to coordinate the types and DAC rules so that the application can connect to it. To achieve it, we will prepare the /var/run/zeo directory and specify an appropriate context for it. We will use the -f -s key to restrict automatic context assigning to sockets only and the -f -d key to assign the context to the directory.

semanage fcontext -a -f -d -t 'httpd_sys_script_rw_t' '/var/run/zeo(/.*)?'
semanage fcontext -a -f -s -t 'httpd_sys_script_rw_t' '/var/run/zeo(/.*)?'
restorecon –R /var/run

It is necessary to specify the communication socket forced location in the ZEO configuration file.

address /var/run/zeo/zeo.socket

Since we plan to run the application as the apache user, we should pay attention to types’ transitivity. We want the running process to obtain the httpd_t type. The files /usr/local/bin/zeoctl and /usr/local/bin/runzeo will have the bin_t context by default. Since they are called from the unconfined_t domain, it is necessary to follow the chain of contexts transition. First of all, the script, to which the initrc_exec_t type was assigned, will be activated from /etc/init.d/. Let's find a transition chain for this case.

sesearch -T -s unconfined_t -t initrc_exec_t | grep " initrc_exec_t"

The found transition chain looks like unconfined_t initrc_exec_t: process initrc_t. We can see that the process will get the initrc_t context. So now we have to find a transition chain that will bring us to the necessary httpd_t type.

sesearch -T -s initrc_t | grep "process httpd_t"

The result will be initrc_t httpd_exec_t: process httpd_t. For the transition to be done we should assign the the httpd_exec_t context to the executable files.

semanage fcontext -a -t httpd_exec_t "/usr/local/bin/zeoctl"
semanage fcontext -a -t httpd_exec_t "/usr/local/bin/runzeo"
restorecon -R /usr/local/bin

Now we are to add permissions for the httpd connection to the socket into the SELinux policy. There are several ways to do it. The easiest way from a user's point of view is audit2allow, a utility which allows generating modules for a policy based on AVC messages from system logs. Be careful using the utility because it only creates permissions for specific actions (detailed instruction is available on the developer's website).

The second way is to generate the module manually, compile and set it for the current policy. As this method provides good visualization of the process, we will manually generate modules for ZEO. We will provide httpd_t with the rights to create and work with the socket to which the httpd_sys_script_rw_t type is assigned. To achieve it, we'll create the /tmp/httpdAllowDjangoZEO.te file with the following content:
module httpdAllowDjangoZEO 1.0;
require {
        type httpd_t;
        type httpd_sys_script_rw_t;
        class sock_file link;
        class sock_file setattr;
        class sock_file create;
        class sock_file unlink;
        class sock_file write;
#============= httpd_t ==============
allow httpd_t httpd_sys_script_rw_t:sock_file link;
allow httpd_t httpd_sys_script_rw_t:sock_file setattr;
allow httpd_t httpd_sys_script_rw_t:sock_file create;
allow httpd_t httpd_sys_script_rw_t:sock_file unlink;
allow httpd_t httpd_sys_script_rw_t:sock_file write;
Then we should create and compile a module.The checkmodule and semodule_package commands will be of use. For the module to be installed for the current policy we'll need the semodule utility.

checkmodule -M -m -o /tmp/httpdAllowDjangoZEO.mod /tmp/httpdAllowDjangoZEO.te
semodule_package --outfile /tmp/httpdAllowDjangoZEO.pp --module /tmp/httpdAllowDjangoZEO.mod
semodule -i httpdAllowDjangoZEO.pp

Finally, we'll configure location contexts of ZoDB database and the ZEO configuration file. In the course of work we'll have to generate technical .lock files. Thus database storage location should have an appropriate context, which will allow file creation. httpd_sys_script_rw_t will suit perfectly.

semanage fcontext –a –t «httpd_sys_script_rw_t» “/var/your_db_path(/.*)?”

There is a specialized type (httpd_config_t) for configuration files.

semanage fcontext –a –t « httpd_config_t» “/etc/zeo(/.*)?”

Now we only have to restart services to complete configuration process.

These rules will allow the whole chain to work properly. Besides we provide the server and its services with SELinux additional protection. If any of Django or ZEO components are compromised, attackers will face restrictions and won't have any chance to reach the system, since they will act within the httpd_t domain.

So we managed to obtain a functional httpd user configuration without disabling SELinux. You can similarly create regulatory policies for any of your applications. This doesn't require ling time or deep theoretical knowledge. So maybe SELinux shouldn't be disabled?

I advise you to check SELinux description for Fedora 13 for deeper understanding of SELinux.

Author: Kirill Ermakov, Positive Research


  1. This comment has been removed by a blog administrator.

  2. This comment has been removed by a blog administrator.

  3. This comment has been removed by a blog administrator.