148 lines
6.1 KiB
Markdown
148 lines
6.1 KiB
Markdown
---
|
||
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`.
|