首页 / 帮助文档 / PostgreSQL数据库角色权限的最小化分配原则

PostgreSQL数据库角色权限的最小化分配原则

在PostgreSQL里谈权限最小化,很多人上来就revoke public,然后直接grant all on database,这其实已经跑偏了。最小化分配不是“先给个大权限再往回扣”,而是从零开始,只授予恰好够用的权限。PostgreSQL的权限体系是累加式的,一个角色拥有的权限等于它自身被授予的权限加上所有继承角色的权限之和。理解这个模型,才能真正做到最小化。

角色与用户的本质区别

PostgreSQL里没有“用户”这个独立概念,只有角色。带login属性的角色就是通常说的登录用户,不带login的就是纯粹的权限集合体,也就是组角色。这个设计非常关键,因为最小化分配的核心策略就是把权限封装在组角色里,然后把人放进对应的组。你永远不应该直接给一个人类用户grant任何对象权限,而是创建一个组角色,把权限授予组角色,再把用户角色通过grant加入组角色。这样做的好处是,当人员变动时,你只需要撤销用户与组角色的成员关系,不需要去逐个对象上回收权限。

public角色的隐患与正确处置方式

每个新建的数据库都会自动创建一个名为public的特殊角色,而且所有角色默认都是public的成员。更危险的是,PostgreSQL默认会向public授予数据库的connect权限和schema的usage与create权限。这意味着任何一个能登录数据库服务器的角色,默认就能连接任何数据库并在其中的public schema里创建对象。这在多租户环境下是灾难性的。正确的做法是:创建数据库后立即执行revoke all on database dbname from public,然后针对具体schema执行revoke create on schema public from public。如果你需要某个角色能连接数据库,单独grant connect on database给那个角色,而不是依赖public的默认权限。

schema层级权限的精细化控制

PostgreSQL的权限分为多个层级:实例级、数据库级、schema级、对象级。很多DBA只关注表级权限,忽略了schema这个中间层。实际上,一个角色即使拥有某个表的所有权限,如果它没有对应schema的usage权限,也完全无法访问那个表。usage权限是schema的“入场券”,没有它,下面的对象权限形同虚设。最小化分配要求你为每个schema明确指定usage权限的授予对象。通常的做法是:生产schema只给应用账号usage权限,分析schema只给只读组usage权限,etl schema只给写入组usage权限。不同业务模块使用不同schema,通过schema级别的usage权限实现第一层隔离。

表级权限的精确授予

到了表这一层,PostgreSQL提供了select、insert、update、delete、truncate、references、trigger七种权限。最小化原则要求你精确到列级别和操作级别。比如一个报表查询角色,只需要select权限,那就只grant select on table,绝不多给。如果某个角色只需要读取表的特定几列,可以用列级授权:grant select (col1, col2) on table to role。列级权限在PostgreSQL中是原生支持的,但很多团队根本不用,因为他们觉得管理起来麻烦。实际上,列级授权配合视图使用,能构建出非常严密的数据访问控制体系。update权限同样可以精确到列,这对于保护敏感字段非常有效。

序列、函数与其他对象的权限陷阱

序列的权限经常被忽略。一个角色拥有表的所有列插入权限,但如果序列没有usage权限,插入操作就会失败,因为主键默认值依赖序列。序列的权限需要单独授予:grant usage on sequence seq_name to role。同理,函数默认的执行权限是授予public的,这意味着任何能连接数据库的角色都能执行你定义的函数。如果你的函数内部有数据修改逻辑或者敏感查询,这就会造成权限泄漏。创建函数后应该执行revoke execute on function from public,然后单独grant execute给需要的角色。这是最容易被攻破的权限薄弱点之一。

默认权限的配置策略

手动逐个对象授权在对象数量多的时候完全不现实,而且容易遗漏。PostgreSQL提供了alter default privileges命令,可以为某个角色设置未来创建对象的默认权限。典型的配置是:为应用账号设置默认权限,让它在指定schema下新建的表自动被只读组获得select权限。命令如下:

alter default privileges for role app_user in schema app_schema grant select on tables to readonly_group;
alter default privileges for role app_user in schema app_schema grant usage on sequences to readonly_group;

注意,默认权限只对设置之后创建的对象生效,已有对象需要手动处理。另外,默认权限是针对创建者角色设置的,不是针对schema设置的,这一点很容易搞混。如果你的表由多个不同角色创建,需要为每个创建者角色分别设置默认权限。

角色继承关系的深度运用

PostgreSQL的角色可以继承其他角色的权限,通过grant parent_role to child_role实现。最小化分配的最佳实践是构建一个三层角色体系:底层是对象权限角色,直接持有表、序列、函数的操作权限;中间层是功能角色,组合多个对象权限角色,代表一个完整的功能模块权限集;顶层是人员角色,代表真实的人,只被授予中间层功能角色的成员资格。人员角色本身不持有任何对象权限,所有权限都通过继承获得。这样当一个人调岗或离职时,只需revoke其人员角色与功能角色的成员关系,所有权限瞬间回收,干净利落。

noinherit属性的妙用

有时候你希望一个角色拥有某个权限组的成员资格,但默认不激活这些权限,需要显式切换时才生效。这时可以用noinherit属性。创建一个角色时指定noinherit,或者用alter role设置,这个角色被授予的组角色权限不会自动继承。用户需要在会话中执行set role group_role来临时激活权限。这对于执行高危操作的DBA角色特别有用——平时以低权限运行,需要时显式切换,操作完成后set role reset回到低权限状态。这是一种操作层面的权限最小化实践,能有效防止误操作。

行级安全策略的补充作用

表级权限控制的是“能不能操作这张表”,行级安全策略控制的是“能操作这张表里的哪些行”。两者结合才能真正做到细粒度权限控制。启用行级安全需要alter table enable row level security,然后创建策略定义可见行规则。例如,一个区域经理只能看到自己区域的数据:

create policy region_policy on sales_data for select to manager_role using (region_id = current_setting('app.current_region')::int);

行级安全策略可以定义针对select、insert、update、delete的不同规则。需要注意的是,表所有者默认绕过行级安全策略,除非你特意alter table force row level security。如果你的应用使用统一的数据库账号,行级安全几乎是实现多租户数据隔离的唯一有效手段。

权限审计与持续监控

权限最小化不是一次性工作,而是一个持续的过程。PostgreSQL提供了丰富的系统视图来审计当前权限状态。information_schema.table_privileges可以查看表级权限,information_schema.role_table_grants显示角色被授予的表权限。更底层的是pg_catalog中的表,如pg_roles查看角色属性,pg_auth_members查看角色成员关系。建议定期运行权限审计脚本,检查是否存在直接授予人员角色的对象权限、是否存在public角色持有的多余权限、是否存在未使用的角色成员关系。权限的膨胀往往是悄无声息的,开发环境临时加的一个权限如果没及时回收,可能一路带到生产环境。

实际落地中的常见反模式

第一个反模式是到处使用超级用户。postgres角色和任何带superuser属性的角色完全绕过所有权限检查,这意味着权限最小化体系对它们无效。应用连接、日常维护都不应该使用超级用户账号。第二个反模式是给应用账号owner权限。表的所有者拥有该表的全部权限且不可被revoke,如果应用账号是表的所有者,你就失去了对应用账号的权限控制能力。正确的做法是使用一个独立的部署角色创建对象,然后把所需权限授予应用账号。第三个反模式是在所有环境使用相同的权限配置。开发、测试、生产环境的权限需求不同,开发环境可能需要更宽松的权限以便调试,但生产环境必须严格收紧。使用基础设施即代码的方式管理权限,确保每个环境的权限配置可追溯、可复现。