--- title: DB TLS через traefik raw TCP с self-signed certs type: concept tags: [traefik, tls, postgres, mariadb, mongo, redis, self-signed, pattern] sources: [../sources/vds-kzntsv-bootstrap-2026-05-20.md] updated: 2026-05-20 --- # Self-signed DB TLS через Traefik raw TCP Pattern для выставления нескольких DB-контейнеров наружу (public internet) через traefik с минимумом сложности cert management. Self-signed на старте, LE-extraction позже. Сочетается с [`traefik-tcp-passthrough-vs-starttls`](traefik-tcp-passthrough-vs-starttls.md) — там объяснено почему `HostSNI(*)` + raw TCP, не SNI passthrough. ## Архитектура ``` Internet client ↓ TLS handshake (порт зависит от DB) ↓ .vds.kzntsv.site:5432/3306/27017/6379 Traefik (raw TCP forward, без TLS inspection) ↓ docker network `proxy` DB container (терминирует TLS своим self-signed cert) ↓ docker network `shared-dbs` Other VDS containers (могут ходить без TLS по dns name `postgres`/`mariadb`/...) ``` Каждая DB: - Подключена к двум networks: `proxy` (для traefik) и `shared-dbs` (для других контейнеров VDS). - Имеет свой self-signed cert (CN = `.vds.kzntsv.site`) в `./certs/`. - Конфигурируется для TLS-required. - Объявляет traefik label с `HostSNI(\`*\`)` на dedicated entrypoint port. Traefik static config: ```yaml entryPoints: postgres: { address: ":5432" } mariadb: { address: ":3306" } mongo: { address: ":27017" } redis: { address: ":6379" } ``` ufw: allow на эти 4 порта. ## Cert generation ```bash for db in postgres mariadb mongo redis; do openssl req -x509 -newkey rsa:2048 \ -keyout /opt/stacks/databases/$db/certs/server.key \ -out /opt/stacks/databases/$db/certs/server.crt \ -days 3650 -nodes \ -subj "/CN=$db.vds.kzntsv.site/O=kzntsv.site/C=RU" \ -addext "subjectAltName=DNS:$db.vds.kzntsv.site" chmod 600 /opt/stacks/databases/$db/certs/server.key done ``` ## DB-specific quirks ### Postgres 16 (non-alpine, uid 999) Postgres key file перм-checks: must be 600 + owned by postgres user OR root. Postgres alpine использует **uid 70**, не 999. Не-alpine Debian — **uid 999**. Если используем alpine — `chown -R 70:70 certs/server.key`; если debian — `chown 999:999`. Лучше debian (`postgres:16`) для совместимости с прочими стеками. Команда: ```yaml command: - postgres - -c - ssl=on - -c - ssl_cert_file=/etc/postgres-certs/server.crt - -c - ssl_key_file=/etc/postgres-certs/server.key ``` ### MariaDB 11.4 ```yaml command: - --ssl-cert=/etc/mariadb-certs/server.crt - --ssl-key=/etc/mariadb-certs/server.key - --require-secure-transport=ON # форсит TLS для всех клиентов ``` ### MongoDB 7 Cert + key должны быть **в одном PEM-файле** (`cat server.crt server.key > server.pem`). Плюс Mongo 7 enforce'ит "chain of trust" — нужен `--tlsCAFile` (для self-signed — указываем server.crt сам как CA). `--tlsAllowConnectionsWithoutCertificates` нужен иначе сервер требует client cert. ```yaml command: - --tlsMode=requireTLS - --tlsCertificateKeyFile=/etc/mongo-certs/server.pem - --tlsCAFile=/etc/mongo-certs/server.crt - --tlsAllowConnectionsWithoutCertificates - --bind_ip_all ``` ### Redis 7 alpine ```yaml command: - redis-server - --port - "0" # disable non-TLS port - --tls-port - "6379" - --tls-cert-file - /etc/redis-certs/server.crt - --tls-key-file - /etc/redis-certs/server.key - --tls-auth-clients - "no" # без mTLS - --requirepass - ``` ## Client connection examples ```bash # Postgres (sslmode=require, не verify-full — self-signed) psql "postgresql://postgres:$PG_PASS@postgres.vds.kzntsv.site:5432/postgres?sslmode=require" # MariaDB (--ssl + --ssl-verify-server-cert=0) mariadb -h mariadb.vds.kzntsv.site -P 3306 -u root -p$MARIA_PASS --ssl --ssl-verify-server-cert=0 # Mongo (tls=true + tlsAllowInvalidCertificates) mongosh "mongodb://root:$MONGO_PASS@mongo.vds.kzntsv.site:27017/admin?tls=true&tlsAllowInvalidCertificates=true" # Redis (--tls --insecure) redis-cli --tls --insecure -h redis.vds.kzntsv.site -p 6379 -a $REDIS_PASS ``` ## Trade-off - ✔ Уровень входа: 5 минут на cert + 5 минут на compose. Никаких lego/cert-manager headache на старте. - ✔ Каждый клиент явно отключает verify — predictable failure mode (если cert меняется случайно — клиент сразу пишет в логи). - ✗ Клиенты должны помнить `verify=disable`. Production-tier клиенты обычно фейлят на self-signed по defaultу. - ✗ Cert не ротируется автоматически. 10-year validity = временное обходное. - ✗ Для каждой DB — отдельный cert (не wildcard). Расширяемо, но если будет mongo + mongo-readonly — у каждого свой. ## Roadmap к LE certs Sidecar контейнер с lego (готовый container `goacme/lego` или собственный) который watch'ит traefik `acme.json` и каждый раз когда меняется (i.e. cert ротировался) — извлекает PEM-bundle на disk + триггерит `docker exec kill -HUP 1` для reload. Каждый DB получает auto-renewed LE cert. Это deferred — см. follow-up task в [`vds-kzntsv`](../entities/vds-kzntsv.md). ## Где применено 4 shared DBs на [`vds-kzntsv`](../entities/vds-kzntsv.md): postgres / mariadb / mongo / redis. Self-signed 10-year certs в `/opt/stacks/databases//certs/`. Strong random hex32 пароли в `~/projects/.common/secrets/vds-kzntsv.env`.