{"id":232,"date":"2014-09-09T11:29:16","date_gmt":"2014-09-09T01:29:16","guid":{"rendered":"https:\/\/icicimov.com\/blog\/?p=232"},"modified":"2017-01-02T17:30:23","modified_gmt":"2017-01-02T06:30:23","slug":"secure-nginx-with-naxsi-ssl-geoip-and-google-page-speed-modules-on-debianubuntu","status":"publish","type":"post","link":"https:\/\/icicimov.com\/blog\/?p=232","title":{"rendered":"Secure Nginx with Naxsi, SSL, GeoIP and Google Page Speed modules on Debian\/Ubuntu"},"content":{"rendered":"<p>We will use the latest stable version of <code>nginx-naxsi<\/code> which has <code>XSS<\/code> (Cross Site Scripting) protection via <code>Naxsi<\/code> module. We will also build and install this Debian way on Ubuntu-12.04 Precise since we want to include some other useful modules, SSL being one of them, that are not enabled by default. The problem with <code>Nginx<\/code> is that it&#8217;s not modular like <code>Apache<\/code> so we can&#8217;t add modules on the fly but we have to recompile from source every time we want to add a new one. It is a small price to pay though for the speed and lightness we get.<\/p>\n<pre><code>root@nginx:~# aptitude install apache2-utils liblua5.1-dev daemon dbconfig-common\nroot@nginx:~# add-apt-repository ppa:nginx\/stable\nroot@nginx:~# aptitude update\nroot@nginx:~# aptitude build-dep nginx-naxsi\nroot@nginx:~# cd \/tmp\nroot@nginx:\/tmp# apt-get source nginx-naxsi\nroot@nginx:\/tmp# cd nginx-1.6.0\nroot@nginx:\/tmp\/nginx-1.6.0# vi nginx-1.6.0\/auto\/options\n...\nchange HTTP_GEOIP=NO to HTTP_GEOIP=YES (and enable some other modules like DAV,SSL,SUB,XSLT etc. if needed)\n...\n<\/code><\/pre>\n<p>Next we setup the page speed module:<\/p>\n<pre><code>root@nginx:\/tmp# wget https:\/\/github.com\/pagespeed\/ngx_pagespeed\/archive\/release-1.9.32.3-beta.zip\nroot@nginx:\/tmp# unzip -d \/tmp\/nginx-1.6.0\/debian\/modules -o release-1.9.32.3-beta.zip\nroot@nginx:\/tmp# mv \/tmp\/nginx-1.6.0\/debian\/modules\/ngx_pagespeed-release-1.9.32.3-beta \/tmp\/nginx-1.6.0\/debian\/modules\/ngx_pagespeed\nroot@nginx:\/tmp# wget https:\/\/dl.google.com\/dl\/page-speed\/psol\/1.9.32.3.tar.gz\nroot@nginx:\/tmp# tar -xzf 1.9.32.3.tar.gz -C \/tmp\/nginx-1.6.0\/debian\/modules\/ngx_pagespeed\/\n<\/code><\/pre>\n<p>Next we change the Nginx version in the changelog file:<\/p>\n<pre><code>root@server:\/tmp\/nginx-1.6.0# vi debian\/changelog\n<\/code><\/pre>\n<p>change the first line:<\/p>\n<pre><code>nginx (1.6.0-1+precise0) precise; urgency=medium\n<\/code><\/pre>\n<p>to:<\/p>\n<pre><code>nginx (1.6.0-1+precise0-naxsi) precise; urgency=medium\n<\/code><\/pre>\n<p>and start the building process:<\/p>\n<pre><code>root@nginx:\/tmp\/nginx-1.6.0# dpkg-buildpackage -uc -b\n<\/code><\/pre>\n<p>After it finishes we install the <code>.deb<\/code> files created in the parent directory of the one we are building in:<\/p>\n<pre><code>root@nginx:\/tmp\/nginx-1.6.0# dpkg -i ..\/nginx-naxsi_1.6.0-1+precise0-naxsi_amd64.deb ..\/nginx-common_1.6.0-1+precise0-naxsi_all.deb ..\/nginx-naxsi-ui_1.6.0-1+precise0-naxsi_amd64.deb\nroot@nginx:\/etc\/nginx# nginx -V\nnginx version: nginx\/1.6.0-1+precise0-naxsi\nTLS SNI support enabled\nconfigure arguments: --with-cc-opt='-g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Wformat-security -Werror=format-security -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro' --prefix=\/usr\/share\/nginx --conf-path=\/etc\/nginx\/nginx.conf --http-log-path=\/var\/log\/nginx\/access.log --error-log-path=\/var\/log\/nginx\/error.log --lock-path=\/var\/lock\/nginx.lock --pid-path=\/run\/nginx.pid --http-client-body-temp-path=\/var\/lib\/nginx\/body --http-fastcgi-temp-path=\/var\/lib\/nginx\/fastcgi --http-proxy-temp-path=\/var\/lib\/nginx\/proxy --http-scgi-temp-path=\/var\/lib\/nginx\/scgi --http-uwsgi-temp-path=\/var\/lib\/nginx\/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --without-mail_pop3_module --without-mail_smtp_module --without-mail_imap_module --without-http_uwsgi_module --without-http_scgi_module --add-module=\/tmp\/nginx-1.6.0\/debian\/modules\/naxsi\/naxsi_src --add-module=\/tmp\/nginx-1.6.0\/debian\/modules\/nginx-cache-purge --add-module=\/tmp\/nginx-1.6.0\/debian\/modules\/nginx-upstream-fair\n<\/code><\/pre>\n<p>Now we have to pin the compiled version so it doesn&#8217;t get overwritten by update, create <code>\/etc\/apt\/preferences.d\/nginx<\/code> file with following content:<\/p>\n<pre><code>Package: nginx-naxsi\nPin: version 1.6.0-1+precise0-naxsi\nPin-Priority: 1001\n\nPackage: nginx-common\nPin: version 1.6.0-1+precise0-naxsi\nPin-Priority: 1001\n\nPackage: nginx-naxsi-ui\nPin: version 1.6.0-1+precise0-naxsi\nPin-Priority: 1001\n<\/code><\/pre>\n<p>If we use the built debian packages on other servers we should remember to do the pinning there too.<\/p>\n<p>To enable Naxsi we create the following <code>\/etc\/nginx\/mysite.rules<\/code> file:<\/p>\n<pre><code>LearningMode; #Enables learning mode\nSecRulesEnabled;\n#SecRulesDisabled;\nDeniedUrl \"\/RequestDenied\";\n## check rules\nCheckRule \"$SQL &gt;= 8\" BLOCK;\nCheckRule \"$RFI &gt;= 8\" BLOCK;\nCheckRule \"$TRAVERSAL &gt;= 4\" BLOCK;\nCheckRule \"$EVADE &gt;= 4\" BLOCK;\nCheckRule \"$XSS &gt;= 8\" BLOCK;\n<\/code><\/pre>\n<p>then we can include the following line in the <code>http {}<\/code> section of the <code>\/etc\/nginx\/nginx.conf<\/code> file:<\/p>\n<pre><code>include \/etc\/nginx\/naxsi_core.rules;\n<\/code><\/pre>\n<p>and the following line:<\/p>\n<pre><code>include \/etc\/nginx\/mysite.rules;\n<\/code><\/pre>\n<p>in any <code>\/etc\/nginx\/sites-enabled\/&lt;site -name&gt;.conf<\/code> file in the <code>location \/ {}<\/code> section.<\/p>\n<p>As added security for sites with limited public access, I block access from countries like China, USA, Russia where most of the hacking attempts come from. For that purpose I use GeoIP module.<\/p>\n<pre><code>root@nginx:\/tmp\/nginx-1.6.0# cd \/usr\/share\/GeoIP\/\nroot@nginx:\/usr\/share\/GeoIP# wget http:\/\/geolite.maxmind.com\/download\/geoip\/database\/GeoLiteCity.dat.gz\nroot@nginx:\/usr\/share\/GeoIP# gzip -d GeoLiteCity.dat.gz\n<\/code><\/pre>\n<p>Then we specify where the GeoIP database is located on our system and tell Nginx which countries are gonna be blocked (see the security file below for details). At the end we put the following at the end of fastcgi config file <code>\/etc\/nginx\/fastcgi_params<\/code>:<\/p>\n<pre><code># GeoIP\nfastcgi_param GEOIP_COUNTRY_CODE $geoip_country_code;\nfastcgi_param GEOIP_COUNTRY_NAME $geoip_country_name;\nfastcgi_param GEOIP_CITY $geoip_city;\nfastcgi_param GEOIP_REGION $geoip_region;\nfastcgi_param GEOIP_POSTAL_CODE $geoip_postal_code;\nfastcgi_param GEOIP_AREA_CODE $geoip_area_code;\nfastcgi_param GEOIP_CITY_CONTINENT_CODE $geoip_city_continent_code;\n<\/code><\/pre>\n<p>Next is the basic Nginx configuration in <code>\/etc\/nginx\/nginx.conf<\/code>:<\/p>\n<pre><code>user www-data;\nworker_processes auto;\n#worker_priority 15; # renice the process, with 15 max cpu usage ~25%=(20-15)\/20\npid \/run\/nginx.pid;\n\nevents {\n    worker_connections 512;\n    multi_accept on;\n}\n\nhttp {\n\n    ##\n    # Basic Settings\n    ##\n    tcp_nopush on;\n    tcp_nodelay on;\n    client_body_timeout 30;\n    client_header_timeout 10;\n    keepalive_timeout 65 20;\n    types_hash_max_size 2048;\n    ignore_invalid_headers on;\n    server_names_hash_bucket_size 128;\n\n    client_header_buffer_size   1k;\n    client_body_buffer_size     128k;\n    large_client_header_buffers 4 4k;\n\n    include                   \/etc\/nginx\/mime.types;\n    default_type              application\/octet-stream;\n    keepalive_requests        100;  # number of requests per connection, does not affect SPDY\n    keepalive_disable         none; # allow all browsers to use keepalive connections\n    max_ranges                1;    # allow a single range header for resumed downloads and to stop large range header DoS attacks\n    msie_padding              off;\n    open_file_cache           max=1000 inactive=2h;\n    open_file_cache_errors    on;\n    open_file_cache_min_uses  1;\n    open_file_cache_valid     1h;\n    output_buffers            1 512k;\n    postpone_output           1460;  # postpone sends to match our machine's MSS\n    read_ahead                512K;  # kernel read head set to the output_buffers\n    reset_timedout_connection on;    # reset timed out connections freeing ram\n    sendfile                  on;    # on for decent direct disk I\/O\n    server_tokens             off;   # version number in error pages\n    server_name_in_redirect   off;   # if off, nginx will use the requested Host header\n    source_charset            utf-8; # same value as \"charset\"\n\n    ## Request limits\n    limit_req_zone  $binary_remote_addr  zone=nginx:1m   rate=1000r\/m;\n\n    ##\n    # SSL Settings\n    ##\n    ssl_session_tickets       on;\n    ssl_session_cache         shared:SSL:20m;\n    ssl_session_timeout       4h;\n    ssl_dhparam               \/etc\/nginx\/ssl\/dhparam.pem;\n    ssl_ecdh_curve            secp384r1;\n    ssl_certificate           \/etc\/nginx\/ssl\/star_mydomain_com.crt;\n    ssl_certificate_key       \/etc\/nginx\/ssl\/star_mydomain_com.crt;\n    ssl_protocols             TLSv1 TLSv1.1 TLSv1.2;\n    ssl_prefer_server_ciphers on;\n    ssl_ciphers           'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK';\n\n    ##\n    # GeoIP\n    ##\n    geoip_country \/usr\/share\/GeoIP\/GeoIP.dat;\n    geoip_city    \/usr\/share\/GeoIP\/GeoLiteCity.dat;\n\n    ##\n    # ngx_pagespeed config\n    ##\n    pagespeed on;\n    pagespeed FileCachePath \"\/var\/cache\/ngx_pagespeed\/\";\n    pagespeed EnableFilters combine_css,combine_javascript;\n    pagespeed PreserveUrlRelativity on;\n\n    ##\n    # LDAP\n    ##\n    auth_ldap_cache_enabled off;\n    auth_ldap_cache_expiration_time 10000;\n    auth_ldap_cache_size 1000;\n\n    ldap_server ldap1 {\n      url ldap:\/\/ldap1.mydomain.com:389\/ou=Users,dc=mydomain,dc=com?uid?sub;\n      binddn \"cn=binduser,ou=Users,dc=mydomain,dc=com\";\n      binddn_passwd bindpassword;\n      group_attribute memberUid;\n      group_attribute_is_dn on;\n      require group \"cn=mygroup,ou=Groups,dc=mydomain,dc=com\";\n      require valid_user;\n    }\n\n    ldap_server ldap2 {\n      url ldap:\/\/ldap2.mydomain.com:389\/ou=Users,dc=mydomain,dc=com?uid?sub;\n      binddn \"cn=binduser,ou=Users,dc=mydomain,dc=com\";\n      binddn_passwd bindpassword;\n      group_attribute memberUid;\n      group_attribute_is_dn on;\n      require group \"cn=mygroup,ou=Groups,dc=mydomain,dc=com\";\n      require valid_user;\n    }\n\n    ##\n    # Logging Settings\n    ##\n    ## Log Format\n    log_format  main  '$remote_addr $host $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" $ssl_cipher $request_time';\n    access_log  \/var\/log\/nginx\/access.log;\n    error_log   \/var\/log\/nginx\/error.log;\n\n    ##\n    # Gzip Settings\n    ##\n    gzip on;\n    gzip_disable \"msie6\";\n    gzip_vary on;\n    gzip_proxied any;\n    gzip_comp_level 6;\n    #gzip_buffers 16 8k;\n    gzip_buffers  128 32k;\n    #gzip_http_version 1.1;\n    #gzip_types text\/plain text\/css application\/json application\/javascript text\/xml application\/xml application\/xml+rss text\/javascript;\n    gzip_types  text\/plain text\/css text\/x-component\n                text\/xml application\/xml application\/xhtml+xml application\/json\n                image\/x-icon image\/bmp image\/svg+xml application\/atom+xml\n                text\/javascript application\/javascript application\/x-javascript\n                application\/pdf application\/postscript\n                application\/rtf application\/msword\n                application\/vnd.ms-powerpoint application\/vnd.ms-excel\n                application\/vnd.ms-fontobject application\/vnd.wap.wml\n                application\/x-font-ttf application\/x-font-opentype;\n\n    ##\n    # Naxsi rules\n    ##\n    include \/etc\/nginx\/naxsi_core.rules;\n\n    ##\n    # Virtual Host Configs\n    ##\n    include \/etc\/nginx\/conf.d\/*.conf;\n    include \/etc\/nginx\/sites-enabled\/*;\n}\n<\/code><\/pre>\n<p>For user access we are using LDAP, see <a href=\"\/blog\/server\/Nginx-LDAP-module\/\">Nginx LDAP module on Debian\/Ubuntu<\/a> for the details about compiling NGINX for LDAP support if needed. Otherwise use the basic authentication with local password file.<\/p>\n<p>We are going to setup an SSL proxy for server <code>myserver<\/code> in a SSL enabled virtual host. We will first configure stronger DHE parameters, default one is 1024 bits and since our cert is 2048 bit we can go with stronger <code>DHE<\/code> (Ephemeral Diffie-Hellman) cryptographic values:<\/p>\n<pre><code>root@nginx:~# openssl dhparam -out \/etc\/nginx\/ssl\/dhparam.pem 2048\n<\/code><\/pre>\n<p>We will also force the use of the strong <code>ECDHE<\/code> (Elliptic DHE) only for <code>PFS<\/code> (Perfect Forward Secrecy) and stipulate this via <code>ssl_prefer_server_ciphers<\/code> parameter enabled in the <code>\/etc\/nginx\/sites-available\/myserver<\/code> file we create:<\/p>\n<pre><code>server {\n    ssl             on;\n    listen          443 ssl backlog=1250 so_keepalive=on;\n    server_name     myserver.mydomain.com www.myserver.mydomain.com;\n    root            \/var\/www\/myserver;\n    index           index.html index.htm;\n\n    access_log      \/var\/log\/nginx\/myserver-access.log main;\n    error_log       \/var\/log\/nginx\/myserver-error.log;\n\n    add_header      Cache-Control \"public\";\n    #add_header     Content-Security-Policy \"default-src 'none';style-src 'self';img-src 'self' data: ;\";\n    add_header      X-Content-Type-Options \"nosniff\";\n    add_header      X-Frame-Options \"DENY\";\n    # Config to enable HSTS(HTTP Strict Transport Security) https:\/\/developer.mozilla.org\/en-US\/docs\/Security\/HTTP_Strict_Transport_Security\n    # To avoid ssl stripping https:\/\/en.wikipedia.org\/wiki\/SSL_stripping#SSL_stripping\n    add_header      Strict-Transport-Security \"max-age=315360000; includeSubdomains\";\n    expires         max;\n    limit_req       zone=myserver burst=200 nodelay; # works in conjuction with limit_req_zone set in nginx.conf\n\n    location \/ {\n       include  \/etc\/nginx\/mysite.rules;\n       try_files $uri $uri\/ \/index.html;\n       auth_ldap \"Restricted\";\n       auth_ldap_servers ldap1;\n       auth_ldap_servers ldap2;\n    }\n\n    ## Some proxy\n    location \/someproxy {\n       proxy_pass http:\/\/127.0.0.1:8080;\n       proxy_read_timeout 90;\n    }\n\n    location ~ \/\\. {\n       deny  all;\n    }\n\n    location \/RequestDenied {\n       return 418;\n    }\n}\n<\/code><\/pre>\n<p>We will also set some cutom security on top of it blocking some bots and using GeoIP, <code>\/etc\/nginx\/conf.d\/security<\/code> file:<\/p>\n<pre><code>## Block some nasty robots\/bots\nif ($http_user_agent ~ (msnbot|Purebot|Baiduspider|Lipperhey|Mail.Ru|scrapbot|Morfeus|masscan) ) {\n     return 403;\n}\n\n## Block torrent trackers ddos attacks\nlocation \/announc {\n    access_log off;\n    error_log off;\n    default_type text\/plain;\n    return 410 \"d14:failure reason13:not a tracker8:retry in5:nevere\";\n}\n\n## Deny scripts inside writable directories\nlocation ~* \/(images|cache|media|logs|tmp)\/.*.(php|pl|py|jsp|asp|sh|cgi)$ {\n     return 403;\n}\n\n## Block HEAD requests\nif ($request_method !~ ^(HEAD)$ ) {\n     return 444;\n}\n\n## Prevent image hotlinking\nlocation ~ .(gif|png|jpe?g)$ {\n     valid_referers none blocked mydomain.com *.mydomain.com;\n     if ($invalid_referer) {\n        return   403;\n    }\n}\n\n## GeoIP\ngeoip_country \/usr\/share\/GeoIP\/GeoIP.dat;\ngeoip_city \/usr\/share\/GeoIP\/GeoLiteCity.dat;\nif ($geoip_country_code ~ (CN|KR|US|RU) ) {\n     return 403;\n}\n<\/code><\/pre>\n<p>At the end we enable the myserver virtual host, disable the default one and restart Nginx:<\/p>\n<pre><code>root@nginx:\/opt# rm -f \/etc\/nginx\/sites-enabled\/default\nroot@nginx:\/opt# ln -sf \/etc\/nginx\/sites-available\/myserver \/etc\/nginx\/sites-enabled\/myserver\nroot@nginx:\/opt# service nginx configtest\nroot@nginx:\/opt# service nginx restart\n<\/code><\/pre>\n<p>NGINX built and deployed in the way described above has given me at least A on <a href=\"https:\/\/www.ssllabs.com\/\">Quallys SSL Labs<\/a> tests. Obviuosly re-building NGINX for every update is going to be a tedious task and that&#8217;s why I&#8217;ve put this procedure into Ansible playbook that does this for us. It creates a new EC2 instance, re-builds NGINX on it, creates an AMI that we can use to launch NGINX instances in our AWS VPC&#8217;s, and terminates the EC2 instance when finished.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We will use the latest stable version of nginx-naxsi which has XSS (Cross Site Scripting) protection via Naxsi module. We will also build and install this Debian way on Ubuntu-12.04 Precise since we want to include some other useful modules,&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-232","post","type-post","status-publish","format-standard","hentry","category-server"],"_links":{"self":[{"href":"https:\/\/icicimov.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/232","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/icicimov.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/icicimov.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/icicimov.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/icicimov.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=232"}],"version-history":[{"count":1,"href":"https:\/\/icicimov.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/232\/revisions"}],"predecessor-version":[{"id":233,"href":"https:\/\/icicimov.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/232\/revisions\/233"}],"wp:attachment":[{"href":"https:\/\/icicimov.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=232"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/icicimov.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=232"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/icicimov.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=232"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}