Unfortunately SELinux is so incredibly complex that few people understand it. If anybody can contribute proper SELinux support or tell me what Phusion Passenger is supposed to do to make it play nice with the default policies, then that would be greatly appreciated.
Hongli Lai, principal author of Phusion Passenger, in a mailing list post
At my day job, I’ve been tasked with deploying an application based on the Sinatra framework on one of our production webservers. Because we are a CentOS shop, and Apache is the path of least resistance, I chose to deploy the application using Passenger Phusion, also known as mod_rails.
A colleague of mine set Passenger up pretty easily on our test/QA server, and, after mutually flogging the Apache configuration, got the Sinatra app up and running. We ran the application through QA, and after a week or two, decided that we’d like to beta test this on our actual production server.
We repeated the same steps to get Passenger installed, but this time, nothing worked at all. Trying to hit the web application returned 403 Forbidden. Weird.
Then I remember a salient difference between our test environment and production: SELinux was enabled and enforced on our production system, but not on our QA server. Uh oh. Sure, we could have just disabled SELinux (an easy fix), but then we wouldn’t benefit from SELinux itself. Plus, everything else was running just fine with SELinux enabled, so why shouldn’t Passenger?
Down the RabbitRat Hole
The Passenger “documentation for Apache” notes that you have to set the context of the Passenger codebase
to httpd_sys_content_t. I followed the directions, and things still didn’t work. No problem, I thought, I’ll just turn SELinux down to permissive mode so I can get the audit messages and use audit2allow to make a policy for Passenger! Sensing I had a slam dunk idea, I did just that, generated my own Passenger policy, and … nothing. For some stupid reason, the Passenger backend process kept dying, leading to messages in /var/log/httpd/error_log like these:
[Wed Mar 17 15:39:54 2010] [error] * Passenger could not be initialized because of this error: Could not connect to the ApplicationPool server: Connection reset by peer (104)
Googling on that error message led me to some lame comments, so I decided to dig into SELinux itself. Lo and behold, the Fedora Project have an excellent FAQ on SELinux, which included something that I suspected might be happening:
Q: I get a specific permission denial only when SELinux is in enforcing mode, but I don’t see any audit messages in
/var/log/messages(or/var/log/audit/audit.logif using the audit daemon). How can I identify the cause of these silent denials?A: The most common reason for a silent denial is when the policy contains an explicit
dontauditrule to suppress audit messages. Thedontauditrule is often used this way when a benign denial is filling the audit logs.To look for your particular denial, enable auditing of all
dontauditrules:
semodule -b /usr/share/selinux/targeted/enableaudit.pp
Well, it turns out that one of the dontaudit rules governed the reading from and writing to UNIX stream sockets that Passenger uses to talk to its backend. It also seemed to screw up the backend’s ability to set its own signal handlers.
Anyways, after one last run of audit2allow, I had a working policy.
tl;dr – The Phusion Passenger SELinux Policy
Here it is.
module passenger 1.0;
require {
type httpd_tmp_t;
type devpts_t;
type httpd_sys_script_t;
type security_t;
type httpd_t;
type unconfined_t;
type selinux_config_t;
type hi_reserved_port_t;
type httpd_sys_content_t;
type var_t;
type cert_t;
class file { getattr read create append };
class process { siginh signal noatsecure rlimitinh };
class unix_stream_socket { read write shutdown };
class chr_file { read write };
class capability { setuid dac_override chown fsetid setgid fowner };
class fifo_file { setattr create getattr unlink };
class sock_file { write getattr setattr create unlink };
class lnk_file { read getattr };
class udp_socket name_bind;
class dir { write read search add_name };
}
#============= httpd_sys_script_t ==============
allow httpd_sys_script_t cert_t:dir search;
allow httpd_sys_script_t cert_t:file { read getattr };
allow httpd_sys_script_t cert_t:lnk_file read;
allow httpd_sys_script_t devpts_t:chr_file { read write };
allow httpd_sys_script_t httpd_sys_content_t:fifo_file setattr;
allow httpd_sys_script_t httpd_sys_content_t:sock_file { create unlink setattr };
allow httpd_sys_script_t httpd_t:unix_stream_socket { read write };
allow httpd_sys_script_t httpd_tmp_t:fifo_file setattr;
allow httpd_sys_script_t httpd_tmp_t:sock_file { write create unlink setattr };
allow httpd_sys_script_t self:capability { setuid chown fsetid setgid fowner dac_override };
allow httpd_sys_script_t unconfined_t:process signal;
allow httpd_sys_script_t var_t:dir { write read add_name };
allow httpd_sys_script_t var_t:file { read getattr create append };
#============= httpd_t ==============
allow httpd_t hi_reserved_port_t:udp_socket name_bind;
allow httpd_t httpd_sys_content_t:fifo_file { create unlink getattr setattr };
allow httpd_t httpd_sys_content_t:sock_file { getattr unlink setattr };
allow httpd_t httpd_sys_script_t:process { siginh rlimitinh noatsecure };
allow httpd_t httpd_sys_script_t:unix_stream_socket { read write shutdown };
allow httpd_t httpd_tmp_t:fifo_file { create unlink getattr setattr };
allow httpd_t httpd_tmp_t:sock_file { getattr unlink setattr };
allow httpd_t security_t:dir search;
allow httpd_t self:capability { fowner fsetid };
allow httpd_t selinux_config_t:dir search;
allow httpd_t var_t:file { read getattr };
allow httpd_t var_t:lnk_file { read getattr };
To install this policy on your SELinux enabled box:
- Copy and paste the above policy into a file called
passenger.te. - Run
checkmodule -M -m -o passenger.mod passenger.te. - Run
semodule_package -o passenger.pp -m passenger.mod. - Finally, to install the policy, run
semodule -i passenger.pp.
Final Thoughts
Having a working policy is great if you want to make things go, but it begs the following questions:
- Is the policy for Passenger that I’ve generated insecure? How can it be improved?
- Are there any ways that Passenger Phusion could be improved to fit into the RHEL/Fedora/CentOS base SELinux policy without having to go through such gymnastics, or is this the norm for applications built on Apache?
As Rails and Ruby have been slowly moving into “enterprisey” shops, it would seem that getting it to play nice with the largest commercial Linux distribution would be a priority for, well, someone. Unfortunately, Ruby has had a history of being being second-class at RedHat; after all, RHEL is still using 1.8.5, which was originally released in 2006, and many things—one being Rails—recommend if not require at least 1.8.7.
RedHatters and SELinux gurus: the ball is in your court, now.