Skip to content

pg_hba.conf 详解 ​

pg_hba.conf(HBA 是 host-based authentication 的缩写,即基于主机的认证)是 PostgreSQL 的「客户端准入控制清单」。每当一个客户端发起连接——无论来自本机的 Unix socket,还是来自远端的 TCP——服务器都会在真正进入密码校验、证书校验等认证动作之前,先拿这条连接的「来源、目标数据库、登录用户」与 pg_hba.conf 中的记录逐条比对,找出第一条匹配的记录,并按照该记录指定的认证方法决定「放行、拒绝,还是要求出示密码/证书」。换句话说,它回答的问题是:谁,从哪里,连哪个库,用什么方式证明自己。

这份文件在《阶段十二:配置管理》的 pg_hba.conf 一页中已经做过入门介绍,那里解决的是「这个文件怎么改、怎么生效」;本页则把它当作安全体系的第一道闸门来讲透:匹配规则为什么是「自上而下、首个匹配生效」,六种连接类型各自精确匹配什么样的连接,地址字段怎么写才既安全又不误伤,以及生产环境里一份成熟模板长什么样。

需要先建立一个整体认知:pg_hba.conf 与角色体系(16.1 的 create-role 等页面所讲的 CREATE ROLE / GRANT)是互补而非替代的两层。角色体系回答「连上之后能做什么」,是数据库内部的授权;pg_hba.conf 回答「能不能连上来、用什么方式证明身份」,是连接入口处的认证。只有两者配合,才构成完整的「认证(authentication)+ 授权(authorization)」链路。

前置知识

建议先阅读:权限速查表

基础概念 ​

先把本页要反复出现的术语摆到一张表里,后面遇到时不再解释:

概念英文 / 记法说明
HBA 文件pg_hba.conf基于主机的认证配置文件,路径由 hba_file 参数指定,默认在数据目录
记录record文件中的一行,描述「一类连接 → 一种认证方法」的规则
连接类型connection type该记录匹配的连接方式:本地 socket 还是 TCP、是否强制 SSL/GSS 加密
认证方法auth method匹配后要求客户端完成的身份证明动作,如 scram-sha-256、cert
用户名映射user name map通过 pg_ident.conf 把「外部身份」翻译成「数据库角色」的机制
首个匹配first match自上而下扫描,第一条匹配的记录生效,其后的记录不再参与
默认拒绝default deny没有任何记录匹配时,连接被拒绝(这是隐含的最后一条 reject)
重载reload让 pg_hba.conf 重新生效的动作,不需要重启实例

三条铁律:自上而下、首个匹配、无匹配拒绝 ​

pg_hba.conf 的行为可以浓缩成三条铁律,理解它们比背选项更重要:

  1. 自上而下逐条扫描。服务器按文件中的书写顺序(而不是「越具体越优先」)逐条与连接信息比对。同一条连接可能在很多记录上都「看起来匹配」,但只有最先出现的那条会被采用。
  2. 首个匹配生效,之后全部短路。一旦命中一条记录,扫描立即停止。排在后面的、可能更严格的记录对这条连接来说等于不存在。
  3. 无匹配即拒绝。如果扫到文件末尾仍没有记录匹配,连接被拒绝,报错 no pg_hba.conf entry for host ...。这等价于文件末尾隐含挂着一条 reject all。

这三条规则带来一个重要的推论:记录的书写顺序本身就是安全策略的一部分。「先写 reject 再写 allow」与「先写 allow 再写 reject」在语义上完全不同——前者的 reject 拦得住人,后者的 reject 永远轮不到执行。

一条记录的五段结构 ​

除了注释和空行,文件中的每一行都由若干字段组成,用空白(空格或制表符)分隔:

段序字段适用记录作用
1连接类型所有记录匹配 local socket 还是 host 系列 TCP 连接
2数据库所有记录连接的目标数据库,支持关键字与列表
3用户所有记录登录使用的数据库角色,支持关键字与列表
4地址仅 host 系列客户端来源地址(CIDR、关键字或主机名)
5认证方法(及选项)所有记录命中后要求完成的认证动作

local 记录没有地址段(本机 socket 谈不上来源网段),因此是四段;host 系列记录是五段。一个典型的文件看起来像这样:

text
# TYPE    DATABASE    USER        ADDRESS          METHOD
local     all         postgres                     peer
host      app         app_rw      10.0.1.0/24      scram-sha-256
hostssl   app         app_ro      10.0.2.0/24      scram-sha-256 clientcert=verify-ca
host      replication repl        10.0.0.0/24      scram-sha-256

一次连接的完整准入时序 ​

把 HBA 放回「一次连接」的全程里看,它的位置在很靠前的环节:

text
客户端                                   PostgreSQL
   │ ──① TCP/socket 连接请求 ──────────────► │
   │ ◄─② 启动消息(用户名/数据库/参数)────── │
   │                                         │ ③ pg_hba.conf 匹配:
   │                                         │    (类型, 库, 用户, 地址) → 首条命中
   │ ◄─④ 认证请求(按命中记录的认证方法)───── │
   │ ──⑤ 提交证明(密码/证书/票证/…)───────► │
   │ ◄─⑥ AuthenticationOk / 拒绝 ──────────── │
   │ ──⑦ 进入会话,受角色权限约束 ──────────► │

两个关键推论:其一,HBA 匹配发生在认证之前——写错的地址段会让「密码正确」的连接也进不了认证环节,报错是 no pg_hba.conf entry 而不是 password authentication failed,报错类型本身就是排查路标;其二,通过认证不等于「权限放行」,第⑦步之后的一切仍由 GRANT 体系裁决——HBA 只负责开门,进屋后的活动是另一套规则。

它解决什么,不解决什么 ​

pg_hba.conf 是「连接级」的控制,粒度到「来源 + 库 + 用户」为止。它能做到「业务网段的 app 用户可以用密码连 app 库」,但做不到「app 用户只能查 orders 表」——那是 GRANT 的职责。同样,它也做不到「密码每 90 天更换一次」这类密码策略。把每一层的职责分清楚,是设计安全方案的第一步。

语法与用法 ​

连接类型:六种记录类型 ​

连接类型决定了记录匹配「什么样的管道」。PostgreSQL 提供六种:

类型匹配的连接典型用途
localUnix domain socket 连接本机管理、脚本、同机工具
hostTCP/IP 连接(SSL 与非 SSL 均可,GSS 加密与否均可)普通业务连接
hostssl仅使用 SSL/TLS 的 TCP 连接强制加密的外部访问
hostnossl仅不使用 SSL 的 TCP 连接显式为明文连接设置限制或拒绝
hostgssenc仅使用 GSSAPI 加密的 TCP 连接Kerberos 环境下用 GSS 加密替代 SSL
hostnogssenc仅不使用 GSSAPI 加密的 TCP 连接排除 GSS 加密连接后单独处理

三个容易混淆的语义务必记牢:

  • host 不等于「非 SSL」——它同时接受 SSL 与非 SSL 连接,也接受 GSSAPI 加密连接。想强制加密必须写 hostssl(或 GSS 加密场景写 hostgssenc),详见 16.3 的 强制 SSL 连接。
  • hostssl 记录只会匹配真正协商出 SSL 的连接。客户端用 sslmode=disable 连进来时,hostssl 记录对它不可见。
  • hostnossl 通常用于「给明文连接单独定规则」:要么更严格(只许内网管理机明文连接),要么直接 reject,避免明文连接「蹭」到通用 host 记录的宽松策略。

数据库字段 ​

写法含义
all匹配所有数据库,但不匹配复制连接
sameuser目标数据库与登录用户同名的库
samerole登录用户(直接或间接)属于同名角色的库
replication仅匹配物理复制连接(不含逻辑复制,逻辑复制走普通连接)
db1,db2,db3逗号分隔的显式列表
@file从指定文件读入库名列表,每行一个

replication 值得一提:物理复制连接不能通过 all 匹配到,必须为复制用户单独写一条包含 replication 的记录,否则流复制会报 no pg_hba.conf entry。逻辑复制的订阅连接是普通数据库连接,按普通记录匹配即可。

用户字段 ​

写法含义
all匹配所有角色
+role_name匹配该角色的所有(直接或间接)成员
user1,user2逗号分隔的显式角色列表
@file从文件读入角色列表

写 + 前缀时要意识到它做的是成员资格判断:+app_group 会匹配所有把 app_group 作为祖先角色的用户。想精确到单个用户,就直接写角色名,不加 +。

地址字段:CIDR 与关键字 ​

host 系列记录的地址段有三种写法:

CIDR 网段。IP地址/掩码位数,例如 192.168.1.0/24 匹配该 C 段的所有地址;10.0.0.53/32 只匹配一个地址。也可以用旧的「地址 + 独立掩码」两字段写法(192.168.1.0 255.255.255.0),但现代配置一律推荐 CIDR。注意掩码位数必须按 IPv4/IPv6 对齐,192.168.1.1/24 这种「主机位没清零」的写法会直接报错。

关键字 samehost。匹配服务器自身的所有 IP 地址——包括 loopback 和后来新增的网卡地址,不用在网卡变更时改配置。

关键字 samenet。匹配服务器所有 IP 所在网段内的任意地址,范围比 samehost 大一层。

此外地址段也可以写主机名(以点开头如 .example.com 匹配整个域,或完整主机名),服务器会对客户端地址做反向 DNS 解析来匹配。这种写法依赖 DNS 的正确性与抗攻击性,解析开销也发生在认证路径上,生产环境一般不推荐,用 CIDR 更可控。

IPv6 地址照常支持:::1/128 匹配本机 IPv6 loopback,fc00::/7 匹配一段 ULA 地址。只配置了 IPv4 记录的机器,别忘了本机的 IPv6 连接会走 ::1 而不是 127.0.0.1。

认证方法总览 ​

最后一段是认证方法。PostgreSQL 18 支持的全部方法如下,本阶段后续页面会逐一展开:

方法机制典型场景
trust无条件放行,不验任何身份本地开发、单人工作站的临时调试
reject无条件拒绝显式拉黑特定用户/网段
scram-sha-256SCRAM-SHA-256 挑战-响应密码认证现代环境的默认密码认证
md5md5 挑战-响应密码认证兼容旧客户端,已不推荐
password明文发送密码仅适合 SSL 加密管道内使用
gssGSSAPI / Kerberos 认证企业 SSO、域环境
sspiWindows SSPI 认证Windows 域环境
ident问客户端 ident 服务拿 OS 用户名早已过时,仅可信内网遗产
peer从 Unix socket 取对端 OS 用户名本机管理的默认选择
ldap把密码交给 LDAP 服务器校验集中账号管理
radius把密码交给 RADIUS 服务器校验对接企业统一认证
cert校验客户端 SSL 证书并取 CN 作为用户名mTLS、无密码自动化
pam交给 PAM 栈校验对接系统级认证(注意 pam 不能配合密码变更)
bsd交给 BSD 认证服务校验BSD 系统集成

每种方法的细节、选项与踩坑点,分别见本阶段 密码认证、LDAP / Kerberos 认证、证书认证、peer / ident 认证 四页。

认证方法选项 ​

认证方法后面还可以跟 选项名=值 形式的附加选项,常用的有:

选项适用方法作用
map=映射名peer / ident / gss / sspi / cert引用 pg_ident.conf 里的映射规则,把外部用户名转换成数据库角色
clientcert=verify-ca除 cert 外的密码类方法要求客户端出示由可信 CA 签发的证书(但身份仍由密码证明)
clientcert=verify-full除 cert 外的密码类方法在 verify-ca 基础上还要求证书 CN 与登录用户匹配
clientname=CN / clientname=DNcert 及 clientcert=verify-full决定用证书的哪个字段做身份比对
radiusserver= / radiussecret=radiusRADIUS 服务器地址与共享密钥
radiusport= / radiusidentifier=radiusRADIUS 端口与 NAS 标识
ldapserver= / ldapbasedn= 等ldapLDAP 服务器地址、绑定 DN、搜索属性等(详见 LDAP 一页)

clientcert 不是「换成证书认证」

在 scram-sha-256 等密码方法上加 clientcert=verify-ca,含义是「既要证书入场券,也要密码」,密码校验照常进行。真正的「用证书代替密码」是 cert 方法。这两件事经常被混为一谈,设计双因素(证书 + 密码)或纯证书方案时一定要分清,详见 证书认证 一页。

include 系列指令拆分配置 ​

PG14 起,pg_hba.conf 支持三种包含指令,可把一份巨型文件拆成多个小文件管理:

conf
# 把指定文件的内容插入到当前位置
include /etc/postgresql/pg_hba_apps.conf

# 把指定目录下以 .conf 结尾的文件按文件名排序依次插入
include_dir /etc/postgresql/pg_hba.d

# 文件存在才包含,不存在也不报错
include_if_exists /etc/postgresql/pg_hba_local_overrides.conf

include_dir 的排序规则是文件名排序(注意不是字典序友好型——10-app.conf 会排在 2-db.conf 前面,所以习惯用两位数字前缀命名)。这带来一种很实用的管理姿势:主文件放基础策略,pg_hba.d/ 里按业务放增量策略,变更审计时只需要看 diff 的那个小文件。

一个按此姿势组织的目录布局预览(详细内容见实战示例):

text
/etc/postgresql/18/main/
├── pg_hba.conf              # 主文件:include_dir + 文件头说明
├── pg_hba.d/
│   ├── 00-local.conf        # 本机 peer 策略
│   ├── 10-replication.conf  # 复制专用记录
│   ├── 20-app.conf          # 业务网段
│   ├── 30-admin.conf        # 运维/堡垒机
│   └── 99-deny.conf         # 兜底 reject(永远排最后)
└── pg_ident.conf

编号即优先级,目录里的增删天然带顺序语义,配合 pg_hba_file_rules 的 file_name 列还能一眼看出每条规则来自哪个文件。

加载与生效:reload 而不是 restart ​

pg_hba.conf 在实例启动时读取一次,之后每次收到 reload 信号都会重新加载,无需重启:

bash
# 方式一:pg_ctl(要指定数据目录或用环境变量)
pg_ctl reload -D $PGDATA

# 方式二:直接给 postmaster 发信号
kill -HUP $(head -1 $PGDATA/postmaster.pid)

# 方式三:最常用——SQL 函数,任何会话里执行
psql -c "SELECT pg_reload_conf();"

重载只影响之后新建立的连接,已有连接不受影响。也就是说,改坏配置不会立刻断开线上会话,但也意味着「改完配置连上去测试」永远测的是新配置,而线上老连接一直用的是老策略。

预检视图 pg_hba_file_rules ​

重载前先用 pg_hba_file_rules 视图做语法预检,可以避免「reload 之后才发现写错了」的尴尬。该视图按规则序号逐条展示文件解析结果:

字段说明
rule_number规则序号,即生效的匹配顺序
file_name该规则所在的文件(include 拆分时可能各不相同)
line_number文件中的行号
type连接类型
database数据库字段(数组)
user_name用户字段(数组)
address地址字段
netmask旧式独立掩码写法才有值
auth_method认证方法
options认证选项(数组)
error解析错误信息,为空即该条有效
sql
-- 只看有问题的行
SELECT rule_number, file_name, line_number, error
FROM pg_hba_file_rules
WHERE error IS NOT NULL;

-- 看某条连接会被哪条规则匹配(按序号人工走查)
SELECT rule_number, type, database, user_name, address, auth_method, options
FROM pg_hba_file_rules
ORDER BY rule_number;

注意两点:视图展示的是「文件当前内容」的解析结果,与「服务器实际生效」之间可能隔了一次未执行的 reload;另外,无匹配拒绝的兜底行为不会出现在视图里,视图只反映文件里写明的规则。

详细说明 ​

匹配顺序为什么如此重要:一个误伤案例 ​

假设有这么一份配置,意图是「应用网段可以连 app 库,其他一律拒绝」:

text
# 反例:顺序错误
host    all     all    0.0.0.0/0    reject
host    app     app_rw 10.0.1.0/24  scram-sha-256

由于首个匹配生效,第一条 reject all 会拦截一切 TCP 连接,第二行的 allow 永远不会被命中——应用直接全挂。正确的写法是把具体规则放前面、兜底规则放最后:

text
# 正例:先具体后兜底
host    app     app_rw 10.0.1.0/24  scram-sha-256
host    all     all    0.0.0.0/0    reject

类似的还有「更具体优先」与「更宽泛优先」的取舍。HBA 没有 longest-prefix-match,完全按书写顺序,因此团队约定里应明确一条:allow 规则必须写在能覆盖它的 reject 规则之前。

reject 的正确用法:白名单里的黑名单 ​

reject 不是用来做全量兜底的(用「不写 allow」就能拒绝),它的价值在于在白名单内部挖洞:

text
# 运维网段整体放行,但先拒绝已离职/高危账号
host    all     legacy_cron     10.0.9.0/24     reject
host    all     +dba            10.0.9.0/24     scram-sha-256

# 应用网段放行,但绝不允许应用网段使用超级用户
host    all     postgres        10.0.1.0/24     reject
host    app     app_rw          10.0.1.0/24     scram-sha-256

第二条是极有价值的一条 hardened practice:应用网段即使密码泄露,postgres 超级用户也无法从那里登录,把「应用被攻破」的爆炸半径与「超级用户」隔离开。

地址匹配的安全边界 ​

写 CIDR 时最常见的错误是「图省事写大网段」:

text
# 危险:0.0.0.0/0 + trust 等于对全网裸奔
host    all    all    0.0.0.0/0    trust

# 常见但值得警惕:/16 远比实际需要的范围大
host    app    app_rw 10.0.0.0/16  scram-sha-256

地址匹配只回答「包从哪个 IP 来」,而 IP 可以被路由转发、可以被容器网络 NAT。因此地址段的安全假设是「网段本身可信」——这也是为什么认证方法永远不能省:网段缩小了攻击面,认证方法才真正验证身份。两者是「与」的关系,缺一不可。

samehost 与 samenet 适合解决「网卡地址漂移」的痛点:云环境下实例扩缩容、VIP 漂移都会改地址,用 samehost 可以让本机回环与新地址自动被覆盖。但 samenet 的范围会随网卡配置扩大,使用前要确认网段边界确实是可信的。

认证方法的选型逻辑 ​

把 14 种方法按「证明强度」和「运维成本」放到二维平面上,选型思路会清晰很多:

强度方法成本一句话定位
低trust零只配出现在测试机的 loopback 上
低password零密码明文过网,只在 hostssl 之内考虑
中md5零兼容层,最终应迁移到 scram
中scram-sha-256低绝大多数生产环境的默认答案
高cert中(要管证书)无密码自动化、mTLS
高gss / sspi高(要 KDC)企业域内 SSO
视后端ldap / radius / pam / bsd中把密码校验外包给集中式认证
特殊peer / ident低依据 OS 用户名,仅限本机 / 可信内网

一个常见误区是「方法越花哨越安全」。实际上 scram-sha-256 + 严格的网段 + reject 超级用户,安全水位往往高于配置了一半的 Kerberos。选型先做减法:能不引入的组件就不引入。

另一种实用视角是按连接来源分派方法,让每种来源走各自最合理的方法:

text
# 本机运维    → peer(身份由 OS 背书)
# 复制/应用   → scram-sha-256(密码质询)
# 自动化作业  → cert(无密码、可吊销)
# 域内用户    → gss(SSO)
# 外部工具    → ldap(账号集中管理)

方法的多样性是按来源设计的——评审新连接需求时先问「它从哪来」,再定方法,而不是全局统一成一种然后为每种来源打补丁。

map 与 pg_ident.conf 的配合 ​

peer、ident、gss、sspi、cert 这些方法给出的「身份」来自数据库之外(OS 用户名、Kerberos 主体、证书 CN),通常不能直接当数据库角色名用。map= 选项把它们接到 pg_ident.conf(路径由 ident_file 参数指定,默认与 pg_hba.conf 同目录),做一次身份翻译:

text
# pg_ident.conf:映射名  外部身份  数据库角色
osmap       postgres            postgres
osmap       deploy              app_migrator
text
# pg_hba.conf:local 记录引用映射
local    app    all    peer map=osmap

不写 map= 时,外部身份必须与数据库角色名完全一致才能通过;写了映射,则只有映射文件里列出的组合被放行,映射之外的组合一律拒绝——映射因此也是一种白名单。关于映射文件自身的语法(含 PG15 起的正则支持),在 peer / ident 认证 一页展开。

include 拆分时的顺序陷阱 ​

include 指令把另一个文件的内容原样插入当前位置,匹配顺序仍然遵循「插入后的物理顺序」。这意味着:

  • 被 include 的文件插入在主文件的第几行,它的规则就排在第几优先级。想让「基础策略先兜底、业务策略后放行」是做不到的——兜底 reject all 若写在主文件前半段,后面 include 进来的所有 allow 全部失效。
  • 因此拆分方案应当是「所有 allow 类规则集中在主文件或靠前的包含文件,兜底 reject 永远放在最后」。用 include_dir 时,按数字前缀命名(00-base.conf、10-app.conf、99-deny.conf)把顺序固化下来。

reload 的原子性与回滚预案 ​

reload 是原子的:解析成功则整套规则一次性替换,解析失败则保持旧配置继续生效,同时在日志里记录错误。这保证了改坏文件不会让新规则半生不熟地生效。但有两点要主动做:

  1. reload 后必须确认日志没有解析错误,否则你以为生效的新策略其实没生效。
  2. 保留回滚通道。改配置前备份 cp pg_hba.conf pg_hba.conf.bak.$(date +%F);更稳妥的做法是在改动前用 pg_hba_file_rules 确认新内容全部无 error,再触发 reload。
sql
-- reload 后的例行确认
SELECT pg_reload_conf();
SELECT rule_number, error FROM pg_hba_file_rules WHERE error IS NOT NULL;
bash
# 同时看服务端日志确认加载结果
tail -50 $PGDATA/log/postgresql-*.log | grep -i 'hba\|authentication'

错误排查:no pg_hba.conf entry for host ​

这是 PostgreSQL 最著名的报错之一,完整形态类似:

text
FATAL:  no pg_hba.conf entry for host "10.0.1.57", user "app_rw", database "app", no encryption

这条消息其实把排查需要的信息全部给齐了——它逐字报告了匹配失败的四元组(host、user、database,PG14 起还附上连接加密状态 no encryption / SSL on / GSS encryption)。排查按固定套路走:

  1. 确认错误里的四元组。host 是服务器看到的客户端地址(经 NAT 后可能不是你以为的地址);user 是数据库角色名不是 OS 用户名;PG14+ 的 no encryption 提示说明连接没走 SSL,hostssl 记录对它无效。
  2. 用完全相同的四元组去走查规则。按 pg_hba_file_rules 的 rule_number 顺序,假装自己是那条连接,逐条问「我会不会被这条匹配」。
  3. 重点检查三类问题:地址段没覆盖(NAT 后网段不同 / 只配了 IPv4 但连接来自 ::1)、用户名大小写或拼写、数据库字段写了 all 却连的是复制连接(replication 不被 all 匹配)。
  4. 确认 reload 真的执行过。改了文件没 reload,是「我以为我改了」的头号来源。
  5. 确认匹配到的认证方法本身可完成。比如记录写的是 peer 但连接来自 TCP——peer 只能匹配 local,TCP 连接会继续往下找,找不到就报这个错。
sql
-- 复现客户端视角:从服务器上模拟一次同参数连接
-- 若能连上而应用连不上,差异通常在地址(NAT)或加密状态
bash
# 从应用主机测试,拿到服务器视角的真实地址
psql "host=pg1.example.com port=5432 dbname=app user=app_rw" -c "SELECT inet_client_addr();"

local 与 Unix socket 的目录问题 ​

local 记录匹配的是 Unix domain socket 连接,而 socket 的位置由 unix_socket_directories 参数决定(默认 /tmp,发行版打包通常改为 /var/run/postgresql 之类)。如果机器上有多个实例分别监听不同 socket 目录,客户端用 psql -h /var/run/postgresql 与 psql -h /tmp 连接会命中同一个实例的同一份 HBA 规则——local 记录不区分 socket 路径。想按「入口」细分本机连接,只能用端口或 socket 目录上的约定,HBA 层面没有对应的匹配维度。

多实例与配置漂移 ​

一台机器跑多套实例(PG10 老实例 + PG18 新实例)时,每套实例有自己的 hba_file / ident_file,规则互不相干。运维事故的高发点是:改了 A 实例的文件以为在改 B 实例。约定做法是:每个实例的数据目录里放一份指向集中管理配置的软链接,用 SHOW hba_file; 确认每个实例实际加载的路径,再把配置内容纳入 Git 管理——「线上文件与仓库 diff 为零」应当成为巡检项。

与连接池、代理层的配合 ​

当流量经过 PgBouncer 或 HAProxy 等代理时,HBA 看到的客户端地址是代理的地址而不是真实客户端的地址。这直接影响地址段的写法:

  • 代理所在网段必须在 HBA 中放行,且真实业务网段的规则对「直接连库」的流量不再适用;
  • 若代理做了认证透传(如 PgBouncer 的 auth_user / SCRAM 透传),最终认证仍是端到端的;若代理自己认证(如固定账号池化),数据库看到的是代理身份,真实用户信息要在代理层留存审计;
  • TLS 会在代理处终止或透传,hostssl 与客户端 sslmode 的语义要在「端到端加密」与「两段式加密」之间想清楚,这是 16.3 强制 SSL 连接 一页的重点。

实战示例 ​

示例一:生产环境的 pg_hba 模板 ​

一份按「本机管理 → 监控 → 复制 → 业务网段 → 兜底」分层组织的模板,可直接作为起点:

conf
# ============================================================
# pg_hba.conf —— 生产模板
# 原则:local 用 peer + 映射;远程一律 scram;superuser 禁止从业务网段进入;
#       兜底 reject 放最后;任何变更先 pg_hba_file_rules 预检再 reload。
# ============================================================

# ---------- 本机管理(Unix socket)----------
# postgres OS 用户以 peer 直连(Debian/Ubuntu 默认行为)
local   all             postgres                                peer
# 其他本机 OS 用户需要通过映射表获得角色
local   all             all                                     peer map=os_admin

# ---------- 复制 ----------
hostssl replication     repl        10.0.0.0/24                 scram-sha-256

# ---------- 监控网段:只读账号 ----------
hostssl all             dbmon       10.0.7.0/24                 scram-sha-256

# ---------- 业务应用网段 ----------
# 先拒绝:业务网段永不放行超级用户与复制角色
host    all             postgres    10.0.1.0/24                 reject
host    replication     all         10.0.1.0/24                 reject
# 再放行:应用账号
hostssl app             app_rw      10.0.1.0/24                 scram-sha-256
hostssl app             app_ro      10.0.1.0/24                 scram-sha-256

# ---------- 运维堡垒机 ----------
hostssl all             +dba        10.0.9.0/24                 scram-sha-256 clientcert=verify-ca

# ---------- 兜底:显式拒绝其余一切 ----------
local   all             all                                     reject
host    all             all         0.0.0.0/0                   reject
host    all             all         ::/0                        reject

配套的映射文件:

text
# pg_ident.conf
# MAPNAME   SYSTEM-USERNAME        PG-USERNAME
os_admin    postgres               postgres
os_admin    backup                 dbbackup
os_admin    root                   dba_admin

示例二:变更前的预检与灰度 ​

改 HBA 的标准动作流:备份 → 编辑 → 预检 → reload → 验证。

bash
# 1. 备份
cp $PGDATA/pg_hba.conf $PGDATA/pg_hba.conf.bak.$(date +%F-%H%M)

# 2. 编辑(任何方式),然后预检——在 psql 里确认没有 error
sql
-- 3. 预检:任何 error 都必须在 reload 前修复
SELECT rule_number, file_name, line_number, error
FROM pg_hba_file_rules
WHERE error IS NOT NULL;

-- 顺手看一眼规则总数有没有异常增减
SELECT count(*) FROM pg_hba_file_rules;
bash
# 4. reload 并检查日志
psql -c "SELECT pg_reload_conf();"
tail -20 $PGDATA/log/postgresql-*.log | grep -iE 'hba|invalid|error'

# 5. 从一台典型客户端验证新策略(成功 + 失败两个方向都要验)
psql "host=pg1 dbname=app user=app_rw sslmode=require" -c "SELECT current_user;"
psql "host=pg1 dbname=app user=postgres sslmode=require" -c "SELECT 1;"   # 预期被拒

示例三:定位一条连接命中的规则 ​

HBA 不会记录「这条连接命中了第几条规则」,但可以用「复制文件 + 删减法」快速定位:

sql
-- 1) 导出当前生效顺序的规则清单(贴进临时文件 pg_hba_debug.conf)
\copy (SELECT rule_number || '  ' || type || ' ' || database::text || ' '
       || user_name::text || ' ' || coalesce(address,'') || ' ' || auth_method
       || ' ' || coalesce(options::text,'')
       FROM pg_hba_file_rules ORDER BY rule_number) TO '/tmp/pg_hba_debug.conf'
bash
# 2) 在临时副本上二分删减规则,配合 pg_ctl -D <测试目录> restart 起测试实例
#    每删一半测试一次连接,loglog 定位到具体规则后回主文件修正
initdb -D /tmp/hba_test >/dev/null
cp /tmp/pg_hba_debug.conf /tmp/hba_test/pg_hba.conf
pg_ctl -D /tmp/hba_test -o "-p 55432" start
psql "port=55432 host=localhost user=app_rw dbname=app" -c "SELECT 1;"

这种「把线上规则搬到一次性实例上做实验」的办法,比直接在生产上反复试错安全得多。

示例四:include_dir 分层管理(对照版生产模板) ​

把示例一的模板改造成 include_dir 形态,适合配置管理与多人协作:

conf
# /etc/postgresql/18/main/pg_hba.conf —— 主文件只留顺序骨架
include_dir /etc/postgresql/18/main/pg_hba.d
text
# pg_hba.d/00-local.conf
local   all             postgres                                peer
local   all             all                                     peer map=os_admin
text
# pg_hba.d/10-replication.conf
hostssl replication     repl        10.0.0.0/24                 scram-sha-256
text
# pg_hba.d/20-app.conf
host    all             postgres    10.0.1.0/24                 reject
host    replication     all         10.0.1.0/24                 reject
hostssl app             app_rw      10.0.1.0/24                 scram-sha-256
hostssl app             app_ro      10.0.1.0/24                 scram-sha-256
text
# pg_hba.d/99-deny.conf —— 兜底永远在最后
local   all             all                                     reject
host    all             all         0.0.0.0/0                   reject
host    all             all         ::/0                        reject
sql
-- 验证拆分后的顺序与来源(file_name 列显示每条规则来自哪个文件)
SELECT rule_number, file_name, type, database, user_name, auth_method
FROM pg_hba_file_rules ORDER BY rule_number;
text
 rule_number |            file_name            |   type   | ...
-------------+---------------------------------+----------+
           1 | pg_hba.d/00-local.conf          | local    |
           2 | pg_hba.d/00-local.conf          | local    |
           3 | pg_hba.d/10-replication.conf    | hostssl  |
           4 | pg_hba.d/20-app.conf            | host     |
...
          10 | pg_hba.d/99-deny.conf           | local    |

运维动作从此收敛为「新增业务改一个编号文件、动兜底要过更高层级审批」,git log pg_hba.d/20-app.conf 就能回答「这段策略是谁为什么加的」。

注意事项 ​

host 不强制加密,hostssl 才强制。 这是最常见的认知偏差:写 host 记录时连接是否加密取决于客户端的 sslmode,默认 prefer 意味着多数场景确实会加密,但降级到明文的口子始终开着。安全要求高的记录应一律写 hostssl。

all 不包含物理复制连接。 给复制用户配置 HBA 时,必须显式写一条 replication 记录,只写 all 会让流复制报 no pg_hba.conf entry。逻辑复制不受此影响。

IPv6 连接不吃 IPv4 规则。 本机用 ::1 连接、Kubernetes Pod 用 IPv6 双栈地址连接时,只配 127.0.0.0/8 或 10.0.0.0/8 的记录匹配不上,必须显式写 ::1/128 或对应 IPv6 网段。

reload 失败不会报警。 语法错误的文件在 reload 时被静默忽略(旧规则继续生效),只在日志里留一条记录。把「reload 后查日志 + 查 pg_hba_file_rules 的 error」写进变更流程,否则可能出现「策略改了但没生效」的错觉安全。

trust 是配置级后门。 它跳过一切身份验证,而且经常在排错时被临时加上然后忘掉。巡检时应把「存在 trust 记录」列为高危项,测试环境的 trust 也应限定在 loopback 网段。

HBA 不感知真实客户端(经代理时)。 经过连接池或负载均衡后,HBA 的地址段匹配到的是代理地址。不要用「HBA 里有网段白名单」来推断「只有业务网段能连库」——任何能连上代理的主机都在那个白名单里。

顺序即策略,diff 审核时必须带行号。 git diff 看内容变更还不够,插入位置的变化同样改变语义。评审 HBA 变更时要求以 rule_number 视角(pg_hba_file_rules 排序后的顺序)确认。

认证方法与数据库字段都区分大小写吗? 关键字(all、sameuser、replication、hostssl 等)必须小写;角色名与数据库名是标识符,遵循 PostgreSQL 标识符规则(未加引号时折叠为小写)。用双引号创建的大写角色在 HBA 里要用双引号写。

最佳实践 ​

  1. 生产环境禁用 trust,本机管理用 peer + 映射。 trust 只允许出现在一次性测试实例上;本机管理的标准姿势是 local ... peer map=...,把允许的 OS 用户显式列入映射表。
  2. 密码认证一律用 scram-sha-256。 md5 与 password 只作为兼容层或 SSL 内特例存在,并制定迁移计划(见下一页)。
  3. 远程记录一律写 hostssl 而不是 host。 把「加密与否」的决定权从客户端收回到服务端,配合 16.3 的强制 SSL 策略使用。
  4. 用 reject 把超级用户挡在业务网段之外。 在业务网段的 allow 规则之前加一条 host all postgres <业务网段> reject,成本一行,收益巨大。
  5. 网段按「最小需要」收敛。 用 /32//24 而不是 /16;用 samehost 表达「本机地址」;DNS 主机名匹配仅在不信任 CIDR 能力且 DNS 强管控的环境使用。
  6. 兜底 reject 放文件最后,并显式覆盖 IPv4 与 IPv6。 0.0.0.0/0 与 ::/0 各一条,让「默认拒绝」从隐含行为变成显式规则,审计时一眼可见。
  7. 所有变更走「备份 → 预检 → reload → 查日志 → 双向验证」五步。 预检用 pg_hba_file_rules 的 error 字段;双向验证指「该连得上的连得上、该被拒的被拒」。
  8. 用 include_dir 拆分配置并固化顺序。 主文件只保留 include 与兜底,业务规则按 NN-name.conf 编号管理,99-deny.conf 永远排最后。
  9. HBA 文件纳入版本管理并做漂移巡检。 SHOW hba_file; 确认实际路径,定期 diff 线上文件与仓库版本,防止「手改未入库」。
  10. 复制与监控账号单独成节维护。 replication 连接、监控探针账号的规则独立分组并注释说明,避免业务规则调整时误伤。
  11. 在 HBA 之外叠加网络层隔离。 HBA 是「应用层白名单」,防火墙/安全组是「网络层白名单」,两者叠加才有纵深;不要把 HBA 当防火墙用。
  12. 为排错保留一条「带证书」的备用通道。 堡垒机网段配 scram-sha-256 clientcert=verify-ca 之类的双因素规则,确保 HBA 改坏时仍有一条已验证的管理入口。

参考链接 ​