Um in einer kleinen, containerisieren Umgebung mehrere Webserver hinter einer IP zu betreiben habe ich mir mal HAProxy als Reverse Proxy angeschaut. Dabei sind mir die mächtigen acls aufgefallen mittels welcher man flexibel unterschiedliche Backends ansprechen kann. Diese acls lassen Konstellationen dieser Art zu:
+-----------+
+-----------+ +-----------+ |sshd |
|Client | |HAProxy +---------------> | |
| | +------------------------------+ | | +-----------+
| | |TLS-Tunnel | | |
| | | | | | +-----------+
| | +------------------------------+ | +---------------> |httpd |
| | | | | |
+-----------+ +-----------+ | |
+-----------+
HAProxy entscheidet anhand der ersten übertragenen Byte an welchen Deamon die Verbindung weitergeleitet werden soll. SSH beispielsweise definiert folgenden identification string in RFC 4253, den Client und Server nach erfolgreich aufgebauter TCP-Verbindung austauschen müssen:
SSH-protoversion-softwareversion SP comments CR LF
Wobei protoversion "2.0" oder (hoffentlich nicht mehr) "1.0" sein können.
Mit folgender einfachen HAProxy-Konfig lässt sich die oben abgebildete Magie realisieren:
global
chroot /var/lib/haproxy
user haproxy
group haproxy
daemon
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
ssl-default-bind-ciphers
ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
ssl-default-bind-options no-sslv3
backend httpd
mode http
rspadd Strict-Transport-Security:\ max-age=31536000
server local_http_server 127.0.0.1:80
backend sshd
mode tcp
server ssh 127.0.0.1:22
timeout server 2h
frontend ssl
mode tcp
bind *:443 ssl crt /etc/haproxy/cert-key.pem no-sslv3
tcp-request inspect-delay 5s
tcp-request content accept if HTTP
acl client_attempts_sshregex req.payload(0,7) -m reg "^SSH-[1|2]{1}\.0.*"
use_backend sshd if client_attempts_sshregex
use_backend httpd if HTTP
Im frontend wird eine acl definiert, die per regulärem Ausdruck SSH-1.0* und SSH-2.0* matched und in diesem Fall das backend sshd als Ziel nutzt. Wenn HTTP gesprochen wird läuft die Verbindung auf den httpd.
Mit openssl lässt sich diese Konstruktion testen. SSH in TLS:
Host:dimi@lnx:~# openssl s_client -connect blog.3or.de:443 -quiet
SSH-2.0
SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.1
Protocol mismatch.
Und HTTP in TLS:
dimi@lnx:~# openssl s_client -connect blog.3or.de:443 -quiet
GET / HTTP/1.0
HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Sun, 13 Nov 2016 19:23:00 GMT
Content-Type: text/html
Content-Length: 28
Last-Modified: Sun, 23 Oct 2016 15:36:53 GMT
Connection: close
ETag: "580cd915-1c"
Accept-Ranges: bytes
Strict-Transport-Security: max-age=31536000
<marquee> hallo. </marquee>
Und über die Konfigurationsoption "ProxyCommand" verheiratet man dieses Konstrukt mit seinem SSH-Client:
Host blog.3or.de
HostName blog.3or.de
User dimi
LocalForward localhost:8118 localhost:8118
IdentityFile /home/dslamar/.ssh/outdoor.id
ProxyCommand proxytunnel --quiet -d blog.3or.de:443 -e
Der OpenSSH-Client ruft zunächst proxytunnel auf, schiebt dann seine SSH-TCP-Verbindung über STDIN/SDTOUT des proxytunnel-Prozess innerhalb des TLS-Tunnel zum HAProxy-Server. Dieser erkennt erkennt den identification string SSH-2.0, leitet an den sshd weiter.
Das tolle an dieser Lösung: Man kommt, im no-interception HTTPS-Proxy-Szenario, an Proxylösungen wie Bluecoat vorbei. Versucht ein Client über einen Bluecoat-Proxy eine TLS-Verbindung zu einer Seite aufzubauen, baut dieser - bevor die TLS-Verbindung zwischen Client und Server erlaubt wird - parallel dazu eine HTTPS-Verbindung zum Server auf. Diese soll dazu dienen zu prüfen ob der Server tatsächlich HTTPS spricht oder ob der Client eine andere TCP-Verbindung durch den Proxy tunneln will. Erst wenn diese Testverbindung erfolgreich war und der Server wirklich HTTPS spricht wird die Verbindung des Clients erlaubt.