██╗ ███████╗████████╗███████╗ ███████╗███╗ ██╗ ██████╗██████╗ ██╗ ██╗██████╗ ████████╗
██║ ██╔════╝╚══██╔══╝██╔════╝ ██╔════╝████╗ ██║██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗╚══██╔══╝
██║ █████╗ ██║ ███████╗ █████╗ ██╔██╗ ██║██║ ██████╔╝ ╚████╔╝ ██████╔╝ ██║
██║ ██╔══╝ ██║ ╚════██║ ██╔══╝ ██║╚██╗██║██║ ██╔══██╗ ╚██╔╝ ██╔═══╝ ██║
███████╗███████╗ ██║ ███████║ ███████╗██║ ╚████║╚██████╗██║ ██║ ██║ ██║ ██║
╚══════╝╚══════╝ ╚═╝ ╚══════╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
██╗
██║
████████╗
██╔═██╔═╝
██████║
╚═════╝
██████╗███████╗██████╗ ████████╗██████╗ ██████╗ ████████╗
██╔════╝██╔════╝██╔══██╗╚══██╔══╝██╔══██╗██╔═══██╗╚══██╔══╝
██║ █████╗ ██████╔╝ ██║ ██████╔╝██║ ██║ ██║
██║ ██╔══╝ ██╔══██╗ ██║ ██╔══██╗██║ ██║ ██║
╚██████╗███████╗██║ ██║ ██║ ██████╔╝╚██████╔╝ ██║
╚═════╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝
▄▄██ ██▄▄
░▄▄▓██████▌ ▐██████▓▄▄░
▀██▓░▀███ ▄▄▄▄ ▄▄▄▄▄█░ ░█▄▄▄▄▄ ▄▄▄▄ ███▀░▓██▀
░ ▀██▄ ▀█▓ ▄▄███████▄ ▀▓▀ ▓█▄ ▄█▓ ▀▓▀ ▄███████▄▄ ▓█▀ ▄██▀ ░
█▄▄ ▀█▓ ▀██▀▀░ ▀▀▓█▒ ░▄▄▄▓███ ░ ░ ███▓▄▄▄░ ▒█▓▀▀ ░▀▀██▀ ▓█▀ ▄▄█
▒███▓▄▄ ▀█▄ ▀███▓▀▀▀░ ░▀▀▀▓███▀ ▄█▀ ▄▄▓███▒
▓███▀▀▓█▄███▄ ▀ ▀ ▄███▄█▓▀▀███▓
██▓██▄▄ ▀▀▓▓ ▓▓▀▀ ▄▄██▓██
░▀▀▀███▄ ▄███▀▀▀░
██████▄██▓ .:: W E L C O M E ::. ▓██▄██████
▓██ ██▓
░█▓ While the examples here are largely Node based, the concepts are applicable to any ██░
██ application based web server where you want to securely deploy any application or ██
█▓ service with HTTPS using Let's Encrypt and Certbot ▓█
█▓ ▓█
█▓ ▓█
█▓ ▓█
▐███▓▄▄▄▄ ▄▌ ▐▄ ▄▄▄▄▓███▌
▀ ▄██▓ ░▄▄▄▄▄██ ██░ ▄▄█ █▄▄ ░██ ██▄▄▄▄▄░ ▓██▄ ▀
▄█████▓██▓▀▀▀██▌▐███▄ ▐▓▓█▄░▄█▄ ▄█▄░▄█▓▓▌ ▄███▌▐██▀▀▀▓██▓█████▄
▄█▓▀▀▀▀▀ ▓██ ██▓▐█▓ ██ ▀█▀ ▀■ ■▀ ▀█▀ ██ ▓█▌▓██ ██▓ ▀▀▀▀▀▓█▄
███▓███ ▀████▌ ▐████▀ ███▓███
░▀ ▀▌ █▀ ▀█ ▐▀ ▀░
Let's Encrypt is a free, automated, and open certificate authority brought to you by the nonprofit Internet Security Research Group (ISRG).
Certbot is a free, open source software tool for automatically using Let’s Encrypt certificates on manually-administrated (emphasis mine) websites to enable HTTPS. Certbot is made by the Electronic Frontier Foundation (EFF), a 501(c)3 nonprofit based in San Francisco, CA, that defends digital privacy, free speech, and innovation
██╗ ██╗██╗ ██╗██╗ ██╗ ██╗ ██╗████████╗████████╗██████╗ ███████╗██████╗
██║ ██║██║ ██║╚██╗ ██╔╝ ██║ ██║╚══██╔══╝╚══██╔══╝██╔══██╗██╔════╝╚════██╗
██║ █╗ ██║███████║ ╚████╔╝ ███████║ ██║ ██║ ██████╔╝███████╗ ▄███╔╝
██║███╗██║██╔══██║ ╚██╔╝ ██╔══██║ ██║ ██║ ██╔═══╝ ╚════██║ ▀▀══╝
╚███╔███╔╝██║ ██║ ██║ ██║ ██║ ██║ ██║ ██║ ███████║ ██╗
╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝
HTTPS is part of the Transport Layer Security (TLS) protocol, the successor of the now-deprecated Secure Sockets Layer (SSL), and is the main technology backing HTTPS (HTTP over TLS). HTTPS provides a secure, encrypted communications channel between the end user (browser) and the website or service it is communicating with. Secure communications become more important when you are exchanging information with users.
Unless you are specifically trying to create a way to get around captive portals, HTTPS is important for a few reasons:
-
It's free, and it's automated. It will cost you nothing other than your time. Set it up once, you won't have to touch it again. This article should help reduce your time adding HTTPS to your app/service easier.
-
An oft told tale of man-in-the-middle attack is touted as a primary reason behind securing your app/service.The basic idea being that some bad actor takes the time to impersonate, and/or alter, or steal your data while in flight. While this may still be true, it's considerably easier, and far more likely, that someone would look for your unencrypted data in the numerous log files of all the devices between the end users browser and the server, including wireless access points, routers, switches, modems, and firewalls. When seen in this is context, and the potential for exposure of your unencrypted data, HTTPS seems like a better approach to secure what is important.
-
In August 2014 Google HTTPS Everywhere initiative began using HTTPS as a ranking signal to give HTTPS related sites priority over HTTP websites. While not a primary signal of rank, it is a factor, and if your web app/service is public, having HTTPS in place legitimizes your hard work. People are far likelier to use your stuff if they don't seen warnings like:
Secure | Insecure |
---|---|
██████╗ ██████╗ █████╗ ██╗ ███████╗
██╔════╝ ██╔═══██╗██╔══██╗██║ ██╔════╝
██║ ███╗██║ ██║███████║██║ ███████╗
██║ ██║██║ ██║██╔══██║██║ ╚════██║
╚██████╔╝╚██████╔╝██║ ██║███████╗███████║
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝
- It should be easy to set up
- It should be easy to maintain
- It should run with least privilege
- It should not compromise security of the app or the operating system
What we know:
Node App | Let's Encrypt |
---|---|
Usually run in the context of the login user | Run as sudo to register, setup, and renew |
Login user usually has sudoers access | Saves certificates as root:root with permissions of 700 |
SHOULD NOT BE RUN AS ROOT | Saves files to /etc/letsencrypt/archive/ and symlinks /etc/letsencrypt/live/ back to /etc/letsencrypt/archive/ |
May have to access assets on the file system with different users/groups/permissions | Documentation recommends creating a publicly accessible key file for validation |
Usually runs on some port other than 80 or 443 | Certificates expire every 90 days |
████████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██╗ ███████╗███╗ ███╗
╚══██╔══╝██║ ██║██╔════╝ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██║ ██╔════╝████╗ ████║
██║ ███████║█████╗ ██████╔╝██████╔╝██║ ██║██████╔╝██║ █████╗ ██╔████╔██║
██║ ██╔══██║██╔══╝ ██╔═══╝ ██╔══██╗██║ ██║██╔══██╗██║ ██╔══╝ ██║╚██╔╝██║
██║ ██║ ██║███████╗ ██║ ██║ ██║╚██████╔╝██████╔╝███████╗███████╗██║ ╚═╝ ██║
╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝
Node running as a non root-user trying to access the Let's Encrypt certificate files that are only available to the root-user causing an EACCES: permission denied
error like:
Error: EACCES: permission denied, open '/etc/letsencrypt/live/some_domain_name/privcert.pem'
at Object.openSync (fs.js:438:3)
at Object.readFileSync (fs.js:343:35)
at Object.<anonymous> (/some_path/server.js:26:12)
at Module._compile (internal/modules/cjs/loader.js:701:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
at Module.load (internal/modules/cjs/loader.js:600:32)
at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
at Function.Module._load (internal/modules/cjs/loader.js:531:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)
at startup (internal/bootstrap/node.js:283:19)
███████╗ ██████╗ ██╗ ██╗ ██╗████████╗██╗ ██████╗ ███╗ ██╗███████╗██████╗
██╔════╝██╔═══██╗██║ ██║ ██║╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝╚════██╗
███████╗██║ ██║██║ ██║ ██║ ██║ ██║██║ ██║██╔██╗ ██║███████╗ ▄███╔╝
╚════██║██║ ██║██║ ██║ ██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║ ▀▀══╝
███████║╚██████╔╝███████╗╚██████╔╝ ██║ ██║╚██████╔╝██║ ╚████║███████║ ██╗
╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ ╚═╝
There are some creative, disturbing, wrong, or relatively complicated advice on Stack Overflow to get this setup to work. Some of the potential solutions below are perfectly valid ways to go about achieving the goal of deploying a Node app/service with Let's Encrypt, but adds a level of complexity that may be overkill for the task at hand. The goal is a simple server to run a app/service, We don't want to be fighting with a lot infrastructure.
Some of the proposed solutions fell into these 4 main categories:
- changing public key, private key, and certificate authority file permissions
- moving public key, private key, and certificate authority files directly into the node application directory
- spin up another web server like Apache or NGiИX for reverse proxying traffic
- using Docker for reverse proxying traffic
e.g. chmod 755 /etc/letsencrypt/live/some_domain_name/*.pem
Pros
- It works
Cons
- It's inherently less secure than other options (read and execute permission for non root users on the file)
- It messes with the default, secure permissions set up by CertBot for the files and directory structure
- It can potentially interfere with the CertBot script functionality (now or in the future) if there are permissions checks in place
- Any user on the system can now access the files
Private keys usually have the permissions of 600 (owner: read+write), Certificates permissions of 700 (owner: read+write+execute) for a reason. They should only be accessible to the root-user and not anyone else, the same logic applies with the Let's Encrypt files. We want to keep the private stuff private and secure.
- ✔️ It should be easy to set up
- ✔️ It should be easy to maintain
- ❌ It should run with least privilege
- ❌ It should not compromise security of the app or the operating system
Potential Solution 2: Moving pubkey.pem, privkey.pem, and ca.pem files directly into the node application directory
e.g. cp /etc/letsencrypt/archive/some_domain_name/* some_path/ && chmod 755 some_path/*.pem
Pros
- It works
Cons
- It's inherently less secure than other options
- This is the equivalent to moving the combination closer to the safe. If your application code, a dependency you rely on, or the web server itself are compromised your keys potentially could be as well
- It introduces another step in a process that should be automated. You may need to manually move the files after the updates, write a script to do so, update the synlinks, etc.
- It messes with the default secure permissions set up by CertBot for the files and directory structure
- It can potentially interfere with the CertBot script functionality (now or in the future) it there are permissions checks in place
- Any user on the system can now access the files
We want to take advantage of what CertBot does and then leave it be. We do not want to introduce any fragility to the process where a script could fail, a symlink disappears etc.
- ✔️ It should be easy to set up
⚠️ It should be easy to maintain- ❌ It should run with least privilege
- ❌ It should not compromise security of the app or the operating system
Potential Solution 3: Spin up an Apache or NGiИX webs erver and reverse proxy requests to the back end Node app server
Pros
- It works
- It scales if you have multiple apps and/or services
- It can be more secure (if Apache or NGiИX are configured correctly)
- Apache and NGiИX run in different contexts (users) than Node
- TLS (HTTPS) termination at this level can reduce the number certificates for individual apps/services
Cons
- This could be overkill for the task at hand
- It's an added layer that can make things harder to diagnose, debug, and maintain
- It requires knowledge of Apache or NGiИX configuration which can a high bar for entry
This is an attractive option, albeit an advanced one, if you need to maintain multiple Node apps and/or services or maintain different apps and/or services written in different languages. One possibility is that Apache or NGiИX can act as a load balancer and handle the HTTPS termination in one place rather than each app and/or service doing so individually
- ❌ It should be easy to set up
⚠️ It should be easy to maintain- ✔️ It should run with least privilege
- ✔️ It should not compromise security of the app or the operating system
Pros
- It works
- It scales (up to a point) if you have multiple apps and/or services
- It can be more secure (if the orchestration platform e.g. Docker Server, Kubernetes and the container are configured correctly)
- Docker runs in different contexts (users) than Node
Cons
- This could be overkill for the task at hand
- It requires existing infrastructure to be in place and secured (e.g. Kubernetes, EKS, GKS, Rancher) Running Docker in production is very different than Docker locally
- Some official Docker images are outdated and insecure
- It's an added layer that can make things harder to diagnose, debug, and maintain
- It requires some knowledge of Docker configuration which may be a high bar for entry
docker run container_name
is inherently more insecure then other options unless limits are added
This is an attractive option, albeit an advanced one, if you need to maintain multiple Node apps and/or services or maintain different apps and/or services written in different languages. If you already have infrastructure in place to support this option, great. If not, then getting something up quickly, securely and cheaply might be an issue. It's important to know that some official Node images can be very insecure. There are many secure options available depending on your needs.
- ❌ It should be easy to set up
- ❌ It should be easy to maintain
⚠️ It should run with least privilege⚠️ It should not compromise security of the app or the operating system
█████╗ ██████╗ ███████╗████████╗████████╗███████╗██████╗ ██╗ ██╗ █████╗ ██╗ ██╗
██╔══██╗ ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗ ██║ ██║██╔══██╗╚██╗ ██╔╝
███████║ ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝ ██║ █╗ ██║███████║ ╚████╔╝
██╔══██║ ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗ ██║███╗██║██╔══██║ ╚██╔╝
██║ ██║ ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║ ╚███╔███╔╝██║ ██║ ██║
╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝
-
You must have a valid domain and that domain must be available via DNS. Lets Encrypt Certificates do not support IP Addresses
-
Your domain needs to be pointing to a VM, Container, or Load Balancer etc. and that VM, Container, or Load Balancer must be reachable on the INTERNET
-
Your VM, Container, or Load Balancer must have SSH access
-
The user must have sudoer access
██╗ ███████╗████████╗███████╗ ██████╗ ██████╗ ██╗
██║ ██╔════╝╚══██╔══╝██╔════╝ ██╔════╝ ██╔═══██╗██║
██║ █████╗ ██║ ███████╗ ██║ ███╗██║ ██║██║
██║ ██╔══╝ ██║ ╚════██║ ██║ ██║██║ ██║╚═╝
███████╗███████╗ ██║ ███████║ ╚██████╔╝╚██████╔╝██╗
╚══════╝╚══════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝
-
Install CertBot by opening https://certbot.eff.org/lets-encrypt/sharedhost-other in a new tab. Select My HTTP website is running None of the above on <YOUR_OPERATING_SYSTEM>
-
Follow the instructions for installing Certbot for your OS. Once you have installed proceed to the next step
-
If this is your first time running Certbot. Register your account by replacing
<[email protected]>
below with your real email addresssudo certbot register --agree-tos -m <[email protected]>
Successful output will look like:
Saving debug log to /var/log/letsencrypt/letsencrypt.log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Would you be willing, once your first certificate is successfully issued, to share your email address with the Electronic Frontier Foundation, a founding partner of the Let's Encrypt project and the non-profit organization that develops Certbot? We'd like to send you email about our work encrypting the web, EFF news, campaigns, and ways to support digital freedom. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: Y Account registered.
Accepting the offer for the EFF to have your email is up to you.
-
It is likely that your app/service is running on some port other than 80. Before we attempt installing the certificates, your app/service needs to be available on the Web. To do this, we will add a temporary iptables rule to route traffic from port 80 => to whatever port your app/service is running on
sudo iptables --table nat --append PREROUTING --in-interface <YOUR_NETWORK_INTERFACE> --protocol tcp --dport 80 --jump REDIRECT --to-port <YOUR_APPLICATION_PORT>
e.g.
sudo iptables --table nat --append PREROUTING --in-interface eth0 --protocol tcp --dport 80 --jump REDIRECT --to-port 3000
By default many network interfaces are
eth0
, however, this really depends on your Operating System and Version of Operating System. If you don't know what your network interface is, or want to confirm typeip addr
(Newer *Nix Distros) orifconfig
(Older *Nix Distros) into your terminal. In the example output below, the interface isens33
e.g.
user@ubuntu: ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:0c:29:a4:f1:4a brd ff:ff:ff:ff:ff:ff inet 192.168.0.1/24 brd 192.168.0.255 scope global dynamic ens33 valid_lft 1138sec preferred_lft 1138sec inet6 fe80::f53e:89e:b44a:c634/64 scope link valid_lft forever preferred_lft forever 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:bc:d3:df:97 brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever
The iptables rules are temporary and will go away on a VM reboot (we'll address this later). This previous rule states
- Intercept any traffic coming across the eth0 network interface on port 80 and redirect that traffic to port 3000
-
Optional: Check to see if the rule is there
sudo iptables --list --table nat
-
To ensure that our VM and app/service is secure, we're going to create a new non-root user to handle just the app/service stuff and only the app/service stuff
sudo useradd -m node-user -s /usr/bin/bash
We are setting the default users login shell to bash, it can be any shell you want bash, rbash, bash, dash, tmux, screen etc.
-
Give the new
non-root
node-user a passwordsudo passwd node-user
-
⚠️ The app/service should run as thenode-user
after setup for the best security profile. If you will be regularly pulling code from some Source Code Management tool like GitHub, GitLab, or BitBucket it is highly recommended that you create ssh keys for thenode-user
before moving to the next step.GitHub SSH Key generation instructions
GitLab SSH Key generation instructions
Bitbucket SSH Key generation instructions
If this is a one-off set up, you can install the app/service under the
root-user
home directory, complete all of the necessary steps to set up Certbot and then move the app/service to the new user home directory and change the owner/group permissions accordinglysudo mv <YOUR_APP_DIRECTORY> ~/node-user/ && chmod -R node_user:node_user ~/node-user/<YOUR_APP_DIRECTORY>
-
Your production version Node app/service needs to be running (Without HTTPS) while we install the certificates with Certbot.
Log in as your new non-root user, enter password when prompted:
su --login node-user
Build your app/service:
npm run build
Run your prod app/service:
npm run prod
Exit out of
node-user
exit
-
Next we are going to install the certificates.
webroot-path
needs to be a publicly available folder in your app directory structure after your build process. Certbot requires this for installation and validation, but will not store the certificate files in this folder.💥 You must be logged in as your root user 💥
sudo certbot certonly -d <MY_DOMAIN_NAME> --webroot --webroot-path <ABSOLUTE_PATH_TO>/<MY_APP_NAME>/public
e.g.
sudo certbot certonly -d mydomain.com --webroot --webroot-path /home/node-user/my_web_app/public
⚠️ There are rate limits for running the above command. If your command fails for any reason, add--dry-run
to the end of the command until you get the correct setup. This moves the registration attempt to Let's Encrypt staging environment where the rate limits are highere.g.
sudo certbot certonly -d mydomain.com --webroot --webroot-path /home/ubuntu/my_web_app/public --dry-run
Successful output will look like
Saving debug log to /var/log/letsencrypt/letsencrypt.log Requesting a certificate for mydomain.com Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/mydomain.com/fullchain.pem Key is saved at: /etc/letsencrypt/live/mydomain.com/privkey.pem This certificate expires on 2021-11-05. These files will be updated when the certificate renews. Certbot has set up a scheduled task to automatically renew this certificate in the background. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - If you like Certbot, please consider supporting our work by: * Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate * Donating to EFF: https://eff.org/donate-le - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Doing it this way prevents having to manually add a file with the validation key to the public folder of your app/service
-
Next, stop your app/service and update the HTTPS/TLS configuration paths in your app/service
Log in as your new non-root user, enter password when prompted:
su --login node-user
Make your edits
const tls_svr = http2.createSecureServer({ ca: fs.readFileSync('/etc/letsencrypt/live/<MY_DOMAIN_NAME>/ca.pem'), cert: fs.readFileSync('/etc/letsencrypt/live/<MY_DOMAIN_NAME>/cert.pem'), key: fs.readFileSync('/etc/letsencrypt/live/<MY_DOMAIN_NAME>/privkey.pem'), });
e.g.
const tls_svr = http2.createSecureServer({ ca: fs.readFileSync('/etc/letsencrypt/live/mydomain.com/ca.pem'), cert: fs.readFileSync('/etc/letsencrypt/live/mydomain.com/cert.pem'), key: fs.readFileSync('/etc/letsencrypt/live/mydomain.com/privkey.pem'), });
As these paths SHOULD NOT change. You can commit file containing these paths in your Source Code Management tool, environment configuration files, AWS Secrets Manager etc.
Exit out of
node-user
exit
-
Now that we have gotten our certificates installed, lets correct our iptables rules to reflect the right configuration To do this, we need to delete our old temporary rule and add an iptables rule to route traffic from port 80 => 443 and another rule to route traffic from port 443 to whatever port your app is running on. Whether the end user goes to
http://mydomain.com
orhttps://mydomain.com
they will got to the secure version of the site💥 You must be logged in as your root user 💥
DELETE:
sudo iptables -t nat -D PREROUTING -i <YOUR_NETWORK_INTERFACE> -p tcp --dport 443 -j REDIRECT --to-port <YOUR_APPLICATION_PORT>
ADD:
sudo iptables -t nat -A PREROUTING -i <YOUR_NETWORK_INTERFACE> -p tcp --dport 80 -j REDIRECT --to-port 443 sudo iptables -t nat -A PREROUTING -i <YOUR_NETWORK_INTERFACE> -p tcp --dport 443 -j REDIRECT --to-port <YOUR_APPLICATION_PORT>
e.g.
DELETE:
sudo iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3000
ADD:
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 443 sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 3000
By default many network interfaces are
eth0
, however, this really depends on your Operating System and version of Operating System. If you don't know what your network interface is, or want to confirm that it is eth0 typeip addr
(Newer *Nix Distros) orifconfig
(Older *Nix Distros) into your terminal.The iptables rules are temporary and will go away on a VM reboot. The rules above state
-
Intercept any traffic coming across the eth0 network interface on port 80 and redirect that traffic to port 443
-
Intercept any traffic coming across the eth0 network interface on port 443 and redirect that traffic to port 3000
-
-
Optonal: Verify iptables rules have been updated as expected
sudo iptables --list --table nat
If the iptables rules do not look like:
Chain PREROUTING (policy ACCEPT) target prot opt source destination REDIRECT tcp -- anywhere anywhere tcp dpt:https redir ports 3000 REDIRECT tcp -- anywhere anywhere tcp dpt:http redir ports 443
or an old rule is still present, you can delete a rule by replacing the -A with a -D on the original rule
e.g.
sudo iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3000
-
We're going to limit who has access to our new certificate files by creating a FACL (File Access Control List). By doing so we only allow the root-user and our new node-user access to the files without exposing the files to anyone else.
sudo setfacl --recursive --modify "u:user:permissions" <file/dir>
e.g.
sudo setfacl --recursive --modify u:node-user:rX /etc/letsencrypt/{live,archive}
This FACL states: recursively (--recursive) set access for node-user with Read+Execute permissions (--modify "u:user:permissions"* on all letsencrypt directories (/etc/letsencrypt/{live,archive}) for mydomain
* The capital "X" in the ACL specification means "add execute permission for directories only" and is convenient for situations where you don't know whether the ACL will be applied to a directory or a file
-
login as the new node-user, enter the password when prompted
su --login node-user
Rerun your application build script to reflect the newest changes
npm run build
Run your prod app/service:
npm run prod
Exit out of
node-user
exit
██████╗ ██████╗ ███╗ ██╗ ██████╗██╗ ██╗ ██╗███████╗██╗ ██████╗ ███╗ ██╗
██╔════╝██╔═══██╗████╗ ██║██╔════╝██║ ██║ ██║██╔════╝██║██╔═══██╗████╗ ██║
██║ ██║ ██║██╔██╗ ██║██║ ██║ ██║ ██║███████╗██║██║ ██║██╔██╗ ██║
██║ ██║ ██║██║╚██╗██║██║ ██║ ██║ ██║╚════██║██║██║ ██║██║╚██╗██║
╚██████╗╚██████╔╝██║ ╚████║╚██████╗███████╗╚██████╔╝███████║██║╚██████╔╝██║ ╚████║
╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝╚══════╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝
If everything went as planned, you should now have a web application running or service with HTTPS enabled which will automatically renew every 90 days with the help of Certbot's auto-renewal script. The entire process accomplished the 4 goals we laid out at the beginning of this article
-
✔️ It should be easy to set up
Hopefully, the most tedious portion of this setup was reading the article and all the copying and pasting
-
✔️ It should be easy to maintain
Certbot's auto renewal script should be worry free.
Set it and forget it
-
✔️ It should run with least privilege
We created a new, non-root user with password, whose sole purpose is to run the app/service. We've then set up a FACL (File Access Control List) allowing only the root-user and the new, non-root user access
-
✔️ It should not compromise security of the app or the operating system
We haven't changed permission on any files or directories to get this to work
If things didn't go smoothly, checkout the Common Issues file