Коротко. ssl = on только разрешает шифрование. Обязательным его делает hostssl в pg_hba.conf, а защиту от подмены сервера дают режим VerifyFull в Npgsql и доверенный корень внутри контейнеров RX. Проверяйте по pg_stat_ssl на сервере базы, цепочку по счёту BEGIN и END, срок сертификата держите под наблюдением.

Служба ИБ требует, чтобы трафик между серверами приложений и базой данных шёл в зашифрованном виде. На сервере PostgreSQL включают ssl = on, перезапускают сервисы Directum RX, и всё вроде бы работает. Через месяц проверка показывает, что половина соединений по-прежнему открытые, а остальные шифруются, но сертификат сервера никто не проверяет. Разбираем, как включить TLS так, чтобы он действительно защищал, и как в этом убедиться.

Симптом

  • После включения SSL на сервере сервисы RX не подключаются к базе, в журналах ошибки TLS или отказ по pg_hba.conf с концовкой no encryption или SSL encryption.
  • Всё работает, но проверка показывает соединения без шифрования.
  • Сервисы на .NET пишут The remote certificate is invalid according to the validation procedure, а psql с того же сервера подключается.
  • Java-приложения рядом с RX, например Keycloak, падают при старте с CertificateException: Incomplete data.
  • После продления сертификата на сервере базы часть клиентов перестаёт подключаться.

Почему так

У TLS до базы три отдельных вопроса, и включение ssl = on отвечает только на первый.

  1. Умеет ли сервер шифровать. Это ssl = on и файлы сертификата и ключа.
  2. Обязан ли клиент шифровать. Это правила в pg_hba.conf: правило host пускает и с шифрованием, и без, hostssl только с шифрованием.
  3. Проверяет ли клиент, что говорит с настоящим сервером. Это режим SSL на стороне клиента. Шифрование без проверки сертификата защищает от того, кто просто слушает сеть, но не от того, кто встал посередине и предъявил свой сертификат.

Сервисы RX подключаются к PostgreSQL через драйвер Npgsql, и режим задаётся параметром SSL Mode в строке подключения к базе в настройках RX.

SSL Mode Шифрует Проверяет сертификат сервера Проверяет имя сервера
Disable нет нет нет
Prefer если сервер умеет нет нет
Require да, иначе ошибка нет нет
VerifyCA да да, по доверенному центру нет
VerifyFull да да да

Значения по умолчанию и поведение Require менялись между версиями Npgsql. В части версий для Require требовался дополнительный параметр Trust Server Certificate=true, в более новых этот параметр устарел. Поэтому режим задаётся явно, а результат проверяется на сервере базы по pg_stat_ssl.

Настройка сервера

# postgresql.conf
ssl = on
ssl_cert_file = '/etc/postgresql/tls/server.crt'   # сертификат сервера и промежуточные, по порядку
ssl_key_file  = '/etc/postgresql/tls/server.key'
ssl_min_protocol_version = 'TLSv1.2'
  • Ключ должен принадлежать пользователю postgres с правами 0600 или root с группой postgres и правами 0640. При более широких правах сервер откажется его читать.
  • В сертификате в поле SAN должно быть имя, по которому подключаются клиенты. Если RX подключается по IP-адресу, этот адрес тоже должен быть в SAN.
  • Настройки SSL перечитываются без перезапуска: SELECT pg_reload_conf();. Уже открытые соединения продолжают работать со старым сертификатом, новые получают новый.

Затем правила доступа. Чтобы закрыть подключения без шифрования для сети приложений, разрешающие правила переводятся на hostssl, а ниже ставится явный запрет:

# pg_hba.conf
hostssl   rx   rx_user   10.10.1.0/24   scram-sha-256
hostnossl all  all       10.10.1.0/24   reject

Порядок важен: PostgreSQL применяет первое подходящее правило. Разбор отказов по pg_hba.conf есть в справочнике: нет подходящего правила и сработал запрет.

Настройка клиента

В строке подключения RX:

Host=db01.example.local;Port=5432;Database=rx;Username=rx_user;Password=...;SSL Mode=VerifyFull

Для VerifyCA и VerifyFull клиенту нужно доверять удостоверяющему центру, который выпустил сертификат базы. Npgsql на Linux проверяет сертификат по системному хранилищу, а сервисы RX работают в контейнерах, и у контейнера хранилище своё. Корневой сертификат на хосте контейнер не видит: его нужно добавить в образ или смонтировать и обновить хранилище внутри. Как это сделать на разных дистрибутивах, описано в справочнике ошибок.

Npgsql умеет брать корневой сертификат и из отдельного файла (параметр Root Certificate), но тогда файл должен быть доступен внутри контейнера по указанному пути.

Если между RX и базой стоит пулер соединений (pgbouncer, Odyssey), TLS настраивается на двух участках: от клиента до пулера и от пулера до базы. Каждый проверяется отдельно.

Проверка

Главная проверка делается на сервере базы. Представление pg_stat_ssl показывает по каждому соединению, зашифровано ли оно и чем:

SELECT a.usename, a.client_addr, a.application_name, s.ssl, s.version, s.cipher
FROM pg_stat_activity a JOIN pg_stat_ssl s USING (pid)
WHERE a.backend_type = 'client backend'
ORDER BY s.ssl, a.client_addr;

Строки с ssl = f это соединения без шифрования. По адресу и имени приложения видно, чьи они.

Проверить сертификат сервера с машины клиента можно без драйвера, OpenSSL умеет начинать TLS по протоколу PostgreSQL:

openssl s_client -starttls postgres -connect db01.example.local:5432 -showcerts </dev/null

В выводе смотрите Verify return code и сколько сертификатов отдал сервер. Если сервер отдаёт один сертификат, а выпущен он промежуточным центром, клиенты, которые не достраивают цепочку сами, откажутся подключаться.

Проверка режима verify-full из psql:

psql "host=db01.example.local dbname=rx user=rx_user sslmode=verify-full sslrootcert=/path/ca.crt" -c 'select 1'

Типовые ошибки

Что видно Причина
Отказ pg_hba.conf с no encryption клиент подключается без TLS, а разрешающее правило только hostssl
RemoteCertificateNameMismatch имени или адреса, по которому подключается RX, нет в SAN сертификата
UntrustedRoot в сервисе RX, а psql на хосте работает корневой сертификат добавлен на хост, но не в контейнер
PartialChain в ssl_cert_file только сертификат сервера, без промежуточных
CertificateException: Incomplete data в Java файл с цепочкой обрезан: потеряна строка END CERTIFICATE при копировании
После продления часть клиентов не подключается новый сертификат выпущен другим центром или без нужного имени в SAN

Последние две ошибки удобно ловить до выкладки. В файле цепочки число строк BEGIN CERTIFICATE должно совпадать с числом END CERTIFICATE: openssl x509 читает только первый сертификат и об обрезанном хвосте молчит.

grep -c 'BEGIN CERTIFICATE' server.crt; grep -c 'END CERTIFICATE' server.crt
openssl x509 -in server.crt -noout -subject -issuer -ext subjectAltName -enddate

Что сделать

  1. Включить ssl = on с полной цепочкой и минимальной версией TLS 1.2, перечитать настройки.
  2. Проверить, что клиенты могут подключиться с VerifyFull: корень доверен в контейнерах, имя в SAN.
  3. Перевести строки подключения RX и остальных клиентов на явный режим VerifyFull.
  4. По pg_stat_ssl убедиться, что соединений без шифрования не осталось.
  5. Перевести правила в pg_hba.conf на hostssl и добавить запрет hostnossl.
  6. Поставить срок действия сертификата базы под наблюдение. Продление делается заранее и проверяется теми же командами.

Чего не делать

  • Не останавливаться на ssl = on. Без правил hostssl клиент может подключиться и без шифрования.
  • Не ставить Require и считать, что сертификат проверяется. Он не проверяется.
  • Не закрывать hostnossl до того, как все клиенты переведены. Перестанет работать то, о чём забыли: резервное копирование, мониторинг, отчётные системы.
  • Не проверять сертификат только с хоста. Сервисы RX смотрят в хранилище своего контейнера.
  • Не выдавать сертификат базы на имя, по которому никто не подключается.