OpenVPN Jump Host¶
The jump host (mbptillo) runs an OpenVPN server that authenticates users against OpenLDAP, providing secure remote access to the homelab network. It shares port 443 with HTTPS and SSH via sslh, so no extra firewall rules are needed.
Traffic Flow¶
flowchart TD
client["OpenVPN client\nvpn.home.tillo.ch:443"]
subgraph bpir4["BPI-R4"]
dnat["TCP :443 DNAT\n→ mbptillo:4443"]
end
subgraph mbptillo["mbptillo"]
sslh["sslh :4443\nprotocol demux"]
openvpn["OpenVPN\n127.0.0.1:9443\nTCP mode"]
sssd["sssd + PAM\nLDAP auth"]
tun["tun10\n10.8.10.0/24"]
end
subgraph k8s["mdapi-prod"]
ldap["OpenLDAP\n192.168.1.52"]
services["K8s services\n192.168.1.x"]
end
client --> dnat --> sslh
sslh -->|"OpenVPN detected"| openvpn
openvpn --> sssd --> ldap
openvpn --> tun --> services
Components¶
| Component | Location | Notes |
|---|---|---|
| sslh | mbptillo :4443 | Protocol demux; routes OpenVPN to :9443, SSH to :22, TLS to ingress-nginx |
| OpenVPN server | mbptillo :9443 (TCP) | Subnet 10.8.10.0/24 via tun10 |
| sssd | mbptillo | PAM auth → OpenLDAP at 192.168.1.52; cache_credentials=true |
| EasyRSA PKI | /etc/openvpn/easyrsa/pki-root |
EC/prime256v1 CA; ta.key for tls-auth |
| vpn-nat.service | mbptillo systemd | iptables MASQUERADE for 10.8.10.0/24 |
| ovpn-admin | K8s openvpn namespace |
Web UI at vpnadmin.home.tillo.ch; reads PKI via hostPath |
Authentication¶
OpenVPN uses PAM for user authentication, which chains through sssd → OpenLDAP. This means VPN access is controlled by the same LDAP directory as mail and other services — no separate user database to maintain. Certificates act as a second factor (tls-auth with ta.key); username/password provides the identity.
cache_credentials=true in sssd ensures users can authenticate even if the cluster is temporarily unreachable (e.g. during a maintenance window).
ovpn-admin¶
ovpn-admin provides a web UI for generating client .ovpn files. It connects to the OpenVPN management socket at 127.0.0.1:7505 and reads the EasyRSA PKI from the host filesystem via a Kubernetes hostPath volume.
The pod runs with hostNetwork: true and is pinned to mbptillo via nodeSelector. It is deployed via kubectl apply against the mdapi-rancher management cluster (not through Fleet) since it has tight coupling to the mbptillo host.
Why sslh instead of a dedicated port?¶
Exposing port 443 externally is reliably unblocked on all networks — hotels, corporate firewalls, mobile carriers. A dedicated VPN port (1194, 1194/UDP, etc.) is frequently blocked. sslh allows SSH, HTTPS, and OpenVPN to coexist on 443, so the jump host is reachable regardless of network restrictions.