25 深入专题:安全加固
认证、权限、加密、审计与常见安全风险的防护。
DuckLake 本身没有细粒度权限,如何组合实现数据访问控制?
DuckLake 无内置 GRANT/RLS/列级权限,需多层叠加:1) READ_ONLY 模式阻止写;2) 数据文件加密防止绕过 DuckLake 直读 Parquet;3) DuckDB 引擎层用 enable_external_access=false + allowed_directories/allowed_paths 限制可访问的 S3 前缀;4) 对象存储 IAM 做 bucket/prefix 级隔离(最强);5) metadata DB 层用 PostgreSQL GRANT 或文件系统权限保护 catalog;6) 用 VIEW 在应用层做行/列过滤。核心结论是最可靠的是基础设施层 IAM + 引擎层路径限制。
PG13 之前对 TDE 透明加密的期待是什么?为什么 TDE 一直难产?
TDE(透明数据加密)指对数据文件、WAL、临时文件等在存储层透明加密,使拿到磁盘/备份副本的人无法直接读取明文。社区长期期待 cluster-level TDE,但实现复杂:需要在 buffer 读写路径、WAL 加解密、密钥管理(KEK/DEK 分层、轮换)、备份恢复、复制链路等多个层面打通,且要与 pgcrypto 等既有加密能力协调。PG13 时仍处于 patch 讨论阶段,后续版本也多次被打回,直到较新版本才逐步推进。
PL/pgSQL 动态 SQL 中标识符、值、语法片段分别该如何安全处理?
三类输入要用不同方式:数据值走参数绑定(EXECUTE … USING $1);标识符(表名/列名)先做白名单校验再用 %I/quote_ident 引用;受限语法片段(ASC/DESC、排序字段)用枚举映射,不接受自由文本。错误示范是把用户输入直接拼进 SQL 文本(如 ‘… WHERE ‘||colname||’=…’),既无白名单又把值拼进语法,攻击者可控制 colname 或 keyvalue 改变语法结构。
PolarDB/PostgreSQL 里密码明明正确却报「密码错误」通常是什么原因?
常见原因不是密码本身错,而是认证链路或环境问题:pg_hba.conf 命中了错误规则导致用了不同认证方式、密码编码问题、SCRAM 与 md5 不一致(改过 password_encryption 但没轮换)、客户端连错实例、角色不存在或 login 权限缺失、以及大小写/特殊字符被 shell 或连接串转义等。排查应从 HBA 规则命中顺序、认证方法、rolpassword 存储形态和连接串参数逐项定位。
PostgreSQL 14 对 TDE 的支持有哪些变化?
PG14 引入了 TDE 相关 patch,目标是支持加密数据文件和加密 WAL 日志文件,属于 cluster 级透明加密的早期落地。核心思路是在存储层对 page 和 WAL 做加解密,密钥由外部 KMS 或专用密钥文件提供。需要强调的是这类改动属于预览/讨论阶段,早期版本中 TDE 多次被打回,最终是否进入 GA 需以 release notes 为准。
PostgreSQL 14 新增的 pg_read_all_data / pg_write_all_data 角色有什么用?
PG14 引入两个预制角色:pg_read_all_data 拥有对数据库中所有表和序列的默认读权限,pg_write_all_data 拥有默认写权限。它们主要用于兼容 MySQL 式的「只读影子用户/读写用户」管理习惯,让 DBA 可以一键授予某个账号全库只读或全库读写能力,而不必逐对象 GRANT。这两个角色只授予数据读写,不授予 DDL 或管理权限,安全性好于直接给 superuser。
PostgreSQL 15 如何把 SET / ALTER SYSTEM 修改 GUC 参数的权限授予普通角色?
PG15 增加了对 GUC 参数的授权机制,允许通过 GRANT 将特定参数的 SET(会话级设置)或 ALTER SYSTEM(全局持久化设置)权限授予指定角色。这样可以把「谁能调整某个参数」做成细粒度权限,而不是只有 superuser 才能改。授权后普通角色即可在授权范围内设置对应 GUC。
PostgreSQL 15 对 database 的 public schema 收回了什么权限?为什么重要?
PG15 之前,每个新建 database 的 public schema 默认对 PUBLIC(所有角色)授予 CREATE 和 USAGE 权限,任何能登录的用户都可在 public schema 建对象,带来安全隐患(如 search_path 劫持)。PG15 起收回 public schema 对 PUBLIC 的 CREATE 权限,只保留 USAGE,模板库相应调整。这堵住了默认状态下任意用户往 public schema 建对象的漏洞。
PostgreSQL 16 新增的 pg_create_subscription 内置角色解决了什么问题?
PG16 新增 pg_create_subscription 预制角色,只有拥有该角色(或 superuser)的用户才能创建逻辑订阅。同时细化了 subscription apply 时的角色选择:应用变更时可按 subscription owner 或 table owner 的身份执行。这避免了必须给普通用户授予过大的复制/写入权限才能建订阅,实现逻辑复制的最小授权。
PostgreSQL 16 的 createrole_self_grant 参数解决什么问题?
PG16 新增 GUC createrole_self_grant。非 superuser 用户在拥有 CREATEROLE 权限并创建新角色后,默认并不会自动获得新角色的成员资格,导致「创建了却无法 SET ROLE 或继承其权限」的尴尬。createrole_self_grant 可配置为 set/inherit,让创建者在建角色时自动反向获得对新角色的 SET ROLE / INHERIT 权限,避免再手工 GRANT。
PostgreSQL 17 引入的 MAINTAIN 权限和 pg_maintain 角色解决什么问题?
PG17 引入 MAINTAIN 权限及配套的 pg_maintain 预制角色,专门覆盖 VACUUM、ANALYZE、REINDEX、CLUSTER 等维护性操作。此前这些操作常需要表 owner 或 superuser,现在可以把「维护权」从「所有权/管理权」中剥离,单独授予,实现最小权限的运维授权——比如让某个账号只能做 vacuum/analyze,不能改数据或结构。
PostgreSQL 18 给 pgcrypto 新增了哪两个更安全的密码哈希算法?
PG18 为 pgcrypto 新增了两个更安全的密码哈希算法(如 bcrypt 和 argon2 方向的增强),用于替代较弱的 md5 口令哈希。相比传统 crypt/md5,这些算法带 cost 因子和抗 GPU/抗字典特性,能显著提高口令库泄露后的离线猜测成本。具体算法名以 GA release notes 为准,但方向是补强 pgcrypto 的口令哈希能力。
PostgreSQL 中 scram_iterations 调高后旧密码会自动变强吗?
不会。scram_iterations 只影响新生成 secret 的迭代次数,已经存在的 rolpassword 字符串里写着自己的迭代次数,后续认证按 secret 里的值执行。所以调高 scram_iterations 后,如果不轮换用户密码(ALTER ROLE … PASSWORD 或用户改密),旧 secret 不会自动变强。迁移 SCRAM 的关键不是只改 password_encryption,而是让密码重新写入。
PostgreSQL 为什么也会遭受 DOS 攻击?认证前有哪些可被利用的攻击面?
数据库同样存在 DOS 风险,典型是利用认证前阶段:客户端在完成认证前就能占用后端的连接槽位,攻击者通过海量连接把 max_connections 耗尽,导致正常用户无法接入。应对手段是设置 authentication_timeout 限制认证阶段时长,并配合连接限制、防火墙、pg_hba 白名单等。另一个认证前攻击面是 Query Cancel 攻击——客户端可以给 postmaster 发送 cancel 请求包,postmaster 需要定位并处理对应 backend,恶意客户端可借此消耗资源。
PostgreSQL 大对象(large object)从 9.0 起增加了什么安全相关改进?
PG9.0 起新增 pg_largeobject_metadata 系统表,用于记录每个大对象的 OID、owner 和权限信息。此前大对象没有独立的元数据表,权限管理困难;有了这张表后可以像普通对象一样查询大对象归属与 ACL,并配合 GRANT/REVOKE 对 large object 做访问控制,弥补了大对象安全管理的空白。
PostgreSQL 如何使用 SSL 证书实现无密码登录(cert 认证)?
使用证书认证时,客户端持有私钥和客户端证书,服务端持有 CA 根证书。服务端在 pg_hba.conf 中把认证方法设为 cert(或 hostssl + clientcert=verify-full),客户端连接串提供 sslcert、sslkey、sslrootcert。握手时服务端用 root.crt 校验客户端证书,并默认把证书的 CN(或通过 map 映射)作为登录用户名,从而无需密码完成身份认证。
PostgreSQL 如何对日志中的敏感信息进行遮罩(Redacting)?
通过扩展的 emit_log_hook 钩子拦截即将写入日志的消息,对其中敏感字段(如密码、token、身份证号等)做脱敏替换后再落盘。该钩子由扩展注册,可在 C 层拿到 ErrorData 并改写消息文本或字段,从而在不侵入内核主代码的前提下实现日志级别的数据遮蔽,降低日志、审计、慢查询采集中的明文泄露风险。
PostgreSQL 用户密码的两种存储方式是什么?为什么建议用 scram-sha-256?
PG 密码在 pg_authid.rolpassword 中有两种存储形态:md5 和 scram-sha-256。md5 存的是 md5(password+username),安全性弱,密码库泄露后防护不足,且官方已标注 deprecated。scram-sha-256 保存 salt+迭代次数+StoredKey+ServerKey,不存可直接重放的 proof,还能做服务端签名和 channel binding。因此新系统应把 password_encryption 设为 scram-sha-256 并轮换旧密码。
PostgreSQL 的 SSL root.crt 文件在什么时候需要配置?它起什么作用?
root.crt 存放的是受信任的 CA 根证书,用于验证对端证书链。当服务端要求验证客户端证书(pg_hba.conf 用 cert 认证或 clientcert=verify-full)时,服务端需要用 root.crt 校验证书;客户端在 sslmode=verify-ca/verify-full 时也需要 root.crt 校验服务端证书。简单说:只要你需要校验对端证书,就应当在对应侧配置 root.crt;如果只加密不校验身份(如 sslmode=require),则不一定需要。
PostgreSQL 的 pg_remote_exec(shell 命令插件)是什么?
pg_remote_exec 插件允许在数据库内执行 shell 命令(如调用系统工具、脚本),把 OS 命令封装成 SQL 函数。它便于在存储过程里触发外部操作,但存在安全风险(SQL 注入可能导致任意命令执行),需严格限制权限。类似还有 pg_curl 插件(库内发 HTTP 请求)。这类插件扩展了数据库与外界的交互能力,但需谨慎授权。
SCRAM-SHA-256 中 StoredKey 和 ServerKey 分别起什么作用?
服务端保存的是 StoredKey 和 ServerKey,而不是明文密码或 ClientKey。StoredKey = H(ClientKey),用于验证客户端 proof:服务端用 StoredKey 算 ClientSignature,再通过 ClientProof XOR ClientSignature 恢复候选 ClientKey,比较 H(ClientKey) 是否等于 StoredKey。ServerKey 用于生成 ServerSignature,让客户端能验证服务端确实持有该用户的认证 secret,实现双向认证。泄露 StoredKey 不能直接冒充客户端,但仍可离线猜密码。
SCRAM-SHA-256-PLUS 的 channel binding 解决什么问题?
普通 SCRAM 能防密码嗅探和重放,但中间人仍可能把客户端和真服务端之间的 SCRAM 消息转发起来。channel binding 把 TLS 服务端证书哈希(tls-server-end-point)纳入 client-final-message 的 c= 字段,使其参与 AuthMessage,进而绑定 ClientProof 和 ServerSignature。这样客户端证明自己知道密码的同时,也把这次证明绑定到了它实际看到的 TLS 服务端身份。高安全场景应使用 sslmode=verify-full + channel_binding=require。
SECURITY DEFINER 函数为什么必须固定 search_path?
SECURITY DEFINER 函数以函数 owner 的高权限执行,如果 search_path 含不可信 schema,攻击者可在可写 schema 中放置同名对象(表、函数、操作符),诱导函数在运行期解析到恶意对象,实现权限提升。因此必须 SET search_path 为受信 schema 加 pg_temp(且 pg_temp 放最后),函数体内对象尽量 schema-qualified,并 REVOKE PUBLIC 默认执行权限后仅授予明确角色。
anon 和 sepgsql 这两个 security label provider 分别是做什么的?
两者都是 PG 的 security label(安全标签)provider,通过 SECURITY LABEL 机制工作。anon 用于数据脱敏/匿名化,可对列配置脱敏规则,让非授权用户看到脱敏后的值;sepgsql 是基于 SELinux 的强制访问控制(MAC)provider,把 SELinux 安全上下文标签绑定到数据库对象,实现操作系统级的安全策略强制执行。它们与 pgsodium 类似,都借助 label provider 扩展 PostgreSQL 的安全语义。
column_encrypt 扩展的 KEK/DEK 两层密钥模型是怎么设计的?
column_encrypt 采用两层密钥:外层 KEK(key encryption key / master passphrase)不存数据库,由外部 KMS 或应用注入;内层 DEK(data encryption key)用于实际加密列数据,DEK 密文(被 KEK 包裹)才存库。列密文头带 2 字节 key version 支撑轮换。这样即使拿到数据库表和数据文件,没有 KEK 也无法解开 DEK,从而保护敏感列。它还通过 session keyring 控制谁能读明文,用 SECURITY DEFINER 与 column_encrypt_user 角色收口权限。
column_encrypt 的 blind index 是做什么的?有什么安全代价?
blind index(盲索引)用于在加密列上支撑等值查询:对明文计算一个确定性/带密钥的摘要值单独存储并建索引,查询等值时匹配摘要而非解密密文。代价是相同明文产生相同 blind index,攻击者可通过摘要分布推断频率,对低基数字段(性别、状态、地区)不适合。同时它不支持范围查询,这是有意为之的设计取舍。
credcheck 插件如何加强 PostgreSQL 用户名与密码安全策略?
credcheck 是一个密码/用户名校验插件,可在建角色或改密码时强制执行安全策略:如密码最小长度、复杂度、禁止使用用户名作为密码、禁止常见弱口令、限制密码有效期等。它通过 hook 在密码写入前做检查,从源头阻断弱密码进入系统,是对 SCRAM 等认证机制在「密码质量治理」层面的补充。
pg_dump 导出带 RLS 行安全策略的表时为什么要加 –enable-row-security?
RLS 表在 pg_dump 导出时,默认 dump 进程运行在会绕过 RLS 的语境(或受策略影响)下,可能报错或导不出数据。加 –enable-row-security 开关后,pg_dump 明确以启用行安全策略的方式读取,从而正确导出符合策略可见的数据。因此导出带 RLS 策略的表时必须显式带上该开关,否则会因策略拦截而失败。
pg_restrict 插件如何实现更精细的 ACL 访问控制?
pg_restrict 提供 master_roles 等配置,形成一套更细粒度的访问控制层。它允许指定若干「master」角色,只有 master 角色成员才能执行受限的 DDL/管理操作,普通用户即使拥有对象权限也会被拦截,从而把「谁能改结构/谁能管理」这类操作从原生 ACL 中剥离出来单独管控,实现类似「白名单角色」的精细化授权。
pgcrypto 的 pgp_sym_encrypt / pgp_sym_decrypt 如何做对称加密?
pgp_sym_encrypt 用口令对文本做 OpenPGP 对称加密,返回 bytea 密文,可通过选项指定 cipher-algo(如 aes256)、compress-algo/compress-level、s2k-mode、s2k-count 等。pgp_sym_decrypt 用相同口令解密,口令错误会报「Wrong key or corrupt data」。它适合存少量敏感值(如账号 token),但要注意密钥容易进入 SQL 文本或日志,生产更推荐配合外部密钥管理。
pgsodium 是什么?它如何利用 security label 实现字段透明加解密?
pgsodium 是基于 libsodium 的 PG 扩展,提供 Server Key Management 和透明列加密 TCE。它通过 register_label_provider 注册 pgsodium label provider,用户用 SECURITY LABEL FOR pgsodium ON COLUMN … IS ‘ENCRYPT WITH KEY …’ 标记列,事件触发器(ddl_command_end)据此生成 BEFORE INSERT/UPDATE 加密触发器和解密视图。业务表存密文,应用通过解密视图读明文,密钥由启动时加载的 root key 派生,raw key 不经 SQL 暴露。
supa_audit 插件如何实现记录审计与版本跟踪?
supa_audit(Generic Table Auditing)是一个审计插件,可对指定表自动记录数据变更历史:每次 INSERT/UPDATE/DELETE 时把变更前后的记录、操作类型、时间、操作者等写入审计表,形成可回溯的版本跟踪。相比 pgaudit 记录 SQL 语句,supa_audit 更偏「行级数据审计/版本」,适合需要追溯每行数据变化历史的合规场景。
为什么参数化查询能防 SQL 注入,而手写转义只是兜底?
参数化查询(如 libpq 的 PQexecParams、驱动占位符、EXECUTE … USING)让 SQL 模板和参数值分通道进入数据库:解析器只解析模板,参数值由类型输入函数处理,不参与 SQL 语法切分,因此攻击者输入的内容只是值而非语法。而转义只处理字符串字面量问题,处理不了标识符、排序方向、第二条语句、search_path 劫持、动态 SQL 等。真正的目标是让用户输入在整个生命周期里保持为值。
为什么在 PG 里有时不需要密码就能连接数据库?
这由 pg_hba.conf 的认证方法决定:trust 方法对匹配的连接无条件放行(不需要密码);peer 方法在本地套接字上通过操作系统用户名与数据库用户名匹配直接认证;ident 通过 ident 协议认证。这些都是「无需密码」的合法配置。所以「不需要密码就能连」通常是 HBA 配了 trust/peer 且规则命中,需要按安全要求收紧 HBA 规则。
为什么字段加密推荐用 AEAD(认证加密)而不是只加密?
只加密只保证「看不懂」,但攻击者可能篡改密文、nonce 或上下文字段,系统无法发现。AEAD(如 ChaCha20-Poly1305、XChaCha20-Poly1305、SIV 类)同时提供保密性和完整性认证,读取时若密文/nonce/associated data 被篡改,认证校验失败会报错而不是返回伪造明文。pgsodium 的 TCE 还支持 ASSOCIATED (tenant_id, secret_name) 把上下文绑定进认证,防止密文跨行搬移攻击。
为什么说 PostgreSQL 的审计功能还有巨大增强空间?
DB 吐槽大会指出 PG 原生审计能力偏弱:缺少内置的细粒度 SQL 审计(谁在什么时候对什么对象执行了什么、影响多少行),通常要依赖 pgaudit 等外部扩展,而外部扩展在对象粒度、语句类型覆盖、审计日志的存储与检索、权限审计等方面仍不完善。相比商业数据库原生的审计中心,PG 需要更强的内置审计与合规能力。
为什么说不建议用 superuser 维护 PG?有哪些内置角色可以替代?
superuser 拥有最高权限,一旦被应用或运维脚本误用,容易造成误删、越权、安全失控。PG 提供了多个内置预制角色来替代 superuser 的常见用途:pg_read_all_data/pg_write_all_data(全库读写)、pg_monitor(监控视图)、pg_signal_backend(取消/终止后端)、pg_maintain(维护操作)、pg_create_subscription(建订阅)等。用这些角色按需授予,能实现最小权限、缩小爆炸半径。
字段加密到底解决什么问题?它不能解决什么?
字段加密回答的是窄问题:当表文件、备份、WAL、逻辑导出或只读副本落到不该拿到的人手里时,敏感字段是否仍是明文。它不能解决:已有解密视图权限的账号读明文、应用被入侵后正常查询读明文、超级用户/root/调试器读进程内存、以及业务仍要对密文做范围/模糊查询的需求。所以字段加密是威胁模型选择,牺牲查询能力和运维便利,换取离线副本泄露后的损失收敛。