Part 2: Public with opnSense Firewall / HAProxy / Let's Encrypt Certificate

Goals

The operation of a Vaultwarden server with a Let's Encrypt certificate and HAProxy is nicely laid out on the OPNsense firewall.

Nevertheless: This expansion stage is now suitable for public use, but must now be regularly updated and maintained. Nothing is worse than an outdated and thus vulnerable installation.

Last updated:

  • 01.01.2024: Customize ports and details
  • 20.08.2023: Automation for HAProxy extended
  • 19.11.2022: Extension with diagram and minor adjustments.
  • 30.10.2022: Initial document.

Precondition for public operation

  • Vaultwarden from the first part is installed and ready.
  • Own subdamain domain with fixed IP or DyDNS address is available.
  • opnSense firewall with the installed plugins os-haproxy and os-acme-client.
    • Important! The admin interface has been moved from port 443 to 4433.
    • With the plugins os-haproxy and os-acme-client installed.
    • Access to TCP port 443 is allowed on WAN.
    • Internally, the EXTERNERHOSTNAME should be set to the IP of the OPNsense (so that internal calls are also routed to the HAProxy).

Screenshot%202022-10-30%20124502

Terminology

  • EXTERALHOSTNAME = The externally accessible hostname
  • ACCOUNTNAME = Name of the Let's Encrypt Account
  • EMAILADDRRESS = Email Adresse of the Let's Encrypt Accounts
  • VAULTIP = IP Adresse of the local Vaultwarden Server
  • VAULTHOSTNAME = Hostname of the local Vaultwarden Server

Diagramm

With this the setup looks now like this:

                                                                  ┌─────────────────────────────┐
                                                                  │ TrueNAS                     │
                                                                  │   192.168.178.100           │
                  ┌──────────────────────┐                        │ ┌─────────────────────────┐ │
                  │ OPNsense             │                        │ │ Vault                   │ │
                  │   192.168.178.1      │                        │ │   192.168.178.101       │ │
                  │                      │                        │ │                         │ │
WAN: 0.0.0.0:80  ─┼─► acme.sh:80         │                        │ │     SQLite              │ │
                  │                      │                        │ │       ▲                 │ │
WAN: 0.0.0.0:443 ─┼─► HAProxy:443 ───────┼─ 192.168.178.101:8000 ─┼─┼─► vaultwarden           │ │
                  │                      │                        │ │       ▲                 │ │                    
                  │                      │     LAN: 0.0.0.0:8000 ─┼─┼───────┘                 │ │
                  │                      │                        │ └─────────────────────────┘ │
                  └──────────────────────┘                        └─────────────────────────────┘

OPNsense

Let's Encrypt certificates

With the ACME Client plugin (os-acme-client) OPNsense is able to create and renew Let's Encrypt certificates automatically. The big advantage is that we have a central certificate management, we don't need a separate management for each internal target system and we don't need to configure NAT or other firewall settings.

To get a certificate for your own domain only a few steps are necessary under "Services - ACME Client":

Services: ACME Client

Settings

To obtain a certificate for your own domain, only a few steps are necessary under "Services - ACME Client". To do this, go to the "Services / ACME Client / Settings" section of OPNsense and check the following boxes:

Enable Plugin: JA  
Auto Renewal: JA  

This activates the plugin and sets the automatic renewal of certificates.

Screenshot%202022-10-30%20123726

Accounts:

Then we switch to the "Services / ACME Client / Accounts" section and create a new account with the "+" sign.

Enabled: JA  
Name: **ACCOUNTNAME**  
E-Mail Address: **EMAILADDRRESS**  
ACME CA: Let's Encrypt [default]  

The account must be registered once. On the far right there is a button "Register Account". If the registration is successful, the account will be marked with "OK (registered)".

Screenshot%202022-10-30%20131719

Challenge Types

We then specify how the challenge should take place. This is done in the "Services / ACME Client / Challenge Types" section. Here, too, we create a new challenge with the following settings by pressing the "+" sign:

Enabled: JA  
Name: VAULTHOSTNAME  
Challenge Type: HTTP-01  
HTTP Service: OPNsense Web Service (automatic port forward)  
Interface: WAN  
Automations

A new entry is created under "Services / ACME Client / Automations".

Name: Restart HAProxy
Run Command: Restart HAProxy
Certificates

The last step is to create the certificate. To do this, we switch to "Services / ACME Client / Certificates" and create a new entry by clicking on the "+" sign. In the field "ACME Account" we select the account we just created, in the field "Challange Type" we select the created challange. We select the remaining settings as follows:

Enabled: JA  
Common Name: EXTERALHOSTNAME  
ACME Account: ACCOUNTNAME (from Accounts)  
Challenge Type: VAULTHOSTNAME (from Challenge Types)  
Key Length: ec-384  
Automations: Restart HAProxy  

Now all settings are done and the certificate can be created on the right side with the button "Issue or renew certificate". If successful (this takes a few minutes) the certificate will be marked with "OK" at "Last ACME Status".

Screenshot%202022-10-30%20132151

HAProxy

HAProxy receives the calls for port 443 from the outside, encrypts the connection and then forwards it to the internal VAULT server on port 8000. The nice thing is that here again the administration takes place centrally, the certificates can be used without any effort from Let's Encrypt and several services can be provided simultaneously on port 443. The difference is made by the called external host name. This makes it possible that e.g. https://vault.domain.de is forwarded to Vaultwarden and https://domain.de to the actual website, although both actually require port 443 on the same external IP address.

The task of the HAProxy here is:

  • Allow external access to the URL https://EXTERALHOSTNAME.
  • Deny external access for the URL https://EXTERALHOSTNAME/admin
  • Harden HTTPS and make it less vulnerable to weak encryption.

The process is: Public Service (EXTERNERHOSTNAME:443) ─► Condition ─► Rule ─► Pool ─► VAULTHOSTNAME:8000

To now get a forwarding of the EXTERNALHOSTNAME to the internal Vaultwarden, only a few steps are necessary under "Services - HAProxy": In the first step we define a server under "Services / HAProxy / Settings" in the tab "Real Servers". Again, this is done by pressing the "+" button.

Services: HAProxy

Settings
Enable HAProxy: YES

Screenshot%202022-10-30%20134041

Real Servers:

Let's start with the "Vault" service, it gets the following entries:

Name: server_HOSTNAME  
Description: IP address of the local Vaultwarden server  
Type: static  
FQDN or IP: VAULTIP  
Port: 8000  
SSL: YES  
Virtual Services: Backend Pools

In the second step we create the "Backend Pools", this we do under "Services / HAProxy / Settings" in the tab "Virtual Services" the entry "Backend Pools".

Name: pool_HOSTNAME  
Description: Pool of local Vaultwarden servers (there is only one here)  
Servers: server_HOSTNAME  
Rules & Checks: Conditions

Now we define the set of rules that determine which incoming connections are forwarded to which service. To do this, we select the "Conditions" entry under "Services / HAProxy / Settings" in the "Rules & Checks" tab and create two conditions:

Name: vault deny admin source check  
Description: Checking whether the access is from the local network  
Condition type: "Source IP matches specified IP"  
Negate condition: YES  
Source IP: 192.168.178.0/24 (Enter your own internal network here)  

Name: vault deny admin url check  
Description: Check if the admin page is called  
Condition type: "Path regex"  
Path Regex: admin  

Name: vault host check  
Description: Check if the EXTERNALHOSTNAME was called  
Condition type: "Host matches"  
Path Regex: EXTERALHOSTNAME  
Rules & Checks: Rules

These conditions are checked by a rule. We create rules as follows under "Services / HAProxy / Settings" in the "Rules & Checks" tab in the "Rules" entry using the "+" button:

Name: vault deny admin rule  
Description: Deny external access to the admin area  
Select conditions: "vault deny admin source check" "vault deny admin url check"  
Execute function: http-request-deny  

Name: vault host rule  
Description: Forward external access to **EXTERNALHOSTNAME** to the direct server (in the pool)  
Select conditions: vault host check  
Execute function: Use specified Backend Pool  
Use backend pool: pool_HOSTNAME  
Virtual Services: Public Services

Finally, we create a "Public Services" under "Services / HAProxy / Settings" in the "Virtual Services" tab the "Public Services" entry

Name: service_https  
Description: Server on port 443 which accepts all external requests  
Listen Addresses: 0.0.0.0:443
Default Backend Pool: none  
Certificates: EXTERALHOSTNAME (ACME Client)  
Default certificate: EXTERALHOSTNAME (ACME Client)  
Cipher List: 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  
Cipher Suites: TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256  
Enable HSTS: YES  
HSTS includeSubDomains: YES  
Select Rules: vault deny admin rule, vault host rule

With "Apply" the configuration is written and the HAProxy service is started.

Please remember to allow port 443 in the firewall.

Now the access to the Vaultwarden from outside is set up and allowed.

Please test the following:

  • The call https://EXTERALHOSTNAME opens the Vaultwarden login screen - OK!
  • The call https://EXTERALHOSTNAME/admin gets a "Forbidden" error message - OK!
  • The call https://EXTERNEIPADRESSE gets a "Forbidden" error message - OK!

Voilá