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 的行为可以浓缩成三条铁律,理解它们比背选项更重要:
- 自上而下逐条扫描。服务器按文件中的书写顺序(而不是「越具体越优先」)逐条与连接信息比对。同一条连接可能在很多记录上都「看起来匹配」,但只有最先出现的那条会被采用。
- 首个匹配生效,之后全部短路。一旦命中一条记录,扫描立即停止。排在后面的、可能更严格的记录对这条连接来说等于不存在。
- 无匹配即拒绝。如果扫到文件末尾仍没有记录匹配,连接被拒绝,报错
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 系列记录是五段。一个典型的文件看起来像这样:
# 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 放回「一次连接」的全程里看,它的位置在很靠前的环节:
客户端 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 提供六种:
| 类型 | 匹配的连接 | 典型用途 |
|---|---|---|
local | Unix domain socket 连接 | 本机管理、脚本、同机工具 |
host | TCP/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-256 | SCRAM-SHA-256 挑战-响应密码认证 | 现代环境的默认密码认证 |
md5 | md5 挑战-响应密码认证 | 兼容旧客户端,已不推荐 |
password | 明文发送密码 | 仅适合 SSL 加密管道内使用 |
gss | GSSAPI / Kerberos 认证 | 企业 SSO、域环境 |
sspi | Windows 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=DN | cert 及 clientcert=verify-full | 决定用证书的哪个字段做身份比对 |
radiusserver= / radiussecret= | radius | RADIUS 服务器地址与共享密钥 |
radiusport= / radiusidentifier= | radius | RADIUS 端口与 NAS 标识 |
ldapserver= / ldapbasedn= 等 | ldap | LDAP 服务器地址、绑定 DN、搜索属性等(详见 LDAP 一页) |
clientcert 不是「换成证书认证」
在 scram-sha-256 等密码方法上加 clientcert=verify-ca,含义是「既要证书入场券,也要密码」,密码校验照常进行。真正的「用证书代替密码」是 cert 方法。这两件事经常被混为一谈,设计双因素(证书 + 密码)或纯证书方案时一定要分清,详见 证书认证 一页。
include 系列指令拆分配置
PG14 起,pg_hba.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.confinclude_dir 的排序规则是文件名排序(注意不是字典序友好型——10-app.conf 会排在 2-db.conf 前面,所以习惯用两位数字前缀命名)。这带来一种很实用的管理姿势:主文件放基础策略,pg_hba.d/ 里按业务放增量策略,变更审计时只需要看 diff 的那个小文件。
一个按此姿势组织的目录布局预览(详细内容见实战示例):
/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 信号都会重新加载,无需重启:
# 方式一: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 | 解析错误信息,为空即该条有效 |
-- 只看有问题的行
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 库,其他一律拒绝」:
# 反例:顺序错误
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 永远不会被命中——应用直接全挂。正确的写法是把具体规则放前面、兜底规则放最后:
# 正例:先具体后兜底
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」就能拒绝),它的价值在于在白名单内部挖洞:
# 运维网段整体放行,但先拒绝已离职/高危账号
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 时最常见的错误是「图省事写大网段」:
# 危险: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。选型先做减法:能不引入的组件就不引入。
另一种实用视角是按连接来源分派方法,让每种来源走各自最合理的方法:
# 本机运维 → 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 同目录),做一次身份翻译:
# pg_ident.conf:映射名 外部身份 数据库角色
osmap postgres postgres
osmap deploy app_migrator# 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 是原子的:解析成功则整套规则一次性替换,解析失败则保持旧配置继续生效,同时在日志里记录错误。这保证了改坏文件不会让新规则半生不熟地生效。但有两点要主动做:
- reload 后必须确认日志没有解析错误,否则你以为生效的新策略其实没生效。
- 保留回滚通道。改配置前备份
cp pg_hba.conf pg_hba.conf.bak.$(date +%F);更稳妥的做法是在改动前用pg_hba_file_rules确认新内容全部无 error,再触发 reload。
-- reload 后的例行确认
SELECT pg_reload_conf();
SELECT rule_number, error FROM pg_hba_file_rules WHERE error IS NOT NULL;# 同时看服务端日志确认加载结果
tail -50 $PGDATA/log/postgresql-*.log | grep -i 'hba\|authentication'错误排查:no pg_hba.conf entry for host
这是 PostgreSQL 最著名的报错之一,完整形态类似:
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)。排查按固定套路走:
- 确认错误里的四元组。host 是服务器看到的客户端地址(经 NAT 后可能不是你以为的地址);user 是数据库角色名不是 OS 用户名;PG14+ 的
no encryption提示说明连接没走 SSL,hostssl记录对它无效。 - 用完全相同的四元组去走查规则。按
pg_hba_file_rules的rule_number顺序,假装自己是那条连接,逐条问「我会不会被这条匹配」。 - 重点检查三类问题:地址段没覆盖(NAT 后网段不同 / 只配了 IPv4 但连接来自
::1)、用户名大小写或拼写、数据库字段写了all却连的是复制连接(replication不被all匹配)。 - 确认 reload 真的执行过。改了文件没 reload,是「我以为我改了」的头号来源。
- 确认匹配到的认证方法本身可完成。比如记录写的是
peer但连接来自 TCP——peer只能匹配local,TCP 连接会继续往下找,找不到就报这个错。
-- 复现客户端视角:从服务器上模拟一次同参数连接
-- 若能连上而应用连不上,差异通常在地址(NAT)或加密状态# 从应用主机测试,拿到服务器视角的真实地址
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 模板
一份按「本机管理 → 监控 → 复制 → 业务网段 → 兜底」分层组织的模板,可直接作为起点:
# ============================================================
# 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配套的映射文件:
# pg_ident.conf
# MAPNAME SYSTEM-USERNAME PG-USERNAME
os_admin postgres postgres
os_admin backup dbbackup
os_admin root dba_admin示例二:变更前的预检与灰度
改 HBA 的标准动作流:备份 → 编辑 → 预检 → reload → 验证。
# 1. 备份
cp $PGDATA/pg_hba.conf $PGDATA/pg_hba.conf.bak.$(date +%F-%H%M)
# 2. 编辑(任何方式),然后预检——在 psql 里确认没有 error-- 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;# 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 不会记录「这条连接命中了第几条规则」,但可以用「复制文件 + 删减法」快速定位:
-- 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'# 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 形态,适合配置管理与多人协作:
# /etc/postgresql/18/main/pg_hba.conf —— 主文件只留顺序骨架
include_dir /etc/postgresql/18/main/pg_hba.d# pg_hba.d/00-local.conf
local all postgres peer
local all all peer map=os_admin# pg_hba.d/10-replication.conf
hostssl replication repl 10.0.0.0/24 scram-sha-256# 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# pg_hba.d/99-deny.conf —— 兜底永远在最后
local all all reject
host all all 0.0.0.0/0 reject
host all all ::/0 reject-- 验证拆分后的顺序与来源(file_name 列显示每条规则来自哪个文件)
SELECT rule_number, file_name, type, database, user_name, auth_method
FROM pg_hba_file_rules ORDER BY rule_number; 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 里要用双引号写。
最佳实践
- 生产环境禁用
trust,本机管理用peer+ 映射。trust只允许出现在一次性测试实例上;本机管理的标准姿势是local ... peer map=...,把允许的 OS 用户显式列入映射表。 - 密码认证一律用
scram-sha-256。md5与password只作为兼容层或 SSL 内特例存在,并制定迁移计划(见下一页)。 - 远程记录一律写
hostssl而不是host。 把「加密与否」的决定权从客户端收回到服务端,配合 16.3 的强制 SSL 策略使用。 - 用
reject把超级用户挡在业务网段之外。 在业务网段的 allow 规则之前加一条host all postgres <业务网段> reject,成本一行,收益巨大。 - 网段按「最小需要」收敛。 用
/32//24而不是/16;用samehost表达「本机地址」;DNS 主机名匹配仅在不信任 CIDR 能力且 DNS 强管控的环境使用。 - 兜底
reject放文件最后,并显式覆盖 IPv4 与 IPv6。0.0.0.0/0与::/0各一条,让「默认拒绝」从隐含行为变成显式规则,审计时一眼可见。 - 所有变更走「备份 → 预检 → reload → 查日志 → 双向验证」五步。 预检用
pg_hba_file_rules的error字段;双向验证指「该连得上的连得上、该被拒的被拒」。 - 用
include_dir拆分配置并固化顺序。 主文件只保留 include 与兜底,业务规则按NN-name.conf编号管理,99-deny.conf永远排最后。 - HBA 文件纳入版本管理并做漂移巡检。
SHOW hba_file;确认实际路径,定期 diff 线上文件与仓库版本,防止「手改未入库」。 - 复制与监控账号单独成节维护。
replication连接、监控探针账号的规则独立分组并注释说明,避免业务规则调整时误伤。 - 在 HBA 之外叠加网络层隔离。 HBA 是「应用层白名单」,防火墙/安全组是「网络层白名单」,两者叠加才有纵深;不要把 HBA 当防火墙用。
- 为排错保留一条「带证书」的备用通道。 堡垒机网段配
scram-sha-256 clientcert=verify-ca之类的双因素规则,确保 HBA 改坏时仍有一条已验证的管理入口。
下一节
参考链接
- PostgreSQL 官方文档:The pg_hba.conf File
- PostgreSQL 官方文档:Authentication Methods
- PostgreSQL 官方文档:User Name Maps
- PostgreSQL 官方文档:Authentication Problems
- PostgreSQL 官方文档:pg_hba_file_rules 视图
- PostgreSQL 官方文档:Connection and Authentication 配置参数
- PostgreSQL 官方文档:File Locations(hba_file / ident_file)
- PostgreSQL 官方文档:Server Applications(pg_ctl)
- PostgreSQL 官方文档:Configuration Settings(include 机制总览)
- PostgreSQL 官方文档:CREATE ROLE