Geeking out with HAproxy on pfSense
Be carefull, this article is still in progress.
Most part of this an update of the article of Julian Pawlowski “Geeking out with HAproxy on pfSense” from 2015. So my purpose was to used it on the latest pfsense release 2.5.2
. The latest at this moment.
All the captures are from me and can be a little bit different from the original text. Australia having recently become the last American aquisition. Thanks to the fella from down under.
I would like to share my experience on how to transform your pfSense appliance into a layer4 router for sharing all the encrypted traffic we have on port 443 with SSH and OpenVPN traffic.
SSLlabs.com will even give you an A+ for this configuration if you follow it closely.
For SSH, this will not only give you enhanced security and encryption but also a whole lot more flexibility for secure remote access to servers in your corporate network. It will even allow you to bypass a lot of corporate proxy servers (as long as there is no content inspection enabled for HTTPS traffic) and is a real alternative instead of using HTTP CONNECT method (which can easily be blocked).
This solution could even be more blown up to fulfill enterprise level requirements, e.g. granular role-based user authentication. However, this will not be our main topic in this article but will be referenced from time to time.
I know there is this shiny litte tool SSLH out there but this solution is much more flexible due to the power of HAproxy.
All in all, this is what we are going to share via port 443 on a single IP:
- normal HTTPS traffic (acting as normal reverse proxy for securing web traffic)
- Backend with reverse proxy (traefik). TCP flows
- backend without any reverse proxy. HTTPS flows
- normal HTTPS traffic with X509 user certificate authentication
- OpenVPN dial-in traffic
- TLS-tunneled SSH traffic including X509 user certificate authentication (SSLH Gateway)
The WAN_443_TCP frontend and the different parts necessary for its operation, are useful when there is a loadbalancer on the target server. In order to test Traefik on Docker servers or to use some project like Cloudbox, I therefore added this functionality. In these cases I don’t want to modify Traefik or nginx conf files to use a proxy, each time a new version of the project come out, modifications needs to been done again and again.
On the graph below, the WIN_443_TCP is marked as using a certificate from LetsEncrypt, IT’s not for making some SSL offloading
for the destination server. It’s not needed as we use TCP flow.
Be carefull, this article is still in progress.
redirect scheme https code 301| W L ---> T T ---->|2043| P M -.-> U N -.-> V P ---->|fa:fa-globe-europe
| X P ---->|fa:fa-globe-europe
| Y P ---->|fa:fa-globe-europe
| Z V -. 2022 .-> Q Q -. fa:fa-terminal .-> X Q -. fa:fa-terminal .-> Y Q -. fa:fa-terminal .-> Z R ---->|fa:fa-user-check| X R ---->|fa:fa-user-check| Y R ---->|fa:fa-user-check| Z U -. 2044 .-> R L --->|*.example.com from| F N ---->|ssh.example.com| E O --->|1194| C O --->|vpn.example.com| D M --->|HTTPS auth certificat check| D A ----> H H ----> I H ---->|*.example.com from| F I -. 2045 .-> J J ---->|fa:fa-filter| X J ---->|fa:fa-filter| Y J ---->|fa:fa-filter| Z subgraph ff[Frontend] A([fa:fa-shield-alt WAN_443
Port 443 Sharing]) B([WAN_HTTP]) end subgraph sb1[Shared Frontend] direction TB L{{fa:fa-globe-europe
WAN_443_HTTPS}} M{{fa:fa-user-check
WAN_443_HTTP_auth}} N{{fa:fa-terminal
WAN_443_SSLH}} O{{WAN_443_OpenVPN}} H{{fa:fa-filter
WAN_443_TCP}} end C[/fa:fa-fingerprint
OpenVPN
Daemon/] subgraph sb2[2nd level frontend] P{{fa:fa-globe-europe
WAN_HTTPS
redirect with ACL}} Q{{fa:fa-terminal
WAN_SSLH
redirect with ACL}} R{{fa:fa-user-check
WAN_HTTPS_auth
redirect with ACL}} J{{fa:fa-filter
WAN_TCP
redirect with ACL}} end subgraph bk1[Specials backend] T[fa:fa-globe-europe
WAN_HTTPS
on 2043] U[fa:fa-user-check
WAN_HTTPS_auth
on 2044] V[fa:fa-terminal
WAN_SSLH
on 2022] I{{fa:fa-filter
WAN_TCP
on 2045}} W[ssl_redirect] end subgraph bk2[backend] X[fa:fa-server server_01] Y[fa:fa-server server_02] Z[fa:fa-server server_03] G[fa:fa-server none] end subgraph cr1[certificates Authorities] D(Acmi VPN Remote Access) F("Let's Encrypt") E(Acmi SSLH Gateway) end linkStyle 4 stroke:red,stroke-width:2px,color:red; linkStyle 1,7,16,17,18,19 stroke:blue,stroke-width:2px,color:blue,stroke-dasharray: 5 5; linkStyle 2,8,12,13,14,15 stroke:green,stroke-width:2px,color:green,stroke-dasharray: 5 5; linkStyle 0,5,6,9,10,11 stroke:orange,stroke-width:2px,color:orange,stroke-dasharray: 5 5; linkStyle 25,26,28,29,30,31 stroke:purple,stroke-width:2px,color:purple,stroke-dasharray: 5 5; linkStyle 20 stroke:orange,stroke-width:2px,color:orange; linkStyle 21 stroke:green,stroke-width:2px,color:green; linkStyle 24 stroke:blue,stroke-width:2px,color:blue; linkStyle 27 stroke:purple,stroke-width:2px,color:purple;
Creating internal Certificate Authorities and certificates
We are going to need 2 separate CA’s which we will be creating in the Cert Manager right now. I recommend using the following settings:
Creation of the first certificate autority for OpenVPN
Descriptive name: Acme VPN Remote Access
Method: Create an internal Certificate Authority
Key Length: 4096
Digest Algorithm: SHA512
Lifetime: 3650
Country code: AU
State: SA
City: Adelaide
Organisation: Acme Inc.
Email address: security@example.com
Common Name: Acmi VPN Remote Access
Creation of the second certifcate autority for SSH
Descriptive name: Acme SSLH Gateway
Method: Create an internal Certificate Authority
Key Length: 4096
Digest Algorithm: SHA512
Lifetime: 3650
Country code: AU
State: SA
City: Adelaide
Organisation: Acme Inc.
Email address: security@example.com
Common Name: Acmi SSLH Gateway
Create internal certificate for OpenVPN
Let’s create a quick internal certificate for the OpenVPN server before we will set it up:
Method: Create an internal Certificate
Descriptive name: vpn.example.com
Certificate authority: Acme VPN Remote Access
Key Length: 4096
Digest Algorithm: SHA512
Certificate Type: Server Certificate
Lifetime: 3650
Country code: AU
State: SA
City: Adelaide
Organisation: Acme Inc.
Email address: security@example.com
Common Name: vpn.example.com
Alternative Names: TYPE=DNS, VALUE=vpn.example.com
Create internal certificate for SSLH Gateway
Let’s also create a quick internal certificate for our SSLH Gateway:
Method: Create an internal Certificate
Descriptive name: ssh.example.com
Certificate authority: Acmi SSLH Gateway
Key Length: 4096
Digest Algorithm: SHA512
Certificate Type: Server Certificate
Lifetime: 3650
Country code: AU
State: SA
City: Adelaide
Organisation: Acme Inc.
Email address: security@example.com
Common Name: ssh.example.com
Alternative Names: TYPE=DNS, VALUE=ssh.example.com
Create user certificates
Each user who needs to be authorized using OpenVPN, HTTPS-auth secured backends or our SSLH gateway will need to have user certificates being created by our internal CA’s.
For OpenVPN and HTTP-auth users, we will create just one single certificate. We will just use the pfSense internal users for this example, you may extend this to more complex setups on your own.
Open the User Manager, click on edit for your user account and then the plus icon next to the User Certificates section (this will automatically assign the created cert to this user account for your convenience).
Let’s create the OpenVPN and *.vpn.example.com cert first:
Method: Create an internal Certificate
Descriptive Name: Acmi John Doe
Certificate authority: Acmi VPN Remote Access
Key length: 4096
Digest Algorithm: SHA512
Certificate Type: User Certificate
Lifetime: 3650
Country code: AU
State: SA
City: Adelaide
Organisation: Acme Inc.
Email address: john.doe@example.com
Common Name: Acmi John Doe
Alternative Names:
TYPE=email, VALUE=john.doe@example.com
TYPE=email, VALUE=netmaster@example.com
You may add other (pseudo/administrative) e-mail addresses here as an alias so you may even restrict access to certain pages/backends, e.g. allow access to the firewall on fw.vpn.example.com only for members of the netmaster staff (meaning this user needs to have netmaster@example.com as an alternative name). This will be kind of role-based authentication if you extend the ACL in the corresponding shared frontend we will be creating for each of the servers later. However, I don’t want this to become too complex here so this is just some inspiration for more advanced enterprise use you may follow up on later.
Let’s now create the SSLH cert:
Method: Create an internal Certificate
Descriptive Name: Acmi-SSLH John Doe
Certificate authority: Acmi SSLH Gateway
Key length: 4096
Digest Algorithm: SHA512
Certificate Type: User Certificate
Lifetime: 3650
Country code: AU
State: SA
City: Adelaide
Organisation: Acme Inc.
Email address: john.doe@example.com
Common Name: Acmi-SSLH John Doe
Alternative Names:
TYPE=email, VALUE=john.doe@example.com
TYPE=email, VALUE=hostmaster@example.com
Same info about the alternative names I mentioned above applies here.
OpenVPN Setup
Create a normal new OpenVPN instance listening on TCP port 1194 (it may use the WAN interface just as normal) using the CA “Acmi VPN Remote Access” and it’s certificate “vpn.example.com” we created above. I will not go any deeper into this as there are much other (and more sophisticated) how-to’s out there in the net.
Basic HAproxy configuration
We will start with a dummy backend and the second level frontends for HTTPS, HTTPS-auth and SSLH and combine them in a first level frontend instance afterwards.
Hardening against vulnerability
In the general settings tab, you want to add this to the Global Advanced Pass Through field:
# generated 2021-09-18, Mozilla Guideline v5.6, HAProxy 1.8.3, OpenSSL 1.1.0i, intermediate configuration
# https://ssl-config.mozilla.org/#server=haproxy&version=1.8.3&config=intermediate&openssl=1.1.0i&guideline=5.6
# set default parameters to the intermediate configuration
ssl-default-bind-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-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-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-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl-dh-param-file /conf/dhparam
_tune.ssl.default-dh-param 2048_
# Time-to-first-Byte (TTFB) value needs to be optimized based on
# the actual public certificate chain see
# https://www.igvita.com/2013/10/24
# /optimizing-tls-record-size-and-buffering-latency/
tune.ssl.maxrecord 1370
Creating a dummy backend
Being in the backend section of the HAproxy configuration gui, create a new instance called “none”. We will use this backend later as our default destination as it’s actually doing nothing but being a placeholder for everything that does not match elsewhere.
Use the following settings:
Mode: disabled
Name: none
Forwardto: address+port
Address: 127.0.0.1
Port: 80 (or any other port which does **not** listen on localhost)
Health check method: none
Click on “save”.
Setup SSL redirect on port 80
For convenience reasons you would normally want to setup a redirect from port 80 to 443.
Listening
on port 80 WAN_HTTP-->>+ssl-redirect:redirect scheme https code 301 Note right of ssl-redirect: 127.0.0.1
Listening
port 8081
Unused port
First, create a backend:
Mode: inactive
Name: ssl-redirect
Forwardto: address+port
Address: 127.0.0.1
Port: 8081 (or any other port which does not listen on localhost)
Backend pass through: redirect scheme https code 301
Health check method: none
Second, create the corresponding primary frontend:
Name: WAN_HTTP
Description: Redirect HTTP traffic to HTTPS
Listen address: WAN address (IPv4)
Port: 80
SSL Offloading: no
Backend server pool: ssl-redirect
Type: HTTP / HTTPS(offloading)
That was easy.
Implementation of all Frontends (First and Second) and Backends
The first Frontend ‘WAN_443’ is use to share the port 443 wirh first frontend
Listening
port 443 rect rgb(204,255,255) loop < WAN_443 to WAN_443_HTTPS WAN_443-->>WAN_443_HTTPS:accept-proxy npn http/1.1 end end Note right of WAN_443_HTTPS: Listening
port 2043 rect rgb(204,229,255) loop WAN_443 to WAN_443_HTTPS_auth WAN_443-->>WAN_443_HTTPS_auth:accept-proxy npn http/1.1 WAN_443-->>WAN_443_HTTPS_auth:Client verification CA certificates end end Note right of WAN_443_HTTPS_auth: Listening
port 2044 rect rgb(93,173,226) loop WAN_443 to WAN_443_SSLH dialog WAN_443-->>WAN_443_SSLH: accept-proxy npn ssh/2.0 end end Note right of WAN_443_SSLH: Listening
port 2022 rect rgb(213,245,227) loop WAN_443 to WAN_443_TCP dialog WAN_443-->>WAN_443_TCP: req.ssl_hello_type 1 WAN_443-->WAN_443_TCP: and req_ssl_sni -i exemple.com WAN_443-->WAN_443_TCP: and not req_ssl_sni -i -m end ssh.exemple.com WAN_443-->WAN_443_TCP: and not req_ssl_sni -i -m end vpn.exemple.com end end Note right of WAN_443_TCP: Listening
port 2045
Creating the HTTPS frontend instance
Switch to the frontend section and create a new instance using the following settings:
Name: WAN_HTTPS
Description: HTTPS Reverse Proxy
External address: localhost (IPv4)
Port: 2043
SSL Offloading: yes
Advanced: accept-proxy npn http/1.1
Backend Server Pool: none (or use any other webserver backend, e.g. to show custom error pages instead of 503)
Type: HTTP / HTTPS(offloading)
Client timeout: 7200000
Use ‘forwardfor’ option: yes
Advanced pass through:
# Remove headers that expose security-sensitive information.
rspidel ^Server:.*$
rspidel ^X-Powered-By:.*$
rspidel ^X-AspNet-Version:.*$
# add some security related headers
rspadd Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval'
rspadd X-Frame-Options: SAMEORIGIN
rspadd X-Content-Type-Options: nosniff
rspadd X-Xss-Protection: 1; mode=block
Certificate: Ideally choose a wildcard cert you uploaded to the Cert Manager before, e.g. from StartCom/StartSSL. We are choosing “*.example.com” here. Put additional certificates as required.
Add ACL for certificate CommonName: No
Advanced SSL options: no-sslv3
Click on “save”.
Creating the HTTPS-Auth frontend instance
Back in the frontend overview, you may just clone the instance we just created by using the plus icon right next to that line. I will only describe what needs to be changed here:
Name: WAN_HTTPS_auth
Description: *.vpn.example.com (HTTPS Reverse Proxy with X.509 Auth)
Port: 2044
Certificate: *.vpn.example.com (this needs to be an official wildcard certificate!)
Client verification CA certificates: Acmi VPN Remote Access (which we created before)
Note: You cannot link any CRL here at the beginning because even though you would have created it using the Cert Manager it’d be still empty at the beginning which HAproxy does not like. Add it here as soon as you actually have revoked any certificate.
Creating the SSLH frontend instance
Now we create the third 2nd level frontend which will do SSH routing for us.
Name: WAN_SSLH
Description: *.ssh.example.com (SSL-secured SSH gateway with X.509 authentication)
External address: localhost (IPv4)
Port: 2022
SSL Offloading: yes
Advanced: accept-proxy npn ssh/2.0
Backend Server Pool: none
Type: SSL / HTTPS(TCP mode)
Client timeout: 7200000
Certificate: ssh.example.com (as created before)
Add ACL for certificate CommonName: No
Advanced ssl options: no-sslv3
Client verification CA certificates: Acmi SSLH Gateway
Creating the port 443 sharing main frontend instance
Now that you’ve come that far you cannot use any services yet due to the localhost listening we used before. We will change this here.
Listening
on port 1194 OpenVPN->>openvpn:To openvpn Note right of openvpn:No backend
openvpn daemon
already listening
OpenVPN
Mode: active
Name: OpenVPN
Forwardto: address+port
Address: 192.168.178.2 (basically your WAN IP running the OpenVPN instance)
Port: 1194
SSL: No
Health check method: none
Connect timeout: 3000
Server timeout: 7200000
Retries: 2
Listening
on port 443 Note over WAN_443,WAN_443_HTTPS: From Main Frontend to Shared Frontend WAN_443->>WAN_443_HTTPS:tcp-request inspect-delay 5s WAN_443->>WAN_443_HTTPS:tcp-request content accept
if { req.ssl_hello_type 1 }
or !{ req.ssl_hello_type 1 } Note over WAN_443_HTTPS,WAN_HTTPS_BKD: From Shared Frontend To Special Backend
From WAN IP to localhost:2043 WAN_443_HTTPS->>WAN_HTTPS_BKD: req.ssl_hello_type 1 WAN_443_HTTPS-->>WAN_HTTPS_BKD: shared Frontend to special backend end end rect rgb(204,229, 255) loop HTTPS WAN_HTTPS_BKD-->>WAN_HTTPS: special backend to Second Frontend WAN_HTTPS_BKD->>WAN_HTTPS:accept-proxy npn http/1.1 Note right of WAN_HTTPS: Listening on
127.0.0.1
port 2043 Note over WAN_HTTPS,backend_01: Host matches docs.example.com WAN_HTTPS-->>backend_01: SSL offloading Note right of backend_01: Listening
port 443 loop Availability backend_01->>backend_01: HAPROXY Check end Note over WAN_HTTPS,backend_02: Host matches blog.example.com WAN_HTTPS-->>backend_02: SSL offloading Note right of backend_02: Listening
port 443 loop Availability backend_02->>backend_02: HAPROXY Check end end end
First, we need to create special backend services for each of the 2nd layer frontends so we can loop back to them from our 1st level frontend instance.
special backends
Mode: active
Name: WAN_TCP
Forwardto: address+port
Address: 127.0.0.1
Port: 2045 (the port we used before for this frontend instance)
SSL: yes
Per server pass thru: send-proxy
Health check method: none
Server timeout: 7200000
Mode: active
Name: WAN_HTTPS
Forwardto: address+port
Address: 127.0.0.1
Port: 2043 (the port we used before for this frontend instance)
SSL: yes
Per server pass thru: send-proxy
Health check method: none
Server timeout: 7200000
Mode: active
Name: WAN_HTTPS_auth
Forwardto: address+port
Address: 127.0.0.1
Port: 2044 (the port we used before for this frontend instance)
SSL: yes
Per server pass thru: send-proxy
Health check method: none
Server timeout: 7200000
Mode: active
Name: WAN_SSLH
Forwardto: address+port
Address: 127.0.0.1
Port: 2022 (the port we used before for this frontend instance)
SSL: yes
Per server pass thru: send-proxy
Health check method: none
Server timeout: 7200000
After this, let’s finally create the main frontend instance:
Name: WAN_443
Description: Sharing port 443
Shared Frontend: No
External address: WAN address (IPv4)
Port: 443
SSL Offloading: No
Backend Server Pool: none
Type: TCP
Client timeout: 7200000
Advanced pass thru:
tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 } or !{ req.ssl_hello_type 1 }
To route to the correct frontend, we will create a “shared frontend” for each of our 2nd level frontent (pfSense names it like this, actually this is simply additional configuration to the assigned primary frontend). So let’s create 4 shared frontends:
Shared Frontend
Name: WAN_443_OpenVPN
Description: OpenVPN
Shared Frontend: Yes
Primary Frontend: WAN_443
Backend Server Pool: OpenVPN
Access Control lists:
NAME=acl1_1 EXPR=Custom NOT=yes VALUE=req.len 0
NAME=acl1_1 EXPR=Custom NOT=yes VALUE=req.ssl_hello_type 1
Name: WAN_443_TCP
Description: TCP
Shared Frontend: Yes
Primary Frontend: WAN_443
Backend Server Pool: WAN_TCP
Access Control lists:
NAME=acl2_1 EXPR=Custom NOT=no VALUE=req.ssl_hello_type 1
NAME=acl2_2 EXPR=Custom NOT=no VALUE=req_ssl_sni -i blog.exemple.com
NAME=acl2_3 EXPR=Custom NOT=no VALUE=req_ssl_sni -i docs.exemple.com
NAME=acl2_4 EXPR=Custom NOT=yes VALUE=req.ssl_sni -m end -i .vpn.example.com
NAME=acl2_5 EXPR=Custom NOT=yes VALUE=req.ssl_sni -m end -i .ssh.example.com
Name: WAN_443_HTTPS
Description: HTTPS
Shared Frontend: Yes
Primary Frontend: WAN_443
Backend Server Pool: WAN_HTTPS
Access Control lists:
NAME=acl3_1 EXPR=Custom NOT=no VALUE=req.ssl_hello_type 1
NAME=acl3_2 EXPR=Custom NOT=yes VALUE=req.ssl_sni -m end -i .vpn.example.com
NAME=acl3_3 EXPR=Custom NOT=yes VALUE=req.ssl_sni -m end -i .ssh.example.com
Name: WAN_443_HTTP_auth
Description: *.vpn.example.com
Shared Frontend: Yes
Primary Frontend: WAN_443
Backend Server Pool: WAN_HTTPS_auth
Access Control lists:
NAME=acl4_1 EXPR=Custom NOT=no VALUE=req.ssl_hello_type 1
NAME=acl4_2 EXPR=Custom NOT=no VALUE=req.ssl_sni -m end -i .vpn.example.com
Name: WAN_443_SSLH
Description: *.ssh.example.com
Shared Frontend: Yes
Primary Frontend: WAN_443
Backend Server Pool: WAN_SSLH
Access Control lists:
NAME=acl5_1 EXPR=Custom NOT=no VALUE=req.ssl_hello_type 1
NAME=acl5_2 EXPR=Custom NOT=no VALUE=req.ssl_sni -m end -i .ssh.example.com
So much for the basic setup! But there is actually no server or web service available right now (beside OpenVPN which should be working right away now). This is what we are going to setup now. But oh wait, of course we obviously miss some DNS stuff so let’s shortly create these canonical name records in your DNS:
_ssh.example.com. IN A <your-WAN-IP-address>
vpn.example.com. IN A <your-WAN-IP-address>
*.vpn.example.com. IN A <your-WAN-IP-address>
Should you be using a dynamic IP address for your WAN device you may also use CNAME records to your DynDNS provider’s name.
Okay, let’s finally start with the examples on how to make internal services available through your ultimate HAproxy setup now.
Make public websites available via SSL offloading
Normal SSL offloading functionality is available creating shared frontends for each backend service an assign WAN_HTTPS as it’s primary frontend. You may just use the standard pre-defined expression for matching hostnames so route to specific backend web servers based on their URL (first create the backend, then create the secondary frontend). Put any server certificates in here as required.
You may also want to enable Strict-Transport-Security and Cookie protection here.
Make internal websites available via SSL offloading
A lot of so called SSL-VPN appliances (e.g. Juniper) allow to granularly give access to internal websites without needing any VPN client software. In fact this is nothing else than some sophisticated reverse proxy and we can have pretty much same functionality (on top of the OpenVPN dial-in for more complex remote access requirements) using HAproxy and authentication via X509 user certificates.
The setup is exactly like with the public website described above, only difference is to assign WAN_HTTPS_auth as it’s primary backend.
For better convenience of this service we are using this wildcard certificate *.vpn.example.com together with wildcard DNS record so we can easily add additional backends without changing DNS or adding certs every time (of course this is still an option should you need a different URL, just takes more effort to set up).
Remember to import the user’s certificate and the public cert of your CA into his browser.
Make internal servers console accessible via SSL-tunneled SSH
You can find a lot of information on this kind of feature in the following article from Michel Mayen about routing ssh through HAPROXY
This is the one most of you will be interested in I guess so we’re finally here :-)
Obviously you need to create a new backend first:
Name: ssh_server
Mode: active
Forwardto: Address+Port
Address: <internal IP or DNS name>
Port: 22
SSL: no
Health check: none
Connection timeout: 3000
Server timeout: 7200000
Retries: 2
After it, let’s create the corresponding shared frontend:
Name: WAN_SSLH_server
Description: server.ssh.example.com
Shared Frontend: yes
Primary Frontend: WAN_SSLH
Backend server pool: ssh_server
Access Control lists:
NAME=acl EXPR=Custom VALUE=ssl_fc_npn -i ssh/2.0
NAME=acl EXPR=Custom VALUE=ssl_fc_sni_reg server.ssh.example.com
```yaml
That’s it!
Optionally, you may add SSHFP records to your DNS based on server.ssh.example.com.
## SSH access examples
So, how you gonna access this server from external now? Basically it’s about using the ProxyCommand of your SSH client together with OpenSSL’s s_client command. This is pretty much forward for any Mac or Linux machine.
### On Windows
With [mobaXterm](https://mobaxterm.mobatek.net/download-home-edition.html):
Download the plugin [Corkscrew](https://mobaxterm.mobatek.net/plugins/Corkscrew.mxt3) (Corkscrew allows to tunnel TCP connections through HTTP proxies) and put it into Mobaxterm folder
If you want to get the help of the plugin:
```bash
man corkscrew
Start a local session in mobaXterm, create .ssh
directory if it doesn’t exist
vim ~/.ssh/config
Without username/password:
Host * ProxyCommand /bin/corkscrew "proxy ip / domain" "port" %h %p
With username/password:
Host * ProxyCommand /bin/corkscrew "proxy ip / domain" "port" %h %p /home/"user name"/.corkscrew-auth
vim .corkscrew-auth
username:password
Add these lines just once to your ~/.ssh/config
file:
Host *.ssh.example.com
ProxyCommand /usr/local/opt/openssl/bin/openssl s_client -verify 1 -verify_return_error -nextprotoneg ssh/2.0 -brief -quiet -servername %h -connect ssh.example.com:443 -CAfile ~/.ssh/sslh-ca.crt -cert ~/.ssh/sslh.crt -key ~/.ssh/sslh.key
If you’re like me, you also want a shortcut for your most used servers so you don’t need to enter the FQDN and user each time. This is still possible:
Host server
Hostname server.ssh.example.com
User root
The ProxyCommand assumes you stored your CA public cert, your personal public cert and private key to ~/.ssh
to handle the X509 authentication which allows you to actually make use of the SSLH gateway (or even access to a specific server behind it as mentioned earlier).
Also note I am not using the openssl installation that comes with OS X but installed a newer version using Homebrew because it gives you some more information and better encryption support. I’m also using Homebrew’s OpenSSH for better SSHFP support, see this page.
Now you can simply SSH into server.ssh.example.com
(or just server
). Your connection will have strong SSL encryption which you can see directly from the connection output (it’s probably better than most single SSH connections as ECDSA often is not available due to outdated SSH server packages even on fairly new servers). Secondly you are using kind of 2-factor-authentication (first X509 cert + username + password or SSH key) to actually login which makes this a really secure way to access any server in your internal network from the outer world.