Оригинал: CNPG Recipe 25 — Declarative Roles and Passwordless TLS in CloudNativePG 1.30 — 6 июля 2026.
CloudNativePG 1.30 приносит две крупные фичи для управления идентичностью и доступом: новый CRD DatabaseRole и встроенную выдачу TLS-клиентских сертификатов. Вместе они позволяют командам приложений заводить учётки декларативно и подключаться к PostgreSQL вообще без паролей.
Почему роли заслужили отдельный ресурс
В PostgreSQL есть три по-настоящему глобальных объекта, живущих вне каталога конкретной БД: роли, базы и tablespaces. Базы уже получили свой CRD (Database), а роли до сих пор жили прямо в спеке Cluster — и это архитектурная проблема.
Корень боли — в RBAC. Дать команде приложения возможность добавить роль в managed.roles означает дать ей право на запись во весь Cluster, а Kubernetes RBAC не умеет сузить это до одного поля в спеке. Прямое нарушение принципа separation of concerns.
Новый DatabaseRole превращает роли в самостоятельные namespaced-объекты Kubernetes с собственным жизненным циклом и полем status. Платформенная команда теперь может выдать команде приложения права create/update на databaseroles.postgresql.cnpg.io не открывая доступ к ресурсу Cluster.
Ключевое про DatabaseRole:
- Миграция с
managed.roles— инкрементальная. - При конфликте имён приоритет у спеки
Cluster. - Удаление управляется через
databaseRoleReclaimPolicy:retain(безопасный дефолт) илиdelete. - При «усыновлении» существующей роли пропущенные атрибуты сбрасываются к дефолтам PostgreSQL.
Это позволяет бутстрапить по-настоящему пустые кластеры без дефолтных идентичностей, где DatabaseRole и Database становятся единственным источником истины.
Беспарольные подключения через TLS-клиентские сертификаты
Вторая фича: DatabaseRole теперь может содержать блок clientCertificate. Оператор генерирует и непрерывно продлевает TLS-клиентские сертификаты, подписанные client-CA кластера, складывая их в Secret’ы <databaserole-name>-client-cert с обычными ключами tls.crt и tls.key.
CloudNativePG и раньше держал self-signed CA для серверных и клиентских сертификатов, выпускал сертификаты для физической репликации и предоставлял команду kubectl cnpg certificate для ручной подписи (см. Рецепт 2). Не хватало ровно одного — декларативного управления этим сертификатом.
«
kubectl cnpg certificate— императивная: запустил один раз, получил Secret, и дальше сертификат живёт сам по себе.»
Новая фича встраивает сертификаты в декларативное состояние:
- оператор продлевает их по тому же расписанию, что и
streaming_replica; - оператор сам удаляет Secret’ы, когда фича выключается или роль удаляется;
login: trueобязателен при включённомclientCertificate(проверяется на admission).
Рабочий пример
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: angus
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:18.4-minimal-trixie
postgresql:
pg_hba:
- hostssl app app all cert
- hostnossl app app all reject
storage:
size: 1Gi
---
apiVersion: postgresql.cnpg.io/v1
kind: DatabaseRole
metadata:
name: angus-app
spec:
cluster:
name: angus
name: app
login: true
clientCertificate:
enabled: true
databaseRoleReclaimPolicy: delete
---
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: angus-app
spec:
cluster:
name: angus
name: app
owner: app
databaseReclaimPolicy: deleteРеальную безопасность обеспечивают записи pg_hba:
hostssl app app all cert— требует TLS-подключение с cert-аутентификацией;hostnossl app app all reject— явно запрещает plaintext-подключения.
Такая конфигурация исключает fallback на пароль. По bootstrap-конвенции CloudNativePG создаёт роль app со случайным паролем (хранится отдельно), но явное правило reject блокирует его использование.
Смотрим сгенерированный pg_hba.conf
kubectl cnpg status angus -vvВывод содержит:
PostgreSQL HBA Rules
#
# FIXED RULES
#
local all cnpg_metrics_exporter peer map=cnpg_metrics_exporter
local all all peer map=local
hostssl postgres streaming_replica all cert map=cnpg_streaming_replica
hostssl replication streaming_replica all cert map=cnpg_streaming_replica
hostssl all cnpg_pooler_pgbouncer all cert map=cnpg_pooler_pgbouncer
#
# USER-DEFINED RULES
#
hostssl app app all cert
hostnossl app app all reject
#
# DEFAULT RULES
#
host all all all scram-sha-256Пользовательские правила попадают между фиксированными правилами оператора и дефолтным catch-all. PostgreSQL читает pg_hba.conf сверху вниз, поэтому SSL-подключения для app сначала матчатся на cert-правило.
Подключение без пароля
Когда кластер здоров и DatabaseRole рапортует applied: true, оператор создаёт Secret angus-app-client-cert:
kubectl get secret angus-app-client-cert -o jsonpath='{.type}'
# kubernetes.io/tlsПример клиентского пода:
apiVersion: v1
kind: Pod
metadata:
name: psql-cert-client
spec:
restartPolicy: Always
securityContext:
runAsNonRoot: true
runAsUser: 26
fsGroup: 26
seccompProfile:
type: RuntimeDefault
volumes:
- name: client-cert
secret:
secretName: angus-app-client-cert
defaultMode: 0640
- name: client-ca
secret:
secretName: angus-ca
defaultMode: 0640
containers:
- name: psql
image: ghcr.io/cloudnative-pg/postgresql:18.4-minimal-trixie
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: client-cert
mountPath: /etc/secrets/tls
readOnly: true
- name: client-ca
mountPath: /etc/secrets/ca
readOnly: true
env:
- name: PGHOST
value: angus-rw
- name: PGPORT
value: "5432"
- name: PGDATABASE
value: app
- name: PGUSER
value: app
- name: PGSSLMODE
value: verify-full
- name: PGSSLCERT
value: /etc/secrets/tls/tls.crt
- name: PGSSLKEY
value: /etc/secrets/tls/tls.key
- name: PGSSLROOTCERT
value: /etc/secrets/ca/ca.crt
- name: HOME
value: /tmp
command: ["sleep", "infinity"]Проверка подключения:
kubectl exec -it pod/psql-cert-client -- \
psql -c 'SELECT * FROM pg_stat_ssl'Пример вывода:
pid | ssl | version | cipher | bits | client_dn | client_serial | issuer_dn
-----+-----+---------+-------------------------+------+-----------+------------------------------------------+-------------------
756 | t | TLSv1.3 | TLS_AES_256_GCM_SHA384 | 256 | /CN=app | 203360611089765729748366787124077718131 | /OU=default/CN=angusПодключение проходит без запроса пароля. Поле client_dn подтверждает, что PostgreSQL аутентифицировал клиента как app по сертификату, а issuer_dn — что сертификат подписан CA кластера.
Почему эта комбинация важна
По отдельности декларативные роли и выдача клиентских сертификатов полезны. Вместе с существующим Database CRD они замыкают полный цикл: команда приложения заводит новую нагрузку тремя манифестами, ни один из которых не трогает Cluster, ни один не содержит пароля и все — обычные GitOps-артефакты.
Плюсы:
- нет паролей — меньше поверхность атаки;
- не нужна ротация учёток по расписанию;
- нет риска утечки пароля через логи или кривой RBAC;
- полный reconciliation, diffing и аудит.
Вместе с pinning’ом search_path и улучшениями SCRAM-кодирования в том же релизе, версия 1.30 демонстрирует комплексное внимание к жизненному циклу учётных данных.
Остальное в 1.30, кратко
- Lease-based примитив выбора primary для сериализованного promotion.
- In-place major-апгрейды для кластеров с расширениями через Image Volume (см. Рецепт 23).
- Управление образами Pooler через Image Catalogs (см. Рецепт 22).
- TLS для metrics-эндпоинтов Pooler.
- Событие
PrimaryStatusCheckFailedдля видимости отложенного failover.
Изменение API: ссылка cluster на ресурсах Database, Pooler, Publication, Subscription и ScheduledBackup теперь иммутабельна (проверяется через CEL-валидацию). Перенаправить эти ресурсы на другой кластер нельзя — операция отклоняется.
Полные детали — в анонсе релиза и release notes 1.30 .
Работа не закончена
DatabaseRole и Database покрывают идентичность и существование. Следующий шаг (цель — 1.31) — привилегии:
- Issue #10826 — стенза
permissionsна CRDDatabaseдляGRANT/REVOKEна уровне БД, в частностиREVOKE CONNECT ON DATABASE x FROM PUBLIC. - Issue #7872 — гранты
CREATEиUSAGEчерез стензуschemas. - Issue #10831 — сделать «отзыв
CONNECTуPUBLIC» поведением по умолчанию для всех управляемых БД (сначала opt-in, чтобы не сломать существующие нагрузки).
CloudNativePG намеренно останавливается на schema-scoped объектах, делегируя управление таблицами, вьюхами и прочим специализированным инструментам вроде Atlas и SchemaHero — чёткая граница между зоной ответственности оператора и schema-migration инструментов.
Выводы
Релиз 1.30 закладывает фундамент для полноценного управления учётными данными и идентичностью в Kubernetes-native PostgreSQL. Роли и сертификаты — это основа для будущих улучшений в сторону полностью декларативного управления привилегиями.
См. также: Рецепт 3 — почему нет superuser по умолчанию, Рецепт 2 — TLS и секреты по умолчанию.
В оригинальной статье автор отмечает, что текст готовился с помощью Claude (Anthropic), но всё техническое содержание, правки и редакторская направленность — авторские.