peer / ident 认证
peer 与 ident 是两种「不设密码、直接借用操作系统用户名」的认证方法:客户端不提交任何凭据,服务器转而去问「这条连接背后是哪个 OS 用户」,并要求该用户名与目标数据库角色一致(或经 pg_ident.conf 映射后一致)。它们解决的是一个非常具体的场景——同一台机器上的本地访问:DBA 在服务器上执行 psql、部署工具与备份脚本直连本机实例、PgBouncer 等同机组件回源数据库。在这些场景里,peer 是最自然的方案:没有密码要保管,没有密码能泄露,身份由操作系统背书。
两者的差异在于「向谁问身份」。peer 只用于 Unix domain socket 连接,直接通过内核接口(Linux 的 SO_PEERCRED)读取 socket 对端的真实用户 ID——这个答案由操作系统提供,客户端进程无法伪造,因此是可信的。ident 用于 TCP 连接,做法是回连客户端主机的 ident 服务(RFC 1413,默认 113 端口)询问「这个端口的连接属于哪个用户」——答案由客户端主机上的一个服务提供,客户端可以随意伪造,因此只有在客户端主机本身受控的内网里才有意义,现代环境基本已被淘汰。
本页的目标很明确:把 peer 用对、用好(包括它与 pg_ident.conf、sudo、脚本、连接池的配合),同时讲清楚 ident 为什么不值得新部署、遇到遗留环境时如何安全地处理它。身份映射文件 pg_ident.conf 的完整语法(含 PG15 起的正则支持)也在本页展开——它在 peer/ident/gss/cert 等所有「外部身份」方法中通用。
前置知识
建议先阅读:证书认证
基础概念
peer 与 ident 的对比
| 维度 | peer | ident |
|---|---|---|
| 适用连接 | 仅 Unix domain socket(local 记录) | 仅 TCP(host 系列记录) |
| 身份来源 | 内核提供的 socket 对端凭证 | 客户端主机上的 ident 服务应答 |
| 可信度 | 高(OS 级凭证,无法伪造) | 低(应答由客户端侧软件生成,可伪造) |
| 网络往返 | 无 | 有(回连客户端 113 端口) |
| 现代定位 | 本地管理的默认方案 | 遗留协议,仅受控内网 |
| 认证选项 | map= | map= |
一句话:能走 socket 就用 peer,需要 TCP 就别用 ident(改用 scram/证书/Kerberos)。ident 在现代文档中的价值主要是「读懂老配置、处理老系统」。
身份等价规则
两种方法的判定逻辑一致:从 OS 拿到用户名后——
- HBA 记录没有
map=时,要求「OS 用户名 == 数据库角色名」(遵循标识符大小写折叠规则,未加引号的Postgres与postgres视为同名); - HBA 记录有
map=时,按pg_ident.conf中该映射的规则把 OS 用户名翻译成数据库角色,翻译不上的组合一律拒绝。
一次 peer 认证的完整时序
把判定逻辑画成时序,排错时可以按图索骥:
OS 用户 backup PostgreSQL 服务器
│ │
│ psql -h /var/run/postgresql -U dbbackup │
│ ──① 建立 Unix socket 连接 ──────────────► │
│ │ ② 内核接口读取对端凭证 → OS 用户 backup
│ │ ③ pg_hba 自上而下匹配 local 记录
│ │ local app all peer map=osmap
│ │ ④ 查 pg_ident.conf:osmap / backup → dbbackup
│ ◄──⑤ 会话以数据库角色 dbbackup 建立 ──────│
│ │四步中的任何一步失败都会直接终止连接:① socket 目录不可达(No such file or directory);② 取不到凭证(正常环境不会发生);③ 没有匹配记录(no pg_hba.conf entry for host "[local]");④ 映射不命中(Peer authentication failed)。另外注意连接串里写的 -U dbbackup 是目标角色名,peer 校验的是「OS 用户经映射后能否成为这个角色」——写错目标角色同样在第④步失败。
为什么 peer 可信而 ident 不可信
peer 的取信机制在内核:进程往 socket 写数据时,内核把自己的 UID 附在连接元数据上,服务器用 SO_PEERCRED(Linux)/getpeereid(BSD)读取。客户端进程无法声明「我是 root」——内核只认它的真实凭证。
ident 的问答发生在应用层:PostgreSQL 向「客户端 IP:113」发一句查询,客户端主机上的 ident 软件回复用户名。谁控制了客户端主机(哪怕只是一个可执行文件),谁就能回复任意答案。这就是 ident 时代「内网里人人可信」假设的遗产——放在今天,它等价于「认证结果由被认证对象自己宣布」。
语法与用法
pg_hba 记录
# TYPE DATABASE USER ADDRESS METHOD
local all postgres peer
local all all peer map=os_admin
host all all ::1/128 ident map=identmappeer 只能配在 local 记录上(它根本没有 TCP 版本);ident 只能配在 host 系列记录上。两者的可选参数都只有 map=。
方法与连接类型的匹配矩阵
两种方法对记录类型有硬性要求,写错组合在预检阶段就会暴露:
| 记录类型 | peer | ident | 说明 |
|---|---|---|---|
local | ✓ 正确组合 | ✗ 预检报错 | peer 的唯一舞台是 socket |
host / hostssl | ✗ 预检报错 | ✓ 正确组合 | ident 的唯一舞台是 TCP |
hostgssenc / hostnossl | ✗ | ✓ | TCP 家族记录都可用于 ident |
矩阵的两端都强调同一件事:身份来源决定连接类型。peer 要问内核要凭证,内核只在 Unix socket 上提供这个信息;ident 要回连客户端的 113 端口,只有 TCP 连接才有「客户端 IP」可回连。实战里偶尔见到把 peer 写在 host 记录上的配置——pg_hba_file_rules.error 会给出明确报错,属于「改完必看预检」能拦截的低级错误。
ident 记录的完整形态与选项细节
ident 记录挂在 host 家族记录上,与 peer 共享同一个映射机制,但网络行为完全不同:
# 仅对来自受控网段的连接启用 ident
host app all 10.20.1.0/24 ident
host app all 10.20.1.0/24 ident map=identmap
# 常见搭配:先给更安全的记录留位置,ident 兜底老设备
hostssl app all 10.20.0.0/16 scram-sha-256
host app all 10.20.1.0/24 ident map=identmap运行时细节:
- 服务器会主动向「客户端 IP:113」发起第二条 TCP 连接完成问答,这条连接可能被客户端防火墙静默丢弃,认证要等到
authentication_timeout(默认 1 分钟)才失败; - 应答中的 OS 用户名按与 peer 相同的规则处理:无
map=时要求与数据库角色一致,有map=时按映射翻译; - 一次认证失败发生在 ident 环节时,日志会记录 ident 交互的相关信息——排错时结合客户端侧
ss -ltnp | grep 113确认 ident 服务是否真的在监听。
pg_ident.conf:三列映射
映射文件(路径由 ident_file 参数指定,默认与 pg_hba.conf 同目录)每行三列:
# MAPNAME SYSTEM-USERNAME PG-USERNAME
os_admin postgres postgres
os_admin backup dbbackup
os_admin deploy app_migrator- 第一列是映射名,被 pg_hba 记录的
map=引用; - 第二列是 OS 侧用户名(
peer/ident场景即 OS 用户); - 第三列是允许登录的数据库角色。
一个映射名下可以有多行——每一行都是一条独立的「授权」,没有命中的组合被拒绝。映射因此天然是一张白名单。
正则映射(PG15+)
PG15 起,第二列以 / 开头时按正则表达式处理(要求对整个用户名完整匹配),第三列可用 ${1} 之类引用捕获组:
# MAPNAME SYSTEM-USERNAME PG-USERNAME
svc_map /^svc_(\w+)$/ ${1}
web_map /^(.*)@example\.com$/ web_\1第一条把 OS 用户 svc_etl 映射为数据库角色 etl,适合「服务账号统一加前缀」的团队约定;第二条展示捕获组拼接的写法(${1} 是 PG16 文档起的标准写法,PG15 早期版本使用 \1,升级实例时注意核对)。正则映射大大减少了「一个映射名下几十行直通规则」的维护负担,但可读性随之下降——团队约定里应规定:简单映射优先,正则必须带注释。
引用映射:map= 选项
local app all peer map=os_admin预检时映射问题同样会显形:pg_hba_file_rules 的 options 列会显示 map=os_admin;而映射文件本身的行不会被预检(pg_hba_file_rules 只管 pg_hba.conf),映射条目的 typo 要靠实际连接测试暴露。
Unix socket 目录
peer 只对 socket 连接生效,而 socket 的位置由 unix_socket_directories 决定:
# postgresql.conf
unix_socket_directories = '/var/run/postgresql, /tmp'默认值是 /tmp;Debian/Ubuntu 的打包把它改为 /var/run/postgresql。多实例可以监听多个 socket 目录,客户端用 psql -h /var/run/postgresql 显式选择。注意 local 记录匹配所有 socket 目录上的连接,HBA 层面无法按 socket 路径区分规则(见 pg_hba.conf 详解 一页的说明)。
详细说明
peer 的取信边界:同机即可信?
peer 的安全模型是「socket 对端的 OS 用户 = 数据库身份」,它的可信度完全继承自操作系统。这意味着两条边界:
- 能以该 OS 用户执行进程,就等于拿到数据库角色。
sudo -u backup psql ...与「backup OS 用户的 crontab」在 peer 的世界里拥有同等权力。OS 层的账号治理(sudoers、shell 权限)成为数据库安全的一部分。 - socket 目录的权限就是门禁。
/var/run/postgresql若是0777,任何本机用户都能发起连接(虽然后续还有角色匹配与映射把关);把 socket 目录权限收紧到0770+ 限定组,可以在「连接发起」这一层再做一次收敛。
Debian/Ubuntu 的默认约定
官方 Debian 包的默认 pg_hba.conf 以这行打头:
local all postgres peer它的含义:OS 用户 postgres 可以以数据库角色 postgres 通过 socket 无密码登录全部数据库。系统管理脚本 sudo -u postgres psql ... 依赖的正是这行。理解了这个默认约定,很多「为什么在 Debian 系机器上 psql -U postgres 报错、sudo -u postgres psql 却正常」的问题就迎刃而解——前者是普通用户想冒充 postgres 角色,peer 忠实地拒绝了它。
映射的四种典型用法
pg_ident.conf 在 peer 场景下的常见形态:
# ① 直通白名单:允许这些 OS 用户连同名角色
osmap postgres postgres
osmap backup backup
# ② 换名映射:OS 用户名与数据库角色命名规范不同
osmap deploy app_migrator
# ③ 组式授权:多个 OS 用户共享一个运维角色
osmap dba1 oncall_dba
osmap dba2 oncall_dba
# ④ 正则批量:服务账号前缀规范
osmap /^svc_(\w+)$/ ${1}注意第③种:多个 OS 用户映射到同一个数据库角色是合法且常用的——数据库侧的权限与审计围绕角色进行,谁在什么时候用了这个角色要看 PostgreSQL 连接日志与 OS 层 sudo 日志的两边对账。
与其他认证方法的协作布局
peer 从来不是「全局方案」,它是认证版图里的本机层。一台生产实例的典型 HBA 布局是三种方法各守一层:
# ── 第 1 层:本机(socket + peer)──────────────
local all postgres peer
local app,appetl all peer map=osmap
# ── 第 2 层:内网 TCP(加密 + 密码/证书)───────
hostssl replication repl 10.0.0.0/24 scram-sha-256
hostssl app app_rw 10.0.1.0/24 scram-sha-256
hostssl app +dba 10.0.9.0/24 scram-sha-256 clientcert=verify-ca
# ── 第 3 层:兜底 ──────────────────────────────
host all all 0.0.0.0/0 reject
host all all ::/0 reject
local all all reject这套布局里每层的安全假设不同:peer 信任「本机 OS 的用户治理」,hostssl+scram 信任「密码 + 加密管道」,clientcert 叠加「持有私钥」。DBA 在本机用 peer 快速操作,远程走加密密码,自动化走证书——方法的多样性是按来源设计的,不是随手混搭。评审任何新连接需求时,先问「它属于哪一层」,再决定用哪种方法。
ident 的协议细节与信任前提
ident(RFC 1413)的交互:PostgreSQL 向客户端 IP 的 113 端口发送「服务器端口, 客户端端口」询问,客户端主机上的 ident 守护进程回答「该连接属于哪个 OS 用户」。它诞生的年代假设是「客户端主机由负责任的管理员维护,ident 服务如实作答」。这个假设在现代彻底不成立:任何客户端都可以运行一个假 ident 服务回配合适的答案,或者干脆在防火墙上丢弃 113 请求(这时认证超时失败)。
因此 ident 的正确打开方式只有一种:客户端主机本身在严格管控下(例如某批只能运行受控镜像的堡垒/批处理机),且网络层只允许 PostgreSQL 服务器访问它们的 113 端口。满足不了这两条,就应当把 ident 记录改写成 scram/证书方案。PG 文档对 ident 的评价是「安全上可疑(dubious)」,新部署没有理由选它。
映射文件里的标识符与大小写细节
pg_ident.conf 的第二、三列遵循 PostgreSQL 标识符规则,这个细节决定了不少「看起来应该匹配却没匹配」的问题:
- 未加引号的名字折叠为小写:映射行
osmap Backup DBBackup实际匹配的是 OS 用户backup到角色dbbackup; - 要匹配大写必须加双引号:
osmap "Backup" "DBBackup"才是字面匹配(前提是 OS 侧真的存在大写用户名,Linux 用户名惯例是小写,实践中很少走到这一步); - 映射名的匹配同样折叠:pg_hba 里的
map=OSMAP与文件里的osmap是同一个映射。
另一个容易忽略的规则:一条连接只做一次映射查找。pg_hba 记录中的用户字段写的是「映射后的角色」,不要误以为「用户字段先过滤、映射再做翻译」是两个连续的过滤器——实际顺序是:连接的目标角色必须等于映射结果(或映射结果授权它),两者在同一个判定里完成。
容器与 sidecar 场景的 peer
容器化部署给 peer 带来新的适用面,也带来新的坑。典型形态:应用容器与数据库 sidecar(或独立 PostgreSQL 容器)共享 socket 卷,应用进程经 socket + peer 免密连库:
# 概念示意:共享 socket 的 emptyDir 卷
volumes:
- name: pgsock
emptyDir: {}
containers:
- name: postgres
volumeMounts: [{ name: pgsock, mountPath: /var/run/postgresql }]
- name: app
volumeMounts: [{ name: pgsock, mountPath: /var/run/postgresql }]三个必须核对的点:
- UID 一致性:peer 认的是 UID 而不是用户名符号。容器里 postgres 用户通常是 UID 70(官方镜像)或 26(部分发行版镜像),应用容器里的运行用户若想直通同名角色,其 UID 必须与预期一致——镜像间 UID 冲突是容器场景 peer 失败的第一大原因;
- socket 目录权限:共享卷的 fsGroup/挂载权限要允许双方读写 socket 文件;
- 同名不同人:多个容器把不同进程跑成同一 UID(如都是 root)时,peer 无法区分它们——本页前面说的「OS 用户被攻破等于角色被攻破」在容器里被放大,适用面要更克制。
客户端如何「选对」socket
local 记录匹配 socket 连接,而客户端要命中 socket,需要用「以 / 开头的主机名」告诉 libpq 走 socket 而不是 TCP:
# 三种等价的本机 socket 连接方式
psql -h /var/run/postgresql app # 显式指定 socket 目录
psql app # PGHOST 未设置时的默认行为:
# libpq 依序尝试 unix_socket_directories
PGHOST=/var/run/postgresql psql app # 环境变量方式查找顺序的细节:客户端不带 -h(且 PGHOST 未设)时,libpq 会按编译内置的默认 socket 目录列表逐个尝试;服务端 unix_socket_directories 配了多个目录时,每个目录上都有一个 socket,先找到哪个就连哪个。因此「本机脚本」的稳妥写法是显式 -h 指定目录,避免「哪天打包改了默认目录、脚本悄悄连去另一个实例」。
本机连接的日志与审计
peer 场景下「免密」不等于「免审计」,反而因为身份来源确定,更值得记录。把 log_connections 打开后,本机连接会产生这样的日志行:
LOG: connection received: host=[local]
LOG: connection authorized: user=dbbackup database=app application_name=pg_dump审计要点:
host=[local]标明这是 socket 连接,结合角色名即可反查「哪个 OS 用户在什么时间连了库」(OS 用户名不进日志,需对照映射文件与映射台账);application_name建议在运维脚本里显式设置(PGAPPNAME=nightly-backup),让 pg_stat_activity 与日志里的业务归因更清晰;- 高敏感实例可开启
log_connections = all(连接日志量可控)并配合log_disconnections,形成「连接对」记录。
ident 遗留环境的替代路径对比
决定淘汰 ident 时,按客户端形态选替代方案:
| 客户端形态 | 推荐替代 | 理由 |
|---|---|---|
| 受控批处理机上的脚本 | 证书(cert)或 SCRAM | 机器场景证书免密最干净 |
| 人从跳板机访问 | SSH 登录数据库主机后 socket + peer | 身份由 SSH 认证 + OS 用户承接,零新组件 |
| 跨机应用直连 | scram-sha-256 + hostssl | 标准方案,配置面最小 |
| 域内工作站 | Kerberos(hostgssenc + gss) | 有域基础设施时 SSO 体验最好 |
「SSH 到主机 + peer」这条路径特别值得一提:它把跨机认证问题转化为本机认证问题(SSH 层已经完成强认证),DBA 的日常操作体验与 ident 时代一致,但信任根基从「客户端自报家门」换成了「SSH 密钥 + OS 用户」,安全性天差地别。
peer 与 sudo 的配合
# 最常见的本机管理模式:以 postgres OS 用户执行
sudo -u postgres psql
# sudoers 细化:只允许运维组免密执行 psql
# %dba ALL=(postgres) NOPASSWD: /usr/bin/psql这套组合的审计链路是:sudo 日志(谁在什么时候提权)→ PostgreSQL 连接日志(哪个角色连了什么)。把 sudoers 的提权命令限制到具体二进制(如上例),比开放完整 shell 的 (postgres) ALL 更可控。
peer 与同机组件的配合
同机部署的组件(PgBouncer、pg_dumpall 定时任务、WAL 归档工具)以某个 OS 用户运行时,走 socket + peer 可以做到「组件配置里没有密码」:
; pgbouncer.ini:以 postgres OS 用户运行、回源走 socket
[databases]
app = host=/var/run/postgresql port=5432 user=postgres dbname=app
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432PgBouncer 以 postgres 用户启动时,经 socket 回源即可通过 peer 认证,后端密码完全不落地。同样的思路适用于本机备份脚本(见实战)。关于 PgBouncer 更全面的配置,见 PgBouncer 一页。
与多实例、多 socket 的相处
一台机器多实例时,local 记录不区分实例——两个实例共享同一份判定逻辑,区别只在各自的 pg_hba.conf。若实例 A 允许 peer 直通而实例 B 要求更严,务必确认每个实例的 hba_file 指向不同的文件,且运维文档写明「改哪个实例的配置」。用不同 socket 目录 + 客户端显式 -h 是把「连哪个实例」从端口号升级为路径的多一层显式化,误连率显著降低。
变更流程:peer 与映射的上线模板
peer 规则与 pg_ident 映射没有 pg_hba_file_rules 那样的预检视图,上线动作更要模板化:
# 1) 备份(带日期,便于回滚定位)
cp $PGDATA/pg_hba.conf{,.bak-$(date +%F)}
cp $PGDATA/pg_ident.conf{,.bak-$(date +%F)}
# 2) 预检 HBA(映射文件没有预检视图,靠人工逐行核对三列拼写)
psql -c "SELECT rule_number, error FROM pg_hba_file_rules WHERE error IS NOT NULL;"
psql -c "SELECT pg_reload_conf();"# 3) 双向验证:允许的方向能连,收紧的方向被拒
sudo -u backup psql -h /var/run/postgresql -d app -c "SELECT current_user;" # 应成功
psql -h /var/run/postgresql -d app -c "SELECT current_user;" # 应失败(若已收紧)回滚即恢复备份文件并再次 reload。这套动作的重心在双向验证:只验证「新允许的能连」而不验证「该拒的被拒」,是 peer 规则放开后越权事故的直接来源——映射写宽一行,授权面就多一格。
排错
| 报错 / 症状 | 根因方向 |
|---|---|
Peer authentication failed for user "postgres" | 当前 OS 用户与目标角色名不一致,且没有(或未命中)映射:换 sudo -u postgres 或补映射 |
no pg_hba.conf entry for host "[local]", user ... | 该角色的 local 记录不存在或顺序被前面规则拦截 |
socket 连接报 No such file or directory | unix_socket_directories 与客户端 -h 路径不一致 |
| map 配了但总是认证失败 | 映射名 typo;映射行格式错(多余空格列);正则没有完整匹配 |
| ident 总是超时 | 客户端 113 端口被防火墙拦截,或无 ident 服务(现代系统常态) |
# 确认客户端与服务器眼中的身份
whoami # OS 用户
sudo -u postgres psql -c "SELECT current_user, session_user;"# 确认 socket 实际位置与连接路径
ps -o cmd= -p $(head -1 $PGDATA/postmaster.pid) | grep -o 'unix_socket_directories=[^ ]*'
psql -h /var/run/postgresql -c "SELECT 1;"实战示例
示例一:本机管理访问(peer + 映射白名单)
目标:postgres 直通;backup 与 deploy 两个 OS 用户分别映射到专用角色;其余本机用户一律拒绝。
# pg_hba.conf
local all postgres peer
local app,appetl all peer map=osmap
local all all reject# pg_ident.conf
# MAPNAME SYSTEM-USERNAME PG-USERNAME
osmap postgres postgres
osmap backup dbbackup
osmap deploy app_migrator-- 数据库侧准备角色
CREATE ROLE dbbackup LOGIN;
GRANT pg_read_all_data TO dbbackup;
CREATE ROLE app_migrator LOGIN;# 生效与验证
psql -c "SELECT rule_number, error FROM pg_hba_file_rules WHERE error IS NOT NULL;"
psql -c "SELECT pg_reload_conf();"
sudo -u postgres psql -c "SELECT current_user;" # → postgres
sudo -u backup psql -c "SELECT current_user;" # → dbbackup
psql app -c "SELECT 1;" # 普通用户 → 被拒FATAL: pg_hba.conf rejects connection for host "[local]",
user "chris", database "app"示例二:无密码的本机备份脚本
#!/usr/bin/env bash
# /usr/local/bin/pg-nightly-backup —— 由 OS 用户 backup 的 crontab 运行
set -euo pipefail
DUMP=/var/backups/pg/app-$(date +%F).dump
pg_dump -h /var/run/postgresql -U dbbackup -Fc -f "$DUMP" app
find /var/backups/pg -name 'app-*.dump' -mtime +14 -delete要点核对:
- crontab 属于
backupOS 用户,进程的真实 UID 就是它,peer 认证通过映射拿到dbbackup角色; - 脚本与 crontab 里没有密码;
pg_dump不带-W(交互提示在 cron 中会导致任务挂起); dbbackup角色的权限只覆盖备份所需(读数据),泄密影响面最小。
# 手工试跑验证
sudo -u backup /usr/local/bin/pg-nightly-backup && ls -lh /var/backups/pg/示例三:处理遗留的 ident 环境
接手一台还在用 ident 的老实例,先评估、后替换:
# 老配置
host all all 10.20.0.0/16 ident# 1) 确认客户端侧 ident 是否真的存在且可信
nc -vz -w 3 10.20.1.15 113 # 不通 → 该规则从未成功放行过任何人
# 2) 查日志确认实际命中情况
grep 'authentication' $PGDATA/log/postgresql-*.log | grep -c ident# 3) 替换方案:批处理机改走证书(机器场景)或 scram(脚本场景)
hostssl all all 10.20.0.0/16 scram-sha-256-- 4) 为迁移期账号设置密码并通知改造
ALTER ROLE legacy_job PASSWORD 'Tr@nsition2026';迁移窗口内可以先同时保留两条记录(ident 行放前面、scram 行放后面),按主机逐台切换客户端后删除 ident 行——HBA 的首个匹配语义让这种「新旧并存灰度」很自然。
示例四:CI 执行器与本机工具链的统一接入
一台兼具「跑 CI runner」与「本机运维」职责的机器:runner 以专用 OS 用户 ci 运行,映射到只读角色 ci_reader;运维走 postgres 直通;服务账号按前缀规范批量映射。三者互不越界。
# pg_hba.conf
local all postgres peer
local app all peer map=osmap
local all all reject# pg_ident.conf
# MAPNAME SYSTEM-USERNAME PG-USERNAME
osmap postgres postgres
osmap ci ci_reader
osmap /^svc_(\w+)$/ ${1} # 服务账号前缀规范:svc_xx → xx-- 数据库侧
CREATE ROLE ci_reader LOGIN;
GRANT USAGE ON SCHEMA public TO ci_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO ci_reader;
CREATE ROLE etl LOGIN; -- 供 svc_etl 正则映射使用
GRANT USAGE ON SCHEMA staging TO etl;# 逐个身份验证(成功 + 失败两个方向)
sudo -u postgres psql app -c "SELECT current_user;" # → postgres
sudo -u ci psql app -c "SELECT current_user;" # → ci_reader
sudo -u svc_etl psql app -c "SELECT current_user;" # → etl
sudo -u root psql app -c "SELECT 1;" # → FATAL: pg_hba.conf rejects ...FATAL: pg_hba.conf rejects connection for host "[local]",
user "root", database "app"这套布局的价值在审计侧一目了然:PostgreSQL 连接日志里的角色名(ci_reader、etl、postgres)与 OS 侧的 sudo 日志(谁提权成谁)两两对应,加上映射文件里的正则白名单,「谁能本机连库」这个问题有且只有一份答案。
示例五:pg_dumpall 全量逻辑备份走 peer
pg_dumpall 要读取全部数据库与全局对象(角色、表空间),通常以超级用户身份运行。用 peer 方案时,给它一个专用 OS 用户 + 映射,避免直接用 postgres:
# pg_ident.conf
osmap postgres postgres
osmap dbadmin postgres # dbadmin OS 用户 → postgres 角色(受控提权)# pg_hba.conf
local all postgres peer#!/usr/bin/env bash
# /usr/local/bin/pg-full-logical-backup —— 由 dbadmin OS 用户运行
set -euo pipefail
STAMP=$(date +%F)
DIR=/var/backups/pgall
install -d -m 0700 "$DIR"
# pg_dumpall 无需密码:peer 认证 + postgres 角色
PGAPPNAME=pg-dumpall-cron pg_dumpall -h /var/run/postgresql -U postgres \
-f "$DIR/globals-$STAMP.sql"
pg_dumpall -h /var/run/postgresql -U postgres --database-only \
-f "$DIR/databases-$STAMP.sql"
gzip "$DIR"/*-$STAMP.sql
find "$DIR" -name '*.sql.gz' -mtime +30 -delete设计说明:
dbadmin → postgres的映射行是受控的超级用户提权通道,必须配合 sudoers 把「谁能以 dbadmin 运行」限定到备份脚本(dbadmin ALL=(dbadmin) NOPASSWD: /usr/local/bin/pg-full-logical-backup);PGAPPNAME让备份连接在pg_stat_activity里一眼可辨,巡检时能区分备份流量与业务流量;- 备份产物里包含
ALTER ROLE ... PASSWORD语句(加密格式),备份目录0700+ 定期清理在脚本里一并处理。
注意事项
peer 认证失败的信息指向角色名而不是密码。 Peer authentication failed 与密码强度、pg_authid 无关,第一时间检查「当前 OS 用户是谁、映射怎么写」,别往密码方向排查。
socket 目录权限是 peer 方案的门面。 /var/run/postgresql 这类目录应由 postgres 属主、0775 或 0770 收紧;把 socket 放在 /tmp(默认值)的多用户机器上,任何用户都能发起连接尝试,靠映射兜底虽然安全但日志噪音很大。
unix_socket_directories 的修改需要重启。 它是只能在服务启动时设置的参数,改 postgresql.conf 后 reload 不生效;新 socket 目录还要确认目录存在、属主与权限正确,否则实例会带着「少一个 socket 入口」的配置直接重启失败或降级。
克隆/恢复出来的实例会原样带回 peer 规则。 用基础备份或文件级复制拉起的新实例,pg_hba/pg_ident 与源机一致——但新机器上的 OS 用户名单、组成员关系不一定相同,「源机上安全的规则」在新机器上可能放开了别人。克隆后把 peer/映射相关记录与目标主机的 OS 账户核对一遍,纳入恢复演练清单。
映射文件不在 pg_hba_file_rules 的预检范围内。 映射条目的错误(typo、列错位)只会在真实连接时暴露。变更映射后,用「成功 + 失败」两个方向的连接各测一次。
ident 应答的延迟会造成连接挂起。 客户端 113 端口被静默丢弃(而非拒绝)时,认证要等到 authentication_timeout(默认 1 分钟)才失败——多个这样的客户端并发足以占满 max_connections。遗留环境里这是「数据库间歇性卡死」的经典成因。
多 OS 用户映射到同一角色会稀释审计。 oncall_dba 这种共享角色的操作日志无法区分到人,必须与 sudo 日志、跳板机会话记录关联。对人使用的角色,尽量一人一角色。
peer 不适用于 TCP,ident 不适用于 socket。 记录类型写错(local + ident、host + peer)会在预检时报错(pg_hba_file_rules.error 有值),属于「改完必看预检」就能拦截的低级错误。
正则映射的替换语法有版本差异。 PG15 早期 \1、PG16 起 ${1};跨版本迁移配置前先在目标实例上做一次映射连通性测试,别把旧语法原样搬进新版本。
多 OS 用户共用 UID 会让 peer 失去区分度。 容器与 chroot 场景里,两个「不同用途」的进程可能跑在同一个 UID 下——peer 只认 UID,无法区分。给需要区分身份的进程分配独立 UID,是最省事的前提。
peer 的信任域包含整个 OS 用户空间。 任何能以该 UID 执行命令的机制(sudo、su、setuid 程序、cron、systemd 服务)都等价于数据库角色持有者。审查 peer 记录时要连同 /etc/sudoers、crontab、systemd unit 一起审。
不要用 peer 给应用做「免密」优化。 应用进程若与本机 socket 同机,确实可以 peer 免密,但这把「OS 用户被攻破」与「数据库被攻破」焊死在同一信任域里。应用连接保持密码/证书,peer 留给运维与组件。
最佳实践
- 本机管理一律
local ... peer(或 peer + 映射),并保留local all postgres peer约定。 这是 Debian/Ubuntu 生态的既成事实,工具链(pg_ctlcluster、sudo -u postgres)都建立在其上。 - 映射文件当白名单维护,而不是当翻译器。 只把确实需要本机访问的 OS 用户写进映射;「先全放行再说」的映射文件等于没有认证。
- socket 目录显式配置并收紧权限。
unix_socket_directories写明路径,目录属主 postgres、权限0770/0775,避免落在多用户可写的/tmp。 - sudo 提权限定到具体命令。 sudoers 用
(postgres) NOPASSWD: /usr/bin/psql而非ALL,让「谁能以 postgres 身份做什么」有明确清单。 - cron/脚本的 peer 场景:一人一角色 + 最小权限。 备份用只读角色、清理任务用专用角色,不要共用 postgres 超级用户跑定时任务。
- 新部署禁用 ident。 任何需要跨机的免密认证,用证书(机器)或 Kerberos(人)替代;ident 只在「客户端主机完全受控 + 网络层限定 113」的遗留条件下短期保留。
- 遗留 ident 迁移用「新旧记录并存」灰度。 利用 HBA 首个匹配语义,先加 scram/证书记录,逐台切换客户端,最后删 ident 行并从预检与日志双侧确认无残留流量。
- 映射变更纳入与 HBA 同级的评审流程。
pg_ident.conf是认证面的一部分,改它和改 pg_hba.conf 一样要走「备份 → 评审 → reload → 双向验证」。 - 本机组件(PgBouncer、归档工具)优先 socket + peer 回源。 组件配置里不出现后端密码,安全审计时少一类「配置里的明文凭据」。
- 在连接日志中区分 peer/ident 与密码认证的流量。 高频认证失败的来源若是 peer(OS 用户与角色不匹配),通常是运维误操作而非攻击——排障优先级与处理方式都不同。
下一节
参考链接
- PostgreSQL 官方文档:Authentication Methods(peer / ident)
- PostgreSQL 官方文档:User Name Maps(pg_ident.conf)
- PostgreSQL 官方文档:The pg_hba.conf File
- PostgreSQL 官方文档:Authentication Problems
- PostgreSQL 官方文档:Connection and Authentication 配置参数(unix_socket_directories)
- PostgreSQL 官方文档:Database Connection Control Functions(连接参数)
- PostgreSQL 官方文档:File Locations(ident_file / hba_file)
- PostgreSQL 官方文档:CREATE ROLE