TLS and HAProxy for development - fieldenms/tg GitHub Wiki
Introduction
HAProxy is an excellent reverse proxy server as well as load balancer. The use of HAProxy when deploying TG applications is highly recommended as it decouples several important concerns away from the application itself:
- TLS termination proxy -- handles TLS infrastructure, including integration with Let's Encrypt.
- HTTP/2 front-end -- provides a simple way to take advantage of HTTP/2 multiplexing.
- Load balancing -- can be used for application load balancing.
Running TG applications behind HAProxy in production makes it also extremely desirable, if not required, to do the same in development. This article discusses how HAProxy and self-signed certificates can be established for local development purposes.
Some assumptions/prerequisites:
- macOS or Ubuntu is the OS of choice.
- Docker is installed.
- Chrome is the browser of choice.
Please note that much of the covered material should be transferrable to Windows.
The following steps are required:
- Generate public/private keys and a self-signed certificate to be used by HAProxy to establish TLS for HTTP(S) and HTTP/2.
- Install and configure HAProxy.
- Register the certificate as trusted with the operating system.
Generate public/private keys and a self-signed certificate
This step involves the use of openssl, which needs to be installed if it isn't to proceed with this step. For Windows OS openssl can be downloaded from here, there is also available version for Win64. It is better to choose full version (not lightweight) for software developers.
In the past Chrome was looking at "commonName" of the certificate to match the domain name and the certificate in use. Since some version (58?) this has changed to look at the certificate's extension "subjectAltName", but the fact of that change is not properly disseminated. Therefore, certificate generation needs to take this into account.
Here is a command that can be used as a template to generate a certificate that will be usable with Chrome:
openssl req \
-x509 -sha256 \
-newkey rsa:4096 \
-days 1024 \
-nodes \
-subj "/C=AU/ST=VIC/O=Fielden/CN=localhost" \
-extensions SAN \
-reqexts SAN \
-config <(cat /etc/ssl/openssl.cnf \
<(printf "\n[SAN]\nsubjectAltName=DNS:localhost,DNS:tgdev.com")) \
-keyout "localhost.key" \
-out "localhost.pem"
The three critical options to generate subjectAltName are:
-extensions SAN-- declares extensionSAN(arbitrary name, which stands for Subject Alt Name).-reqexts SAN-- declares that extensionSANis used forreqextensions.-config <(cat /etc/ssl/openssl.cnf \ <(printf "\n[SAN]\nsubjectAltName=DNS:localhost,DNS:tgdev.com")) \-- appends section[SAN]with entrysubjectAltName=DNS:localhost,DNS:tgdev.comto the content of the defaultopenssl.cnfconfiguration file.
Having both values DNS:localhostand DNS:tgdev.com for subjectAltName ensures that accessing either https://localhost or https://tgdev.com should correctly identify domain names by Chrome. Naturally, value DNS:tgdev.com can be changed to any other appropriate domain name that you may use for local development. However, remember to use that consistently throughout in all places where tgdev.com is referenced in this article.
For Windows PC certificate can be generated with following instruction:
openssl req ^
-x509 -sha256 ^
-newkey rsa:4096 ^
-days 1024 ^
-nodes ^
-subj "/C=AU/ST=VIC/O=Fielden/CN=localhost" ^
-addext "subjectAltName = DNS:localhost,DNS:tgdev.com" ^
-keyout "localhost.key" ^
-out "localhost.pem"
This instruction also generates certificate with additional subjectAltName extension set to localhost and tgdev.com
NOTE: Need to test Android / iOs devices on local network? The easiest way to do this is to use your local static IP address for certificate generation by adding
IP:192.168.1.40to the script (this results toprintf "\n[SAN]\nsubjectAltName=DNS:localhost,DNS:tgdev.com,IP:192.168.1.40"). After that use that IP address consistently throughout in all places wheretgdev.comis referenced in this article. Also, the following command can be very usefull when you go to other network infrastructure, perhaps even with other domain:sudo ip address add 192.168.1.40/24 dev wlp1s0-- this creates IP address alias on wifi interfacewlp1s0(linux and macOs). Changewlp1s0to whatever interface you are using there.
There should be two files generated as the result of running the above command -- localhost.key and localhost.pem. It is a good idea to verify that certificate contains the subjectAltName. This can be done by running the following command:
openssl x509 -in ./localhost.pem -text -noout
The output should look like the screen capture below:

The two generated files need to be concatenated into file haproxy.pem, which is going to be used by HAProxy:
cat ./localhost.pem localhost.key > haproxy.pem
And for Windows PC:
type localhost.pem localhost.key >> haproxy.pem
All of these steps have been organised as a single script for UNIX-based operating systems and this for Windows machines.
Installing docker for windows
There are two is one available option to install docker for windows:
~ Install Docker Toolbox. This is the only option for older windows versions. Installing docker toolbox will require slightly different etc/host configuration that is discussed in Adjusting haproxy.cfg. Please follow this instruction to install it.~ Docker Toolbox has been deprecated and is no longer in active development. Please use Docker Desktop instead.
- Install Docker Desktop. This is an option for
Windows 10 64-bit: Pro, Enterprise, or Education (Build 16299 or later). This option might have some issues running applications on 80 port, this will be discussed in Adjustingstart_haproxy.shorstart_haproxy.batsection. Please follow this instructions to install Docker Desktop for windows
Installing and configuring HAProxy
HAProxy version 1.9.8 is assumed. Installing HAProxy with Docker is a breeze by running:
docker pull haproxy:1.9.8
For convenience, it is best to create a separate directory to contain HAProxy configuration files and a startup script to start/restart it. Let's say this directory is /Users/username/haproxy (macOS) or /home/username/haproxy (Ubuntu) or C:\Users\username\haproxy (Windows).
This folder should contain three files:
haproxy.pem, which was generated in the previous step.haproxy.cfg, which a configuration file for HAProxy and can be downloaded from here; requires some changes for local use.start_haproxy.sh, which is a script to start/restart HAProxy and can be downloaded from here; requires some changes for local use.start_haproxy.bat, which is a script to start/restart HAProxy on Windows PC and can be downloaded from here; requires some changes for local use.
Adjusting haproxy.cfg
As mentioned earlier, it is assumed that domain tgdev.com is used for running TG applications locally.
This means that file application.propeties for TG applications has entry web.domain=tgdev.com and file /etc/hosts contains a mapping between this domain name and the local IP address (e.g. 192.168.1.43 tgdev.com). In case of Windows PC and docker toolbox /etc/hosts entry for tgdev.com should look like this: 192.168.99.100 tgdev.com, where 192.168.99.100 is a default ip for Linux VM on which docker toolbox runs docker.
Let's also assume that file application.propeties has ports specified as 8091:
port.listen=8091
port=8091
The domain name is used in the referenced file haproxy.cfg on line 75 -- acl is_tgdev hdr_beg(host) tgdev.com, which can remain as is if tgdev.com is used.
The port as well as the local IP address need to be specified on line 104 of haproxy.cfg -- server eclipse1 local-ip-address:port check. More specifically, part local-ip-address:port needs to be changed to reflect the local IP address and the port designated for a TG app. For example, server eclipse1 192.168.1.43:8091 check.
This is pretty much all that needs to be adjusted in haproxy.cfg.
Adjusting start_haproxy.sh or start_haproxy.bat
The startup script contains the command to run HAProxy, which needs to include a mapping between the directory where HAProxy configuration lives locally and inside the running Docker container. Here is an excerpt from the referenced shell script:
docker run -d \
-p 80:80 -p 443:443 -p 9000:9000 \
--restart=always \
--name haproxy \
-v <local haproxy config directory>:/usr/local/etc/haproxy:ro \
haproxy:1.9.8
And for windows:
docker run -d ^
-p 80:80 -p 443:443 -p 9000:9000^
--restart=always^
--name haproxy^
-v <local haproxy config directory>:/usr/local/etc/haproxy:ro^
haproxy:1.9.8
Line -v <local haproxy config directory>:/usr/local/etc/haproxy:ro \ is of interest. Its part <local haproxy config directory> needs to be changed to the path where all three of the files mentioned above are located. Let's say this is directory /home/username/haproxy, and so start_haproxy.sh should be changed to reflect this:
docker run -d \
-p 80:80 -p 443:443 -p 9000:9000 \
--restart=always \
--name haproxy \
-v /home/username/haproxy:/usr/local/etc/haproxy:ro \
haproxy:1.9.8
Docker toolbox or Docker desktop will run haproxy on separate VM in that case the question might rise: "What value should be for<local haproxy config directory>. Let's assume that the directory for haproxy config file was created in C:\Users\username\haproxy\ in that case <local haproxy config directory> will be /c/Users/username/haproxy, so the previous script for windows should look like this:
docker run -d ^
-p 80:80 -p 443:443 -p 9000:9000^
--restart=always^
--name haproxy^
-v /c/Users/username/haproxy:/usr/local/etc/haproxy:ro^
haproxy:1.9.8
Also separate start_haproxy.bat file is provided here
Please note also the use of option --restart=always. It means that HAProxy will be started automatically upon crashes and Docker or computer restarts. Remove this option if it is preferred to start/stop HAProxy manually. For more details refer Docker documentation.
And as the last step, make the script executable by running chmod +x start_haproxy.sh.
Note on running haproxy with Docker Desktop
When running haproxy via Docker Desktop the error might happen. The error is in the screenshot in the red rectangle.

There are two possible solutions:
- As it can be seen from screenshot the problem is port
80which for some reasons can not be accessed. In that case this port can be changed andhaproxyrun script should look like this:
docker run -d ^
-p 8080:80 -p 443:443 -p 9000:9000^
--restart=always^
--name haproxy^
-v /c/Users/username/haproxy:/usr/local/etc/haproxy:ro^
haproxy:1.9.8
- The another solution requires to find the application that listens port
80which makes it inaccessible for Docker. It might be another web server or IIS etc.netstat -aon |find ":80"will help to findPIDof application that does it. It could be a case whenPIDof application is4which is theSYSTEM, then you may turn it off this post describes how to do that. But it might be a bit dangerous as it requires to edit register.
When first time running haproxy you should share it with Docker Desktop. After that you can stop, start or restart it using GUI like on the picture below
.
--restart=always option will start haproxy when docker starts. Docker Desktop will be added to autostart by default. For Docker Toolbox it is required to provide additional configurations to make it start on PC startup. The next batch commands should be added to autostart folder:
docker-machine start default
docker run -d ^
-p 80:80 -p 443:443 -p 9000:9000^
--restart=always^
--name haproxy^
-v /c/Users/username/haproxy:/usr/local/etc/haproxy:ro^
haproxy:1.9.8
The batch file is here
Register the certificate as trusted with the operating system
Now everything should be ready for us to start a TG app behind HAProxy. And this is required so that we could obtain the certificate from Chrome to register it as trusted with OS.
Ordinarily the order in which TG app and HAProxy are started hardly matters. However, for the first time it highly recommended to first start a TG app and then, only after it is fully loaded, start HAProxy by running ./start_haproxy.sh.
Please note that TG app must be started in HTTP mode, not HTTPS. Make sure that the following lines appear in the console (e.g. Eclipse console) before starting HAProxy:
Starting the Jetty [HTTP/1.1] server on port 8091
Starting fielden.webapp.WebUiResources application
If HAProxy starts with an alert about tgdev having no server available, as depicted in the screen capture below, then either the TG app has not started or HAProxy binding on line 104 was not correctly updated. In that case please re-read section "Adjusting haproxy.cfg" above and make sure it is followed properly.

Assuming that HAProxy started without the above alert, open Chrome and load https://tgdev.com/login.
Regardless of the OS you're using, the result should look like the screen capture below. Open the Developer Tools and switch to the Security tab.

The steps to make our certificate trusted are different for macOS and Ubuntu. Let's start with macOS.
Making certificate trusted in macOS
Click "View certificate" button as indicated with label 1 in the screen capture below -- a certificate dialog is opened.

Drag the certificate icon from the dialog to some directory in Finder. This should create file localhost.cer on that folder.

Double click that file to open it in the Keychain Access application (this will prompt for a system password). The following screen capture shows the result of this after selecting category "Certificates" in this application to see only certificates. As you can see entry "localhost" is present.

Double click entry "localhost" in the Keychain Access window, and mark it as "Always Trusted" under "Trust", option "When using this certificate".

Closing this dialog will prompt for a system password to apply changes. And once applied, entry "localhost" should have a little "+" sign at the start as depicted in the screen capture below.

Now close the Keychain Access app, delete file localhost.cer as no longer needed and refresh the page in Chrome. The page should load successfully without any privacy exceptions as per the screen capture below.

Please note that you might need to restart Chrome for it to load updated certificate policies, but it was not necessary in my case.
Making certificate trusted in Ubuntu
The situation with Ubuntu is slightly more complicated, but does not requires as many screen captures (:. First, export the certificate from the certificate dialog, which appears after clicking button "View certificate" (the same as under macOS). The "Export" button is located in tab "Details".

Make sure your select option "single certificate" during the export as indicated in the screen capture below.
Take a note of where the file is exported and the file name -- localhost.crt. This is needed for the steps that follow.

Start a terminal and change the directory to the one where localhost.crt has been exported.
Then execute the following commands:
sudo apt-get install libnss3-tools— install utilitycertutil, which is needed to manage keys and certificates (you only need to install this once, the first time you want to import a certificate).certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n localhost.crt -i localhost.crt— import the certificate into the local database.certutil -d sql:$HOME/.pki/nssdb -L— this is just to list what the resultant DB contains to make sure our certificate is present.- go to chrome://settings/certificates, find
org-FieldeninAuthoritiestab, open, edit UNTRUSTEDlocalhostto make it trusted
This is it -- refresh the page in Chrome (may need to restart it) and the privacy exception should be no more.
For Firefox users running Linux: Firefox does not have a 'central' location where it looks for certificates. It just looks into the current profile (reference).
certutil -d $HOME/.mozilla/firefox/<YOUR_PROFILE_FOLDER>/ -A -t "C,," -n localhost.crt -i localhost.crt- import the certificate into the profile DB.certutil -d $HOME/.mozilla/firefox/<YOUR_PROFILE_FOLDER>/ -L— this is just to list the profile DB.- go to
about:preferences, find Certificates section (in Security) and open View Certificates.... In the Authorities tab the certificate should be present.
Making certificate trusted in Windows
First, export the .crt certificate file from the certificate dialog, which appears after clicking button View site information in the Chrome URL field. The Copy to file button is located in the tab Details. Now you can make it trusted.
- Start the Microsoft Management Console by running
mmccommand in Powershell. - Enter the
Filemenu and selectAdd/Remove Snap In. - Choose
Certificates Snap-Inand add it to the selected. ChooseComputer accountin the following wizard.

- Now you can view your certificates in the MMC snap-in. Select
Console Rootin the left pane, then expandCertificates (Local Computer). UnderTrusted Root Certification Authoritiesyou can import new certificate file (.crt).

- Now refresh the Chrome page using
Ctrl+F5. Things should be fine.
Making certificate trusted in Android and iOS
- Generate certificate request from
localhost.pemandlocalhost.key
openssl x509 -x509toreq -in localhost.pem -out localhost.csr -signkey localhost.key
- Generate
CA.crtfrom certificate request with special options
openssl x509 -req -days 397 -in localhost.csr -signkey localhost.key -extfile ./android_options.txt -out CA.crt
where android_options.txt has only one line: basicConstraints=CA:true
- Convert it to DER form
openssl x509 -inform PEM -outform DER -in CA.crt -out CA.der.crt
-
Place
CA.der.crtto some location on Android / iOs file system or download it -
iOS:
Files-> tap onCA.der.crtand seeProfile Downloaded. Review the profile in the Settings app if you want to install it.
5a. iOS: Settings -> General -> VPN & Device Management -> localhost -> install it
-
iOS:
Settings->General->About->Certificate Trust Settings->localhost-> enable full trust -
Android:
Settings->Security and Privacy->More security settings->Encryption & credentials->Install a certificate->CA certificate-> choose in local file system -
Android (check):
Settings->Security and Privacy->More security settings->Encryption & credentials->Trusted credentials->User