OpenSSL, Podman, NGINX, and more

Background

As a web developer managing multiple sites hosted on a variety of platforms and ISPs, Docker (and now Podman) greatly simplified my workflow by allowing each development environment to perfectly match its production environment (web server, PHP version, etc.).

My primary Linux workstation simply runs one container for NGINX and an additional container for each of the domains being developed. Each domain’s container can obviously be configured to exactly match the project’s production environment. Nice and clean.

While the production TLD is likely to be a “.com” or “.org”, the development domain is configured as “.test”. Although “.dev” was used previously for this purpose, in 2015 Google’s Chrome browser began requiring anything using the “.dev” TLD to be served via HTTPS. Instead of bothering with configuring self-signed certificates, it was easier to just switch the development domains over to “.test”. Problem solved.

The only inconvenience in this setup (and yes, it’s nothing more than an inconvenience) was when I would switch from the production URL to the development URL in the browser’s same tab. For example, if I were viewing myproject.com and wanted to go back to the development URL, I would have to change the “.com” to “.test” and change the protocol from “https://” to “http://”. Yeah, I could simply open up another tab but this has bugged me for quite awhile so I decided to figure out if there was a relatively straight-forward way of using self-signed certificates so that these “.test” domains could use SSL.

After trying several different approaches, it became clear that the simplest solution would be to create a self-signed Root Certificate Authority that would only need to be installed once into the necessary browsers and then create standard (self-signed) certificates for each domain based on that rootCA. As long as each new domain’s certificate was created with the single rootCA, all would be good.

Overview of the Process

  • Create a self-signed Root Certificate Authority key and certificate. This allows all the subsequent certificates (for each domain) to be authorized.
  • Import Root Certificate Authority (rootCA) into Chrome or any other browsers used for development.
  • Create a certificate (using the rootCA) for each domain being served
  • Update the NGINX “run” command to listen to port 443.
  • Update the NGINX configuration for each domain to include the certificates and redirect port 80 to port 443.

Create the Root Certificate Authority (rootCA)

Generate a key and create the rootCA with the following two commands:

openssl genrsa -out rootCA.key 4096
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt

Import the Root Certificate Authority into Chrome

Import the Root Certificate (rootCA.crt) into the required browsers. Pretty straight-forward, at least for Chrome:

  • Settings -> Advanced -> Privacy and Security
  • Trusted Root Certificate Authorities -> Import

Make sure the rootCA.crt is placed in the Trusted Certificates Store (especially on the Windows version of Chrome) or your domain certificates won’t be utilized.

Create the Certificate for Each Domain

Apparently using “.test” as the TLD for development adds an additional caveat requiring the certificate to include the FQDN as the “Subject Alternative Name (SAN)” in addition the “CN”. Otherwise, the browser (at least Chrome) will throw the error “NET::ERR_CERT_COMMON_NAME_INVALID”. Unfortunately, the SAN cannot be supplied via openssl’s command line and requires a configuration file to be used. A typical config file would contain:

[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[dn]
C = US
ST = Virginia
L = Virginia Beach
O = MYCOMPANY
OU = DEVELOPMENT
emailAddress = address@foo.com
CN = domain.test
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = domain.test

Edit the “[dn]” section as desired but note that the “CN” and “DNS.1” lines will be updated for each domain’s certificate you intend to create.

Now create the domain’s certificate key, using this key to create a Certificate Signing Request (CSR), and then use the CSR to create the actual certificate.

openssl genrsa -out domain.key 2048
openssl req -new -key .key -out domain.csr -config openssl.cnf
openssl x509 -req -in .csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out domain.crt -days 500 -sha256 -extfile openssl.cnf -extensions req_ext 

Configure NGINX to use the New Certificate

The first “server” block simply has NGINX redirect “http” requests to “https” (just as you would in the production environment.) The second block contains the “listen” and “ssl_certificate” directives for the newly created certs. The proxy directives will obviously be unique to your own network configuration. They’ll be a similar configuration file for each domain being proxy’d by NGINX.

server {
   listen 80;
   server_name domain.test;
   return 301 https://domain.test;
 }
 server {
   server_name  domain .test;
   listen       443 ssl;
   ssl_certificate /etc/nginx/conf.d/domain.crt;
   ssl_certificate_key /etc/nginx/conf.d/domain.key;
   location / {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_pass http://192.168.1.3:8080;
   }
 }

Have NGINX Listen on Port 443

Finally, when starting the podman container for NGINX, add the additional port mapping in order to listen to port 443 as well as port 80.

podman run -d -p 80:80 -p 443:443 -v ../nginx/conf.d:/etc/nginx/conf.d nginx

Comments?

If you found this post useful or have suggestions, feel free to comment below. And thanks for reading!

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.