Traefik serves default cert for HostSNI (tls over tcp)

Hello, I'm trying to deploy traefik for tcp tls server, but it fails with default cert, which causes no response for tls client connection (I see in logs, requests passes well). Compose config has a https service, which acquires cert and works ok. I tried both HostSNI(*) and HostSNI(${DOMAIN}), the second one just not working

version: "3.7"

services:
  traefik:
    image: "traefik:latest"
    container_name: traefik
    restart: unless-stopped
    command:
      # - --metrics
      # - --metrics.prometheus.buckets=0.1,0.3,1.2,5.0
      - --log.level=DEBUG
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      # - --accesslog=true
      # - --accesslog.filepath=/var/log/traefik/access.log
      - --accesslog.format=json

      - --certificatesresolvers.md-resolver.acme.tlschallenge=true
      - --certificatesresolvers.md-resolver.acme.email=support@mydomain.com
      - --certificatesresolvers.md-resolver.acme.storage=/letsencrypt/acme.json

      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --entryPoints.websecure.http.tls=true
      - --entryPoints.websecure.http.tls.certResolver=md-resolver

      - --entryPoints.ldap.address=:389
      - --entryPoints.ldaps.address=:636

      - --entrypoints.web.http.redirections.entryPoint.to=websecure
      - --entrypoints.web.http.redirections.entryPoint.scheme=https
      - --entrypoints.web.http.redirections.entrypoint.permanent=true
    ports:
      - "80:80"
      - "8080:8080"
      - "443:443"
      - "389:389"
      - "636:636"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - ./certs:/letsencrypt

    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 96M
        reservations:
          cpus: '0.25'
          memory: 32M

  ldap_server:
    env_file:
      .env
    labels:
        # this works fine with any ldap client
      - traefik.enable=true
      - traefik.tcp.routers.ldap_server.rule=HostSNI(`*`)
      - traefik.tcp.routers.ldap_server.service=ldap_server-svc
      - traefik.tcp.services.ldap_server-svc.loadbalancer.server.port=389

        # default cert error
      - traefik.tcp.routers.ldaps_server.rule=HostSNI(`*`)
      - traefik.tcp.routers.ldaps_server.tls=true
      - traefik.tcp.routers.ldaps_server.tls.passthrough=false  # pass decrypted traffic to server
      - traefik.tcp.routers.ldaps_server.entrypoints=ldaps
      - traefik.tcp.routers.ldaps_server.tls.certresolver=md-resolver
      - traefik.tcp.routers.ldaps_server.service=ldaps_server
      - traefik.tcp.services.ldaps_server.loadbalancer.server.port=389

  api_server:
    hostname: api_server
    labels:
        # works fine, cert acquired
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=Host(`${DOMAIN}`) && PathPrefix(`/api`)"
      - "traefik.http.routers.api.tls.certresolver=md-resolver"
      - "traefik.http.routers.api.entrypoints=websecure"
      - "traefik.http.services.api.loadbalancer.server.port=8000"
      - "traefik.http.routers.api.service=api"
      - "traefik.http.routers.api.middlewares=api_strip"
      - "traefik.http.middlewares.api_strip.stripprefix.prefixes=/api"
      - "traefik.http.middlewares.api_strip.stripprefix.forceslash=false"

For LetsEncrypt to create certs, you need to use Host() or HostSNI() with a domain name or explicitly state the domains (main and sans). (Doc)

Maybe compare to this simple Traefik TCP example.

You have a deploy section in there. Are you using Docker Swarm? Then you need to place the labels within deploy and enable Swarm in the Docker provider.

Nope, thats a compose config, I found another solution. Traefik cannot serve tcp tls socket by itself, cz it does not recieve any info about domain on pure tcp connection. I read acme.json, there are usual key and cert inside, encoded with base64, and then I start ssl handshake inside my container.

There are some Python code for example:


import base64
import ssl

SSL_CERT = "/certs/cert.pem"
SSL_KEY = "/certs/privkey.pem"

with open('/certs/acme.json') as certfile:
    data = json.load(certfile)

# NOTE: here, I take first domain as a single domain in my config
# your config may be different
cert_data = data['md-resolver']['Certificates'][0]

print(f'loaded cert for {cert_data['domain']['main']}')

cert = base64.b64decode(cert_data ['certificate'].encode('ascii')).decode()
key = base64.b64decode(cert_data ['key'].encode('ascii')).decode()

with open(SSL_CERT, "w+") as certfile, open(SSL_KEY, "w+") as keyfile:
    certfile.write(cert)
    keyfile.write(key)

ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(
    self.settings.SSL_CERT,
    self.settings.SSL_KEY,
)

Than, use this ssl context with your tcp server, or maybe proxy server, I use python streams server.

Traefik can serve TLS TCP connections. It can create certs with LE when you tell it what the domain names are.

You can use HostSNI() on TLS TCP connections when you provide an existing certificate to Traefik.

For the first two cases Traefik can optionally terminate TLS and forward decrypted traffic.

When Traefik has no certificate, you can only use HostSNI(`*`) to forward traffic plain encrypted to the target service.

LDAP and Redis clients does not provide HostSNI information about domain on handshake, that's why it won't work, you can check this ticket on github and link above for more info.

To my knowledge you can use HostSNI(`*`) in rule just as a placeholder in a TCP connection, it doesn’t even have to be TLS encrypted. It will always match. It could even be a SSH connection not using TLS.

See simple Traefik TCP example.

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.