Рецепт 25. Декларативные роли и беспарольный TLS в CloudNativePG 1.30

CloudNativePG 1.30: новый CRD DatabaseRole выносит роли из спеки Cluster в отдельный namespaced-ресурс, а встроенная выдача TLS-клиентских сертификатов позволяет подключаться к PostgreSQL вообще без паролей. Полный рабочий пример с pg_hba и cert-аутентификацией.
Опубликовано:

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:

Это позволяет бутстрапить по-настоящему пустые кластеры без дефолтных идентичностей, где 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, и дальше сертификат живёт сам по себе.»

Новая фича встраивает сертификаты в декларативное состояние:

Рабочий пример

YAML
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:

Такая конфигурация исключает fallback на пароль. По bootstrap-конвенции CloudNativePG создаёт роль app со случайным паролем (хранится отдельно), но явное правило reject блокирует его использование.

Смотрим сгенерированный pg_hba.conf

BASH
kubectl cnpg status angus -vv
Нажмите, чтобы развернуть и увидеть больше

Вывод содержит:

PLAINTEXT
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:

BASH
kubectl get secret angus-app-client-cert -o jsonpath='{.type}'
# kubernetes.io/tls
Нажмите, чтобы развернуть и увидеть больше

Пример клиентского пода:

YAML
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"]
Нажмите, чтобы развернуть и увидеть больше

Проверка подключения:

BASH
kubectl exec -it pod/psql-cert-client -- \
  psql -c 'SELECT * FROM pg_stat_ssl'
Нажмите, чтобы развернуть и увидеть больше

Пример вывода:

PLAINTEXT
 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-артефакты.

Плюсы:

Вместе с pinning’ом search_path и улучшениями SCRAM-кодирования в том же релизе, версия 1.30 демонстрирует комплексное внимание к жизненному циклу учётных данных.

Остальное в 1.30, кратко

Изменение API: ссылка cluster на ресурсах Database, Pooler, Publication, Subscription и ScheduledBackup теперь иммутабельна (проверяется через CEL-валидацию). Перенаправить эти ресурсы на другой кластер нельзя — операция отклоняется.

Полные детали — в анонсе релиза и release notes 1.30 .

Работа не закончена

DatabaseRole и Database покрывают идентичность и существование. Следующий шаг (цель — 1.31) — привилегии:

CloudNativePG намеренно останавливается на schema-scoped объектах, делегируя управление таблицами, вьюхами и прочим специализированным инструментам вроде Atlas и SchemaHero — чёткая граница между зоной ответственности оператора и schema-migration инструментов.

Выводы

Релиз 1.30 закладывает фундамент для полноценного управления учётными данными и идентичностью в Kubernetes-native PostgreSQL. Роли и сертификаты — это основа для будущих улучшений в сторону полностью декларативного управления привилегиями.

См. также: Рецепт 3 — почему нет superuser по умолчанию, Рецепт 2 — TLS и секреты по умолчанию.


В оригинальной статье автор отмечает, что текст готовился с помощью Claude (Anthropic), но всё техническое содержание, правки и редакторская направленность — авторские.

Начать поиск

Введите ключевые слова для поиска статей

↑↓
ESC
⌘K Горячая клавиша