HAProxy community

SMTP & IMAP proxy based on domain (pass-through)

Hello,

My scenario is as follows:
I have a single server with multiple domains. For each domain I’d like to have a separate docker container (won’t go into reasons why I want this, but it does make sense) as an email server (postfix + dovecot). I’ve researched this extensively for months and believe this should be possible using haproxy.
I’d like to achieve this without ssl termination - basically using pass-though or in other words, read the TLS SNI header (domain) and decide based on that which upstream to forward the traffic to.
Something like this is even described on: https://www.haproxy.com/blog/enhanced-ssl-load-balancing-with-server-name-indication-sni-tls-extension/

I’m not sure if I’ve configured something wrong or am I completely missing something here?
If I set a default server, this works, but proxying based on domain (sni) does not.

My configuration is as follows:

defaults
    timeout client 30s
    timeout server 30s
    timeout connect 5s

    option tcplog
    log global


frontend smtp_submission

    mode tcp
    bind *:587

    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }

    use_backend smtp_submission


frontend imap

    mode tcp
    bind *:993

    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }

    use_backend imap


backend smtp_submission

    mode tcp

    acl mail_domain1_match req_ssl_sni -i smtp.domain1.com
    acl mail_domain2_match req_ssl_sni -i smtp.domain2.com

    use-server mail_domain1_smtp_submission if mail_domain1_match
    use-server mail_domain2_smtp_submission if mail_domain2_match

    option ssl-hello-chk

    server mail_domain1_smtp_submission 172.17.0.12:587 weight 0
    server mail_domain2_smtp_submission 172.17.0.11:587 weight 0


backend imap

    mode tcp

    acl mail_domain1_match req_ssl_sni -i imap.domain1.com
    acl mail_domain2_match req_ssl_sni -i imap.domain2.com

    use-server mail_domain1_imap if mail_domain1_match
    use-server mail_domain2_imap if mail_domain2_match

    option ssl-hello-chk

    server mail_domain1_imap 172.17.0.12:993 weight 0
    server mail_domain2_imap 172.17.0.11:993 weight 0

No, this is not really possible, because you have to support a) cleartext and b) STARTTLS, neither allows SNI based routing decisions, because the SNI value, if it exist at all, is not in the first packet.

You can only do this with implicit TLS, meaning imaps on TCP Port 993 and SMTP submission with implicit TLS on port 465.

Port 587 is unencrypted until you start the TLS session with STARTTLS. It’s therefor impossible to route based on SNI.

I don’t think SNI contains the domain. It usually contains the hostname you are connecting to.

I’m also not sure if MUA’s really do SNI, and if it’s supported widely enough so that it’s actually usable.

About the first packet part, I was under the assumption that “tcp-request inspect-delay 5s” would help with waiting for the sni packet, maybe not. I haven’t tried smtp 465. I’ve encountered issues with port 993 mostly.

How would you suggest I go by making this work? Using certificates to decrypt and reading host and routing based on that? Should traffic be re-encrypted or not after this? Any tips?

Waiting does not fix the issue. This is a catch-22:

You need a information that only appears on the wire when there is already a bidirectional communication channel established between the client and the backend server, however you need that information in the very beginning to decide which backend to choose.

When you make the routing decision, you need to have the information. But you only have the information when you already made the routing decision. So this will never work for explicit TLS (or plaintext).

This only works for HTTPS and implicit TLS based protocols, because the very first packet is a client_hello that contains the SNI value. It cannot work if the SNI value appears in some a protocol conversation further down the road, when the connection is already setup.

No, there is no Host header here. This is not HTTP. And we cannot make routing decisions based on IMAP or SMTP protocol conversations either, because they too, just like SNI, come after the load-balancing decision.

Haproxy is a TCP and HTTP reverse proxy and load-balancer. It will never do this.

You need an application that knows about IMAP and SMTP and actively speaks those protocols. I suggest you research this topic in mail related forums, but frankly I doubt that this is a common configuration. Try researching nginx smtp proxy modules and dovecot’s proxying functionality.

But haproxy is definitely the wrong tool for this job.