TCP TLS server with non-TLS TCP router

I have an older application that has no TLS support that needs to make TLS TCP connections to a certain IP. I was hoping to use Traefik v2 for this. Can Traefik listen for TCP connections that don't use TLS, and then make a TLS connection to a backend service? Here's how I was envisioning the configuration:

[tcp]
  [tcp.routers]
    [tcp.routers.FrontendTCPRouter]
      entryPoints = ["EntryPoint0"]
      # Catch every request (only available rule for non-tls routers. See below.)
      rule = "HostSNI(`*`)"
      service = "BackendTCPService"
  [tcp.services]
    [tcp.services.BackendTCPService.loadBalancer]
       [[tcp.services.BackendTCPService.loadBalancer.servers]]
         address = "localhost:8050"
         tls = true
1 Like

looking for a similar solution! is it possible to have a TCP SSL/TLS Backend-service? or is this not supported yet?
Best Regards

I ended up not using Traefik to solve this. I used Stunnel. It's not Dockerized and the documentation is a little clunky, but I got it working.
https://www.stunnel.org

I managed to achieve this. My goal was to placing mqtt broker (rabbitmq) behind traefik, so that for single container I am exposing 3 endpoints https for management, mqtt for unencrypted TCP traffic and mqtts for encrypted TCP traffic.

part of traefik.toml:

[entryPoints]
  [entryPoints.http]
    address = ":80"
  [entryPoints.https]
    address = ":443"
  [entryPoints.mqtt]
    address = ":1883"
  [entryPoints.mqtts]
    address = ":8883"

and part of docker-compose.yml:

  rabbitmq:
    image: rabbitmq:management
    restart: always

    volumes:
      - ./config/rabbitmq/advanced.config:/etc/rabbitmq/advanced.config:ro
      - ./config/rabbitmq/enabled_plugins:/etc/rabbitmq/enabled_plugins:ro
      - rabbitmq_data:/var/lib/rabbitmq

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.router-broker-mgmt.rule=Host(`broker.${PROXY_BASE_DOMAIN}`)"
      - "traefik.http.routers.router-broker-mgmt.tls=true"
      - "traefik.http.routers.router-broker-mgmt.tls.certresolver=le"
      - "traefik.http.routers.router-broker-mgmt.entrypoints=https"
      - "traefik.http.routers.router-broker-mgmt.service=service-broker-mgmt"
      - "traefik.http.services.service-broker-mgmt.loadbalancer.server.port=15672"

      - "traefik.tcp.routers.router-broker-mqtts.rule=HostSNI(`broker.${PROXY_BASE_DOMAIN}`)"
      - "traefik.tcp.routers.router-broker-mqtts.tls=true"
      - "traefik.tcp.routers.router-broker-mqtts.tls.certresolver=le"
      - "traefik.tcp.routers.router-broker-mqtts.entrypoints=mqtts"
      - "traefik.tcp.routers.router-broker-mqtts.service=service-broker-mqtts"
      - "traefik.tcp.services.service-broker-mqtts.loadbalancer.server.port=1883"

      - "traefik.tcp.routers.router-broker-mqtt.rule=HostSNI(`*`)"
      - "traefik.tcp.routers.router-broker-mqtt.entrypoints=mqtt"
      - "traefik.tcp.routers.router-broker-mqtt.service=service-broker-mqtt"
      - "traefik.tcp.services.service-broker-mqtt.loadbalancer.server.port=1883"

you have to play with converting labels into toml.

Ok, that's interesting: when using the wildcard '*' it works just fine with no TLS options at all, but when using an explicit HostSNI Traefik complains about the lack of TLS option. For instance (using file provider):

tcp:
  routers:
    system-db:
      entrypoints:
        - "postgres"
      rule: "HostSNI(`system-db.example.com`)"
      service: "system-db"
      tls: {}
    gnucash-db:
      entrypoints:
        - "postgres"
      rule: "HostSNI(`*`)"
      service: "gnucash-db"

  services:
    system-db:
      loadBalancer:
        servers:
          - address: "10.0.0.33:8086"
    gnucash-db:
      loadBalancer:
        servers:
          - address: "10.0.0.33:8085"

The above just works and Traefik's monitor dashboard correctly states the first route (system-db) has TLS enabled while the second (gnucash-db) is non-TLS. However, if I change the second to use "HostSNI(gnucash-db.example.com)", instead of the wildcard, I get the following error:

invalid rule: "HostSNI(gnucash-db.example.com)" , has HostSNI matcher, but no TLS on router

Why is TLS enforced in such cases?

Traefik can only read HostSNI when TLS is enabled and the certs are present.

TLS is used to encrypt the traffic, it is doing this even for the Host, so no one can see during transit what is happening.

Ah ok! I completely missed the fact that SNI is an extension of TLS!
In the docs, there even is a specific note about that:

It is important to note that the Server Name Indication is an extension of the TLS protocol.
Hence, only TLS routers will be able to specify a domain name with that rule. However, there
is one special use case for HostSNI with non-TLS routers: when one wants a non-TLS router
that matches all (non-TLS) requests, one should use the specific HostSNI(*) syntax.