跳至内容

23.6 Pigsty 安全基线

Pigsty 能把角色、HBA、服务入口、证书和日志参数声明化,但“声明化”不等于 “使用默认值即可满足任何生产 threat model”。官方安全说明明确指出,默认 配置面向可信内网中的开发、测试和演示;生产环境要按自身威胁模型配置凭据、 网络边界、认证、证书、备份和审计。

本节建立四层对账:

policy intent
  -> Pigsty inventory
      -> rendered files and service configuration
          -> PostgreSQL/PgBouncer runtime facts
              -> end-to-end positive and negative observations

只有最下面一层能证明客户端实际经历了什么;只有最上面一层能解释为什么这样 设计。

23.6.1 角色、HBA、证书与服务入口声明

角色声明

Pigsty 用:

pg_default_roles    环境级共享角色
pg_users            集群级业务角色/用户

用户按数组顺序创建,因此被引用的 group role 应先定义。下面是结构示例, 不是可直接投产的 secret 文件:

pg_users:
  - name: app_owner
    login: false
    superuser: false
    createdb: false
    createrole: false
    inherit: false
    replication: false
    bypassrls: false
    comment: "NOLOGIN owner for app objects"

  - name: app_runtime
    login: false
    inherit: false
    comment: "NOLOGIN normal DML capability"

  - name: app_readonly
    login: false
    inherit: false
    comment: "NOLOGIN read-only capability"

  - name: app_login
    login: true
    password: "<SCRAM-VERIFIER-FROM-PRIVATE-SECRET-PIPELINE>"
    superuser: false
    createdb: false
    createrole: false
    inherit: false
    replication: false
    bypassrls: false
    connlimit: 100
    roles:
      - {name: app_runtime, admin: false, inherit: false, set: true}
      - {name: app_readonly, admin: false, inherit: false, set: true}
    pgbouncer: true
    pool_mode: transaction
    pool_connlimit: 80
    comment: "application authentication identity"

示例标记必须由私密交付机制替换,不能把真实明文或 verifier 提交到本书、Git、 工单或聊天记录。Pigsty 支持 plaintext 或 SCRAM verifier,但官方也把 plaintext inventory 标为不推荐。角色字段与 membership object 格式见 Pigsty:User/Role

这里还要注意:

  • roles 管理是 additive;未声明的历史 membership 不会自动消失;
  • 要撤销旧 membership,应显式使用 state: absent
  • PostgreSQL 16+ 才支持 membership 的 set/inherit 细分;
  • pgbouncer: true 会把 login 纳入 PgBouncer 认证面;
  • NOLOGIN owner/runtime/readonly 不应加入池用户列表;
  • connlimit 与 PgBouncer pool limit 是不同层的限制。

已存在集群修改角色:

bin/pgsql-user <cluster> <username>

它是声明式、可重复入口。删除角色的 state: absent 会涉及断开连接、转移 ownership 和 DROP ROLE,属于破坏性变更;必须先 dry-run/依赖审查和审批, 不能把“脚本支持安全删除”理解为无需变更控制。官方管理流程见 Pigsty:用户管理

membership 与对象 ACL 分开

Pigsty pg_users 可声明 cluster role 和 membership;schema/table/function ACL、owner、default privilege 与 RLS policy 仍应由经过版本控制的 migration 完成:

Pigsty inventory
  -> identity attributes, membership, pool admission

application migration
  -> schema/table owner, ACL, default privileges, RLS policies

不要在多个无序启动脚本里同时管理同一份 grant。平台和应用必须明确各自 owner 和收敛时点。

PostgreSQL 与 PgBouncer HBA

Pigsty 有四组参数:

参数范围作用
pg_default_hba_rulesglobalPostgreSQL 环境默认规则
pg_hba_rulesglobal/cluster/instancePostgreSQL 增量规则
pgb_default_hba_rulesglobalPgBouncer 环境默认规则
pgb_hba_rulesglobal/cluster/instancePgBouncer 增量规则

规则会按 order 排序,数值越小越靠前。未显式指定通常进入 1000+ 区域; 如果前面已有宽泛默认 allow,它可能永远匹配不到。

生产应用入口的概念示例:

pg_hba_rules:
  - title: "app direct path from approved application subnet"
    user: app_login
    db: appdb
    addr: "10.20.10.0/24"
    auth: ssl
    order: 50

  - title: "reject app direct path from all other sources"
    user: app_login
    db: appdb
    addr: world
    auth: deny
    order: 51

pgb_hba_rules:
  - title: "app pooled path from approved application subnet"
    user: app_login
    db: appdb
    addr: "10.20.10.0/24"
    auth: ssl
    order: 50

  - title: "reject app pooled path from all other sources"
    user: app_login
    db: appdb
    addr: world
    auth: deny
    order: 51

auth: ssl 的当前 Pigsty alias 会渲染为 hostssl ... scram-sha-256;生产变更 仍应检查目标版本的渲染结果,不能永久依赖书中的 alias 解释。HBA 字段、 alias、role filter 和 order 规则见 Pigsty:HBA Rules

若应用只允许经 pool/service 入口访问,可进一步在网络与 PostgreSQL HBA 中拒绝应用子网直连 5432,只允许本机 PgBouncer 或指定代理身份访问后端。 这能避免客户端绕过 PgBouncer 的连接限制、认证面与事务池合同。

intra 只是地址别名

Pigsty 的 intra/intranet 通常展开为 RFC 1918:

10.0.0.0/8
172.16.0.0/12
192.168.0.0/16

这些地址不是天然可信。企业办公网、VPN、容器网、其他租户 VPC 和开发环境都 可能落在其中。生产规则应尽量使用准确的应用 subnet/security group,而不是 把整个 RFC 1918 视为一个 trust zone。

刷新而非手工改文件

修改 inventory 后:

bin/pgsql-hba <cluster>

会重新渲染并 reload PostgreSQL/PgBouncer 相关 HBA。不要直接编辑:

/pg/data/pg_hba.conf
/etc/pgbouncer/pgb_hba.conf

下次 playbook 会覆盖手工改动,而且 inventory 与运行事实从此分叉。紧急手工 变更若无法避免,也必须同步回声明源、记录例外并尽快恢复收敛。

证书声明和使用

Pigsty 默认基础设施 CA 会为 PostgreSQL、Patroni、etcd、MinIO、Nginx 等 内部服务签发证书。生产评审要区分:

CA exists
server has certificate
listener supports TLS
HBA requires TLS
client trusts the intended CA
client verifies the intended DNS/IP name
client certificate, if required, is mapped and revocable

这六件事不能合并成“开启 SSL”。

证书 SAN 应覆盖客户端实际使用的:

  • HAProxy/VIP DNS name;
  • PostgreSQL direct service name;
  • 节点名/IP(若允许直连);
  • planned disaster-recovery endpoint。

不要让应用用 hostaddr 绕过期望的 DNS name verification,除非同时提供可 校验的 host 语义并完成测试。

CA private key 所在目录是根信任资产。需要离线/受限备份、读取审计和恢复演练。 证书签发与客户端安装流程见 Pigsty:CA and Certificates

两段 TLS 分别声明

若应用经 PgBouncer:

client ==TLS policy A==> PgBouncer
PgBouncer ==TLS/socket policy B==> PostgreSQL

Pigsty 的 PostgreSQL server TLS 默认开启,不表示 HBA 默认要求 TLS; PgBouncer client TLS 默认也不是开启状态。当前官方安全说明明确列出:

PostgreSQL TLS supported
default intranet HBA may not require it
PgBouncer TLS controlled separately by pgbouncer_sslmode
Patroni REST TLS controlled separately

生产要逐入口决定:

链路加密对端验证允许来源认证主体
app → HAProxy/PgBouncerrequiredservice nameapp subnetapp login
PgBouncer → PostgreSQLTLS 或受控 local socketnode/serviceproxy nodesserver login
admin → PostgreSQLrequiredadmin endpointbastion/infranamed admin
replica → primaryrequired or isolated equivalentnode identitycluster onlyreplication role

service entrypoint

第 22 章区分了 direct PostgreSQL、PgBouncer、HAProxy primary/replica/offline 等入口。安全基线要为每个入口建立:

purpose
listener address/port
source allowlist
HBA/authentication
TLS name and CA
allowed roles/databases
pool mode
rate/connection limit
audit label
owner

未使用入口应关闭或从网络上不可达。保留一个“以后可能调试”的公网 5432 会变成长期旁路。

23.6.2 管理面、监控面和数据库面的网络边界

先画流量矩阵

至少区分:

平面典型组件主要主体失陷后风险
管理面meta/Ansible、SSH、sudo、secret store平台管理员/自动化改写全部配置与密钥
控制面Patroni REST、etcdcluster agent错误选主、拓扑控制
数据面HAProxy、PgBouncer、PostgreSQL应用/分析/迁移数据读写与租户越权
监控面exporter、Prometheus、Grafana、日志monitoring identities查询/拓扑/日志泄露
备份面pgBackRest repo、WAL/archivebackup identities全量数据泄露或恢复破坏

“都是内网服务”不是边界设计。每条 flow 应记录:

source identity/network
destination service/address/port
direction
TLS/mTLS
authentication
authorization scope
availability dependency
log/audit
owner and review date

管理面

管理节点通常能:

  • 通过 SSH 到所有节点;
  • sudo/root;
  • 读取 inventory 或 vault integration;
  • 运行 Ansible/playbook;
  • 访问 CA material;
  • 变更防火墙、HBA 和 service。

因此它不是普通“运维跳板”。应:

named human identity + MFA
short-lived SSH certificate/key
no shared permanent root password
separate automation service identity
command/change audit
restricted egress and inbound sources
workstation and bastion hardening
backup/recovery for control repository

把数据库 superuser 密码从 inventory 移走,却允许所有工程师无审计地 sudo -iu postgres,并没有实现职责分离。

控制面

Patroni REST 和 etcd 决定 cluster topology。它们不应暴露给业务 subnet。 控制面规则通常只允许:

cluster members
approved management/monitoring nodes

health check 入口与管理 API 要区分。HAProxy 为角色判断访问 Patroni health endpoint,不表示应用客户端也应访问完整 Patroni API。

控制面 TLS、认证和 ACL 必须按目标 Pigsty 版本验证。不能因为 etcd 使用 TLS, 就推断 Patroni REST 也已经使用 TLS。

数据面

建议把应用路径收敛为:

application subnet
  -> primary/replica service VIP/DNS
      -> HAProxy
          -> local/cluster PgBouncer
              -> PostgreSQL

然后按实际需要开放 direct path:

management/migration subnet -> PostgreSQL direct
replication nodes            -> PostgreSQL replication
backup/monitor               -> dedicated minimum roles

网络控制至少三层:

cloud security group / network ACL
host firewall
listener + HBA

HBA 不是防 DDoS 的边界:连接已经到达 PostgreSQL 才会检查。安全组/防火墙也 不理解 database/user。二者互补。

Pigsty node_firewall_mode=zone 的默认可信 intranet 必须与真实边界对账。 官方建议也明确提醒 RFC 1918 范围可能过宽,生产 demo 配置通常应移除无必要 的 5432 暴露。参见 Pigsty:Security Recommendations

监控面

monitor role 看起来只读,但往往能访问:

  • pg_stat_activity 查询文本;
  • replication/topology;
  • database/object names;
  • slow-query samples;
  • connection source/user;
  • logs and dashboards。

因此:

  • monitor 不应使用 superuser;
  • exporter query 必须受版本控制;
  • dashboard 与 Prometheus API 要认证;
  • label 不能含 password/token/高基数 PII;
  • 跨环境 metrics/logs 要隔离;
  • support snapshot 要脱敏;
  • 监控不可用不能使数据库认证 fail-open。

备份面

backup repository 持有跨 RLS、跨租户的完整数据与 WAL。网络上只允许备份 主体和恢复路径;读取、删除、retention 改动应分权。数据库 RLS/HBA 无法保护 一份已被复制到对象存储的备份。

恢复环境同样要隔离:把生产备份恢复到宽松开发网,常比攻击生产数据库更容易 造成泄露。第 21、32、33 章会继续处理备份与恢复的控制。

egress 也要控制

数据库节点出站能力可能被:

  • extension/FDW;
  • COPY PROGRAM 高权能力;
  • untrusted language;
  • backup/archive command;
  • 运维脚本;
  • 被攻陷进程

用于外带数据。仅做 inbound firewall 不够。生产需要明确允许的软件源、 backup endpoint、DNS/NTP/monitoring 等 egress,并监控异常。

23.6.3 从配置渲染到运行事实的差异检查

四份状态

对同一项控制记录:

desired       reviewed inventory/policy
rendered      node-local file/service config
runtime       catalogs, settings, sockets, process state
observed      actual client connection or rejected action

示例:

控制desiredrenderedruntimeobserved
app TLSauth: sslhostsslpg_hba_file_rulesdisable fails, verify-full succeeds
roleNOINHERITgenerated SQLpg_rolesraw login SELECT fails
membershipSET truegrant statementpg_auth_membersSET LOCAL succeeds
RLSmigrationtable/policy DDLpg_class/pg_policiescross-tenant writes fail
pool identitypgbouncer: trueuserlist presenceSHOW USERS projectionpool auth succeeds

任何一列缺失都不能宣告闭环。

inventory 评审

先检查:

  • 是否把 secret 直接写入 Git;
  • role 高权 flag;
  • inherit 和 membership admin/set/inherit
  • pgbouncer 暴露范围;
  • HBA order 与宽规则;
  • host/hostssl
  • world/intra alias 展开;
  • primary/replica/offline role filter;
  • listener/service port;
  • PostgreSQL/PgBouncer/Patroni TLS;
  • logging/audit;
  • firewall intranet。

inventory diff 应显示结构,不应把 password/verifier 展开到 PR。

渲染检查

变更前先生成/审查候选,应用后检查节点:

/pg/data/pg_hba.conf
/etc/pgbouncer/pgb_hba.conf
PgBouncer main/user options, secret-redacted projection only
PostgreSQL include/config source
HAProxy listener/backend projection
certificate public metadata
host firewall projection

不要把 /etc/pgbouncer/userlist.txt 原文放进证据;它可能含 verifier。

HBA 应验证:

SELECT *
FROM pg_hba_file_rules
ORDER BY rule_number;

并确认 error IS NULL、顺序与 inventory 相符。

runtime 检查

角色:

SELECT
    rolname, rolcanlogin, rolsuper, rolcreatedb, rolcreaterole,
    rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil
FROM pg_roles
ORDER BY rolname;

membership:

SELECT
    granted.rolname AS granted_role,
    member.rolname AS member,
    m.admin_option,
    m.inherit_option,
    m.set_option
FROM pg_auth_members AS m
JOIN pg_roles AS granted ON granted.oid = m.roleid
JOIN pg_roles AS member ON member.oid = m.member;

对象与 RLS:

owners
schema/table/function/sequence ACL
default ACL
relrowsecurity / relforcerowsecurity
pg_policies
view/function security attributes

TLS:

SHOW ssl / ssl_min_protocol_version
certificate SAN, issuer, validity, fingerprint
private key owner/mode, never contents
pg_stat_ssl on real sessions
verify-full positive and wrong-name negative

日志:

log_statement
duration/sample thresholds
parameter logging limits
connection/disconnection settings
shared_preload_libraries
pg_extension
collector/forwarder health

observed 检查

必须从与真实 workload 相同的网络和 driver 路径执行:

allowed source + right login + right DB     succeeds
wrong source                               rejected
wrong password                             rejected
sslmode=disable where TLS required          rejected
verify-full matching name                  succeeds
verify-full wrong name                     rejected
pool login                                independently succeeds
direct bypass where prohibited             rejected
raw login object access                    rejected
approved SET LOCAL role                    succeeds
cross-tenant action                        rejected

只在 database node 上用 Unix socket psql,无法验收远程网络、proxy HBA、 TLS name 或客户端 CA distribution。

本章沙箱差异

在 Pigsty v4.4.0 nonproduction sandbox 中,正式捕获发现:

控制期望/能力运行事实判定
PostgreSQL TLSserver 可加密on,最低 TLS 1.2能力通过
cert identitySAN + verify-full正例成功,错名失败通过
direct channel bindingSCRAM over TLS成功通过
business direct HBA生产应强制 TLS+dbrole_readonly 内网为 host待整改
PgBouncer client TLS生产应按 threat model 启用disabled待整改
pool → PostgreSQL受控链路local Unix socket事实符合设计
CRL撤销路径file/dir unset待整改
pgAudit目标要求对象审计absent/not preloaded待整改
bind loggingsecret-minimized非错误参数上限 -1待整改

它诚实地同时证明:

TLS mechanism works
AND
non-TLS direct business connection is still admitted

第一条不能抵消第二条。

分阶段整改

安全变更也会造成可用性风险,应分阶段:

Phase 1 inventory
  client versions, source networks, DNS names, CA trust, pool entrypoints

Phase 2 prepare
  issue correct SAN certs
  distribute CA
  make clients use verify-full
  establish metrics and rollback

Phase 3 enforce client-to-pool TLS
  enable PgBouncer TLS
  positive/negative tests
  roll clients

Phase 4 enforce PostgreSQL HBA
  add high-priority hostssl allow
  retain controlled exception if necessary
  prove non-TLS rejection

Phase 5 audit and secret minimization
  install matching pgAudit if required
  select classes/objects
  reduce bind parameter logging
  validate volume and redaction

Phase 6 remove exceptions
  expire compatibility HBA/CA/password
  terminate old sessions
  update runbook and evidence

一次性把所有 host 改成 hostssl,如果客户端尚未安装 CA 或仍用 IP 不匹配 证书,会把安全整改变成全站故障。

例外不是口头备注

暂时保留非 TLS/旧客户端时,例外记录至少包含:

exact source/destination/role/database
business reason
threat and data classification
compensating network control
owner and approver
created/expires
remediation milestone
monitoring
tested rollback

无到期日的例外就是新默认。

生产判定

最终报告只允许:

判定含义
pass所有必须项有运行与负例证据
pass-with-exception明确、批准、限时、受补偿控制的差距
pending机制可用,但必需控制尚未实施/证明
reject存在不可接受暴露或证据冲突

本章沙箱是 pending。它完全适合教学实验,却不能被包装成生产审批。这种区分 正是安全工程成熟度的一部分。Pigsty 当前生产注意事项见 Pigsty:Security Considerations


上一节:密钥、审计与敏感信息 · 返回本章目录 · 下一节:实战:隔离两个租户 · 查看全书目录 · 查看索引中心

最后更新于