Files
admin/.wiki/concepts/db-tls-self-signed-via-traefik-raw-tcp.md

148 lines
6.1 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: 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)
↓ <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 = `<db>.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
- <password>
```
## 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 <db> 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/<engine>/certs/`. Strong random hex32 пароли в `~/projects/.common/secrets/vds-kzntsv.env`.