Tuesday, January 05, 2010

Apache, mod_wsgi, Django, SELinux

I've come to rely on the fact that if something isn't working after an installation on Centos 5, it is probably due to bad SELinux permissions. I've learned to live with SELinux and generally can handle any complication it throws at me. I've learned to keep an eye on /var/log/messages and /var/log/audit/audit.log. I've learned to use sealert and how to make my own local policies. Yet still it finds a way to throw me under the bus.

This time around I'm installing Django under Apache. I finally moved from mod_python to mod_wsgi. With mod_wsgi, less configuration is needed in httpd.conf, but more is needed in an external config file. For example, my original mod_python configuration looked like this:


<Directory "/var/www/html/python">
AddHandler mod_python .py
PythonHandler mptest
PythonDebug On
</Directory>

<Location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE hsm.settings
PythonDebug On
PythonPath "['/home/stuff/src/python', '/home/stuff/src/python/hsm'] + sys.path"
</Location>


The second section is the Django deployment. (The first I included to remind myself how simple it is to deploy a very basic test program using mod_python. )

With mod_wsgi, the apache config looks like this. A single line identifies the location of the wsgi configuration, and the Directory element is used to give Apache permission to access the script.


WSGIScriptAlias / /usr/local/www/wsgi-scripts/hsm.wsgi

<Directory /usr/local/www/wsgi-scripts>
Order allow,deny
Allow from all
</Directory>



My custom wsgi script includes these lines. The last line keeps the wsgi handler from puking on
print lines that might be included in your file.


import os
import sys

os.environ['DJANGO_SETTINGS_MODULE'] = 'hsm.settings'

import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

sys.stdout = sys.stderr


For this particular Django application, I decided to use sqlite3 instead of mysql. But attempting to launch proved problematic.



[Tue Jan 05 11:02:08 2010] [error] [client 10.8.8.62] return query.execute_sql(return_id)
[Tue Jan 05 11:02:08 2010] [error] [client 10.8.8.62] File "/usr/lib/python2.4/site-packages/django/db/models/sql/subqueries.py", line 320, in execute_sql
[Tue Jan 05 11:02:08 2010] [error] [client 10.8.8.62] cursor = super(InsertQuery, self).execute_sql(None)
[Tue Jan 05 11:02:08 2010] [error] [client 10.8.8.62] File "/usr/lib/python2.4/site-packages/django/db/models/sql/query.py", line 2369, in execute_sql
[Tue Jan 05 11:02:08 2010] [error] [client 10.8.8.62] cursor.execute(sql, params)
[Tue Jan 05 11:02:08 2010] [error] [client 10.8.8.62] File "/usr/lib/python2.4/site-packages/django/db/backends/util.py", line 19, in execute
[Tue Jan 05 11:02:08 2010] [error] [client 10.8.8.62] return self.cursor.execute(sql, params)
[Tue Jan 05 11:02:08 2010] [error] [client 10.8.8.62] File "/usr/lib/python2.4/site-packages/django/db/backends/sqlite3/base.py", line 193, in execute
[Tue Jan 05 11:02:08 2010] [error] [client 10.8.8.62] return Database.Cursor.execute(self, query, params)
[Tue Jan 05 11:02:08 2010] [error] [client 10.8.8.62] OperationalError: attempt to write a readonly database


To read and write to the sqlite database, I had to:

1. Ensure the database file and its parent directory was owned by the apache user. (The location of the database file is specified in the Django settings.py file for your project.)

2. Add a Directory entry to the httpd.conf file for the location of my database file. By default, apache does not have access to directories not under DocumentRoot.

3. Apply SELinux level permissions to the database file and its parent directory.

It took me quite awhile to discover that I had SELinux level permissions because I did not receive notifications in the /var/log/messages file. Normally all security exceptions arrive there and it is easy enough to tail the file and look for events. For whatever reason, the alerts were not appearing in the log, at least not every time. For example, the settroubleshoot alert browser (sealert -b) showed the exact problem that occurred at 11:02 AM (below), but the /var/log/messages file had no corresponding entry. The log did have a similar message from 10:59. The log either does not receive duplicate messages for SELinux events, or that some kind of bug is to blame.


Summary:

SELinux is preventing the httpd from using potentially mislabeled files
./sqlite3.db (usr_t).

Detailed Description:

SELinux has denied the httpd access to potentially mislabeled files
./sqlite3.db. This means that SELinux will not allow httpd to use these files.
Many third party apps install html files in directories that SELinux policy
cannot predict. These directories have to be labeled with a file context which
httpd can access.

Allowing Access:

If you want to change the file context of ./sqlite3.db so that the httpd daemon
can access it, you need to execute it using chcon -t httpd_sys_content_t
'./sqlite3.db'. You can look at the httpd_selinux man page for additional
information.



The solution was to modify the SELinux permissions on the folder that contained the sqlite database:


sudo chcon -R system_u:object_r:httpd_sys_content_t database_folder


Hope this information will help the next unlucky person to deploy in a similar environment.

No comments: