Nginx (Reverse SSL Proxy) with ModSecurity (Web App Firewall) on CentOS 7 (Part 1)

Today I’ll demonstrate how to install the Nginx webserver/reverse proxy, with the ModSecurity web application firewall, configured as a reverse SSL proxy, on CentOS 7.  This is useful in scenarios where you are terminating incoming SSL traffic at a centralized location and are interested in implementing a web application firewall to protect the web servers sitting behind the proxy.  This setup can also support multiple sites (ie different host names) using Server Name Indication (SNI) for SSL.  If multiple sites are being protected you will need a different SSL certificate for each site (unless a wildcard or SAN certificate will suffice for the sites in question, in which case SNI is not used).  There is an abundance of information available online showing how to do this on Ubuntu/Debian, but not for CentOS.  In Part 1 (this post) we’ll compile everything, install Nginx, and configure a reverse SSL proxy and in Part 2 we’ll configure ModSecurity.

The first step is to deploy a fresh VM with CentOS 7.  We are using the x64 version, but I don’t see why the x86 version would be any different.

Log on to the server and either elevate to a root shell or execute all commands below using sudo.

Here we are downloading and installing the EPEL (Enterprise Packages for Enterprise Linux)
repository to provide access to some of the prerequisite packages we need.  After that we install all of the required packages.

      
rpm -ivh http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm       
yum update       
yum install gc gcc gcc-c++ pcre-devel zlib-devel make wget openssl-devel libxml2-devel libxslt-devel gd-devel perl-ExtUtils-Embed GeoIP-devel gperftools gperftools-devel libatomic_ops-devel perl-ExtUtils-Embed libtool httpd-devel automake curl-devel       

After installing all of the prerequisite packages we create a user named “nginx” for the daemon to run as.  We also set the login shell to “nologin” to prevent a compromised account from gaining access to a real shell.

      
useradd nginx       
usermod -s /sbin/nologin nginx       

Now let’s download ModSecurity to the /usr/src/ directory, untar it, and change to the unpacked folder.  At the time of this writing the latest version was 2.9.0.  You can find downloads for the latest version at https://www.modsecurity.org.

      
cd /usr/src/       
wget https://www.modsecurity.org/tarball/2.9.0/modsecurity-2.9.0.tar.gz       
tar xzvf modsecurity-2.9.0.tar.gz       
cd modsecurity-2.9.0/       

Now inside the source directory, run autogen.sh, then configure with the “enable-standalone-module” option, and finally make the module.

      
./autogen.sh       
./configure --enable-standalone-module       
make       

After building the ModSecurity module, move back to the /usr/src/ directory, download Nginx, untar it, and then enter the unpacked directory.  The latest version of Nginx at the time of this writing was 1.9.7.  Newer versions can be found at http://www.nginx.org.

      
cd /usr/src/       
wget http://nginx.org/download/nginx-1.9.7.tar.gz       
tar xzvf nginx-1.9.7.tar.gz       
cd nginx-1.9.7/       

After navigating to the extracted directory run configure, then make, and then make install.  The “add-module” option set with configure will point to the nginx/modsecurity/ directory which should be inside of the extracted ModSecurity directory from earlier.  The default behavior of nginx is to install to /usr/local/nginx, but in the command below we’re changing the location of where some files are stored. First, the binary “nginx” is being saved directly in /usr/sbin/ to make things easier dealing with $PATH. We’re setting the conf path to the /etc/nginx/ directory, the PID path to the /var/run/ directory, and the log paths to /var/log/nginx/. I have two reasons for changing these paths: 1 – to keep the respective folders in the standard locations, and 2 – to allow SELinux to function using default rules. There are default SELinux rules related to /sbin/nginx, /etc/nginx/, and /var/log/nginx/, and I’ve found it much easier to work with SELinux if I just keep everything possible in the locations that are already predefined. There will be more later showing how to add SELinux rules to account for the directories that aren’t predefined.

      
./configure --user=nginx --group=nginx --with-pcre-jit --with-debug --with-ipv6 --with-http_ssl_module --add-module=/usr/src/modsecurity-2.9.0/nginx/modsecurity --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --pid-path=/var/run/nginx.pid --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log
make       
make install       

Now let’s setup a systemd service to allow Nginx to autostart with the server.  In the first block of code we’ll navigate to the proper directory, then we’ll create a file to tell systemd what to do.  The second block of code will contain the contents of the file we created before that.

cd /usr/lib/systemd/system
nano nginx.service
#
# nginx signals reference doc:
# http://nginx.org/en/docs/control.html
#
[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target

[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /var/run/nginx.pid
TimeoutStopSec=5
KillMode=mixed

[Install]
WantedBy=multi-user.target

Now we need to edit the conf file for Nginx.  Before we do that though we create two folders: sites-available and sites-enabled, just like in Apache, to hold our vhosts.

mkdir /etc/nginx/sites-available
mkdir /etc/nginx/sites-enabled
nano /etc/nginx/nginx.conf

Change the config file to what’s listed below. NOTE: For “worker_processes” set the number to the number of cores you wish to dedicate to the server.

user nginx;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    gzip  on;
    include /etc/nginx/sites-enabled/*;
}

Here we generate a Diffie-Hellman key larger than OpenSSL’s default size of 1024. We’ll do 4096.

cd /etc/ssl/certs
openssl dhparam -out dhparam.pem 4096

Now we need to create a folder to hold our SSL files. Inside of that folder we’ll create subfolders for each vhost’s certs. Though not shown below, place your certificate and key files in the vhost SSL directory. We also create a vhost config file in the sites-available directory.

mkdir /etc/nginx/ssl
mkdir /etc/nginx/ssl/localhost
nano /etc/nginx/sites-available/ssl.conf

Add the following to the file just created. Make sure for the “ssl_certificate” and “ssl_certificate_key” lines you point to the proper locations. Set the “server_name” line to the hostname of your server (make sure it matches the SSL certificate). Set the “proxy_pass” line to point to the server you’re proxying to. In the example below we’re connecting to port 80 locally, but in most cases this would be a separate server. If you have several SSL certificates and want to define one of the vhosts as default, add “default_server” on the “listen” line, between the port number and semicolon.

server {
        listen   443;

        server_name localhost;
        ssl                  on;
        ssl_certificate      /etc/nginx/ssl/localhost/localhost.crt;
        ssl_certificate_key  /etc/nginx/ssl/localhost/localhost.key;

        ssl_session_timeout  5m;

        ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_dhparam /etc/ssl/certs/dhparam.pem;

        location / {
          proxy_set_header X-Real-IP  $remote_addr;
          proxy_set_header X-Forwarded-For $remote_addr;
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_read_timeout 90;
          proxy_pass http://127.0.0.1;         
        }
}

Create a symlink from the site(s) in the sites-available directory to the sites-enabled directory to activate the sites. Then test to make sure the config files don’t contain any errors.

ln -s /etc/nginx/sites-available/ssl.conf /etc/nginx/sites-enabled/ssl.conf
nginx -T

Assuming nginx -T doesn’t return any errors, everything should be all ready to go! Enable the service, start it, then verify it’s running.

systemctl enable nginx.service
systemctl start nginx.service
systemctl status nginx.service

If you need to open up the firewall to allow incoming traffic, use the command below

firewall-cmd --add-port=443/tcp --permanent
firewall-cmd --reload

1 thought on “Nginx (Reverse SSL Proxy) with ModSecurity (Web App Firewall) on CentOS 7 (Part 1)”

Leave a Comment

Your email address will not be published. Required fields are marked *