in Developer Experience

Allowing Nginx to use a Puma/Unicorn UNIX socket with SELinux

If you are deploying Ruby web application on Fedora with Puma (or other) application server and Nginx web server as a proxy, you might encounter SELinux forbidding Nginx to use the Puma socket. Here is how to create a SELinux policy module that you can use during your server provisioning.

First of all, how can we find out that our 502 Bad Gateway error produced by Nginx is actually connected with SELinux? Let’s look at Nginx logs first to see if it’s not something else:

# tail /var/log/nginx/error.log
...
2016/04/04 08:47:05 [crit] 6624#0: *713 connect() to unix://var/run/puma/app.sock failed (13: Permission denied) while connecting to upstream, client: 36.85.249.146, server: **.**.**.**, request: "GET / HTTP/1.1", upstream: "http://unix://var/run/puma/app.sock:/", host: "**.**.**.**"

So this clearly tells us there is a permission error accesing our /var/run/puma/app.sock. Let’s see what the SELinux logs contain:

# tail /var/log/audit/audit.log
...
type=MAC_STATUS msg=audit(1459759618.872:689918): enforcing=1 old_enforcing=0 auid=0 ses=90
type=AVC msg=audit(1459759625.320:689919): avc:  denied  { write } for  pid=6624 comm="nginx" name="app.sock" dev="tmpfs" ino=425982 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:var_run_t:s0 tclass=sock_file permissive=0

Seems like we are right. To confirm lets temporally disable SELinux (setenforce 0). If things are working without SELinux running, it’s clearly a SELinux issue. Let’s see what would SELinux suggests us to change by using a tool called audit2allow.

Note: if you are missing some of the SELinux policy tools, install policycoreutils-python and policycoreutils-python-utils packages.

# grep nginx /var/log/audit/audit.log | audit2allow -m nginx

The output gives us suggestions on our future policy file. My final policy file looks like this:

# cat nginx.te
module nginx 1.0;

require {
	type unconfined_t;
	type user_home_t;
	type httpd_t;
	type var_run_t;
	class file { read open };
	class sock_file write;
	class unix_stream_socket connectto;
}

#============= httpd_t ==============

allow httpd_t unconfined_t:unix_stream_socket connectto;
allow httpd_t var_run_t:sock_file write;
allow httpd_t user_home_t:file { read open };

So what that all means? We need to allow httpd_t-labeled process (which is Nginx in our case) to connect to our UNIX socket and write to it (the socket is located under /var/run/ therefore the default var_run_t label). And besides that we need it to read from a user home directory (since our app files are located in the appuser’home). That means, don’t just copy this policy file, but find out what policies your own setup need.

Afterwards we need to compile it into a SELinux module and load the module with semodule:

# checkmodule -M -m -o nginx.mod nginx.te
# semodule_package -o nginx.pp -m nginx.mod
# semodule -i nginx.pp

If everything works I suggest you to commit the nginx.te policy file in your source control and compile it to a module during provisioning with the commands mentioned above.

Also note that by investigating SELinux issues you might fix something but still getting into some other troubles, so you need to investigate and run audit2allow more times. The policy shown in this post is one that I merged by going through the process twice.

Write a Comment

Comment