Hi Willy,
I cant replicate your results here....
I cloned from git and built the package with the debian/ubuntu build
scripts from https://launchpad.net/~vbernat/+archive/ubuntu/haproxy-1.7
... updating the changelog to add a 1.8-dev2 version and calling
./debian/rules binary to build the package.
The git log shows:
commit 2ab88675ecbf960a6f33ffe9c6a27f264150b201
Author: Willy Tarreau <w...@1wt.eu>
Date: Wed Jul 5 18:23:03 2017 +0200
MINOR: ssl: compare server certificate names to the SNI on
outgoing connections
So I'm sure its in there unless a ./debian/rules binary build is
breaking something.
this is my config.
haproxy-min-sni.cfg
global
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
defaults
mode http
option httplog
option dontlognull
option forwardfor
option http-server-close
option log-health-checks
timeout connect 5000
timeout client 50000
timeout server 50000
frontend www-https
bind :::443 v4v6 ssl crt /etc/haproxy/certs/www.example.ca.pem crt
/etc/haproxy/certs/
reqadd X-Forwarded-Proto:\ https
use_backend www-backend-https
backend www-backend-https
http-response set-header X-Server %s
balance roundrobin
server app2 10.10.0.5:443 ssl verify required sni ssl_fc_sni
ca-file /etc/ssl/certs/ca-certificates.crt check check-ssl
--
/usr/sbin/haproxy -d -f haproxy-min-sni.cfg
--
Loading ssltest-broken.example.ca (that the backend server has no cert
for and so serves from the default tls vhost (app2.example.ca in this
case))... This shows a secure page in the browser, however the
connection to the backend cannot be secure.
[WARNING] 205/165327 (16816) : Health check for server
www-backend-https/app2 succeeded, reason: Layer6 check passed, check
duration: 5ms, status: 3/3 UP.
00000000:www-https.accept(0004)=0007 from [::ffff:<redacted>:36565]
ALPN=<none>
00000001:www-https.accept(0004)=0006 from [::ffff:<redacted>:45955]
ALPN=<none>
00000002:www-https.accept(0004)=0005 from [::ffff:<redacted>:44474]
ALPN=<none>
00000000:www-https.clireq[0007:ffffffff]: GET / HTTP/1.1
00000000:www-https.clihdr[0007:ffffffff]: Host: ssltest-broken.example.ca
00000000:www-https.clihdr[0007:ffffffff]: Connection: keep-alive
00000000:www-https.clihdr[0007:ffffffff]: Cache-Control: max-age=0
00000000:www-https.clihdr[0007:ffffffff]: Upgrade-Insecure-Requests: 1
00000000:www-https.clihdr[0007:ffffffff]: User-Agent: Mozilla/5.0
(Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like
Gecko) Chrome/59.0.3071.115 Safari/537.36
00000000:www-https.clihdr[0007:ffffffff]: Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
00000000:www-https.clihdr[0007:ffffffff]: Accept-Encoding: gzip, deflate, br
00000000:www-https.clihdr[0007:ffffffff]: Accept-Language: en-US,en;q=0.8
00000000:www-backend-https.srvrep[0007:0008]: HTTP/1.1 200 OK
00000000:www-backend-https.srvhdr[0007:0008]: Date: Tue, 25 Jul 2017
16:49:19 GMT
00000000:www-backend-https.srvhdr[0007:0008]: Server: Apache
00000000:www-backend-https.srvhdr[0007:0008]: Vary: Accept-Encoding
00000000:www-backend-https.srvhdr[0007:0008]: Content-Encoding: gzip
00000000:www-backend-https.srvhdr[0007:0008]: Content-Length: 515
00000000:www-backend-https.srvhdr[0007:0008]: Connection: close
00000000:www-backend-https.srvhdr[0007:0008]: Content-Type: text/html;
charset=UTF-8
00000000:www-backend-https.srvcls[0007:0008]
Loading ssltest.example.ca
[WARNING] 205/165327 (16816) : Health check for server
www-backend-https/app2 succeeded, reason: Layer6 check passed, check
duration: 5ms, status: 3/3 UP.
00000000:www-https.accept(0004)=0005 from [::ffff:<redacted>:45095]
ALPN=<none>
00000001:www-https.accept(0004)=0006 from [::ffff:<redacted>:41897]
ALPN=<none>
00000002:www-https.accept(0004)=0007 from [::ffff:<redacted>:37526]
ALPN=<none>
00000000:www-https.clireq[0005:ffffffff]: GET / HTTP/1.1
00000000:www-https.clihdr[0005:ffffffff]: Host: ssltest.example.ca
00000000:www-https.clihdr[0005:ffffffff]: Connection: keep-alive
00000000:www-https.clihdr[0005:ffffffff]: Upgrade-Insecure-Requests: 1
00000000:www-https.clihdr[0005:ffffffff]: User-Agent: Mozilla/5.0
(Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like
Gecko) Chrome/59.0.3071.115 Safari/537.36
00000000:www-https.clihdr[0005:ffffffff]: Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
00000000:www-https.clihdr[0005:ffffffff]: Accept-Encoding: gzip, deflate, br
00000000:www-https.clihdr[0005:ffffffff]: Accept-Language: en-US,en;q=0.8
00000000:www-backend-https.srvrep[0005:0008]: HTTP/1.1 200 OK
00000000:www-backend-https.srvhdr[0005:0008]: Date: Tue, 25 Jul 2017
16:53:33 GMT
00000000:www-backend-https.srvhdr[0005:0008]: Server: Apache
00000000:www-backend-https.srvhdr[0005:0008]: Vary: Accept-Encoding
00000000:www-backend-https.srvhdr[0005:0008]: Content-Encoding: gzip
00000000:www-backend-https.srvhdr[0005:0008]: Content-Length: 458
00000000:www-backend-https.srvhdr[0005:0008]: Connection: close
00000000:www-backend-https.srvhdr[0005:0008]: Content-Type: text/html;
charset=UTF-8
00000000:www-backend-https.srvcls[0005:0008]
--
Via the haproxy box.
#curl -D - https://ssltest-broken.example.ca
curl: (51) SSL: certificate subject name (app2.example.ca) does not
match target host name 'ssltest-broken.example.ca'
# curl -D - https://ssltest.example.ca
HTTP/1.1 200 OK
Date: Tue, 25 Jul 2017 16:54:39 GMT
Server: Apache
Vary: Accept-Encoding
Content-Length: 1044
Content-Type: text/html; charset=UTF-8
--
# printf "GET / HTTP/1.0\r\n\r\n" | openssl s_client -connect
10.10.0.5:443 -quiet -servername ssltest.example.ca
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = ssltest.example.ca
verify return:1
# printf "GET / HTTP/1.0\r\n\r\n" | openssl s_client -connect
10.10.0.5:443 -quiet -servername ssltest-broken.example.ca
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = app2.example.ca
verify return:1
So as you can see, ssltest-broken is hitting the app2 default
vhost/cert. The backend server has no knowledge of the ssltest-broken
certificate. The verifyhost is /not/ checked between the backend and the
haproxy. Further, I think the health check should probably fail too
because its trying to load via the ip-as-hostname and the cert im using
doesnt have the IP in it. So that should fail hostname check too.
I'm confident that the verifyhost is not being done... I suspect your
test case is failing because the dom4 is totally unknown to the haproxy,
whereas in my case, the haproxy has a cert for ssltest-broken but the
backend does not.
--
Kevin
On 2017-07-25 5:26 AM, Willy Tarreau wrote:
Hi again Kevin,
On Tue, Jul 25, 2017 at 07:26:07AM +0200, Willy Tarreau wrote:
frontend www-https
bind :::443 v4v6 ssl crt /etc/haproxy/certs/default.example.ca.pem crt
/etc/haproxy/certs/
use_backend www-backend-https
backend www-backend-https
server app default.example.ca:443 ssl verify required sni ssl_fc_sni
ca-file /etc/ssl/certs/ca-certificates.crt check check-ssl
If you visit https://should-be-broken.example.ca you will get the page for
default.example.ca, but the browser/visitor will show the
should-be-broken.example.ca cert from the haproxy and the page will appear
secure, despite the backend apache instance having no access to
should-be-broken's virtual host or certificate and serving a certificate for
default.example.ca to the haproxy.
Thanks, I'll retry it. I'm surprized because what you describe here is
*exactly* what I did and it worked fine for me, I remember getting a 503
when connecting with the wrong name. But obviously there must be a
difference so I'll try to find it.
So I tried again to replicate it and cannot confirm your issues. Here's
what I've done :
- I'm having haproxy serve as the origin because I don't have an apache
instance running and don't know how to set it up so I'm not going to
waste my time on it ;
- this origin server responds on 3 different domain names and thus
serves 3 different certificates (dom{1,2,3}.example.com).
- a front gateway responds on a dummy cert, and connects to the server
passing the front connection's SNI to the server.
- the client connects to this front gateway with 4 different names, the
3 supported ones and an unsupported one
What I'm seeing is that the first 3 domains work well and the 4th fails.
Here's the config :
listen gateway
mode http
bind :4430 ssl crt rsa2048.pem
server app 127.0.0.1:4431 ssl sni ssl_fc_sni verify required ca-file
ca.pem check check-ssl
frontend origin
mode http
bind :4431 ssl crt dom1.example.com.pem crt dom2.example.com.pem crt
dom3.example.com.pem
http-request redirect location /called-with-%[ssl_fc_sni]
Command to start this and output :
$ ./haproxy -d -f sni-srv-bug.cfg
Test with dom1..dom3 :
$ printf "GET / HTTP/1.0\r\n\r\n" | openssl s_client -connect 127.0.0.1:4430
-quiet -servername dom1.example.com
Haproxy's output :
00000004:origin.accept(0005)=0007 from [127.0.0.1:36664] ALPN=<none>
00000004:origin.clicls[0007:ffffffff]
00000004:origin.closed[0007:ffffffff]
00000005:gateway.accept(0004)=0006 from [127.0.0.1:56942] ALPN=<none>
00000005:gateway.clireq[0006:ffffffff]: GET / HTTP/1.0
00000006:origin.accept(0005)=0008 from [127.0.0.1:36668] ALPN=<none>
00000006:origin.clireq[0008:ffffffff]: GET / HTTP/1.0
00000006:origin.clicls[0008:ffffffff]
00000006:origin.closed[0008:ffffffff]
00000005:gateway.srvrep[0006:0007]: HTTP/1.1 302 Found
00000005:gateway.srvhdr[0006:0007]: Cache-Control: no-cache
00000005:gateway.srvhdr[0006:0007]: Content-length: 0
00000005:gateway.srvhdr[0006:0007]: Location: /called-with-dom1.example.com
00000005:gateway.srvhdr[0006:0007]: Connection: close
00000005:gateway.srvcls[0006:0007]
00000005:gateway.clicls[0006:0007]
00000005:gateway.closed[0006:0007]
00000007:origin.accept(0005)=0007 from [127.0.0.1:36670] ALPN=<none>
00000007:origin.clicls[0007:ffffffff]
00000007:origin.closed[0007:ffffffff]
OpenSSL output :
depth=0 C = FR, ST = Some-State, O = test, CN = localhost
verify error:num=18:self signed certificate
verify return:1
depth=0 C = FR, ST = Some-State, O = test, CN = localhost
verify return:1
HTTP/1.1 302 Found
Cache-Control: no-cache
Content-length: 0
Location: /called-with-dom1.example.com
Connection: close
Test with dom4:
$ printf "GET / HTTP/1.0\r\n\r\n" | openssl s_client -connect 127.0.0.1:4430
-quiet -servername dom4.example.com
Haproxy's output :
00000000:origin.accept(0005)=0007 from [127.0.0.1:36640] ALPN=<none>
00000000:origin.clicls[0007:ffffffff]
00000000:origin.closed[0007:ffffffff]
00000001:gateway.accept(0004)=0006 from [127.0.0.1:56918] ALPN=<none>
00000001:gateway.clireq[0006:ffffffff]: GET / HTTP/1.0
fd[0007] OpenSSL error[0x14090086] ssl3_get_server_certificate: certificate
verify failed
fd[0008] OpenSSL error[0x14094438] ssl3_read_bytes: tlsv1 alert internal
error
00000002:origin.accept(0005)=0008 from [127.0.0.1:36646] ALPN=<none>
00000002:origin.clicls[0008:ffffffff]
00000002:origin.closed[0008:ffffffff]
fd[0007] OpenSSL error[0x14090086] ssl3_get_server_certificate: certificate
verify failed
fd[0008] OpenSSL error[0x14094438] ssl3_read_bytes: tlsv1 alert internal
error
fd[0007] OpenSSL error[0x14090086] ssl3_get_server_certificate: certificate
verify failed
fd[0008] OpenSSL error[0x14094438] ssl3_read_bytes: tlsv1 alert internal
error
00000003:origin.accept(0005)=0008 from [127.0.0.1:36652] ALPN=<none>
00000003:origin.clicls[0008:ffffffff]
00000003:origin.closed[0008:ffffffff]
fd[0007] OpenSSL error[0x14090086] ssl3_get_server_certificate: certificate
verify failed
fd[0008] OpenSSL error[0x14094438] ssl3_read_bytes: tlsv1 alert internal
error
00000001:gateway.clicls[0006:adfd]
00000001:gateway.closed[0006:adfd]
OpenSSL output :
depth=0 C = FR, ST = Some-State, O = test, CN = localhost
verify error:num=18:self signed certificate
verify return:1
depth=0 C = FR, ST = Some-State, O = test, CN = localhost
verify return:1
HTTP/1.0 503 Service Unavailable
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>503 Service Unavailable</h1>
No server is available to handle this request.
</body></html>
So as you can see it works as expected here. I hardly know what else
can be checked. What exact version are you using ? Are you certain it
contains the patch I mentionned ?
Regards,
Willy