23.5 密钥、审计与敏感信息
数据库安全材料不只是一串密码:
password and SCRAM verifier
TLS private key and certificate
CA private key, trust bundle and CRL
OAuth client secret / token
LDAP bind credential
backup encryption key
replication credential
PgBouncer authentication material
break-glass credential其中任何一项若进入 Git、命令行历史、日志、监控标签或实验产物,后续的 权限设计都可能失效。另一方面,为了“绝不记录敏感信息”而关闭所有日志,也 会让越权事件无法发现和调查。
本节处理的是这组张力:秘密必须最小暴露,安全行为必须留下足够而受保护的 证据。
23.5.1 凭据生成、存放、轮换和撤销
先建立 secret inventory
每类 secret 都要有 owner 和生命周期:
| 字段 | 要回答的问题 |
|---|---|
| identity | 它代表哪个人、服务或组件 |
| scope | 能访问哪些入口、数据库和角色 |
| source | 谁生成,熵和算法是否合格 |
| storage | secret manager、HSM、受限文件还是其他载体 |
| delivery | 哪个 workload 如何获得 |
| readers | 哪些人、服务账户和进程可读 |
| lifetime | 创建、启用、到期和最大使用时长 |
| rotation | 是否支持重叠版本,多久轮换 |
| revocation | 如何阻止新认证 |
| session eviction | 如何处理既有连接 |
| downstream | 是否复制到代理、CI、备份或灾备 |
| evidence | 如何证明已完成且不导出秘密 |
如果连“有几份副本”都不知道,就无法声称秘密已经撤销。
生成
机器凭据应由密码学安全随机源生成,避免:
human memorable password
service-name + environment + year
one shared password for all replicas/apps
copy production password to staging长度和字符集要兼容客户端、URI、配置格式与 secret manager;不要为了规避 转义问题而把熵降得过低。优先通过结构化参数或独立字段传递,避免把密码拼进 连接 URI。
证书与 key 的生成还要固定:
key algorithm and size
signature algorithm
SAN identities
extended key usage
issuer and path length
validity and renewal window
private-key exportabilityCA private key 与数据库 server key 不应由同一批日常运维主体任意读取。
存放和交付
首选工作流:
secret manager / HSM
-> authenticated workload
-> short-lived retrieval
-> memory or private runtime file
-> database driver如果使用文件:
- 明确 owner/group;
- 通常使用
0600或经过评审的0640; - 目录同样不可遍历;
- 不写入镜像层、共享 volume 或备份;
- 不把内容输出到 diagnostics;
- 用完安全删除临时副本,并考虑文件系统/快照语义。
.pgpass 要求严格权限,它适合受控本地客户端,不是企业 secret manager。
环境变量适合某些运行时注入,但必须处理进程继承、crash dump、support bundle
和调度平台元数据风险。
最危险的交付路径通常很方便:
psql "postgresql://app:plaintext@db/app"URI 可能进入 process list、shell history、trace、错误和工单。即使工具会 隐藏部分内容,也不应把安全性押在每个中间层都正确脱敏。
数据库密码变更
交互式 psql 可使用:
\password app_login它在客户端提示密码并发送 verifier,避免明文出现在命令历史和 server log。
官方说明见 psql \password。直接执行:
ALTER ROLE app_login PASSWORD 'plaintext';可能让明文进入 client history 或 server log;PostgreSQL 官方也明确警告
这一点,参见 ALTER ROLE。
自动化系统应通过 secret-safe API、受控 stdin/fd 或专门管理函数完成,且 验证流水线不会回显命令与异常。
轮换是状态机
密码轮换不能只有“ALTER 成功”:
S0 old active
-> S1 new generated and stored
-> S2 database accepts new
-> S3 workloads use new
-> S4 old new-auth rejected
-> S5 old sessions drained/terminated
-> S6 old copies destroyed每个状态需要证据与回滚条件。若 PostgreSQL role 只能保存一个当前 verifier, S2 与 S3 之间的兼容窗口可以采用:
- 蓝绿两个 login role;
- 应用小批量快速 rollout;
- 代理/身份系统支持的双版本机制;
- 计划内短暂重连窗口。
不要假装一个 role 可以同时接受两个普通 PostgreSQL 密码。
撤销新认证与终止旧会话是两件事
本章的受控临时 login 实测:
password v1 new auth success
change to password v2
password v1 new auth rejected
password v2 new auth success
session opened with v1 still usable
ALTER ROLE ... NOLOGIN
all new auth rejected
existing session still usable
final NOLOGIN + PASSWORD NULL verified因此应急撤销至少有两条动作:
ALTER ROLE compromised_login NOLOGIN PASSWORD NULL;以及在识别范围并评估事务影响后:
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE usename = 'compromised_login'
AND pid <> pg_backend_pid();第二条是有破坏性的:会中断事务,产生 commit outcome uncertainty,并可能 触发重连风暴。必须先阻止代理和应用继续获取旧 secret,再终止既有 session。
代理层也要轮换
PgBouncer 的 auth_file、auth_query、auth_user 和 server login 都可能
持有或派生认证材料。轮换时要明确:
client -> PgBouncer identity
PgBouncer -> PostgreSQL identity哪一段在变。更新 PostgreSQL verifier 后:
- PgBouncer 是否缓存旧认证材料;
- 是否需要
RELOAD; - 是否需要
RECONNECTserver pools; - 已有 client connection 是否继续使用;
- 所有 PgBouncer 节点是否完成;
- 直连和池化入口是否给出一致的允许/拒绝结果。
本章临时 role 直连新认证成功,但池入口拒绝,因为它没有进入 PgBouncer 认证面。这是预期的最小暴露,不是轮换失败。
证书和 CA 轮换
证书轮换同样要区分:
trust distribution
certificate/key deployment
service reload
new connection negotiation
old connection lifetime
old trust removal
revocation publicationPostgreSQL reload 新 server certificate 不会让所有现有 TLS session 自动 重新握手。PgBouncer 两侧又各有 TLS 状态。验收必须建立新连接并核对证书 fingerprint/issuer/SAN,而不是只看文件 mtime。
CA rollover 先扩充 trust bundle,再切 server/client certificate,最后等 fleet 全部迁移后删除旧 CA。倒序操作会把尚未更新的客户端全部拒绝。
应急凭据
break-glass credential 应:
- 与日常 workload secret 分开;
- 双人或受审批取用;
- 短时激活;
- 不进入普通自动化;
- 使用后立即轮换;
- 关联 incident/change id;
- 从数据库外部收集不可篡改证据;
- 定期演练“能取出、能使用、能回收”。
一个从未测试、到事故时才发现过期的应急密码,不是恢复能力。
secret-free 证据
证明轮换无需保存 secret:
role name
credential version id / hash reference
change timestamp
new-auth accepted boolean
old-auth rejected boolean
existing-session observation
NOLOGIN/password-present boolean
operator/change id
artifact hash本章证据显式拒绝:
plaintext passwords
SCRAM verifiers
raw PgBouncer userlist
server/CA private keys
connection strings containing credentials它检查“secret signature 不存在”,同时对证据文件使用 0600。文件权限不能
替代内容最小化;二者都要做。
23.5.2 审计目标、日志范围与访问控制
从调查问题反推记录
先定义要回答的问题:
who authenticated, and by which original identity
which login and effective role executed
from which source and entrypoint
which database/object/action
when it began and ended
whether it succeeded
which rows/records were affected, at safe granularity
which policy/config/role changed
which approved request/incident authorized it
whether evidence could have been altered“记录所有 SQL”并不能自动回答这些问题,反而可能产生无法检索的海量敏感 数据。
四类证据互相补充
| 来源 | 擅长回答 | 局限 |
|---|---|---|
| PostgreSQL standard log | 连接、错误、慢 SQL、DDL、运行事件 | 不是完整对象审计 |
| pgAudit | 结构化 session/object audit classes | 有容量成本,superuser 不可可靠自审 |
| Pigsty/config repository | 谁声明、评审、发布了配置 | 不证明运行实例已收敛 |
| host/proxy/secret manager/IdP | 网络入口、secret 读取、外部身份 | 不知道 SQL 对象语义 |
还应关联:
application audit end-user and business action
database audit login/effective role and SQL object
platform audit configuration/deployment/operator
security audit secret access and identity events共享应用 login 下,数据库看不到每个终端用户,应用必须提供受信 actor/request
关联。不要把用户可自行填写的 application_name 当成强身份。
standard log
PostgreSQL 18 可分别记录 connection receipt、authentication、 authorization、setup duration,以及 disconnect。还可记录:
- error statement;
- DDL/DML/all statement classes;
- duration threshold 或 sampling;
- lock/recovery/autovacuum/checkpoint;
- SQLSTATE、session id、transaction id、query id;
- application/database/user/client address。
log_line_prefix 至少要支持跨行关联,例如:
timestamp session-id pid user database application client SQLSTATE具体字段按日志格式和数据分类选择。若使用 JSON/CSV,仍要确保 collector、 rotation、磁盘满和转发失败被监控。PostgreSQL logging collector 为避免丢 消息可能在落后时阻塞 backend;syslog 则可能选择丢消息。这也是容量设计, 不是简单开关。
pgAudit 能补什么
pgAudit 将行为分为 READ、WRITE、FUNCTION、ROLE、DDL、MISC
等审计类,并能提供更适合对象审计的记录。部署要点:
matching pgAudit branch/version for PostgreSQL major
shared_preload_libraries includes pgaudit
restart completed
CREATE EXTENSION pgaudit before setting pgaudit.log
selected classes/object audit configured
volume and sensitive-data test passed只安装 package 或只执行 CREATE EXTENSION 都不等于审计已经运行。官方项目
说明见 pgAudit。
不要默认 pgaudit.log=all:
- 高频 SELECT 会产生巨大日志;
- bind parameter 可能含 PII/secret;
- 日志 IO/转发可能成为负载瓶颈;
- 噪声可能淹没 role/DDL 等关键事件;
- retention 成本和访问面迅速扩大。
应从审计目标选择 session classes 或 object audit,并对新表纳入策略做持续 检查。
superuser 不能可靠地审计自己
pgAudit 官方明确指出,不能可靠审计 superuser。superuser 能改变设置、停用 扩展、修改日志路径或干预本机数据。解决思路不是再加一条数据库内 trigger, 而是:
restrict direct superuser
-> named operator identity
-> approved, time-bounded escalation
-> external control-plane/host audit
-> remote immutable-ish log copy
-> post-use review“不可变”要具体:谁拥有 bucket retention policy、谁能删除 collector、日志 在源端滞留多久、断网时如何缓冲、时间如何同步,都要写进控制设计。
角色和授权变更
高价值事件:
CREATE / ALTER / DROP ROLE
GRANT / REVOKE role membership
GRANT / REVOKE object privilege
ALTER ... OWNER
ALTER DEFAULT PRIVILEGES
ENABLE / DISABLE / FORCE RLS
CREATE / ALTER / DROP POLICY
SECURITY DEFINER function changes
HBA / TLS / proxy authentication changes
secret reads and rotations
break-glass use仅记录成功 DDL 不够。还需:
- 失败尝试;
- 变更前后投影或配置 diff;
- 审批 id;
- 执行主体;
- 节点收敛状态;
- 正负验收;
- 回滚结果。
日志本身是敏感数据
日志可能包含:
user and client IP
database/schema/table names
SQL text and literal values
bind parameters
error context
tenant/request identifiers
certificate distinguished names
security policy and topology clues因此要实施:
- 最小读权限,读日志本身也审计;
- 传输和静态加密;
- retention 与合法删除;
- 环境/租户隔离;
- 索引系统访问控制;
- 防止下载到个人设备;
- incident legal hold;
- collector health 与 ingestion gap 告警。
本章环境结论
沙箱运行事实:
pgAudit installed/preloaded no
standard log_statement ddl
log_min_duration_statement 100 ms它能支持实验和部分运维诊断,不能被描述为完整生产审计。生产 gate 因此保留
pending,不是把“有日志”写成“满足审计要求”。
23.5.3 参数、SQL 文本与日志脱敏
参数绑定解决注入,不保证不落日志
应用正确使用:
SELECT *
FROM app.account
WHERE email = $1;能避免把 $1 当 SQL 语法解释,但 extended query protocol 的 Bind value
仍可能被 statement/duration/audit/error logging 记录。安全评审必须把:
SQL construction safety
log data exposure当成两个问题。
PostgreSQL 的参数日志开关
PostgreSQL 18:
log_parameter_max_length
-1 非错误 statement log 可记录完整 bind 参数
0 禁止在这类日志记录 bind 参数
>0 每个参数截断到指定字节
log_parameter_max_length_on_error
0 error message 不附 bind 参数
-1 可附完整参数
>0 截断精确行为见 PostgreSQL:错误报告与日志。
本章沙箱:
log_parameter_max_length -1
log_parameter_max_length_on_error 0含义是:
- error path 默认不附 bind values;
- 非 error 的 statement/duration logging 路径可能保留完整 bind values。
因此它被标记为生产差距。不能只因为 error 参数为 0 就得出“参数不进日志”。
截断不是脱敏
把每个参数截断到 64 bytes 仍可能完整暴露:
password
API token prefix
email
phone
credit-card number
small JSON secret0 能阻止特定 PostgreSQL log path 记录 bind 参数,但:
- SQL literal 仍在 statement text;
- application log 可能记录参数;
- pgAudit/extension 行为要单独验证;
- error message 可能引用业务值;
- trigger/function 自己可能
RAISE LOG; - proxy/APM/driver trace 可能复制 SQL。
脱敏必须是端到端数据流评审。
不要把秘密写进 SQL literal
高风险语句:
ALTER ROLE app PASSWORD 'secret';
INSERT INTO integration_config(api_token) VALUES ('secret');
SELECT call_remote_service('secret');即便业务表有 RLS,这些 literal 也可能进入 statement、DDL、audit、客户端 历史或 trace。对于 credential provisioning,使用专门的 secret-safe 通道; 对于业务 secret,使用参数绑定并缩小数据库日志范围。
在源头分类字段
建议把请求数据分成:
| 类别 | 示例 | 日志策略 |
|---|---|---|
| public operational | version、region、status | 可结构化记录 |
| internal identifier | request id、tenant surrogate id | 最小化、受控保留 |
| personal/confidential | email、address、business data | 默认不记 value |
| credential/cryptographic | password、token、private key | 永不记录 |
| regulated/highly sensitive | payment/health/government id | 专门政策与审计 |
数据库团队不能只靠列名猜分类。应用 schema、数据目录和日志 policy 要共享 同一份分类元数据。
SQL fingerprint 与 value 分离
性能分析通常不需要参数值。优先保留:
normalized query / query id
duration
rows
wait/error class
database/user/application
safe request correlation id
plan/statistics reference而不是:
full statement + all bind values for every request第 25、26 章会用 pg_stat_statements、query id 和计划证据分析性能;这些
方法能显著减少为了可观测而复制业务值的必要。
pipeline 脱敏是第二道防线
collector 侧可以:
- 删除已知敏感字段;
- token/password pattern 检测;
- 限制异常样本和 payload;
- 对 identifier 做受控 pseudonymization;
- 阻止包含 private key/verifier 的事件;
- 记录 redaction rule version。
但 regex 不能成为唯一控制。SQL 语法、编码、嵌套 JSON、base64 和未知字段 会绕过它。首要措施仍是在源端不产出秘密。
脱敏测试
上线前使用纯 synthetic canary:
unique fake password marker
unique fake token marker
unique fake PII marker执行经过批准的测试请求,然后检查:
PostgreSQL local logs
PgBouncer / HAProxy logs
central log index
APM traces
application logs
CI artifacts
support bundles
chapter/evidence output期望是 credential marker 零命中,其他 marker 只出现在预先批准的位置。不要 用真实 secret 做日志泄漏测试。
审计与隐私的验收
最终不是“多记”或“少记”,而是:
security event has enough attributable evidence
AND
credential/business values are absent unless explicitly required
AND
evidence readers and retention are controlled
AND
ingestion gaps and tampering attempts are observable这是安全日志与普通调试日志的根本区别。
上一节:行级安全与连接池上下文 · 返回本章目录 · 下一节:Pigsty 安全基线 · 查看全书目录 · 查看索引中心