Files
admin/.wiki/concepts/traefik-tcp-passthrough-vs-starttls.md

67 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Traefik TCP passthrough vs STARTTLS-protocols
type: concept
tags: [traefik, tls, tcp, sni, postgres, mariadb, mongo, redis, gotcha]
sources: [../sources/vds-kzntsv-bootstrap-2026-05-20.md]
updated: 2026-05-20
---
# Traefik TCP passthrough не работает с STARTTLS
## Симптом
Traefik TCP router с `HostSNI(<hostname>)` + `tls.passthrough=true`. Mongo / Redis (TLS-from-start) — TLS handshake проходит, виден правильный cert. **Postgres / MariaDB — TLS connection hangs / timeout**, никакого handshake'а не происходит.
Probe `openssl s_client -connect postgres.vds.kzntsv.site:5432 -servername postgres.vds.kzntsv.site -starttls postgres` → timeout 10s.
## Root cause
`tls.passthrough=true` означает: traefik не терминирует TLS, а маршрутизирует TCP-connection raw. Для маршрутизации по `HostSNI` traefik читает SNI **из TLS ClientHello** — первого TLS-сообщения, отправляемого клиентом сразу после TCP handshake.
**Mongo / Redis** (TLS-from-start) — клиент шлёт TLS ClientHello как первое сообщение. SNI там присутствует. Traefik читает, маршрутизирует, остаток TCP forwarding'ом. Работает.
**Postgres / MariaDB / MySQL** — используют **STARTTLS pattern**:
1. Клиент после TCP-handshake шлёт `SSLRequest` (plaintext, специфичный для протокола).
2. Сервер отвечает `'S'` (готов на TLS).
3. **Только после этого** клиент начинает TLS ClientHello.
Первые байты от клиента — **plaintext protocol bytes**, не TLS ClientHello. Traefik ищет SNI в TLS ClientHello, не находит → не может смаршрутизировать → connection hangs (висит на чтении от клиента).
## Решение — Raw TCP forward + HostSNI(*)
```yaml
labels:
- traefik.enable=true
- "traefik.tcp.routers.postgres.rule=HostSNI(`*`)"
- traefik.tcp.routers.postgres.entrypoints=postgres
- traefik.tcp.routers.postgres.service=postgres
- traefik.tcp.services.postgres.loadbalancer.server.port=5432
# NO tls.* — traefik forwards raw TCP без inspect
```
Каждая DB — на **своей dedicated entrypoint port** (`postgres:5432`, `mariadb:3306`, `mongo:27017`, `redis:6379`). Routing — по entrypoint, не по SNI. Traefik просто forwards bytes к backend без чтения TLS ClientHello. STARTTLS protocols договариваются о TLS напрямую с DB-контейнером.
`HostSNI(\`*\`)` — единственное значение для TCP router'а **без** `tls.*` секции (traefik требует какое-то правило, wildcard catch-all уместен здесь).
## Trade-off
- ✔ Работает для всех 4 protocols (STARTTLS + TLS-from-start).
- ✔ Uniform config, не нужно ветвить compose под тип protocol'а.
- ✗ Каждая DB требует своего entrypoint:port в traefik static config. 4 DB → 4 entrypoints. Если хотим много инстансов одного движка — теряем muxing на один port через SNI.
- ✗ Traefik в этом случае не видит контент трафика — только TCP-bytes мимо. Невозможно сделать middleware (rate limit, IP allowlist на уровне traefik) — это нужно делать на DB side.
## Альтернативный путь (отвергнут)
Traefik TLS termination (без passthrough) + SNI muxing на 443 entrypoint. Не работает для PG/MariaDB по той же причине — STARTTLS встроен в protocol stack, и traefik терминирующий TLS отдаёт plaintext бэкенду, который ждёт SSLRequest и предлагает свой TLS handshake. Двойная TLS обёртка вокруг STARTTLS — broken.
LE-cert provisioning при passthrough TCP — на DB side, не traefik. Сейчас self-signed; follow-up — lego sidecar extracting из traefik acme.json.
## Где применено
Все 4 DBs на [`vds-kzntsv`](../entities/vds-kzntsv.md): postgres:5432, mariadb:3306, mongo:27017, redis:6379. Traefik v2.11 LTS. См. также [`db-tls-self-signed-via-traefik-raw-tcp`](db-tls-self-signed-via-traefik-raw-tcp.md) — связанный паттерн self-signed cert provisioning'а.
## Ссылки
- Traefik docs: [TCP routers](https://doc.traefik.io/traefik/routing/routers/#configuring-tcp-routers)
- Сессия где обнаружили: [`vds-kzntsv-bootstrap-2026-05-20`](../sources/vds-kzntsv-bootstrap-2026-05-20.md) Phase 2.