Skip to content

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 的对比 ​

维度peerident
适用连接仅 Unix domain socket(local 记录)仅 TCP(host 系列记录)
身份来源内核提供的 socket 对端凭证客户端主机上的 ident 服务应答
可信度高(OS 级凭证,无法伪造)低(应答由客户端侧软件生成,可伪造)
网络往返无有(回连客户端 113 端口)
现代定位本地管理的默认方案遗留协议,仅受控内网
认证选项map=map=

一句话:能走 socket 就用 peer,需要 TCP 就别用 ident(改用 scram/证书/Kerberos)。ident 在现代文档中的价值主要是「读懂老配置、处理老系统」。

身份等价规则 ​

两种方法的判定逻辑一致:从 OS 拿到用户名后——

  1. HBA 记录没有 map= 时,要求「OS 用户名 == 数据库角色名」(遵循标识符大小写折叠规则,未加引号的 Postgres 与 postgres 视为同名);
  2. HBA 记录有 map= 时,按 pg_ident.conf 中该映射的规则把 OS 用户名翻译成数据库角色,翻译不上的组合一律拒绝。

一次 peer 认证的完整时序 ​

把判定逻辑画成时序,排错时可以按图索骥:

text
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 记录 ​

text
# TYPE   DATABASE   USER      ADDRESS   METHOD
local    all        postgres            peer
local    all        all                 peer map=os_admin
host     all        all       ::1/128   ident map=identmap

peer 只能配在 local 记录上(它根本没有 TCP 版本);ident 只能配在 host 系列记录上。两者的可选参数都只有 map=。

方法与连接类型的匹配矩阵 ​

两种方法对记录类型有硬性要求,写错组合在预检阶段就会暴露:

记录类型peerident说明
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 共享同一个映射机制,但网络行为完全不同:

text
# 仅对来自受控网段的连接启用 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 同目录)每行三列:

text
# 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} 之类引用捕获组:

text
# 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= 选项 ​

text
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 决定:

conf
# 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 以这行打头:

text
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 场景下的常见形态:

text
# ① 直通白名单:允许这些 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 布局是三种方法各守一层:

text
# ── 第 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 免密连库:

yaml
# 概念示意:共享 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 }]

三个必须核对的点:

  1. UID 一致性:peer 认的是 UID 而不是用户名符号。容器里 postgres 用户通常是 UID 70(官方镜像)或 26(部分发行版镜像),应用容器里的运行用户若想直通同名角色,其 UID 必须与预期一致——镜像间 UID 冲突是容器场景 peer 失败的第一大原因;
  2. socket 目录权限:共享卷的 fsGroup/挂载权限要允许双方读写 socket 文件;
  3. 同名不同人:多个容器把不同进程跑成同一 UID(如都是 root)时,peer 无法区分它们——本页前面说的「OS 用户被攻破等于角色被攻破」在容器里被放大,适用面要更克制。

客户端如何「选对」socket ​

local 记录匹配 socket 连接,而客户端要命中 socket,需要用「以 / 开头的主机名」告诉 libpq 走 socket 而不是 TCP:

bash
# 三种等价的本机 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 打开后,本机连接会产生这样的日志行:

text
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 的配合 ​

bash
# 最常见的本机管理模式:以 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 可以做到「组件配置里没有密码」:

ini
; 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 = 6432

PgBouncer 以 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 那样的预检视图,上线动作更要模板化:

bash
# 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();"
bash
# 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 directoryunix_socket_directories 与客户端 -h 路径不一致
map 配了但总是认证失败映射名 typo;映射行格式错(多余空格列);正则没有完整匹配
ident 总是超时客户端 113 端口被防火墙拦截,或无 ident 服务(现代系统常态)
bash
# 确认客户端与服务器眼中的身份
whoami                                    # OS 用户
sudo -u postgres psql -c "SELECT current_user, session_user;"
bash
# 确认 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 用户分别映射到专用角色;其余本机用户一律拒绝。

text
# pg_hba.conf
local   all             postgres            peer
local   app,appetl      all                 peer map=osmap
local   all             all                 reject
text
# pg_ident.conf
# MAPNAME   SYSTEM-USERNAME   PG-USERNAME
osmap       postgres          postgres
osmap       backup            dbbackup
osmap       deploy            app_migrator
sql
-- 数据库侧准备角色
CREATE ROLE dbbackup LOGIN;
GRANT pg_read_all_data TO dbbackup;
CREATE ROLE app_migrator LOGIN;
bash
# 生效与验证
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;"                              # 普通用户 → 被拒
text
FATAL:  pg_hba.conf rejects connection for host "[local]",
        user "chris", database "app"

示例二:无密码的本机备份脚本 ​

bash
#!/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 属于 backup OS 用户,进程的真实 UID 就是它,peer 认证通过映射拿到 dbbackup 角色;
  • 脚本与 crontab 里没有密码;pg_dump 不带 -W(交互提示在 cron 中会导致任务挂起);
  • dbbackup 角色的权限只覆盖备份所需(读数据),泄密影响面最小。
bash
# 手工试跑验证
sudo -u backup /usr/local/bin/pg-nightly-backup && ls -lh /var/backups/pg/

示例三:处理遗留的 ident 环境 ​

接手一台还在用 ident 的老实例,先评估、后替换:

text
# 老配置
host  all  all  10.20.0.0/16  ident
bash
# 1) 确认客户端侧 ident 是否真的存在且可信
nc -vz -w 3 10.20.1.15 113          # 不通 → 该规则从未成功放行过任何人
# 2) 查日志确认实际命中情况
grep 'authentication' $PGDATA/log/postgresql-*.log | grep -c ident
text
# 3) 替换方案:批处理机改走证书(机器场景)或 scram(脚本场景)
hostssl  all  all  10.20.0.0/16   scram-sha-256
sql
-- 4) 为迁移期账号设置密码并通知改造
ALTER ROLE legacy_job PASSWORD 'Tr@nsition2026';

迁移窗口内可以先同时保留两条记录(ident 行放前面、scram 行放后面),按主机逐台切换客户端后删除 ident 行——HBA 的首个匹配语义让这种「新旧并存灰度」很自然。

示例四:CI 执行器与本机工具链的统一接入 ​

一台兼具「跑 CI runner」与「本机运维」职责的机器:runner 以专用 OS 用户 ci 运行,映射到只读角色 ci_reader;运维走 postgres 直通;服务账号按前缀规范批量映射。三者互不越界。

text
# pg_hba.conf
local   all   postgres             peer
local   app   all                  peer map=osmap
local   all   all                  reject
text
# pg_ident.conf
# MAPNAME   SYSTEM-USERNAME   PG-USERNAME
osmap       postgres          postgres
osmap       ci                ci_reader
osmap       /^svc_(\w+)$/     ${1}     # 服务账号前缀规范:svc_xx → xx
sql
-- 数据库侧
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;
bash
# 逐个身份验证(成功 + 失败两个方向)
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 ...
text
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:

text
# pg_ident.conf
osmap   postgres   postgres
osmap   dbadmin    postgres        # dbadmin OS 用户 → postgres 角色(受控提权)
text
# pg_hba.conf
local   all   postgres   peer
bash
#!/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 留给运维与组件。

最佳实践 ​

  1. 本机管理一律 local ... peer(或 peer + 映射),并保留 local all postgres peer 约定。 这是 Debian/Ubuntu 生态的既成事实,工具链(pg_ctlcluster、sudo -u postgres)都建立在其上。
  2. 映射文件当白名单维护,而不是当翻译器。 只把确实需要本机访问的 OS 用户写进映射;「先全放行再说」的映射文件等于没有认证。
  3. socket 目录显式配置并收紧权限。 unix_socket_directories 写明路径,目录属主 postgres、权限 0770/0775,避免落在多用户可写的 /tmp。
  4. sudo 提权限定到具体命令。 sudoers 用 (postgres) NOPASSWD: /usr/bin/psql 而非 ALL,让「谁能以 postgres 身份做什么」有明确清单。
  5. cron/脚本的 peer 场景:一人一角色 + 最小权限。 备份用只读角色、清理任务用专用角色,不要共用 postgres 超级用户跑定时任务。
  6. 新部署禁用 ident。 任何需要跨机的免密认证,用证书(机器)或 Kerberos(人)替代;ident 只在「客户端主机完全受控 + 网络层限定 113」的遗留条件下短期保留。
  7. 遗留 ident 迁移用「新旧记录并存」灰度。 利用 HBA 首个匹配语义,先加 scram/证书记录,逐台切换客户端,最后删 ident 行并从预检与日志双侧确认无残留流量。
  8. 映射变更纳入与 HBA 同级的评审流程。 pg_ident.conf 是认证面的一部分,改它和改 pg_hba.conf 一样要走「备份 → 评审 → reload → 双向验证」。
  9. 本机组件(PgBouncer、归档工具)优先 socket + peer 回源。 组件配置里不出现后端密码,安全审计时少一类「配置里的明文凭据」。
  10. 在连接日志中区分 peer/ident 与密码认证的流量。 高频认证失败的来源若是 peer(OS 用户与角色不匹配),通常是运维误操作而非攻击——排障优先级与处理方式都不同。

下一节

继续学习:SSL 配置(ssl / ssl_cert_file)

参考链接 ​