Eigene Dienste öffentlich mit OPNsense anbieten

Ziele

Auch unterwegs ist es doch schön auf die eigenen Dienste zugreifen zu können. Der Betrieb eines öffentlich zugänglichen, aber im lokalen Netzwerk installierten Dienstes kann mit einem Let's Encrypt Zertifikat und HAProxy auf der OPNsense Firewall genial einfach realisiert werden.

Dennoch: Diese Ausbaustufe ist zwar nun für den öffentlichen Betrieb geeignet, muss aber regelmäßig nun aktualisiert und gepflegt werden. Nichts ist schlimmer als eine veraltete und damit angreifbare Installation.

Letzte Aktualisierung:

  • 02.09.2024: In eigenständigen Artikel umgewandelt und als Modul für andere umgebaut
  • 03.03.2024: Kleinigkeiten angepasst
  • 05.01.2024: Diverse Kleinigkeiten angepasst
  • 20.08.2023: Automatisierung für HAProxy erweitert
  • 29.07.2023: Initiales Dokument

Voraussetzungen

  • Ein Dienst ist bereits installiert und intern erreichbar, (z.B. FreshRSS, Gitea oder Vaultwarden
  • Eine eigene Subdomain mit fester IP oder DyDNS Adresse ist verfügbar (z.B. dienstname.domain.de, oder meinhostname.dyndns.org)
  • Eine opnSense Firewall
    • Der TCP port wurde unter System - Settings - Administration auf 8443 gesetzt (vorher 443)
    • Mit den installierten Plugins os-haproxy und os-acme-client
    • Der Zugriff auf TCP Port 443 ist an der WAN Schnittstelle unter Firewall - Rules - WAN erlaubt. (Protocol: TCP, Source: Any, Destination: This Firewall, Port: 443)
    • Intern sollte der EXTERNHOSTNAME auf die IP der OPNsense gesetzt werden (damit auch interne Aufrufe durch HAProxy geleitet werden). In der Regel mit den Overrides im Unbound unter Services

Diagramm

Damit sieht das Setup so aus:

                                                          ┌─────────────────────────────┐
                                                          │ TrueNAS / FreeBSD           │
                  ┌──────────────────────┐                │ ┌─────────────────────────┐ │
                  │ OPNsense             │                │ │ jails/jailname          │ │
WAN: 0.0.0.0:80  ─┼─► acme.sh:80  ───────┼─ LAN: IP:PORT ─┼─┼─► service               │ │
WAN: 0.0.0.0:443 ─┼─► HAProxy:443        │                │ │                         │ │
                  └──────────────────────┘                │ └─────────────────────────┘ │
                                                          └─────────────────────────────┘

Begrifflichkeiten

  • ACCOUNTNAME = Name des Let's Encrypt Accounts (z.B. bsdbox)
  • EMAIL = Email Adresse des Let's Encrypt Accounts (z.B. marcel@bsdbox.de)
  • EXTERNHOSTNAME = Der extern erreichbare Hostname (z.B. git.bsdbox.de oder bsdbox.dyndns.org)
  • IP = IP Adresse des lokalen Servers
  • HOSTNAME = Hostname des lokalen Servers (z.B. git.bsdbox.local)

Zertifikate

OPNsense ist mit dem ACME Client Plugin (os-acme-client) in der Lage Let's Encrypt Zertifikate zu erstellen und auch automatisch zu erneuern. Der riesen Vorteil ist, dass wir eine zentrale Zertifikatsverwaltung haben, nicht mühselig auf den internen Zielsystemen jeweils eine eigene Verwaltung benötigen und keine NAT oder anderen Firewall Einstellungen vornehmen müssen.

Services: ACME Client

Settings

Um ein Zertifikat für die eigene Domain zu erhalten, sind nur wenige Schritte unter Services - ACME Client notwendig. Hierfür wechseln wir in den Bereich Services - ACME Client - Settings der OPNsense und setzen folgende Haken:

Enable Plugin: JA  
Auto Renewal: JA  

Hierdurch wird das Plugin aktiviert und die automatische Erneuerung der Zertifikate festgelegt.

Accounts:

Danach wechseln wir in den Bereich Services - ACME Client - Accounts´ und erstellen mit dem +-Zeichen einen neuen Account.

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

Der Account muss einmalig registriert werden. Ganz Rechts gibt es dazu den Knopf Register Account. Bei Erfolg wird der Account dann bei Status mit OK (registered) gekennzeichnet.

Tipp: Es reicht eine Registrierung, egal wie viele Zertifikate wir am Ende benötigen.

Challenge Types

Im Anschluss legen wir fest, wie die Überprüfung (Challenge) stattfinden soll. Dies geschieht im Bereich Services - ACME Client - Challenge Types. Auch hier erzeugen wir durch Betätigung des +-Zeichens eine neue Challenge mit den folgenden Einstellungen:

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

Automations

Unter Services - ACME Client - Automations wird ein neuer Eintrag erstellt.

Name: Restart HAProxy
Run Command: Restart HAProxy

Certificates

Im letzten Schritt geht es an die Erstellung des Zertifikates. Hierfür wechseln wir nach Services - ACME Client - Certificates und erstellen erneut einen neuen Eintrag durch das +-Zeichen. Im Feld ACME Account wählen wir den soeben erstellen Account aus, in dem Feld Challange Type die erstellte Challange. Die übrigen Einstellungen wählen wir wie folgt:

Tipp: Sollte hier bereits ein Zertifikat eines anderen Dienstes verwendet werden, kann das bereits existierende um weitere EXTERNHOSTNAME erweitert werden! Das vereinfacht die Dinge ungemein.

Enabled: JA  
Common Name: EXTERNHOSTNAME  
ACME Account: ACCOUNTNAME (aus Accounts)  
Challenge Type: HOSTNAME (aus Challenge Types)  
Key Length: ec-384  
Automations: Restart HAProxy  

Damit sind alle Einstellungen vorgenommen und das Zertifikat kann ganz Rechts mit dem Knopf Issue or renew certificate erstellt werden. Bei Erfolg (das dauert ein paar Minuten) wird das Zertifikat bei Last ACME Status mit OK gekennzeichnet.

Fertig! Die Erneuerung des Zertifikates erfolgt nun gemäß den Einstellungen alle 90 Tage vollautomatisch.

HAProxy

HAProxy nimmt von außen die Aufrufe für den Port 443 entgegen, verschlüsselt die Verbindung und leitet diese dann an den interen Port weiter. Das schöne ist, das auch hier die Verwaltung wieder zentral stattfindet, die Zertifikate ohne Aufwand von Let's Encrypt genutzt und mehrere Dienste gleichzeitig auf Port 443 bereitgestellt werden können. Unterschieden wird über den aufgerufenen externen Hostnamen. Damit ist es möglicht das z.B https://dienst.domain.de an den Dienst und https://domain.de zur eigentlichen Webseite geleitet wird, obwohl beide eigentlich Port 443 auf der gleiche externen IP Adresse benötigen.

Aufgabenstellung des HAProxy ist hier:

  • Externen Zugriff erlauben für die URL https://EXTERNHOSTNAME
  • HTTPS härten und unanfälliger für schwache Verschlüsselungen machen

Der Prozess ist: Public Service (EXTERNHOSTNAME:443) ─► Condition ─► Rule ─► Pool ─► HOSTNAME:PORT

Um nun eine Weiterleitung der EXTERNERHOSTNAME auf den interen Server zu erhalten, sind nur wenige Schritte unter Services - HAProxy notwendig: Im ersten Schritt definieren wir unter Services - HAProxy - Settings in der Lasche Real Servers einen Server. Dies geschieht wieder jeweils durch Betätigung der +-Schaltfläche.

Services: HAProxy

Settings

Enable HAProxy: YES

Real Servers:

Beginnen wir mit dem Service, dieser erhält die folgenden Einträge:

Name: server_HOSTNAME  
Description: IP Adresse des lokalen Servers  
Type: static  
FQDN or IP: IP  
Port: PORT  

Virtual Services: Backend Pools

Im zweiten Schritt erstellen wir die Backend Pools, dies erledigen wir unter Services - HAProxy - Settings in der Lasche Virtual Services den Eintrag Backend Pools.

Name: pool_HOSTNAME  
Description: Pool an lokalen realen Server (hier gibts nur einen)  
Servers: server_HOSTNAME  

Rules & Checks: Conditions

Nun hinterlegen wir das Regelwerk, welche bestimmen, welche eingehenden Verbindungen an welchen Server weitergeleitet werden. Hierfür wählen wir unter Services - HAProxy - Settings in der Lasche Rules & Checks den Eintrag Conditions aus und erstellen zwei Bedingungen:

Name: HOSTNAME host check  
Description: Prüfung ob der EXTERNHOSTNAME aufgerufen wurde  
Condition type: Host matches  
Path Regex: EXTERNHOSTNAME  

Rules & Checks: Rules

Diese Bedingungen werden durch eine Regel überprüft. Regeln erzeugen wir wie folgt unter Services - HAProxy - Settings in der Lasche Rules & Checks im Eintrag Rules durch die Schaltfläche +:

Name: HOSTNAME host rule  
Description: Externer Zugriff an den richten Server (im Pool) weiterleiten  
Select conditions: HOSTNAME host check  
Execute function: Use specified Backend Pool  
Use backend pool: pool_HOSTNAME  

Virtual Services: Public Services

Abschließend erstellen wir einen Public Services unter Services - HAProxy - Settings in der Lasche Virtual Services den Eintrag Public Services:

Name: service_https  
Description: Server auf Port 443 der alle externen Anfragen entgegenimmt  
Listen Addresses: 0.0.0.0:443
Default Backend Pool: none  
Certificates: EXTERNHOSTNAME (ACME Client)  
Default certificate: EXTERNHOSTNAME (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: HOSTNAME host rule  

Tipp: Bei mehreren internen Servern, kann Select Rules mehrfache Einträge erhalten.

Mit Apply wird die Konfiguration geschrieben und der HAProxy Dienst gestartet.

Damit ist nun von außen der Zugriff eingerichtet und erlaubt.

Bitte daran denken den Port 443 in der Firewall zu erlauben.

Voilá