Tuesday, July 29, 2014

Nginx on CentOS 7 & SELinux

Install Nginx is quite easy here https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-centos-7

About Nginx

Nginx is a high performance web server software. It is a much more flexible and lightweight program than Apache HTTP Server.
This tutorial will teach you how to install and start Nginx on your CentOS 7 server.


The steps in this tutorial require the user to have root privileges. You can see how to set that up by following steps 3 and 4 in the Initial Server Setup with CentOS 7 tutorial.

Step One—Add Nginx Repository

To add the CentOS 7 Nginx yum repository, open terminal and use the following command:
sudo rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm

Step Two—Install Nginx

Now that the Nginx repository is installed on your server, install Nginx using the followingyum command:
sudo yum install nginx
After you answer yes to the prompt, Nginx will finish installing on your virtual private server (VPS).

Step Three—Start Nginx

Nginx does not start on its own. To get Nginx running, type:
sudo systemctl start nginx.service
You can do a spot check right away to verify that everything went as planned by visiting your server's public IP address in your web browser (see the note under the next heading to find out what your public IP address is if you do not have this information already):
You will see the default CentOS 7 Nginx web page, which is there for informational and testing purposes. It should look something like this:
CentOS 7 Nginx Default
If you see this page, then your web server is now correctly installed.
Before continuing, you will probably want to enable Nginx to start when your system boots. To do so, enter the following command:
sudo servicectl enable nginx.service
Congratulations! Nginx is now installed and running!

How To Find Your Server's Public IP Address

You can run the following command to reveal your server’s public IP address:
ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//'

Server Root and Configuration

If you want to start serving your own pages or application through Nginx, you will want to know the locations of the Nginx configuration files and default server root directory.

Default Server Root

The default server root directory is /usr/share/nginx/html. Files that are placed in there will be served on your web server. This location is specified in the default server block configuration file that ships with Nginx, which is located at /etc/nginx/conf.d/default.conf.

Server Block Configuration

Any additional server blocks (known as Virtual Hosts in Apache) by creating new configuration files in /etc/nginx/conf.d. Files that end with .conf in that directory will be loaded when Nginx is started.

Nginx Global Configuration

The main Nginx configuration file is located at /etc/nginx/nginx.conf. This is where you can change settings like the user that runs the Nginx daemon processes, and the number of worker processes that get spawned when Nginx is running, among other things.


The hard part is the SELinux here http://axilleas.me/en/blog/2013/selinux-policy-for-nginx-and-gitlab-unix-socket-in-fedora-19/

Quite similar above

SELinux policy for nginx and GitLab unix socket in Fedora 19

by axil 
in linux.
The installation of GitLab in Fedora 19 went fine. I followed the official installation guide with some deviations where necessary, mostly taken from the CentOS guide in gitlab-recipes. I setup nginx using the ssl config, and poked some holes in iptables. For systemd services I used these files.
So, everything is set, configuration tests pass, services are started, nginx is started and I finally point firefox to my FQDN (which by the way is fedora.axilleas.me, no secret) just to see a big fat 502 Bad Gateway.
As wikipedia suggests:
502 Bad Gateway
The server was acting as a gateway or proxy and received an invalid response from the upstream server.
Spot on! The server (nginx) is acting as a proxy and received an invalid response from the upstream server (unicorn). But what was that invalid response?
I could reach ip_addr:8080 at which unicorn was listening, but not through my fqdn which nginx was serving. So there clearly was something wrong with nginx.

Error hunting

So the first thing when such an error occurs is to look through the logs.


In /var/log/nginx/gitlab_error.log I could see this error repeating:
2013/08/26 21:43:01 [crit] 2597#0: *50 connect() to unix:/home/git/gitlab/tmp/sockets/gitlab.socket failed (13: Permission denied) while connecting to upstream, client, server: fedora.axilleas.me, request: "GET /users/sign_in HTTP/1.1", upstream: "http://unix:/home/git/gitlab/tmp/sockets/gitlab.socket:/users/sign_in", host: "fedora.axilleas.me"
So we got a permission denied while nginx is trying to connect to the unix socket of GitLab. After some hours searching and reading answers in stackoverflow, it sroke to me to check whether SELinux is to blame. I set it to permissive mode with setenforce 0 and voila, nginx was suddenly recieving requests.

SELinux you crafty little blocker

I remembered the awesome introductory guide of SELinux at CentOS wiki, which I had used when rewriting the CentOS installation guide for GitLab and immediately started reading.
By default, SELinux log messages are written to /var/log/audit/audit.log via the Linux Auditing System auditd. If the auditd daemon is not running, then messages are written to /var/log/messages. SELinux log messages are labeled with the AVC keyword so that they might be easily filtered from other messages, as with grep.
So, by greping nginx in /var/log/audit/audit.log I found those relative AVC messages, which indicate indeed a denial of nginx connection to gitlab.socket.
type=AVC msg=audit(1377542938.307:248364): avc:  denied  { write } for  pid=2597 comm="nginx" name="gitlab.socket" dev="vda1" ino=1180273 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:httpd_sys_content_t:s0 tclass=sock_file
type=AVC msg=audit(1377542938.307:248364): avc:  denied  { connectto } for  pid=2597 comm="nginx" path="/home/git/gitlab/tmp/sockets/gitlab.socket" scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:system_r:initrc_t:s0 tclass=unix_stream_socket
Using a tool called audit2allow we are able to clear the AVC messages. If you haven't got it installed, it is shipped with the policycoreutils-devel package.
grep nginx /var/log/audit/audit.log | audit2allow
and the result is:
#============= httpd_t ==============

#!!!! This avc is allowed in the current policy
allow httpd_t http_cache_port_t:tcp_socket name_connect;

#!!!! This avc is allowed in the current policy
allow httpd_t httpd_log_t:file setattr;

#!!!! This avc is allowed in the current policy
allow httpd_t httpd_sys_content_t:sock_file write;

#!!!! This avc is allowed in the current policy
allow httpd_t initrc_t:unix_stream_socket connectto;

#!!!! This avc is allowed in the current policy
allow httpd_t user_home_dir_t:dir search;

#!!!! This avc is allowed in the current policy
allow httpd_t user_home_t:dir { search getattr };

#!!!! This avc is allowed in the current policy
allow httpd_t user_home_t:sock_file write;

#!!!! This avc is allowed in the current policy
allow httpd_t var_run_t:file { read write };
These are the policies that should be used with SELinux. Notice that user_home is essential since GitLab's APP_ROOT is in /home/git/. Similarly, you notice a policy related to the denied socket connection: unix_stream_socket connectto.

Create a custom SELinux policy module

After all the investigation we are closer to the solution. All we have to do is useaudit2allow to generate a set of policy rules that would allow the required actions. We can generate a local nginx Type Enforcement policy file (nginx.te):
grep nginx /var/log/audit/audit.log | audit2allow -m nginx > nginx.te
cat nginx.te

module nginx 1.0;

require {
    type var_run_t;
    type user_home_dir_t;
    type httpd_log_t;
    type httpd_t;
    type user_home_t;
    type httpd_sys_content_t;
    type initrc_t;
    type http_cache_port_t;
    class sock_file write;
    class unix_stream_socket connectto;
    class dir { search getattr };
    class file { read write setattr };
    class tcp_socket name_connect;

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

#!!!! This avc is allowed in the current policy
allow httpd_t http_cache_port_t:tcp_socket name_connect;
allow httpd_t httpd_log_t:file setattr;
allow httpd_t httpd_sys_content_t:sock_file write;
allow httpd_t initrc_t:unix_stream_socket connectto;

#!!!! This avc is allowed in the current policy
allow httpd_t user_home_dir_t:dir search;

#!!!! This avc is allowed in the current policy
allow httpd_t user_home_t:dir { search getattr };
allow httpd_t user_home_t:sock_file write;
allow httpd_t var_run_t:file { read write };
We are not done yet, as this is a file for review only. We can then go ahead and use audit2allow to make a custom policy module to allow these actions:
grep nginx /var/log/audit/audit.log | audit2allow -M nginx
semodule -i nginx.pp
We can check the policy module loaded correctly by listing loaded modules with semodule -l.
After that, remember to enable SELinux again with setenforce 1.

Add nginx to git group

Unrelated to this article, but it is also needed for nginx to access the unix socket. First we add nginx to git group, and then we make sure the group that owns /home/git/ has read and execute permissions:
usermod -a -G git nginx
chmod g+rx /home/git/


To fix all nginx 502 issues, as root run:
yum install -y policycoreutils-{python,devel}
grep nginx /var/log/audit/audit.log | audit2allow -M nginx
semodule -i nginx.pp
usermod -a -G git nginx
chmod g+rx /home/git/

Integration of SELinux error messages with journald

In a very interesting article, Dan Walsh explains how this whole process of error hunting will be much easier with Fedora 20. I urge you to read it.
With the upcoming changes, the error would have appeared at systemd's status log:
systemctl status nginx
and the possible solutions with:
journalctl  -r -o verbose -u nginx.service
Pretty cool, huh?

No comments:

Post a Comment