SSL Handshake failure on ssh

Trying to add specific routing depending on SSH destination fails.

My haproxy.cfg looks like this:

global
	log /dev/log	local0 info
	log /dev/log	local1 info 
	chroot /var/lib/haproxy
	user haproxy
	group haproxy
	daemon

	# Default SSL material locations
	ca-base /etc/ssl/certs
	crt-base /etc/ssl/private

	tune.ssl.default-dh-param 2048
	ssl-default-bind-options no-sslv3 no-tls-tickets
	ssl-default-bind-ciphers ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA

defaults
	log	global
	#mode	http
	option	httplog
	option	dontlognull
	timeout connect 50000
	timeout client  500000
	timeout server  500000
	errorfile 400 /etc/haproxy/errors/400.http
	errorfile 403 /etc/haproxy/errors/403.http
	errorfile 408 /etc/haproxy/errors/408.http
	errorfile 500 /etc/haproxy/errors/500.http
	errorfile 502 /etc/haproxy/errors/502.http
	errorfile 503 /etc/haproxy/errors/503.http
	errorfile 504 /etc/haproxy/errors/504.http

frontend ssh
	bind *:22 ssl no-sslv3 crt /path/to/cert.pem
	mode tcp
	option tcplog
	log-format "%ci:%cp [%t] %ft %b/%s %Tw/%Tc/%Tt %B %ts %ac/%fc/%bc/%sc/%rc %sq/%bq dst:%[var(sess.dst)] "
	tcp-request content set-var(sess.dst) ssl_fc_sni
	tcp-request inspect-delay 5s
	acl valid_payload req.payload(0,7) -m str "SSH-2.0"
	tcp-request content reject if !valid_payload
	tcp-request content accept if { req_ssl_hello_type 1 }
	acl gitlab ssl_fc_sni gitlab.contoso.com
#	tcp-request content reject if !gitlab
	default_backend bck_gitlab_ssh

backend bck_gitlab_ssh
	mode tcp
	server gitlab a.b.c.d:22 check

frontend http-in
	bind *:80
	bind *:443 ssl no-sslv3 crt /path/to/cert.pem
	mode	http
	option	httplog
        [...]

I launched the openssl s_client -connect subdomain.contoso.com:22 with following result:

CONNECTED(00000003)
— Certificate chain 0 s:CN = *.subdomain.contoso.com i:C = GB, ST = Greater Manchester, L = Salford, O = Sectigo Limited, CN =
Sectigo RSA Domain Validation Secure Server CA 1 s:C = GB, ST =
Greater Manchester, L = Salford, O = Sectigo Limited, CN = Sectigo RSA
Domain Validation Secure Server CA i:C = US, ST = New Jersey, L =
Jersey City, O = The USERTRUST Network, CN = USERTrust RSA
Certification Authority 2 s:C = US, ST = New Jersey, L = Jersey City,
O = The USERTRUST Network, CN = USERTrust RSA Certification Authority
i:C = US, ST = New Jersey, L = Jersey City, O = The USERTRUST Network,
CN = USERTrust RSA Certification Authority
— Server certificate
-----BEGIN CERTIFICATE----- […]
-----END CERTIFICATE----- subject=CN = *.subdomain.contoso.com

issuer=C = GB, ST = Greater Manchester, L = Salford, O = Sectigo
Limited, CN = Sectigo RSA Domain Validation Secure Server CA

— No client certificate CA names sent Peer signing digest: SHA256 Peer signature type: RSA-PSS Server Temp Key: X25519, 253 bits
— SSL handshake has read 5242 bytes and written 396 bytes Verification: OK
— New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 Server public key is 2048 bit Secure Renegotiation IS NOT supported Compression: NONE
Expansion: NONE No ALPN negotiated Early data was not sent Verify
return code: 0 (ok)
— HTTP/1.0 400 Bad request Cache-Control: no-cache Connection: close Content-Type: text/html

400 Bad request

Your browser sent an invalid request.

— Post-Handshake New Session Ticket arrived: SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: FF80D974E787152BE195D533AF095842F2257CD340F8EB40AF5EA8D70D661229
Session-ID-ctx:
Resumption PSK: A4A117A4D8C504590DFBACDB00B46A2D87AA15FDFE4D0AA83CAE9341330AABFE4488C3D7EB73BAE8386EE748240E3514
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 7200 (seconds)
TLS session ticket:
0000 - b4 05 de bd c7 b1 d3 71-53 99 b3 28 6e 46 6d 23 …qS…(nFm#
0010 - a6 fc cc bd c9 a2 c7 86-61 6a 25 f5 80 83 29 33 …aj%…)3

Start Time: 1664029485
Timeout   : 7200 (sec)
Verify return code: 0 (ok)
Extended master secret: no
Max Early Data: 0

— read R BLOCK
— Post-Handshake New Session Ticket arrived: SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: 6AC8A5104001D9DA6A4ACF750352B1429D258452FED10584B4C6398CF6DC740C
Session-ID-ctx:
Resumption PSK: 354465EF5C784F3B021A721FFD739E7F0FB7BBF666B7D165CC324A313C7B446A7FF6E65CB54081E00DE9389F780AC177
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 7200 (seconds)
TLS session ticket:
0000 - 27 2d ca 6c 4d af 6b ce-dc cb 67 39 f3 60 20 3a '-.lM.k…g9.` :
0010 - b0 a5 25 58 6d 4a ef db-7d db 0d 97 5a 54 aa 03 …%XmJ…}…ZT…

Start Time: 1664029485
Timeout   : 7200 (sec)
Verify return code: 0 (ok)
Extended master secret: no
Max Early Data: 0

— read R BLOCK closed

However when I do a SSH command to subdomain contoso.com it fails with an SSL handshake failure.

Aside note:

  • The default works for the http(s) frontend
  • If i comment the ssl no-sslv3 crt and what is after it works (except that it disables the routing part…)

Any help on this?

My Haproxy version :

HA-Proxy version 1.8.19-1+deb10u3 2020/08/01
Copyright 2000-2019 Willy Tarreau willy@haproxy.org

Build options :
TARGET = linux2628
CPU = generic
CC = gcc
CFLAGS = -O2 -g -O2 -fdebug-prefix-map=/build/haproxy-OkbS59/haproxy-1.8.19=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fno-strict-aliasing -Wdeclaration-after-statement -fwrapv -Wno-format-truncation -Wno-null-dereference -Wno-unused-label
OPTIONS = USE_GETADDRINFO=1 USE_ZLIB=1 USE_REGPARM=1 USE_OPENSSL=1 USE_LUA=1 USE_SYSTEMD=1 USE_PCRE2=1 USE_PCRE2_JIT=1 USE_NS=1

Default settings :
maxconn = 2000, bufsize = 16384, maxrewrite = 1024, maxpollevents = 200

Built with OpenSSL version : OpenSSL 1.1.1d 10 Sep 2019
Running on OpenSSL version : OpenSSL 1.1.1n 15 Mar 2022
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3
Built with Lua version : Lua 5.3.3
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND
Encrypted password support via crypt(3): yes
Built with multi-threading support.
Built with PCRE2 version : 10.32 2018-09-10
PCRE2 library supports JIT : yes
Built with zlib version : 1.2.11
Running on zlib version : 1.2.11
Compression algorithms supported : identity(“identity”), deflate(“deflate”), raw-deflate(“deflate”), gzip(“gzip”)
Built with network namespace support.

Available polling systems :
epoll : pref=300, test result OK
poll : pref=200, test result OK
select : pref=150, test result OK
Total: 3 (3 usable), will use epoll.

Available filters :
[SPOE] spoe
[COMP] compression
[TRACE] trace

SSH and SSL are two completely different protocols, they don’t have anything to do with each other.

I think that I indeed totally mistaken about this so I will maybe reformulate my question.

Is there a way to redirect traffic depending on the SSH host choosen by end-user like it’s done for HTTP(S)?

ssh user@gitlab.contoso.com redirecting to a.b.c.d:22
ssh user@sonarqube.contoso.com redirecting to w.x.y.z:22
and so on ?

No, you cannot.

Dunno if you’re still trying to do this, but this comes up on a search so if you or someone else’s looking to do this:

Now let’s define our backends. Each one lists only a single server. Each backend name is the server name to expect in the SNI:

backend server1
   mode tcp
   server s1 192.168.0.201:22 check

backend server2
   mode tcp
   server s2 192.168.0.202:22 check

backend server3
   mode tcp
   server s2 192.168.0.203:22 check

From your clients, you can reach your SSH servers with these commands:

$ ssh -o ProxyCommand="openssl s_client -quiet -connect 172.16.0.10:2222 -servername server1" dummyName1
$ ssh -o ProxyCommand="openssl s_client -quiet -connect 172.16.0.10:2222 -servername server2" dummyName2
$ ssh -o ProxyCommand="openssl s_client -quiet -connect 172.16.0.10:2222 -servername server3" dummyName3

It clearly doesn’t use external SNI matching (which I may or may not want to do), but it’s an option. And, as the blog post states, it leverages TLS to provide the handshake information to SSH even though “SSH and SSL are two completely different protocols,” as are TLS and SSH.