PostgreSQL Field Guide

安全基线

用网络、认证、角色、对象权限和行级策略形成多层边界

分离角色

CREATE ROLE app_owner NOLOGIN;
CREATE ROLE app_runtime LOGIN;
CREATE ROLE app_migrator LOGIN NOINHERIT;

CREATE SCHEMA app AUTHORIZATION app_owner;
GRANT app_owner TO app_migrator;
GRANT CONNECT ON DATABASE commerce TO app_runtime, app_migrator;
GRANT USAGE ON SCHEMA app TO app_runtime;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_runtime;

ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_runtime;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
  GRANT USAGE, SELECT ON SEQUENCES TO app_runtime;

运行时角色不拥有对象,迁移角色只在迁移期间 SET ROLE app_owner,所有者角色不登录。ALTER DEFAULT PRIVILEGES 只影响未来由指定创建者创建的对象,不会回补已有对象。

为 BI 工具、支持查询和 AI agent 增加一个只读层,补全分层:

CREATE ROLE app_readonly LOGIN;
ALTER ROLE app_readonly SET default_transaction_read_only = on;

GRANT CONNECT ON DATABASE commerce TO app_readonly;
GRANT USAGE ON SCHEMA app TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readonly;

ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
  GRANT SELECT ON TABLES TO app_readonly;

default_transaction_read_only 保证即使之后误加了写权限,意外写入也会直接失败。

认证与网络

  • 只监听需要的接口,用防火墙/安全组限制来源。
  • 远程连接要求 TLS,并验证服务端证书;高敏场景考虑客户端证书。
  • 新密码认证使用 SCRAM,逐步淘汰 MD5 配置。
  • pg_hba.conf 按具体网络、database、role 从窄到宽编排;修改后 reload 并测试允许与拒绝两条路径。
  • 管理入口与应用入口分开,避免向公网暴露数据库端口。

与上述顺序对应的 pg_hba.conf 示例——先匹配先生效,所以窄规则在前:

# TYPE   DATABASE  USER          ADDRESS        METHOD
local    all       all                          peer
hostssl  commerce  app_runtime   10.0.1.0/24    scram-sha-256
hostssl  commerce  app_readonly  10.0.2.0/24    scram-sha-256
host     all       all           0.0.0.0/0      reject

修改后执行 SELECT pg_reload_conf();,并分别测试放行与拒绝两条路径。

search_path 防护

不要信任可写 schema 中的同名对象解析。撤销 public 的默认创建权,并为安全敏感函数固定路径:

REVOKE CREATE ON SCHEMA public FROM PUBLIC;

CREATE FUNCTION app.current_tenant() RETURNS bigint
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = pg_catalog, app
AS $$ SELECT current_setting('app.tenant_id')::bigint $$;

REVOKE ALL ON FUNCTION app.current_tenant() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION app.current_tenant() TO app_runtime;

SECURITY DEFINER 函数以所有者权限运行,必须审计所有参数、对象限定名、search path 和执行权限。

行级安全 RLS

ALTER TABLE app.orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE app.orders FORCE ROW LEVEL SECURITY;

CREATE POLICY tenant_orders ON app.orders
USING (tenant_id = current_setting('app.tenant_id')::bigint)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::bigint);

RLS 启用后,没有适用策略时默认拒绝。超级用户、BYPASSRLS 角色以及通常的表所有者可绕过;FORCE ROW LEVEL SECURITY 让所有者在普通访问中也受策略约束。仍需普通对象权限。

用真实应用角色测试

管理员测试成功不能证明 RLS 有效。测试允许租户、其他租户、缺失 tenant context、插入与更新,并确认连接池每次借出/归还时正确设置和清除上下文。

Secret 与日志

凭据轮换、短期化并由 secret manager 分发。数据库日志避免记录绑定值和敏感 DDL;审计日志限制访问与保留期。pg_stat_activity 也可能显示 SQL 文本,读取监控视图的权限同样需要控制。

审计

内建的起点是语句日志:

ALTER SYSTEM SET log_statement = 'ddl';
SELECT pg_reload_conf();

需要可追责的审计轨迹时使用 pgaudit,它必须通过 shared_preload_libraries 加载:

shared_preload_libraries = 'pgaudit'
pgaudit.log = 'write, ddl, role'

需要提前规划的边界:

  • pgaudit 是扩展而非核心功能,可用性和版本取决于发行方式或云服务商。
  • pgaudit.log = 'all' 这类宽类别在繁忙系统上日志量巨大。先从 ddl, role 开始,按合规要求逐步增加类别;对少数敏感表用对象级审计(pgaudit.log_relation),而不是全量记录。
  • 会话审计仍可能记录敏感值,上一节的日志访问、保留和脱敏规则同样适用于审计输出。

Last updated on

On this page