How to secure your Shiny app with HTTPS

This article belongs to the series How to deploy a Shiny app on AWS, divided into 7 parts. To access the other articles, use the following table of contents:

In the previous article, we changed the ugly IP address of our Shiny app in a good-looking domain name, so that we could reach the app by typing an URL like http://shiny.charlesbordet.com/movie-explorer.

Nice!

Except there is still a problem.

Especially if you want to put your app in production for a client.

The app is not secured.

You may have noticed the subtle message from your browser:

Shiny AWS HTTP non sécurisé

Uh oh…

Indeed, the app is using the HTTP protocol, which is not secure.

Nowadays, everyone uses HTTPS, the secured version of HTTP.

That’s why, in this article, we’ll talk about:

  • What is HTTPS and why we need it
  • How to get certificates SSL/TLS for free with Let’s Encrypt
  • How to set up the certificates on your server
  • How to force HTTPS over HTTP

Let’s get started!

What is HTTPS?

You certainly have heard of it, but you’re not sure how HTTPS works and why we need to use it.

Well. Imagine that each time you land on a webpage, you send a request from your home to the data center where the webpage is hosted.

Then, a request containing the webpage comes back to you so that your browser can display it.

These messages drive all over the world, from router to router, until they reach their destination.

You don’t control who own these routers, nor what they do with it.

And when you use HTTP, those messages are not encrypted.

It means that anyone can intercept the requests, open them, read them, even change them, and send them back without anyone knowing.

Shiny AWS Man in the Middle Attack

That’s called a Man-in-the-Middle attack.

It’s not cool when the requests contain passwords, data or your credit card numbers.

That’s why we decided to upgrade HTTP into something secure: HTTPS.

HTTPS guarantees that the messages are encrypted. If someone intercepts the request, they can’t do anything with it.

At first HTTPS was mostly used for e-commerce, where the data is confidential, but later on, it was used for the whole internet.

Because privacy is good.

That’s why your browser displays this big red warning.

In short. You want:

  • that your data stays secured and encrypted
  • that your client or boss doesn’t get scared seeing a warning from the browser

So we use HTTPS.

How?

First, we need certificates (SSL/TLS) for the server. And keys.

I won’t get into too many details, but the certificate says “Yea it’s me the real server” and it contains encrypting keys (a public and a private one).

Create a Let’s Encrypt certificate

You can get certificates from trusted organizations.

Most of the time, it costs money.

But not with Let’s Encrypt. It’s free and automatic.

So.. let’s use it.

They even have some small piece of software to automatically renew the certificates. Otherwise, they expire every 3 months.

So, you do this setup once, and then you won’t have to touch it again.

To get started, visit the Cerbot website:.

Choose:

  1. Software = Nginx
  2. System = Ubuntu 18.04 (change if you’re on a different system)

Shiny AWS Certbot

Scroll down and copy/paste the installation instructions in your server. If you don’t remember how to do it, you can go back on the article Install R and R Shiny.

$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository universe
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update

$ sudo apt-get install certbot python-certbot-nginx 

Then, you can automatically get a certificate by typing:

$ sudo certbot certonly --nginx -d shiny.charlesbordet.com

Don’t forget to change the domain name for yours.

The program will speak to you then:

Renewing an existing certificate
Performing the following challenges:
http-01 challenge for shiny.charlesbordet.com
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/shiny.charlesbordet.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/shiny.charlesbordet.com/privkey.pem
   Your cert will expire on 2020-02-04. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"

The most important part is where it says that your certificates and keys are in the /etc/letsencrypt/live/shiny.charlesbordet.com directory.

We’ll use that later.

How to set up your new SSL/TLS certificates with nginx

For this section to work, you need to have an existing nginx configuration.

That’s the optional section I wrote in the previous article. Check it here: Set up a reverse proxy with Nginx.

If you haven’t done it yet, go there and come back once it’s set up.

We’ll wait for you.

Remember, you had installed a piece of software called nginx.

nginx is a web server. One of its features is the reverse proxy that we set up last time.

In the previous article, I explained that Nginx is like a middleman. It listens on port 80 and then redirects visitors to the correct port (3838 if they ask for shiny for example).

Port 80, that was for the HTTP protocol.

But now, we want to use HTTPS.

The port for HTTPS is 443.

How do I know this?

Each protocol has its own dedicated port. It’s not really up to us. For example, SSH is on 22, FTP on 21, etc.

It’s all listed on this Wikipedia page: List of TCP and UDP port numbers.

It goes up to 1024, and then any number after 1024 is free to use. That’s why Shiny picked 3838 by default (but we can change it).

Anyway.

We’ll change the Nginx configuration to use 443 instead of 80.

As a reminder, here is the path to the config file:

sudo nano /etc/nginx/sites-available/shiny.conf

You should get the file we modified last time.

We’ll change a few lines.

1. Change the port

First, let’s change the port number.

Find the two lines that start with listen and replace them with the following ones:

# listen 443 means the Nginx server listens on the 443 port.
listen 443 ssl http2;
listen [::]:443 ssl http2;

2. Configure SSL

Next, let’s add the SSL parameters.

SSL, that’s the encrypting protocol. Think of it like HTTP + SSL = HTTPS.

We should say TLS now, but everyone keeps talking about SSL.

I’m no expert in SSL.

So I can’t tell you every tiny detail of it.

And that’s why I use Mozilla’s recommendations. They have an SSL Configuration Generator.

Check the link and add these lines in the config file:

# certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
ssl_certificate /path/to/signed_cert_plus_intermediates;
ssl_certificate_key /path/to/private_key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions
ssl_session_tickets off;

# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam.pem
ssl_dhparam /path/to/dhparam.pem;

# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;

# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;

# verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;

That’s a lot.

And there are a few things to change:

The two first rows: ssl_certificate and ssl_certificate_key. You need to feed them with the certificates we generated earlier.

I changed them with:

ssl_certificate /etc/letsencrypt/live/shiny.charlesbordet.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/shiny.charlesbordet.com/privkey.pem;

(Of course, don’t forget to put your domain name)

3. Download the DH parameters

Then, the DH parameter.

The comment tells us to download them. Let’s do it:

$ curl https://ssl-config.mozilla.org/ffdhe2048.txt > /etc/nginx/snippets/dhparam.pem;

And then change the path for ssl_dhparam:

ssl_dhparam /etc/nginx/snippets/dhparam.pem;

About the HSTS section, I recommend you deactivate it for now.

HSTS is used to tell the browser: “From now on, and for years to come, you only use HTTPS for this domain and never HTTP.”

It’s a good practice.

Except if you have issues to set up HTTPS and want to come back to HTTP, you’re screwed.

And since it’s your first time, you should expect things to go wrong.

Even if you’re following my article.

So let’s deactivate it for now, and only when we’re sure everything works we’ll activate it again. I’m adding an # before:

# add_header Strict-Transport-Security "max-age=63072000" always;

4. Use the Let’s Encrypt certificate

Finally, we’re only left with the last line:

ssl_trusted_certificate /etc/letsencrypt/live/shiny.charlesbordet.com/chain.pem;

And… that’s it!

It’s test time!

Save the file, and check that there are no syntax errors:

sudo nginx -t

You should get the following response:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If not, it means you have done a syntax error. The error message should tell you what’s wrong.

Once the syntax is validated, let’s restart nginx:

sudo systemctl restart nginx

And finally.

Check your domain name (type https at the beginning): https://shiny.charlesbordet.com/movie-explorer.

Your browser should display a nice lock with a comforting message:

Shiny AWS HTTPS secured

Good job!

We’re done.

Almost.

How to force HTTPS over HTTP

I told you earlier to deactivate HSTS.

Now that everything is working smoothly, let’s activate it:

add_header Strict-Transport-Security "max-age=63072000" always;

And we’ll do a second change.

In the SSL configuration generator, they added another server bloc at the top of the file.

Well, this other bloc is listening on port 80, and its only job is to automatically redirect to port 443.

That’s for the users who will visit your app with HTTP.

Instead of getting a 404 page, they’ll get redirected to the secured page.

So, let’s add these few lines at the top of your config file:

server {
    listen 80;
    listen [::]:80;

    # redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
    return 301 https://$host$request_uri;
}

Now we’re good!

Don’t forget to restart nginx once again:

sudo systemctl restart nginx

Now, your app is secured, and the data you send with it is encrypted.

Woah, this is getting serious!

We’re a bit far away from the R language now and we can say we entered the sysadmin world.

I don’t know about you, but I love this new world.

I find it super interesting to learn about how servers work, and even the Internet.

Please share in the comments if you’ve successfully set up HTTPS (or if you’re stuck somewhere).

In the meantime, there is one last topic to tackle in this series: How to protect the app with a password.

Updated:

Comments

Leave a Comment

Required fields are marked *

Loading...

Comments are validated manually. The page will refresh after validation.