Security in Node.js Applications: Implementing HTTPS in Docker for deployed Node.js applications

The current best practice is that every website must be accessed with HTTPS. Gone are the days when it was okay to transmit unencrypted information over the internet. That old model is susceptible to problems such as man-in-the-middle attacks, and other threats.

Using SSL and HTTPS means that connections over the internet are authenticated and encrypted. The encryption is good enough to keep out all but the most advanced of snoops, and the authentication means we are assured the website is what it says it is. HTTPS uses the HTTP protocol but is encrypted using SSL, or Secure Sockets Layers. Implementing HTTPS requires getting an SSL certificate and implementing HTTPS support in the web server or web application.

Given a suitable SSL certificate, Node.js applications can easily implement HTTPS because a small amount of code gives us an HTTPS server. But there’s another route that offers an additional benefit. NGINX is a well-regarded web server, and proxy server, that is extremely mature and feature-filled. We can use it to implement the HTTPS connection, and at the same time gain another layer of shielding between potential miscreants and the Notes application.

We have already deployed Notes using Docker swarm on an AWS EC2 cluster. Using NGINX is a simple matter of adding another container to the swarm, configured with the tools required to provision SSL certificates. For that purpose, we will use a Docker container that combines NGINX with a Let’s Encrypt client program, and scripting to automate certificate renewal. Let’s Encrypt is a non-profit operating an excellent service for free SSL certificates. Using their command-line tools, we can provision and otherwise manage SSL certificates as needed.

In this section, we will do the following:

  1. Configure a domain name to point to our swarm
  2. Incorporate a Docker container containing NGINX, Cron, and Certbot (one of the Let’s Encrypt client tools)
  3. Implement automated processes in that container for managing certificate renewal
  4. Configure NGINX to listen on port 443 (HTTPS) alongside port 80 (HTTP)
  5. Configure the Twitter application to support the website on HTTPS

This may seem like a lot of work, but every task is simple. Let’s get started.

1. Assigning a domain name for an application deployed on AWS EC2

The Notes application is deployed using a Docker swarm built on AWS EC2 instances. One of those instances has a public IP address and a domain name assigned by AWS. It is best to assign a domain name to the EC2 instance because the name assigned by AWS is not only user-unfriendly, but will change the next time you redeploy the cluster. Giving the EC2 instance a domain name requires having a registered domain name, adding an A record listing its IP address, and updating the A record every time the EC2 IP address changes.

What does it mean to add an A record? The domain name system (DNS) is what lets us use a name such as geekwisdom.net for a website rather than the IP address, 216.239.38.21. In the DNS protocol, there are several types of records that can be associated with domain name entries in the system. For this project, we need to only concern ourselves with one of those record types, the A record, for recording IP addresses for domain names. A web browser that’s been told to visit any domain looks up the A record for that domain and uses that IP address to send HTTP(S) requests for website content.

The specific method to add an A record to the DNS entries of a domain varies considerably from one domain registrar to another. For example, one registrar (Pair Domains) has this screen:

In the dashboard for a specific domain, there might be a section for adding new DNS records. In this registrar, a dropdown lets you choose among the record types. Select the A record type, then for your domain name enter the IP address in the right-hand box, and in the left-hand box enter the subdomain name. In this case, we are creating a subdomain, notes.geekwisdom.net, so we can deploy a test site without disturbing the main site hosted on that domain. This also lets us avoid the expense of registering a new domain for this project.

As soon as you click the ADD RECORD button, the A record will be published. Since it usually takes some time for DNS records to propagate, you might not be able to visit the domain name right away. If this takes more than a couple of hours, you might have done something wrong.

Once the A record is successfully deployed, your users will be able to visit the Notes application at a nice domain like notes.geekwisdom.net.

Note that the IP address will change every time the EC2 instances are redeployed. If you redeploy the EC2 instances, you will need to update the A record for the new address.

In this section, we have learned about assigning a domain name to the EC2 instance. This will make it easier for our users to access Notes, while also letting us provision an HTTPS/SSL certificate.

Adding the domain name means updating the Twitter application configuration so that Twitter knows about the domain name.

2. Updating the Twitter application

Twitter needs to know which URLs are valid for our application. So far, we’ve told Twitter about test URLs on our laptop. We have Notes on a live domain, we need to tell Twitter about this.

We’ve already done this several times, so you already know what to do. Head to developers.twitter.com, logging in with your Twitter account, and go to the Apps dashboard. Edit the application related to your Notes instance, and add your domain name to the list of URLs.

We will be implementing both HTTP and HTTPS for the Notes application, and therefore Notes will have both http:// and https:// URLs. This means you must not only add the HTTP URLs to the Twitter configuration site, but also the HTTPS URLs.

In the compose-stack/docker-compose.yml file, the TWITTER_CALLBACK_HOST environment variable in the svc-notes configuration must also be updated with the domain.

We now have both a domain name associated with the EC2 cluster, and we’ve informed Twitter of the domain name. We should be able to redeploy Notes to the swarm and be able to use it with the domain name. That includes being able to log in using Twitter, creating and deleting notes, and so forth. At this point, you cannot put an HTTPS URL into TWITTER_CALLBACK_HOST because we’ve not implemented HTTPS support.

These steps prepared the way for implementing HTTPS on Notes using Let’s Encrypt. But first, let’s examine how Let’s Encrypt works so we can better implement it for Notes.

3. Planning how to use Let’s Encrypt

Like every HTTPS/SSL certificate provider, Let’s Encrypt is required to be certain that you own the domain for which you’re requesting the certificate. Successfully using Let’s Encrypt requires successful validation before any SSL certificates are issued.

Once a domain is registered with Let’s Encrypt, the registration must be renewed at least every 90 days, because that’s the expiry time for their SSL certificates. Domain registration, and certificate renewal, are therefore the two primary tasks we must accomplish.

In this section, we’ll discuss how the registration and renewal features work. The goal is gaining an overview of how we’ll manage an HTTPS service for any domain we plan to use.

Let’s Encrypt supports an API and there are several client applications for this API. Certbot is the recommended user interface for Let’s Encrypt requests. It is easily installed on a variety of operating systems. For example, it is available through the Debian/Ubuntu package management system.

Validated domain ownership is a core feature of HTTPS, making it a core requirement for any SSL certificate supplier to be certain it is handing out SSL certificates correctly. Let’s Encrypt has several validation strategies, and in this project, we’ll focus on one, the HTTP-01 challenge.

The HTTP-01 challenge involves the Let’s Encrypt service making a request to a URL such as http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN>.

The <TOKEN> is a coded string supplied by Let’s Encrypt, which the Certbot tool will write as a file in a directory. Our task is then to somehow allow the Let’s Encrypt servers to retrieve that file using this URL.

Once Certbot successfully registers the domain with Let’s Encrypt, it receives a pair of PEM files comprising the SSL certificate. Certbot tracks various administrative details, and the SSL certificates, in a directory, by default /etc/letsencrypt. The SSL certificate in turn must be used to implement the HTTPS server for Notes.

Let’s Encrypt SSL certificates expire in 90 days, and we must create an automated administrative task to renew the certificates. Certbot is also used for certificate renewal, by running certbot renew. This command looks at the domains registered on this server, and for any that require renewal it reruns the validation process.

Therefore the directory required for the HTTP-01 challenge must remain enabled.

With SSL certificates in hand, we must configure some an HTTP server instance to use those certificates to implement HTTPS. It’s very possible to configure the svc- notes service to handle HTTPS on its own. In the Node.js runtime is an HTTPS server object that could handle this requirement. It would be a small rewrite in notes/app.mjs to accommodate SSL certificates to implement HTTPS, as well as the HTTP-01 challenge.

But there is another possible approach. Web servers such as NGINX are very mature, robust, well tested, and, most importantly, support HTTPS. We can use NGINX to handle the HTTPS connection, and use what’s called a reverse proxy to pass along the traffic to svc-notes as HTTP. That is, NGINX would be configured to accept in- bound HTTPS traffic, converting it to HTTP traffic to send to svc-notes.

Beyond the security goal of implementing HTTPS, this has an additional advantage of using a well-regarded web server (NGINX) to act as a shield against certain kinds of attacks.

Having looked over the Let’s Encrypt documentation, we have a handle on how to proceed. There is a Docker container available that handles everything we need to do with NGINX and Let’s Encrypt. In the next section, we’ll learn how to integrate that container with the Notes stack, and implement HTTPS.

4. Using NGINX and Let’s Encrypt in Docker to implement HTTPS for Notes

We just discussed how to implement HTTPS for Notes using Let’s Encrypt. The approach we’ll take is to use a pre-baked Docker container, Cronginx (https://hub.docker.com/r/robogeek/cronginx), which includes NGINX, Certbot (a Let’s Encrypt client), and a Cron server with a Cron job for managing SSL certificate renewal. This will simply require adding another container to the Notes stack, a little bit of configuration, and running a command to register our domain with Let’s Encrypt.

Before starting this section, make sure you have set aside a domain name that we will use in this project.

In the Cronginx container, Cron is used for managing a background task to renew SSL certificates. Yes, Cron, the server Linux/Unix administrators have used for decades for managing background tasks.

The NGINX configuration will both handle the HTTP-01 challenge and use a reverse proxy for the HTTPS connection. A proxy server acts as an intermediary; it receives requests from clients and uses other services to satisfy those requests. A reverse proxy is a kind of proxy server that retrieves resources from one or more other servers, while making it look like the resource came from the proxy server. In this case, we will configure NGINX to access the Notes service at http://svc-notes:3000, while making the appearance that the Notes service is hosted by the NGINX proxy.

If you don’t know how to configure NGINX, don’t worry because we’ll show exactly what to do, and it’s relatively simple.

4.1. Adding the Cronginx container to support HTTPS on Notes

We’ve determined that adding HTTPS support requires the addition of another container to the Notes stack. This container will handle the HTTPS connection and incorporate tools for managing SSL certificates provisioned from Let’s Encrypt.

In the compose-stack directory, edit docker-compose.yml like so:

services:

svc-notes:

# ports:

# – “80:3000”

environment:

TWITTER_CALLBACK_HOST: “http://YOUR-DOMAIN”

cronginx:

image: robogeek/cronginx

container_name: cronginx

deploy:

replicas: 1

placement:

constraints:

– “node.labels.type==public”

networks:

– frontnet

ports:

– 80:80

– 443:443

dns:

– 8.8.8.8

– 9.9.9.9

restart: always

volumes:

– type: bind

source: /home/ubuntu/etc-letsencrypt

target: /etc/letsencrypt

– type: bind

source: /home/ubuntu/webroots

target: /webroots

– type: bind

source: /home/ubuntu/nginx-conf-d

target: /etc/nginx/conf.d 

Because the svc-notes container will not be handling inbound traffic, we start by disabling its ports tag. This has the effect of ensuring it does not export any ports to the public. Instead, notice that in the cronginx container we export both port 80 (HTTP) and port 443 (HTTPS). That container will take over interfacing with the public internet.

Another change on svc-notes is to set the TWITTER_CALLBACK_HOST environment variable. Set this to the domain name you’ve chosen. Remember that correctly setting this variable is required for successful login using Twitter. Until we finish implementing HTTPS, this should have an HTTP URL.

The deploy tag for Cronginx is the same as for svc-notes. In theory, because svc- notes is no longer interacting with the public it could be redeployed to an EC2 instance on the private network. Because both are attached to frontnet, either will be able to access the other with a simple domain name reference, which we’ll see in the configuration file.

This container uses the same DNS configuration, because Certbot needs to be able to reach the Let’s Encrypt servers to do its work.

The final item of interest is the volume mounts. In the previous section, we discussed certain directories that must be mounted into this container. As with the database containers, the purpose is to persist the data in those directories while letting us destroy and recreate the Cronginx container as needed. Each directory is mounted from /home/ubuntu because that’s the directory that is available on the EC2 instances. The three directories are as follows:

  • /etc/letsencrypt: As discussed earlier, Certbot uses this directory to track administrative information about domains being managed on the server. It also stores the SSL certificates in this directory.
  • /webroots: This directory will be used in satisfying the HTTP-01 request to the http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN> URL.
  • /etc/nginx/conf.d: This directory holds the NGINX configuration files for each domain we’ll handle using this Cronginx instance.

For NGINX configuration, there is a default config file at /etc/nginx/nginx.conf. That file automatically includes any configuration file in /etc/nginx/conf.d, within an http context. What that means is each such file should have one or more server declarations. It won’t be necessary to go deeper into learning about NGINX since the config files we will use are very straightforward.

It will be a useful convention to follow to have one file in the /etc/nginx/conf.d directory for each domain you are hosting. That means, in this project, you will have one domain, and therefore you’ll store one file in the directory named YOUR- DOMAIN.conf. For the example domain we configured earlier, that file would be notes.geekwisdom.net.conf.

4.1. Creating an NGINX configuration to support registering domains with Let’s Encrypt

At this point, you have selected a domain you will use for Notes. To register a domain with Let’s Encrypt, we need a web server configured to satisfy requests to the http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN> URL, and where the corresponding directory is writable by Certbot. All the necessary elements are contained in the Cronginx container.

What we need to do is create an NGINX configuration file suitable for handling registration, then run the shell script supplied inside Cronginx. After registration is handled, there will be another NGINX configuration file that’s suitable for HTTPS. We’ll go over that in a later section.

Create a file for your domain named initial-YOUR-DOMAIN.conf, named this way because it’s the initial configuration file for the domain. It will contain this:

# HTTP — redirect all traffic to HTTPS server {

listen 80;

# listen [::]:80 default_server ipv6only=on;

# Here put the domain name the server is to be known as.

server_name YOUR-DOMAIN www.YOUR-DOMAIN;

access_log /var/log/YOUR-DOMAIN.access.log main;

error_log /var/log/YOUR-DOMAIN.error.log debug;

# This is for handling the ACME challenge. Substitute the # domain name here.

location /.well-known/ {

root /webroots/YOUR-DOMAIN/;

}

# Use this to proxy for another service location / {

proxy_pass http://svc-notes:3000/;

}

}

As we said, the NGINX configuration files are relatively simple. This declares a server, in this case listening to port 80 (HTTP). It is easy to turn on IPv6 support if you wish.

The server_name field tells NGINX which domain name to handle. The access_log and error_log fields, as the name implies, specify where to send logging output.

The location blocks describe how to handle sections of the URL space for the domain. In the first, it says that HTTP-01 challenges on the /.well-known URL are handled by reading files from /webroots/YOUR-DOMAIN. We’ve already seen that directory referenced in the docker-compose.yml file.

The second location block describes the reverse proxy configuration. In this case, we configure it to run an HTTP proxy to the svc-notes container at port 3000. That corresponds to the configuration in the docker-compose.yml file.

That’s the configuration file, but we need to do a little work before we can deploy it to the swarm.

4.2. Adding the required directories on the EC2 host

We’ve identified three directories to use with Cronginx. Remember that each of the EC2 hosts is configured by a shell script we supply in the user_data field in the Terraform files. That script installs Docker and performs another setup. Therefore, we should use that script to create the three directories.

In terraform-swarm, edit ec2-public.tf and make this change:

resource “aws_instance” “public” {

user_data = join(“\n”, [

// Make directories required for cronginx container

“mkdir /home/ubuntu/etc-letsencrypt”,

“mkdir /home/ubuntu/webroots”,

“mkdir /home/ubuntu/nginx-conf-d”

]);

}

There is an existing shell script that performs the Docker setup. These three lines are appended to that script and create the directories.

With this in place, we can redeploy the EC2 cluster, and the directories will be there ready to be used.

4.3. Deploying the EC2 cluster and Docker swarm

Assuming that the EC2 cluster is currently not deployed, we can set it up as we did in Chapter 12, Deploying a Docker Swarm to AWS EC2 with Terraform. In terraform- swarm, run this command:

$ terraform apply 

By now you will have done this several times and know what to do. Wait for it to finish deploying, record the IP addresses and other data, then initialize the swarm cluster and set up remote control access so you can run Docker commands on your laptop.

A very important task is to take the IP address and go to your DNS registrar and update the A record for the domain with the new IP address.

We need to copy the NGINX configuration file into /home/ubuntu/nginx-conf-d, so let’s do so as follows:

$ ssh ubuntu@PUBLIC-IP-ADDRESS sudo chown ubuntu nginx-conf-d

$ scp initial-YOUR-DOMAIN.conf \

ubuntu@PUBLIC-IP-ADDRESS:/home/ubuntu/nginx-conf-d/

YOUR-DOMAIN.conf 

The chown command is required because when Terraform created that directory it became owned by the root user. It needs to be owned by the ubuntu user for the scp command to work.

At this point make sure that, in compose-swarm/docker-compose.yml, the TWITTER_CALLBACK_HOST environment variable for svc-notes is set to the HTTP URL (http://YOUR-DOMAIN) rather than the HTTPS URL. Obviously you have not yet provisioned HTTPS and can only use the HTTP domain.

With those things set up, we can run this:

$ printf ‘…’ | docker secret create TWITTER_CONSUMER_SECRET –

xgfpl4f7grcx33e7hn3pjmep9

$ printf ‘…’ | docker secret create TWITTER_CONSUMER_KEY –

1xen2h4cjige0uonxnyyg8icq 

$ docker stack deploy –with-registry-auth \ 

–compose-file docker-compose.yml notes

Creating network notes_frontnet

Creating network notes_authnet

Creating network notes_svcnet

Creating service notes_db-notes

Creating service notes_svc-notes

Creating service notes_redis

Creating service notes_cronginx

Creating service notes_db-userauth

Creating service notes_svc-userauth 

This adds the required secrets to the swarm, and then deploys the Notes stack. After a few moments, the services should all show as having launched. Notice that Cronginx is one of the services.

Once it’s fully launched, you should be able to use Notes as always, but using the domain you configured. You should even be able to log in using Twitter.

4.4. Registering a domain with Let’s Encrypt

We have just deployed the Notes stack on the AWS EC2 infrastructure. A part of this new deployment is the Cronginx container with which we’ll handle HTTPS configuration.

We have Notes deployed on the swarm, with the cronginx container acting as an HTTP proxy. Inside that container came pre-installed the Certbot tool and a script (register.sh) to assist with registering domains. We must run register.sh inside the cronginx container, and once the domain is registered we will need to upload a new NGINX configuration file.

Starting a shell inside the cronginx container can be this easy:

$ docker ps

…. look for container name for cronginx

$ docker exec -it notes_cronginx.1.CODE-STRING bash

root@d4a81204cca4:/scripts# ls

register.sh renew.sh 

You see there is a file named register.sh containing the following:

#!/bin/sh

mkdir -p /webroots/$1/.well-known/acme-challenge

certbot certonly –webroot -w /webroots/$1 -d $1 

This script is designed to both create the required directory in /webroots, and to use Certbot to register the domain and provision the SSL certificates. Refer to the configuration file and you’ll see how the /webroots directory is used.

The certbot certonly command only retrieves SSL certificates and does not install them anywhere. What that means is it does not directly integrate with any server, but simply stashes the certificates in a directory. That directory is within the /etc/letsencrypt hierarchy.

The –webroot option means that we’re running in cooperation with an existing web server. It must be configured to serve the /.well-known/acme-challenge files from the directory specified with the -w option, which is the /webroots/YOUR- DOMAIN directory we just discussed. The -d option is the domain name to be registered.

In short, register.sh fits with the configuration file we created. The script is executed like so:

root@d4a81204cca4:/scripts# sh -x register.sh notes.geekwisdom.net

+ mkdir -p /webroots/notes.geekwisdom.net/.well-known/acme-challenge

+ certbot certonly –webroot -w /webroots/notes.geekwisdom.net -d

notes.geekwisdom.net

Saving debug log to /var/log/letsencrypt/letsencrypt.log

Plugins selected: Authenticator webroot, Installer None

Enter email address (used for urgent renewal and security notices)

(Enter ‘c’ to

cancel): … 

We run the shell script using sh -x register.sh and supply our chosen domain name as the first argument. Notice that it creates the /webroots directory, which is required for the Let’s Encrypt validation. It then runs certbot certonly, and the tool starts asking questions required for registering with the service.

The registration process ends with this message:

Obtaining a new certificate

Performing the following challenges:

http-01 challenge for notes.geekwisdom.net

Using the webroot path /webroots/notes.geekwisdom.net for all unmatched domains.

Waiting for verification…

Cleaning up challenges

 

IMPORTANT NOTES:

– Congratulations! Your certificate and chain have been saved at:

/etc/letsencrypt/live/notes.geekwisdom.net/fullchain.pem Your key file has been saved at:

/etc/letsencrypt/live/notes.geekwisdom.net/privkey.pem

Your cert will expire on 2020-09-23. 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”

– Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. 

The key data is the pathnames for the two PEM files that make up the SSL certificate. It also tells you to run certbot renew every so often to renew the certificates. We already took care of that by installing the Cron job.

As they say, it is important to persist this directory elsewhere. We took the first step by storing it outside the container, letting us destroy and recreate the container at will. But what about when it’s time to destroy and recreate the EC2 instances? Place a task on your backlog to set up a backup procedure, and then during EC2 cluster initialization install this directory from the backup.

Now that our domain is registered with Let’s Encrypt, let’s change the NGINX configuration to support HTTPS.

5. Implementing an NGINX HTTPS configuration using Let’s Encrypt certificates

Alright, we’re getting so close we can taste the encryption. We have deployed NGINX plus Let’s Encrypt tools into the Notes application stack. We’ve verified that the HTTP-only NGINX configuration works correctly. And we’ve used Certbot to provision SSL certificates for HTTPS from Let’s Encrypt. That makes it time to rewrite the NGINX configuration to support HTTPS and to deploy that config to the Notes stack.

In compose-stack/cronginx create a new file, YOUR-DOMAIN.conf, for example notes.geekwisdom.net.conf. The previous file had a prefix, initial, because it served us for the initial phase of implementing HTTPS. Now that the domain is registered with Let’s Encrypt, we need a different configuration file:

# HTTP — redirect all traffic to HTTPS server {

listen 80;

# listen [::]:80 default_server ipv6only=on;

# Here put the domain name the server is to be known as.

server_name YOUR-DOMAIN www.YOUR-DOMAIN;

access_log /var/log/YOUR-DOMAIN.access.log main;

error_log /var/log/YOUR-DOMAIN.error.log debug;

# This is for handling the ACME challenge.

Substitute the # domain name here.

location /.well-known/ {

root /webroots/YOUR-DOMAIN/;

}

# Use this to force a redirect to the SSL/HTTPS site

return 301 https://$host$request_uri;

}

This reconfigures the HTTP server to do permanent redirects to the HTTPS site. When an HTTP request results in a 301 status code, that is a permanent redirect. Any redirect tells web browsers to visit a URL provided in the redirect. There are two kinds of redirects, temporary and permanent, and the 301 code makes this a permanent redirect. For permanent redirects, the browser is supposed to remember the redirect and apply it in the future. In this case, the redirect URL is computed to be the request URL, rewritten to use the HTTPS protocol.

Therefore our users will silently be sent to the HTTPS version of Notes, with no further effort on our part.

To implement the HTTPS server, add this to the config file:

# HTTPS service

server {

# simple reverse-proxy # Enable HTTP/2

listen 443 ssl http2;

# listen [::]:443 ssl http2;

# Substitute here the domain name for the site

server_name YOUR-DOMAIN www.YOUR-DOMAIN;

access_log /var/log/YOUR-DOMAIN.access.log main;

error_log /var/log/YOUR-DOMAIN.error.log debug;

# Use the Let’s Encrypt certificates

# Substitute in the domain name

ssl_certificate /etc/letsencrypt/live/YOUR-DOMAIN/fullchain.pem;

ssl_certificate_key /etc/letsencrypt/live/YOUR-DOMAIN/privkey.pem;

# Replication of the ACME challenge handler. Substitute

# the domain name.

location /.well-known/ {

root /webroots/YOUR-DOMAIN/;

}

# See:

# https://stackoverflow.com/questions/29043879/socket-io-with-nginx location ^~ /socket.io/ {

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_set_header Host $http_host;

proxy_set_header X-NginX-Proxy false;

proxy_pass http://svc-notes:3000; proxy_redirect off;

proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade;

proxy_set_header Connection “upgrade”;

}

# Use this for proxying to a backend service

# The HTTPS session is terminated at this Proxy.

# The back end service will see a simple HTTP service. location / {

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_set_header X-NginX-Proxy true;

proxy_pass http://svc-notes:3000/;

proxy_ssl_session_reuse off;

proxy_set_header Host $http_host;

proxy_cache_bypass $http_upgrade;

proxy_redirect off;

}

}

This is an HTTPS server implementation in NGINX. There are many similarities to the HTTP server declaration, but with a couple of HTTPS – specific items. It listens on port 443, the standard port for HTTPS, and tells NGINX to use SSL. It has the same configuration for the server name and logging.

The next segment tells NGINX the location of the SSL certificates. Simply replace this with the pathname that Certbot gave you.

The next segment handles the /.well-known URL for future validation requests with Let’s Encrypt. Both the HTTP and HTTPS server definitions have been configured to handle this URL from the same directory. We don’t know whether Let’s Encrypt will request validation through the HTTP or HTTPS URL, so we might as well support this on both servers.

The next segment is a proxy server to handle the /socket.io URL. This requires specific settings because Socket.IO must negotiate an upgrade from HTTP/1.1 to WebSocket. Otherwise, an error is printed in the JavaScript console, and the Socket.IO features will not work. For more information, see the URL shown in the code.

The last segment is a reverse proxy set up to proxy HTTPS traffic to an HTTP backend server. In this case, the backend server is the Notes application running on port 3000.

Having created a new configuration file, we can upload it to the notes-public EC2 instance like so:

$ scp YOUR-DOMAIN.conf \

ubuntu@52.32.117.130:/home/ubuntu/nginx-conf-d/YOUR-DOMAIN.conf 

The next question is how do we restart the NGINX server so it reads the new configuration file? One way is to send a SIGHUP signal to the NGINX process, causing it to reload the configuration:

$ docker exec -it notes_cronginx.1.8c2in59gz7b4g2asxfxgd1y3q bash

root@31a813dad28c:/scripts# kill -HUP ‘cat /var/run/nginx.pid’ 

The nginx.pid file contains the process ID of the NGINX process. Many background services on Unix/Linux systems store the process ID in such a file. This command sends the SIGHUP signal to that process, and NGINX is written to reread its configuration upon receiving that signal. SIGHUP is one of the standard Unix/Linux signals, and is commonly used to cause background processes to reload their configuration like this. For more information, see the signal(2) man page.

However, using Docker commands we can do this:

$ docker service update –force notes_cronginx

notes_cronginx

overall progress: 1 out of 1 tasks

1/1: running

verify: Service converged

 

That will kill the existing container and start a new one.

Instead of that rosy success message, you might get this instead:

service update paused: update paused due to failure or early

termination of task flueg3xg8aclciq05r1o2bk1w 

This says that Docker swarm saw that the container exited, and it was therefore unable to restart the service.

It is easy to make mistakes in NGINX configuration files. First take a careful look at the configuration to see what might be amiss. The next stage of diagnosis is to look at the NGINX logs. We can do that with the docker logs command, but we need to know the container name. Because the container has exited, we must run this:

$ docker ps -a 

The -a option causes docker ps to return information about every container, even the ones that are not currently running. With the container name in hand, we can run this:

$ docker logs notes_cronginx.1.bytadzur7fyj0c3xtwokpcrv0

2020/06/25 18:36:18 [emerg] 8#8: unknown directive “Use” in

/etc/nginx/conf.d/YOUR-DOMAIN.conf:26 

And indeed, the issue is a syntax error, and it even helpfully tells you the line number.

Once you have successfully restarted the cronginx service, visit the Notes service you’ve deployed and verify that it is in HTTPS mode.

In this section, we successfully deployed HTTPS support for the Notes application stack on our AWS EC2 based Docker swarm. We used the files Docker container created in the previous section and deployed the updated Notes Stack to the swarm. We then ran Certbot to register our domain with Let’s Encrypt. And we rewrote the NGINX configuration to support HTTPS.

Our next task is to verify the HTTPS configuration is working correctly.

6. Testing HTTPS support for the Notes application

We have done ad hoc testing, and more formal testing, of Notes all through this book. Therefore you know what to do to ensure Notes is working in this new environment. But there are a couple of HTTPS-specific things to check.

In your browser, head to the domain name where you’ve hosted the application. If all went well, you will be greeted by the application, and it will have redirected to the HTTPS port automatically.

So that we humans know that a website is on HTTPS, most browsers show a lock icon in the location bar.

You should be able to click on that lock icon, and the browser will show a dialog giving information about the certificate. The certificate will verify that this is indeed the correct domain, and will also show the certificate was issued by Let’s Encrypt via the Let’s Encrypt Authority X3.

You should be able to browse around the entire application and still see the lock icon.

You should be on the lookout for mixed content warnings. These will appear in the JavaScript console and occur when some content on an HTTPS-loaded page is loaded using an HTTP URL. The mixed content scenario is less secure, and therefore browsers issue warnings to the user. Messages might appear in the JavaScript console inside the browser. If you have followed the instructions in this book correctly you will not see this message.

Finally, head to the Qualys SSL Labs test page for SSL implementations. This service will examine your website, especially the SSL certificates, and give you a score. To examine your score, see https://www.ssllabs.com/ssltest/.

Having completed this task, you may want to bring down the AWS EC2 cluster. Before doing so, it’s good form to de-register the domain from Let’s Encrypt. That’s also a simple matter of running Certbot with the right command:

$ docker ps

$ docker exec -it notes_cronginx.1.lgz1bi8cvr2c0gapuvibegkrn bash

root@f896d97f30d5:/scripts#

root@f896d97f30d5:/scripts# certbot delete –domain YOUR-DOMAIN

As before, we run docker ps to find out the exact container name. With that name, we start a command shell inside the container. The actual act is simple, we just run certbot delete and specify the domain name.

Certbot doesn’t just go ahead and delete the registration. Instead, it asks you to verify that’s what you want to do, then it deletes the registration.

In this section, we have finished implementing HTTPS support for Notes by learning how to test that it is implemented correctly.

We’ve accomplished a redesign of the Notes application stack using a custom NGINX-based container to implement HTTPS support. This approach can be used for any service deployment, where an NGINX instance is used as the frontend to any kind of backend service.

But we have other security fish to fry. Using HTTPS solves only part of the security problem. In the next section, we’ll look at Helmet, a tool for Express applications to set many security options in the HTTP headers.

Source: Herron David (2020), Node.js Web Development: Server-side web development made easy with Node 14 using practical examples, Packt Publishing.

Leave a Reply

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