身份认证

Pigsty 以声明式方式管理 PostgreSQL 与 PgBouncer 的 HBA 规则,配合 SCRAM 密码与客户端证书,回答“谁能连进来、如何证明身份”。

PostgreSQL 使用 pg_hba.conf 进行 基于主机的认证(Host-Based Authentication):谁(用户)、从哪里(来源地址)、访问什么(数据库)、需要以何种方式证明身份(认证方法)。

这套机制足够强大,但在集群环境中手工维护的成本很高:主库与从库可能需要不同规则,配置文件又分布在每个实例的数据目录中。 如果缺少统一声明和刷新流程,各实例的规则很容易发生漂移。

Pigsty 的答案与 声明式配置 一脉相承:HBA 规则是配置清单的一部分,由剧本统一渲染与下发。


HBA 即代码

集群的 HBA 规则由两组参数拼接而成:全局默认规则 pg_default_hba_rules 与集群自定义规则 pg_hba_rulesPgBouncer 连接池 另有独立的两组对应参数(pgb_default_hba_rulespgb_hba_rules)。

每条规则可以用两种形式书写。别名形式 是推荐的方式,一条规则一行,语义一目了然:

pg_hba_rules:
  - { user: dbuser_app ,db: app ,addr: 10.1.0.0/16 ,auth: ssl ,order: 50 ,title: 'app user access via ssl' }

原始形式 则直接给出 pg_hba.conf 的原文,用于表达别名覆盖不了的特殊规则。

除了四要素之外,规则还有两个控制字段:

  • order:渲染顺序。HBA 按“先匹配先生效”的原则工作,顺序即优先级。约定 0-99 保留给用户的高优先级规则,100-999 是默认规则集,未指定 order 的规则排在最后。
  • role:实例角色过滤。commondefault 规则对所有实例生效;primaryreplicaofflinestandbydelayed 规则仅在对应角色的实例上启用; role: offline 的规则还会额外下发给标记了 pg_offline_query 的实例。同一份声明渲染到不同实例,得到的是各自角色对应的规则 —— 主从差异不再需要手工维护。

修改声明后,使用封装好的脚本应用变更,规则会被重新渲染并重载生效:

bin/pgsql-hba pg-meta          # 重新渲染并应用 pg-meta 集群的 HBA 规则

pg_hba_rules 用于追加规则,不会自动收窄范围更宽的默认规则。需要建立更严格的边界时,应同时审查 pg_default_hba_rules,并在变更后检查各实例实际生成的 pg_hba.conf


地址与认证别名

别名形式的价值在于把常见场景抽象为语义化的词汇。addr 字段的别名展开为具体的地址块:

别名展开为含义
localUnix Socket仅本地套接字
localhostUnix Socket、127.0.0.1/32::1/128本机
admin<admin_ip>/32管理节点
infra各 INFRA 节点的 /32 地址基础设施节点
cluster集群各成员的 /32 地址集群内部
intra10.0.0.0/8172.16.0.0/12192.168.0.0/16内网网段,可通过 node_firewall_intranet 定制
world0.0.0.0/0::/0任意地址
CIDR 地址原样保留自定义网段

auth 字段的别名决定认证方法,以及是否强制 TLS 连接:

别名认证方法说明
denyreject显式拒绝
trusttrust无条件放行,慎用
pwdscram-sha-256md5pg_pwd_enc 而定,默认 SCRAM
shascram-sha-256强制 SCRAM
md5md5兼容旧客户端
sslhostssl 与密码认证密码认证,且必须走 TLS
ssl-shahostsslscram-sha-256TLS 与强制 SCRAM
certhostsslcert客户端证书认证
identosident(PgBouncer 中为 peer操作系统用户映射
peerpeer本地操作系统用户

用户字段支持四个占位符,渲染时替换为实际用户名:${dbsu}(超级用户)、${repl}(复制用户)、${monitor}(监控用户)、${admin}(管理用户); +role 前缀表示匹配该角色的所有成员。

这里还要区分两件事:auth: ssl 只要求连接使用 TLS,并不要求客户端验证服务端身份。安全敏感的客户端还应使用 sslmode=verify-full 和可信 CA,详见 加密通信


默认规则解读

Pigsty 的默认 HBA 规则集体现了一个简单的原则:来源越远,要求越严。以下是 PostgreSQL 侧的默认规则(源码原文):

pg_default_hba_rules:             # postgres default host-based authentication rules, order by `order`
  - {user: '${dbsu}'    ,db: all         ,addr: local     ,auth: ident ,title: 'dbsu access via local os user ident'  ,order: 100}
  - {user: '${dbsu}'    ,db: replication ,addr: local     ,auth: ident ,title: 'dbsu replication from local os ident' ,order: 150}
  - {user: '${repl}'    ,db: replication ,addr: localhost ,auth: pwd   ,title: 'replicator replication from localhost',order: 200}
  - {user: '${repl}'    ,db: replication ,addr: intra     ,auth: pwd   ,title: 'replicator replication from intranet' ,order: 250}
  - {user: '${repl}'    ,db: postgres    ,addr: intra     ,auth: pwd   ,title: 'replicator postgres db from intranet' ,order: 300}
  - {user: '${monitor}' ,db: all         ,addr: localhost ,auth: pwd   ,title: 'monitor from localhost with password' ,order: 350}
  - {user: '${monitor}' ,db: all         ,addr: infra     ,auth: pwd   ,title: 'monitor from infra host with password',order: 400}
  - {user: '${admin}'   ,db: all         ,addr: infra     ,auth: ssl   ,title: 'admin @ infra nodes with pwd & ssl'   ,order: 450}
  - {user: '${admin}'   ,db: all         ,addr: world     ,auth: ssl   ,title: 'admin @ everywhere with ssl & pwd'    ,order: 500}
  - {user: '+dbrole_readonly',db: all    ,addr: localhost ,auth: pwd   ,title: 'pgbouncer read/write via local socket',order: 550}
  - {user: '+dbrole_readonly',db: all    ,addr: intra     ,auth: pwd   ,title: 'read/write biz user via password'     ,order: 600}
  - {user: '+dbrole_offline' ,db: all    ,addr: intra     ,auth: pwd   ,title: 'allow etl offline tasks from intranet',order: 650}

逐层来看:

  • 本地最受信任:超级用户 postgres 只能通过本地 Unix Socket 以 ident 方式进入 —— 不需要密码,但也无法在远程使用。这就是为什么 dbsu 默认不设密码:不存在可以被窃取的口令。
  • 内网次之:复制与业务账号在内网使用 SCRAM 口令认证;监控和管理用户的远程访问主要面向 INFRA 节点。
  • 公网最严:默认只有管理员可以从任意地址访问,且必须同时提供口令与 TLS 连接。

PgBouncer 侧的默认规则更保守一层:监控与管理账号从公网访问连接池会被显式 deny,业务用户则限定在本机与内网。

需要特别说明,默认 +dbrole_offline 规则没有设置 role,因此会应用到所有实例。要把离线用户限制到 pg_role: offline 或设置了 pg_offline_query: true 的实例,必须在对应 HBA 规则上显式增加 role: offline

这份默认规则集是“可用性优先”的取舍:业务账号在内网使用口令认证即可接入。 ha/safe 模板将主要 TCP 规则改为 ssl,管理员从非内网位置访问必须持有客户端证书(cert);本地 ident 与部分 localhost 口令规则仍然保留。


密码策略

Pigsty 默认使用 PostgreSQL 官方推荐的 scram-sha-256 算法存储密码(pg_pwd_enc),仅在需要兼容老旧客户端时才应降级为 md5

密码处理链路会在执行 ALTER USER ... PASSWORD 前临时关闭语句日志(SET log_statement TO 'none'),避免口令进入 PostgreSQL 日志。 但明文口令仍会出现在配置清单中,渲染后的用户 SQL 也会以 0640 权限写入 /pg/tmp/pg-user-<name>.sql;相关任务没有完整使用 Ansible no_log。因此应限制管理节点、配置仓库和自动化输出的访问,并避免对含凭据任务使用 --diff

密码强度默认不做强制,需要时可以预加载 passwordcheck 扩展,或使用规则更丰富的 credcheck 扩展:

pg_libs: '$libdir/passwordcheck, pg_stat_statements, auto_explain'   # 拒绝弱密码

ha/safe 模板显式设置了上述 pg_libs;单独选择 CRIT 参数模板不会自动加载 passwordcheck

账号有效期通过用户定义中的 expire_in(自创建起天数)或 expire_at(截止日期)声明,配合组织的密码轮换制度使用:

pg_users:
  - { name: dbuser_app ,password: '<独立随机口令>' ,roles: [dbrole_readwrite] ,expire_in: 365 }

证书认证

口令终究可能被钓鱼、复用或撞库。对管理员这样的高权限账号,可以在 HBA 中使用 auth: cert 要求 客户端证书认证: 客户端必须持有由本地 CA 签发、CN 与数据库用户名一致的证书才能建立连接。在 HBA 仅接受 cert 的前提下,单独泄露口令不足以通过认证。

使用内置的 cert.yml 剧本签发客户端证书:

./cert.yml -e cn=dbuser_dba            # 为 dbuser_dba 签发客户端证书,默认有效期 20 年
./cert.yml -e cn=dbuser_dba -e expire=365d   # 或指定较短的有效期

签发的证书位于 files/pki/misc/<cn>.keyfiles/pki/misc/<cn>.crt。客户端私钥应通过受控渠道交付;客户端仍需使用 verify-full 验证数据库服务端,证书体系详见 加密通信


连接池与组件 API

数据库本体之外,还有两类入口需要认证:

PgBouncer 连接池 使用独立的 HBA 规则集与用户列表。默认关闭 pgbouncer_auth_query,此时只有声明了 pgbouncer: true 的用户才会进入 userlist.txt 并通过连接池认证;启用动态认证查询后,应重新评估可登录用户范围。

Patroni REST API 承载高可用控制指令(重启、切换、重载配置),写操作要求 HTTP Basic 认证(patroni_usernamepatroni_password), 且来源受地址白名单限制;启用 patroni_ssl_enabled 后 API 全程走 HTTPS。

Grafana、HAProxy 管理界面、MinIO、etcd 等组件的凭证同样在配置清单中声明,完整清单与修改方式见 合规实践


接下来