23.2 认证与连接准入
客户端拿到数据库连接之前,至少要连续通过五道关:
network reachability
-> listener / proxy entry
-> TLS negotiation and peer verification
-> HBA first matching record
-> authentication method
-> database CONNECT privilege这五道关回答的问题不同。防火墙放行不等于 HBA 放行,HBA 选中
scram-sha-256 不等于密码正确,认证成功也不等于角色拥有
CONNECT,更不等于它可以读取业务表。安全评审必须逐层给出证据,不能用
“我连上了”概括全部连接准入。
23.2.1 pg_hba.conf 的匹配顺序与证据
HBA 是有序规则,不是规则集合
PostgreSQL 对一条新连接从上到下检查 HBA:
- 找到第一条在连接类型、数据库、用户和来源地址上都匹配的记录;
- 使用这条记录指定的方法认证;
- 认证失败就拒绝,不会继续尝试后面的记录;
- 没有任何记录匹配也会拒绝。
因此下面两段配置语义完全不同:
# A:先拒绝高风险网段,后允许业务网段
host appdb +app_login 10.20.30.0/24 reject
hostssl appdb +app_login 10.20.0.0/16 scram-sha-256# B:宽规则已经接住连接,后面的 reject 永远不会命中
hostssl appdb +app_login 10.20.0.0/16 scram-sha-256
host appdb +app_login 10.20.30.0/24 rejectHBA 不是防火墙 ACL 的“最具体规则优先”,也没有失败后回退。官方文档明确 规定了 first-match 语义;任何生成器、模板或平台都不能改变这一点。 参见 PostgreSQL:客户端认证配置文件。
一条记录匹配哪些维度
常见 record type:
| 类型 | 传输条件 | 典型用途 |
|---|---|---|
local | Unix-domain socket | 节点本地管理或 peer 认证 |
host | TCP,TLS 与非 TLS 都可 | 仅当两种传输都明确允许 |
hostssl | TCP 且已经建立 TLS | 业务和远程管理的常见下限 |
hostnossl | TCP 且未使用 TLS | 显式拒绝或受控兼容例外 |
hostgssenc | TCP 且使用 GSS 加密 | 采用 GSSAPI 的环境 |
匹配列还包括:
database -> user -> client address -> authentication method/options几个容易误判的细节:
all很宽,不表示“最末默认规则”;sameuser、samerole等数据库关键字有特定语义;- user 列中的
+role_name匹配该角色的直接或间接成员,而不是匹配字符串 前缀; - database 列匹配的是客户端请求的数据库;
- hostname 规则需要正反向名称解析,延迟和失败模式不同于 CIDR;
- replication 连接有专门的 database 关键字和权限要求;
hostssl只说明客户端到该 PostgreSQL listener 的这段链路用了 TLS。
如果入口是 PgBouncer,客户端首先连接的是 PgBouncer。此时:
client -> PgBouncer HBA/auth/TLS
PgBouncer -> PostgreSQL HBA/auth/TLS or local socket这是两条独立的准入链。PostgreSQL 的 pg_hba.conf 不会替 PgBouncer
过滤客户端来源;PgBouncer 的 HBA 也不会自动约束绕过代理、直连
PostgreSQL 的流量。
pg_hba_file_rules 是解析证据
不要只读取模板文件。PostgreSQL 提供
pg_hba_file_rules,可把当前 HBA 文件解析为行:
SELECT
rule_number,
file_name,
line_number,
type,
database,
user_name,
address,
netmask,
auth_method,
options,
error
FROM pg_hba_file_rules
ORDER BY rule_number NULLS LAST, file_name, line_number;它能证明:
- PostgreSQL 从哪些文件和行解析出规则;
- include 后的最终顺序;
- 方法、地址、选项是否符合预期;
- 是否存在语法或解析错误。
它不能单独证明:
- 某条规则可从目标网段实际到达;
- 防火墙、安全组、HAProxy 或 PgBouncer 是否放行;
- DNS 名称匹配是否如预期;
- 密码、证书或外部身份提供方是否可用;
- 连接最后究竟命中了哪条规则。
因此验收还要从允许与禁止的真实来源分别连接,并把时间、目标地址、目标 数据库、login、TLS 属性和结果关联起来。生产系统不应为了测试负例而从未知 公网来源扫描数据库;应使用预先批准的测试节点。
HBA 不负责对象授权
下面的连接可能通过 HBA 和 SCRAM,却仍被数据库拒绝:
REVOKE CONNECT ON DATABASE appdb FROM app_login;反过来,CONNECT 只是进入数据库:
GRANT CONNECT ON DATABASE appdb TO app_login;它没有授予:
- schema 的
USAGE; - table 的
SELECT; - sequence 的
USAGE; - function 的
EXECUTE; - 切换到某个业务角色的 membership。
这也是为什么 HBA 不能被称为“权限配置”。它选择认证方法并执行连接准入, 对象授权要在 23.3 单独证明。
安全变更顺序
HBA 变更采用“声明—解析—负例—正例—回滚”:
1. 从 inventory / policy source 生成候选配置
2. 检查宽规则、shadowed rule、host/hostssl 与来源 CIDR
3. 在节点上进行语法/解析检查
4. 保留当前管理连接与独立 break-glass 路径
5. reload,不把 reload 误写成 restart
6. 从允许来源做正例,从禁止来源做负例
7. 核对 pg_hba_file_rules 和认证日志
8. 失败则恢复上一份已验证配置并 reload在 Pigsty 中,应同时检查 PostgreSQL 与 PgBouncer 的 HBA 声明和渲染产物。
23.6 会把这条流程映射到 pg_hba_rules、pgb_hba_rules 及默认规则。
23.2.2 SCRAM、证书与外部身份
先区分四件事
“数据库密码安全”常把四个问题混在一起:
| 问题 | 典型机制 |
|---|---|
| 服务器保存什么 | SCRAM verifier、外部身份映射、客户端证书映射 |
| 线上如何证明身份 | SCRAM exchange、certificate、GSS/SSPI、LDAP、OAuth |
| 链路是否加密 | TLS 或 GSS encryption |
| 客户端是否找对服务器 | CA chain + hostname/IP identity verification |
只把 password_encryption 设为 scram-sha-256,不会自动启用 TLS;只使用 TLS
也不会自动把数据库中旧的 MD5 verifier 变成 SCRAM。
SCRAM 的角色
PostgreSQL 使用 SCRAM-SHA-256 时,服务器保存的是 salted verifier,而不是 可直接用于登录的明文密码。新密码应在:
SHOW password_encryption;返回 scram-sha-256 的受控环境中设置。不要通过命令行参数、shell history、
CI 日志或 Git 文件传递明文:
bad: psql postgresql://user:plain-password@host/db
bad: ALTER ROLE user PASSWORD 'plain-password'; # copied into ticket/log
good: secret manager -> short-lived private file/fd/env contract -> client环境变量也不是天然的 secret manager:它可能被子进程继承、被诊断工具采集, 或留在流水线元数据中。重点是限制创建、读取、传递和销毁它的主体与时间。
PostgreSQL 18 已将 MD5 密码支持标记为弃用。迁移时可以先把 HBA 目标方法改为 SCRAM-compatible 路径,再逐个重置用户密码生成 SCRAM verifier,并验证所有 驱动。官方迁移说明见 PostgreSQL:密码认证。
channel binding
SCRAM channel binding 把认证交换绑定到当前 TLS channel,降低凭据交换被代理 到另一条 TLS 会话的风险。支持它的 libpq 客户端可以要求:
sslmode=verify-full
channel_binding=require这里两个选项不可互相替代:
verify-full 验证证书链和目标名称
channel_binding 把 SCRAM 认证绑定到已建立的 TLS channel部署前必须确认驱动版本、TLS 库和中间代理是否支持。不能因为服务端支持 SCRAM
就假设所有客户端都支持 SCRAM-SHA-256-PLUS。
本章沙箱的直连实验证明 verify-full + channel_binding=require 可以成功;
这是一条兼容性证据,不代表 PgBouncer 客户端入口也自动具备同样属性。
客户端证书
cert 认证由 TLS 客户端证书证明身份,通常还需用 map= 把证书主体映射到
PostgreSQL role。它适合:
- 节点间或服务间受管身份;
- 有成熟 CA、签发、吊销和轮换系统的环境;
- 不希望长期共享密码的管理链路。
它并不自动适合每个终端用户。必须解决:
- private key 存放与文件权限;
- 客户端证书分发;
- SAN/subject 与 role 的映射;
- 有效期和轮换重叠期;
- 离职、设备丢失与 CRL/OCSP;
- 代理终止 TLS 后如何继续传递可信身份。
如果 PgBouncer 终止客户端 TLS,PostgreSQL 后端看到的是 PgBouncer 的连接, 不能凭空看到原始客户端证书。身份终止点必须在架构图和审计模型中明确。
外部身份不是“无密码”捷径
PostgreSQL 还可以接入 LDAP、GSS/SSPI、PAM、RADIUS、OAuth 等方法,具体可用 范围取决于版本和构建。它们把一部分认证判断交给外部系统,但数据库仍需定义:
external principal -> PostgreSQL login role -> effective business role评审时要问:
- 外部主体如何唯一映射,是否会因重名或大小写碰撞映射错误;
- 身份提供方不可用时是 fail-closed 还是出现旁路;
- token/ticket 的 audience、issuer、有效期和撤销如何验证;
- 数据库本地 break-glass 是否独立保管;
- PgBouncer 是否支持该认证方法,还是需要
auth_query/代理集成; - 外部组变化多久才能反映到数据库 session;
- 已建立 session 在外部身份撤销后何时终止。
身份联邦减少的是一类凭据管理,不会消除 role graph、对象 ACL、RLS 和审计。
PgBouncer 的身份交付面
PgBouncer 需要知道如何验证 client login,并以何种 server identity 连接 PostgreSQL。常见入口包括:
auth_file PgBouncer 本地认证材料
auth_query 从受控数据库函数/视图获取认证材料
auth_user 执行 auth_query 的受限身份这意味着“PostgreSQL 中创建了 LOGIN role”并不必然让 PgBouncer 接受该用户。 本章轮换探针刻意创建了一个未进入池认证面的临时 login:
direct PostgreSQL new authentication succeeds
PgBouncer authentication fails这个负例证明两套身份面必须分别验收。不要为了让探针通过而临时把高权用户 加入 PgBouncer userlist。
23.2.3 TLS 验证、吊销与密钥轮换
sslmode 分别证明什么
libpq 的主要模式可理解为:
sslmode | 加密 | 验证 CA | 验证目标名称 | 适用判断 |
|---|---|---|---|---|
disable | 否 | 否 | 否 | 只用于明确受控的非 TLS 路径 |
allow | 不保证 | 不保证 | 不保证 | 兼容优先,不是生产安全基线 |
prefer | 不保证 | 不保证 | 不保证 | 默认兼容行为,不是证明 |
require | 是 | 通常不证明名称 | 否 | 防窃听,不充分防冒充 |
verify-ca | 是 | 是 | 否 | 对端由受信 CA 签发 |
verify-full | 是 | 是 | 是 | 生产客户端通常应达到 |
require 能加密,但若不核对服务器名称,客户端可能把密码交给持有另一张受信
证书或被错误路由的服务器。生产应用应优先使用 verify-full。libpq 的精确
行为和 root certificate 兼容细节见
PostgreSQL:SSL 支持。
名称验证依赖 SAN 和连接名
verify-full 校验的是连接参数中的 host 与证书身份。证书应在
Subject Alternative Name 中声明实际使用的 DNS name 或 IP:
application DSN host=pg-primary.example.com
certificate SAN DNS:pg-primary.example.com若客户端使用 VIP、HAProxy 名称或 Kubernetes service name,证书必须覆盖这个 稳定入口;只给后端节点名签证书并不能验证入口名称。
本章沙箱节点证书包含 localhost、集群名、节点名、loopback 与节点 IP 的 DNS/IP SAN。正式实验从证书解析 SAN,再执行三种连接:
sslmode=require success, TLS 1.3
sslmode=verify-full success with matching name
verify-full wrong name rejected第三条负例和前两条正例同等重要。没有负例,无法排除客户端根本没有执行名称 校验。
看 pg_stat_ssl,但不要只看它
服务器端可把 session 与 TLS 属性关联:
SELECT
a.pid,
a.usename,
a.application_name,
a.client_addr,
s.ssl,
s.version,
s.cipher,
s.bits,
s.client_dn,
s.issuer_dn
FROM pg_stat_activity AS a
JOIN pg_stat_ssl AS s USING (pid)
WHERE a.pid = pg_backend_pid();正式直连观察到:
ssl true
version TLSv1.3
cipher TLS_AES_256_GCM_SHA384
bits 256但 pg_stat_ssl.ssl=true 只证明 PostgreSQL 看到的这一跳使用 TLS。若拓扑是:
client ==TLS==> PgBouncer --Unix socket--> PostgreSQLPostgreSQL 看到的后端连接自然是 ssl=false,不能据此断言客户端链路未加密。
反之,后端 TLS 为 true 也不能证明 client-to-proxy 使用 TLS。两跳要分别测。
CA、CRL 与 private key
服务端最少要治理:
- server certificate;
- server private key;
- trusted client CA(若验证客户端证书);
- CRL 或其他撤销机制;
- 文件 owner、mode 和可读取主体;
- reload/restart 语义;
- 到期时间、提前轮换窗口和告警。
PostgreSQL 要求 private key 权限受到严格限制。本章节点上的服务端 key 为
0600。这只是静态权限证据,还需确认备份、配置管理缓存、工单附件和监控
采集器没有复制密钥。
沙箱的 CRL file/dir 未配置,因此不能声称已经具备客户端证书撤销闭环。证书 有效期很长也不等于安全或不安全;必须根据签发自动化、暴露面和撤销能力制定 生命周期,而不是只看 expiry date。
双版本轮换,而不是瞬间替换
CA、服务器证书和客户端证书/密码轮换都应保留重叠窗口:
prepare
-> distribute trust for old + new
-> deploy new identity material
-> reload/reconnect
-> prove new works
-> prove fleet migrated
-> revoke old
-> prove old fails对于 CA:
client trust store: old CA + new CA
server certificate: switch old-signed -> new-signed
fleet evidence: all active clients trust and use new chain
client trust store: remove old CA对于密码:
create new secret version
change database verifier
roll clients to new version
terminate/drain old sessions when policy requires
destroy old secret versionPostgreSQL role 只有一个当前 password verifier,没有天然的“双密码同时有效”。 应用侧重叠通常要借助两个 login、连接池分批切换,或把数据库密码变更与快速 客户端 rollout 精确协调。
凭据撤销不等于 session 撤销
本章做了一个容易被忽略的实验:
1. password v1 建立连接 success
2. 改为 password v2
3. 使用 v1 建立新连接 rejected
4. 使用 v2 建立新连接 success
5. 原来用 v1 建立的 session 继续查询 success
6. ALTER ROLE ... NOLOGIN
7. 新认证 rejected
8. 已建立 session 继续查询 success
9. 最终 PASSWORD NULL,NOLOGIN verified结论是:
credential revocation != session revocation若处置的是泄露或人员离职,还要:
- 在应用池和代理层停止新借用;
- 找出目标 role/session;
- 评估事务影响后终止连接;
- rotate downstream secret;
- 检查复制、备份、日志和导出物;
- 保存不含秘密的取证证据。
本章沙箱的诚实结论
形式化验收同时发现:
| 项目 | 运行事实 | 结论 |
|---|---|---|
| PostgreSQL TLS | 开启,最低 TLS 1.2 | 基础能力存在 |
直连 verify-full | 成功,错误名称失败 | 名称校验可用 |
| SCRAM channel binding | 直连成功 | 该客户端路径兼容 |
| 业务 HBA | 内网存在 host 规则 | 非 TLS 直连仍可成功 |
| PgBouncer client TLS | 禁用 | 客户端到池入口不加密 |
| PgBouncer backend | 本地 Unix socket | 该跳不使用 TLS,符合本地链路事实 |
| CRL | 未配置 | 吊销闭环缺失 |
所以沙箱适合验证机制,不满足本章定义的生产安全门槛。正确结论不是因为发现 缺口就隐藏实验,而是输出:
mechanism proof PASS
production security PENDING remediation下一节在已经确定 login 身份之后,继续回答 effective role 与对象权限问题。
上一节:威胁模型与信任边界 · 返回本章目录 · 下一节:角色与最小权限 · 查看全书目录 · 查看索引中心