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
Nice article. Just what i had been looking for. Is part 2 out yet? Thank you.