Charlene
Assistant Engineer
Assistant Engineer
  • UID626
  • Fans1
  • Follows1
  • Posts52
Reads:47977Replies:0

Configure SSL encryption in nginx (one-way/two-way authentication, partial HTTPS)

Created#
More Posted time:Nov 9, 2016 14:36 PM
It would have been quite easy to configure SSL encryption in nginx, either by purchasing an SSL certificate from the certificate authority or using self-signed certificates. But there is a new requirement from the company’s OA recently, for which reason I have the practical chance to research and implement it. At the very beginning, I adopted full-site encryption and all requests to access http:80 were forcibly rewritten to https. Later the automation testers gave feedback that the response was too slow and access to https was 30 times slower than to http. How could that be, I thought. Who knows how they did the test to get such a ridiculous result. So I tried to implement HTTPS for part of the pages (instead of encryption to only some certain dynamic requests) and two-way authentication. I will explain the topics in separate sections.
By default, the SSL module is not installed in nginx. You need to add the --with-http_ssl_module option when compiling and installing nginx.
Tips: The communication between nginx and backend servers usually goes through the intranet, so it is not encrypted.
1. Full-site SSL encryption
Full-site SSL encryption is the most common scenario. The default port is 443 and one-way authentication is generally adopted.
server {
        listen 443;
        server_name example.com;

        root /apps/www;
        index index.html index.htm;

        ssl on;
        ssl_certificate ../SSL/ittest.pem;
        ssl_certificate_key ../SSL/ittest.key;

#        ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
#        ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
#        ssl_prefer_server_ciphers on;

}


To forcibly rewrite http requests to https:
server {
  listen      80;
  server_name example.me;
  rewrite     ^   https://$server_name$request_uri? permanent;

### Using return can help to improve the efficiency.
#  return 301 https://$server_name$request_uri;
}


The ssl_certificate is actually a public key that will be sent to every client connected to the server. The ssl_certificate_key private key is used for decryption, so its permission should be protected, but it should be readable by the main processes of nginx. Of course, the private key and certificate can be placed into one certificate file. In this way, only the public key certificate will be sent to the client.
The ssl_protocols command is used to start a specific encryption protocol. After nginx V1.1.13 and V1.0.12, ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2 is the default setting. To utilize TLSv1.1 and TLSv1.2, you should ensure that OpenSSL >= 1.0.1. SSLv3 is still widely used, but it has many vulnerabilities prone to attacks.
The ssl_ciphers is used to select the encryption suite. Different browsers may support different (and different orders of) suites. Here a writing method recognizable by the OpenSSL library is specified. You can view the supported writing methods through openssl -v cipher ‘RC4:HIGH:!aNULL:!MD5’ (the later part of it is the encryption algorithm for your specified suite).
When using ssl_prefer_server_ciphers on to set negotiations for encryption algorithms, you should prioritize the encryption suites at our server end, instead of the encryption suites of the browser on the client side.
HTTPS optimization parameters
ssl_session_cache shared:SSL:10m;: Set the type and size of SSL/TLS session cache. This parameter is usually set to shared. Setting it to buildin may cause memory fragmentation of the parameter. The default setting is none, similar to off which is to disable the cache. For example, the shared:SSL:10m indicates that all of my nginx working processes will share the SSL session cache. The official website introduces that 1MB of cache can store around 4,000 sessions. For details, refer to the Q&A on the serverfault ssl_session_cache.
• ssl_session_timeout: The client side can reuse the timeout duration of the SSL parameter in the session cache. Five minutes is too short by default for the intranet system, and you can set it to 30m, that is, 30 minutes, or 4h (four hours).
Setting a long keepalive_timeout duration can also reduce the overhead for requesting SSL session negotiations, but at the same time, you have to consider the thread concurrency.
Tips: When generating the csr file (Certificate Signing Request), if you entered a password, nginx will prompt to input this password every time it is started. You can also use the private key to generate a decrypted key to achieve the same password-free restart:
openssl rsa -in ittest.key -out ittest_unsecure.key

Import a certificate
If your certificate is signed by a renowned SSL certificate authority such as VeriSign, Wosign or StartSSL, the browser has incorporated and trusted these root certificates. If you use a self-signed certificate or obtain second-level CA certification, you need to add the CA certificate to the browser so that the requested website won’t be identified as an unsafe link. This article will not detail the methods for adding CA certificates to various browsers.
2. SSL encryption for part of pages
Not all the information at a site is confidential. For example, for an online shopping mall, browsing general commodities can go without https, while user login and payment should be forced to go through https transmission, so that user’s access speed and security can both be guaranteed.
But do not get it wrong that we can encrypt a page instead of a request. The URL on a page or in the address bar usually initiates many requests, including static css/png/js files and dynamic Java or PHP requests. So the content to be encrypted should contain other resource files in the page, otherwise http content and https content may be mixed up. When an HTTP page is mixed with HTTPS content, the page layout will not be distorted; When an HTTPS page contains images and js resources referenced in the HTTP approach, the browser will block the loading for the sake of security.
Below is an example to encrypt the login page of example.com/account/login:
root /apps/www;
index index.html index.htm;

server {
    listen      80;
    server_name example.com;

    location ^~ /account/login {
        rewrite ^ https://$server_name:443$request_uri? permanent;
    }
    location / {
        proxy_pass  http://localhost:8080;

        ### Set headers ####
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_redirect     off;
    }
}

server {
    listen 443 ssl;
    server_name example.com;

    ssl on;
    ssl_certificate ../SSL/ittest.pem;
    ssl_certificate_key ../SSL/ittest.key;
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
    ssl_prefer_server_ciphers on;

    location ^~ /account/login {
        proxy_pass  http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_redirect     off;

        ### Most PHP, Python, Rails, Java App can use this header -> https ###
        proxy_set_header X-Forwarded-Proto  $scheme;
    }
    location / {
        rewrite  ^  http://$server_name$request_uri? permanent;
    }
}


When the browser accesses http://example.com/account/login.xx, it is navigated to https://example.com/account/login.xx with the 301 Moved Permanently code. In this SSL-encrypted virtual machine, the /account/login is also matched, and is routed to the backend server through reverse proxy. The subsequent transmission process is not HTTPS. Other resources under this login.xx page also issue requests to nginx in HTTPS. After successful login, the link of the home page uses HTTP, which may be configured in the development code.
• The configuration above uses proxy_set_header X-Forwarded-Proto $scheme. Call the request.getScheme() method on a jsp page and what you get is the HTTPS. If you don’t set the $scheme protocol of the request in the header, the backend jsp page will always think it is an HTTP page, leading to response exceptions.
• The SSL configuration block also has a location / which is similar to the unencrypted Port 80. Its role is to automatically navigate the access to the unencrypted port when a user visits the HTTPS homepage directly. You can remove it to allow users to visit the HTTPS homepage directly.
3. Implement two-way SSL authentication
The above two configuration methods both identify whether the domain names of the visited sites are authentic and credible and encrypt the transmission process. But the server end doesn’t certify the trustworthiness of the clients. (In fact, except particularly important scenarios like bank USB keys, there is no need to certify the visitor.)
To implement two-way authentication of HTTPS, the CA certificate must be imported on the nginx server (root certificate/intermediate certificate) because currently the server end validates the information of the client end through CA. In addition, you must generate the client certificate in the same method at the same time when you apply for the server certificate. After you get the client certificate, you also need to convert it to a format recognizable by the browser (most browsers recognize the PKCS12 format):
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12

Then you should send the client.p12 to a trusted client and let it import it to the browser. When you access the site to set up a connection, nginx will require the client side to send this certificate to it for validation. If this certificate is absent, the access will be denied.
At the same time, do not forget to configure a trusted CA in nginx.conf: (If it is a second-level CA, please put the root CA certificate at the end to form a CA certificate chain.)
proxy_ignore_client_abort on;

    ssl on;
    ...
    ssl_verify_client on;
    ssl_verify_depth 2;
    ssl_client_certificate ../SSL/ca-chain.pem;

#Add the following below the two-way location:
    proxy_set_header X-SSL-Client-Cert $ssl_client_cert;


Extended: Use geo module
Nginx has a ngx_http_geo_module installed by default. This geo module can create values for variables according to the client IP address and is used to apply two-way authentication when there is a login request from, for example, the 172.29.73.0/24 IP segment, while requests from other IP segments will still undergo one-way authentication.
geo $duplexing_user {
    default 1;
    include geo.conf;  # Note: After the 0.6.7 version, include is relative to the directory where nginx.conf is located.
}


The syntax geo [$address] $variable { … } is in the HTTP section. The default address is $reoute_addr and we suppose the content of conf/geo.conf is as follows:
127.0.0.1/32    LOCAL;  # Local
172.29.73.23/32 SEAN;   # An IP address
172.29.73.0/24  1;      # IP segment. The value of the later part of it can be defined according to the country or region.


You need to configure another virtual host server{ssl 445}. Apply the aforementioned two-way authentication writing in it, and then use the variable $duplexing_user for identification in Port 80 or Port 443. If it is 1, rewrite it to 445; otherwise, rewrite it to 443.
Guest