1. 项目概览:为什么数据库对象权限管理是刚需
先说一个扎心的事实:大部分数据库安全问题,不是被外部攻击攻破的,而是内部权限失控导致的。某个开发同事离职后账号没回收、某个应用账号用了超级用户权限跑业务、某张薪酬表人人都能SELECT——这些问题在各类企业里比比皆是。而权限管理,就是数据库安全体系里最基础也最容易被忽视的一环。
KingbaseES作为国内主流的国产关系型数据库,兼容PostgreSQL生态,语法和原理上大量继承自PG,但在实际生产落地时又有不少细节差异。这篇实战文章,我想从数据库对象访问权限管理这个角度切入,完整拆解KingbaseES里的权限模型、授权/回收语句、角色设计、默认权限机制、以及实践中容易踩的坑。
这篇文章适合谁看:
- 刚接手国产数据库运维的DBA,需要快速掌握权限管理核心技能
- 应用开发人员,尤其是要自己建表、自己授权给服务账号的场景
- 打算从Oracle或MySQL迁移到KingbaseES的DBA,想搞清楚权限模型差异
我不打算讲那些官网上已经写清楚的基础SQL,而是把重点放在"为什么要这样授权""实际生产里怎么设计权限体系""遇到权限问题怎么排查"上。读完这篇,你应该能独立规划一套符合最小权限原则的权限方案,并且遇到权限报错时能快速定位原因。
2. 权限体系设计与基础模型拆解
2.1 三种权限层级:系统权限、对象权限、默认权限
KingbaseES的权限管理和PostgreSQL高度相似,整体可以划分成三个层级:
第一层:系统权限(或叫实例级权限)。比如能不能登录数据库(CONNECT)、能不能创建数据库(CREATEDB)、能不能创建角色(CREATEROLE)、能不能超级用户登录。这一层通常只在创建角色时一次性指定,后续很少改动。
第二层:对象权限。这是最核心的部分,指某个用户对某个具体对象(表、视图、序列、函数、模式等)能做什么操作。比如能否SELECT某张表、能否INSERT、能否执行某个函数。这一层是日常授权回收操作的主要战场。
第三层:默认权限(Default Privileges)。这个机制解决一个痛点:新建的对象,默认只有owner有权限,如果每次建表都要手动授权,简直能烦死人。默认权限可以预先设定好,以后某用户新建的表,自动就给指定角色赋权。
举个例子帮助理解:系统权限像"员工能不能进公司大楼",对象权限像"能进哪个办公室、能不能打开某个文件柜",默认权限则像"HR规定了新入职员工自动拿到哪些门禁权限"。
2.2 权限的粒度与可授权对象
KingbaseES支持的对象权限类型和PG基本对齐,常见的包括:
| 对象类型 | 可授予的权限 | 说明 |
|---|---|---|
| 表/视图 | SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | TRIGGER权限慎授,涉及在表上创建触发器 |
| 序列 | USAGE, SELECT, UPDATE | USAGE是基本使用权,SELECT/UPDATE在新版PG中已废弃但Kingbase仍兼容 |
| 函数/存储过程 | EXECUTE | 默认授予PUBLIC,这点尤其要注意 |
| 模式 | USAGE, CREATE | USAGE允许访问模式下对象,CREATE允许在模式下建对象 |
| 数据库 | CONNECT, CREATE, TEMPORARY | CREATE允许在库中创建模式 |
| 表空间 | CREATE | 允许在表空间中创建对象 |
权限控制粒度最细到列级别——可以只授权某张表的某几列给某个角色,这对于处理敏感字段(比如用户手机号、身份证号)特别实用。
另外要特别强调一个概念:PUBLIC。PUBLIC不是一个真实用户,而是一个"所有人"的通配角色。对PUBLIC授权的意思是所有角色都继承该权限。函数和存储过程的EXECUTE权限默认就授予了PUBLIC,这也是很多权限泄露的隐患来源。
2.3 权限模型的继承与叠加逻辑
KingbaseES的权限判断遵循"有权限就通过,不叠加抵消"的逻辑。注意几个关键点:
- 权限是"或"的关系,不是"与"的关系。也就是说,只要通过任何路径(直接授权、角色继承、PUBLIC)拿到了某对象的某个权限,就能执行对应操作,不会被其他路径"抵消"。
- 角色(ROLE)和用户(USER)在KingbaseES里本质上是同一个东西。CREATE ROLE和CREATE USER的区别仅仅是后者默认带有LOGIN属性。所以不要把"用户"和"角色"理解成两种不同的实体,它们只是同一个概念的不同用法。
- 权限的继承通过角色的成员关系实现。把授权给角色A,再把角色A赋予角色B,那么B就自动拥有了A的全部权限。B能否在会话中"切换"到A的权限身份,取决于角色的INHERIT属性和是否有SET ROLE权限。
为什么理解这个模型很重要?因为权限问题排查的本质,就是沿着"用户→角色→对象"这条链路逐层追踪。我在生产环境里见过太多人遇到"明明授权了怎么还没权限"的报错,最后发现是角色继承关系没搞清楚。
3. 对象权限管理的核心实操:授权、回收与查询
3.1 GRANT授权:语法细节与参数选择
GRANT语句是所有权限管理操作的基础。基础语法如下:
GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } [, ...] | ALL [ PRIVILEGES ] } ON { [ TABLE ] table_name [, ...] | ALL TABLES IN SCHEMA schema_name [, ...] } TO role_specification [, ...] [ WITH GRANT OPTION ];实际使用中最常见的几个场景:
-- 场景1:给应用账号授一张表的查询权限 GRANT SELECT ON TABLE public.users TO app_readonly; -- 场景2:给开发账号授某个模式下所有表的增删改查权限 GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO dev_team; -- 场景3:给数据分析账号授特定列权限(列级授权) GRANT SELECT (id, username, created_at) ON TABLE public.users TO analyst_role; -- 场景4:授序列的使用权限(涉及自增主键场景,应用必须有序列USAGE权限) GRANT USAGE ON SEQUENCE public.users_id_seq TO app_service;注意,场景2有个大坑:ALL TABLES IN SCHEMA只对执行该语句时已经存在的表生效,之后新建的表不会自动带上权限。解决办法就是第5章要讲的默认权限。
WITH GRANT OPTION这个参数要谨慎使用。一旦给某人带了GRANT OPTION,意味着这个人不仅可以执行对应操作,还能把该权限转授给其他人。在内部管理规范中,只有权限管理员或者需要代授场景才应该使用,普通应用账号一律不要带这个属性。
3.2 REVOKE回收:容易被忽视的连锁反应
REVOKE和GRANT是配套的,但REVOKE的语义比GRANT要复杂。基础语法:
REVOKE [ GRANT OPTION FOR ] { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } [, ...] | ALL [ PRIVILEGES ] } ON { [ TABLE ] table_name [, ...] | ALL TABLES IN SCHEMA schema_name [, ...] } FROM { [ GROUP ] role_specification [, ...] | PUBLIC } [ CASCADE | RESTRICT ];最容易踩坑的是这两个点:
第一,REVOKE默认只回收直接授予的权限,但不会自动回收通过角色继承获得的权限。假设你把dev_team角色授予了zhangsan,然后针对某张敏感表执行REVOKE ALL ON sensitive_table FROM dev_team,此时zhangsan对该表的权限也随之消失,因为他是通过dev_team角色拿到的权限,角色被回收了,权限链路就断了。但如果你是直接授予zhangsan权限后又把他加进dev_team,那么REVOKE dev_team之后,zhangsan仍然保留直接授予的权限。
第二,GRANT OPTION FOR和CASCADE的配合使用。如果你只想回收某人的"转授权限",保留基础使用权限,用REVOKE GRANT OPTION FOR ...。如果这个人的GRANT OPTION已经被他转授给了别人,那么必须指定CASCADE才能连带回收下游的权限,否则会报错。
实际工作中,我通常建议在REVOKE之前先跑一遍权限查询脚本,确认影响面,再决定是直接回收还是带CASCADE回收。这个习惯能避免不少"回收权限把业务搞挂"的事故。
3.3 权限查询:3张视图搞定90%的排查需求
KingbaseES的权限元数据分散在几张系统表/视图中,常用的有三张:
| 视图名 | 查询内容 | 使用场景 |
|---|---|---|
| information_schema.table_privileges | 表级权限授予情况 | 快速查谁对哪张表有什么权限 |
| information_schema.role_table_grants | 当前用户可查看的表权限 | 比table_privileges多过滤一层当前用户可见性 |
| pg_roles / pg_auth_members | 角色信息与成员关系 | 追踪角色继承链 |
常用查询SQL我封装了两个,直接抄作业就行:
-- 查询某张表的所有授权情况 SELECT grantee, privilege_type, is_grantable FROM information_schema.table_privileges WHERE table_schema = 'public' AND table_name = 'users' ORDER BY grantee; -- 查询某个用户的全部角色和成员关系 SELECT r.rolname AS role_name, m.rolname AS member_name, r.rolsuper AS is_super, r.rolinherit AS inherits FROM pg_roles r LEFT JOIN pg_auth_members am ON r.oid = am.roleid LEFT JOIN pg_roles m ON am.member = m.oid WHERE m.rolname = 'zhangsan' OR r.rolname = 'zhangsan';很多权限问题的排查,其实不需要去翻官方文档,先把table_privileges查一遍,再沿着角色成员关系往下捋,基本都能找到原因。这部分内容可以配合第6章的问题排查一起看。
4. 角色设计与权限继承机制深入解析
4.1 角色vs用户:别再傻傻分不清
很多从Oracle过来的DBA刚接触KingbaseES时,都会困惑"用户和角色到底有什么区别"。在KingbaseES里,CREATE USER和CREATE ROLE创建出来的东西,物理上都是pg_roles表里的一条记录,唯一的区别是是否带LOGIN属性:
-- 下面两条语句等价,只是可读性不同 CREATE USER app_user WITH PASSWORD 'xxx'; CREATE ROLE app_user WITH LOGIN PASSWORD 'xxx'; -- 也可以后来才加上登录属性 ALTER ROLE app_user WITH LOGIN;所以我的设计建议是:用CREATE ROLE定义业务角色(如app_readonly、app_rw、dba_admin),用CREATE USER定义真正要登录数据库的账号。
这样做的价值在于:当人员变动时,只需要把"人"从"角色"中移除或加入,不需要重新执行一大堆GRANT语句。角色的复用性让权限管理从"按人授权"升级为"按职能授权",这是权限治理的第一步。
4.2 INHERIT与SET ROLE:角色继承的两条路
角色成员关系的权限传递有两种方式:被动继承(INHERIT)和主动切换(SET ROLE / SET SESSION AUTHORIZATION)。
默认情况下,CREATE ROLE创建的角色INHERIT属性为TRUE,意味着成员角色自动继承被成员角色的权限。比如:
CREATE ROLE dev_team; GRANT SELECT ON ALL TABLES IN SCHEMA public TO dev_team; CREATE USER zhangsan IN ROLE dev_team; -- zhangsan此时就自动拥有了dev_team的所有权限但如果某个角色是NOINHERIT属性,那么即使你是它的成员,默认也不会继承它的权限,必须通过SET ROLE切换过去才能使用。举例:
CREATE ROLE sensitive_role NOINHERIT; GRANT SELECT ON sensitive_table TO sensitive_role; CREATE USER lisi IN ROLE sensitive_role; -- lisi直接SELECT sensitive_table会报权限不足 -- 必须先执行 SET ROLE sensitive_role;这种设计适合什么场景?高权限操作的"临时工"模式。日常用低权限身份干活,需要查敏感数据时才临时切换,操作审计日志里也会留下SET ROLE的记录,做到了权限最小化和行为可追溯。
NOINHERIT这种设计在Oracle里叫"权限角色不默认生效",在PG生态里也算常见做法。如果你的业务有类似"平时只读,偶尔要写"的场景,建议用NOINHERIT角色来约束。
4.3 角色设计最佳实践:三类账号模型
我在实际项目里推荐按以下模型设计数据库账号体系:
| 角色类型 | 命名示例 | 权限范围 | 用途 |
|---|---|---|---|
| 超级管理员 | system_dba | 超级用户权限 | 只能DBA使用,用于日常运维管理 |
| 业务应用账号 | app_service | 所属业务库的增删改查 + 序列USAGE | 应用服务器连接数据库用 |
| 开发/查询账号 | dev_readonly / analyst | 指定模式的只读权限 | 开发调试、数据分析、报表查询 |
这套模型遵循了最基本的权限管理原则:超级用户给DBA专用,应用账号只给业务所需最小权限,只读账号绝不分配写权限。
还有一个容易被忽略的设计细节:数据库连接权限(CONNECT)也应该被管理。如果某个开发账号只需要连接业务库A,不要给它整个数据库实例所有库的CONNECT权限。在创建角色时可以通过REVOKE CONNECT实现:
REVOKE CONNECT ON DATABASE business_db FROM PUBLIC; GRANT CONNECT ON DATABASE business_db TO app_service, dev_readonly;这样做的好处是,其他数据库的权限不会因为PUBLIC默认权限而暴露。尤其是数据库实例上挂着多个业务库时,这个操作能有效减少横向越权风险。
5. 默认权限与批量授权:自动化时代的必备工具
5.1 为什么需要ALTER DEFAULT PRIVILEGES
前文提到,GRANT ALL ON ALL TABLES IN SCHEMA只对已有对象生效。实际业务里,应用的表结构是不断迭代的,开发今天新建一张表,如果每次都要手动授权,既容易遗漏,也不可持续。
ALTER DEFAULT PRIVILEGES的语义就是:设定一个规则,以后某个用户在指定模式下新建的对象,自动授予指定角色相应权限。相当于给"新对象"预置权限模板。
举个例子,假设业务表的owner是table_owner,应用账号是app_service,希望table_owner每建一张新表,app_service自动获得增删改查权限:
ALTER DEFAULT PRIVILEGES FOR ROLE table_owner IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_service; ALTER DEFAULT PRIVILEGES FOR ROLE table_owner IN SCHEMA public GRANT USAGE ON SEQUENCES TO app_service;这里FOR ROLE table_owner指这条默认权限规则是"针对table_owner新建的对象"生效的。如果不写FOR ROLE,默认针对当前执行者的新建对象。
还要提醒一下,ALTER DEFAULT PRIVILEGES只影响"新建的对象"——已经存在的旧表不受影响,需要手动授权一次。所以上线前要做一次全量授权,上线后再依赖默认权限覆盖增量对象。
5.2 批量授权脚本的思路
当库里有几十上百张表时,写一段可重复执行的授权脚本就很有必要。我一般这么处理:
-- 生成批量授权语句 SELECT 'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ' || schemaname || '.' || tablename || ' TO app_service;' FROM pg_tables WHERE schemaname = 'public';然后把输出结果复制到psql里执行,或者用\gexec在psql客户端中直接执行。不过更稳妥的方式,还是把授权逻辑放进SQL脚本里,加上事务控制,保证"要么全成功,要么全回滚":
BEGIN; DO $$ DECLARE r RECORD; BEGIN FOR r IN SELECT schemaname, tablename FROM pg_tables WHERE schemaname = 'public' LOOP EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.%I TO app_service', r.schemaname, r.tablename); END LOOP; END $$; -- 再加默认权限兜底 ALTER DEFAULT PRIVILEGES FOR ROLE table_owner IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_service; COMMIT;用%I做格式化的原因是要防SQL注入——表名虽然一般不会人为恶搞,但内部工具生成脚本时,保不齐出现一些奇怪字符,用format函数能保证标识符被正确引用。
5.3 谨慎处理函数与存储过程的默认EXECUTE权限
这是一个特别容易忽略的安全问题。在KingbaseES和PostgreSQL中,函数和存储过程的EXECUTE权限默认授予PUBLIC。也就是说,任何能登录数据库的用户,都能直接执行库中任意函数。
这个设计初衷可能是为了让函数调用更方便,但安全隐患非常大。比如一个函数内部执行了UPDATE accounts SET balance = 0,虽然是业务逻辑封装,但你肯定不希望任何人都有权限调用。
处理方式很简单,在业务函数上线后立刻回收PUBLIC权限,再显式授权给指定角色:
-- 先收掉PUBLIC的默认权限 REVOKE ALL ON FUNCTION public.update_account_balance(INTEGER, NUMERIC) FROM PUBLIC; -- 再授权给应用账号 GRANT EXECUTE ON FUNCTION public.update_account_balance(INTEGER, NUMERIC) TO app_service;如果库里的函数特别多,可以结合information_schema.routines视图生成批量脚本统一处理。这也是安全审计时的一个必检项。
另外一个值得启动的机制是SECURITY DEFINER函数。这类函数执行时使用函数owner的权限,而不是调用者的权限。这在某些场景很有用:比如只读账号需要跨表做汇总,但不想给它全部表的权限,就可以封装一个以读权限owner身份执行的函数。但权力越大风险越大,SECURITY DEFINER函数必须严格限制调用者,做好入参校验,防止提权攻击。
6. 实战场景配置:从零搭建一套完整的权限方案
6.1 业务场景描述与需求拆解
假设现在要上线一个电商订单系统,数据库采用KingbaseES,涉及角色如下:
- DBA:负责日常运维,使用超级用户
- 应用服务器:通过
app_order账号访问业务库 - 数据分析师:通过
analysis_ro账号读取订单相关数据,用于BI报表 - 开发工程师:通过
dev_backend账号进行开发调试
业务需求就三条:应用只能操作订单库的表、数据分析师只能读取不能修改、开发在开发库有完整的DML权限但生产库最多只读。
这个需求非常典型,几乎每家公司的权限设计都会碰到。接下来我按实际操作为大家完整演示一遍。
6.2 完整操作流程实录
第一步:创建业务库和专用角色
CREATE DATABASE order_db; REVOKE CONNECT ON DATABASE order_db FROM PUBLIC;这里先把PUBLIC的CONNECT权限收回,杜绝未授权用户连接。然后创建业务角色:
CREATE ROLE app_order LOGIN PASSWORD 'OrderApp@2025'; GRANT CONNECT ON DATABASE order_db TO app_order; CREATE ROLE analysis_ro LOGIN PASSWORD 'AnalysisRo#2025'; GRANT CONNECT ON DATABASE order_db TO analysis_ro; CREATE ROLE dev_backend LOGIN PASSWORD 'DevBackend*2025'; GRANT CONNECT ON DATABASE order_db TO dev_backend;第二步:连接order_db创建业务表并授权
\c order_db -- 创建表(owner默认是当前DBA用户,需要将owner转移给一个专属的表owner角色更规范) CREATE ROLE table_owner; CREATE TABLE orders ( order_id BIGSERIAL PRIMARY KEY, user_id INTEGER NOT NULL, amount NUMERIC(10,2) NOT NULL, status VARCHAR(20) DEFAULT 'pending', created_at TIMESTAMP DEFAULT now() ); ALTER TABLE orders OWNER TO table_owner;把表owner设定为table_owner而不是某个具体业务账号,好处是将来人员变动不影响表与企业资产的归属关系。
第三步:按角色授权
-- 应用账号授增删改查 + 序列USAGE GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_order; GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO app_order; -- 分析账号只授SELECT GRANT SELECT ON ALL TABLES IN SCHEMA public TO analysis_ro; -- 开发账号在生产库也走只读 GRANT SELECT ON ALL TABLES IN SCHEMA public TO dev_backend; -- 配置默认权限,覆盖未来新建的表 ALTER DEFAULT PRIVILEGES FOR ROLE table_owner IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_order; ALTER DEFAULT PRIVILEGES FOR ROLE table_owner IN SCHEMA public GRANT SELECT ON TABLES TO analysis_ro, dev_backend; ALTER DEFAULT PRIVILEGES FOR ROLE table_owner IN SCHEMA public GRANT USAGE ON SEQUENCES TO app_order;第四步:处理函数PUBLIC权限隐患
-- 假设有个计算订单总价的函数 CREATE FUNCTION calculate_order_total(oid BIGINT) RETURNS NUMERIC AS $$ SELECT SUM(amount) FROM orders WHERE order_id = oid AND status != 'cancelled'; $$ LANGUAGE SQL; REVOKE ALL ON FUNCTION calculate_order_total(BIGINT) FROM PUBLIC; GRANT EXECUTE ON FUNCTION calculate_order_total(BIGINT) TO app_order, analysis_ro;第五步:验证权限是否生效
-- 用 analysis_ro 连接,尝试更新数据 \c order_db analysis_ro SELECT * FROM orders LIMIT 5; -- 应该成功 UPDATE orders SET status = 'shipped' WHERE order_id = 1; -- 应该报权限不足如果UPDATE没有报权限错误,说明授权模型存在漏洞,需要回头检查是不是表owner设置错误或者PUBLIC权限没收干净。
6.3 为什么要这样设计:方案背后的逻辑
这套方案的几个关键决策点:
为什么把表owner单独设为table_owner?因为默认权限的FOR ROLE依赖owner身份。如果owner直接是超级用户,那ALTER DEFAULT PRIVILEGES FOR ROLE sys_dba看起来不直观,而且后续如果把表owner转给业务账号,默认权限的绑定关系也会乱。单独建一个table_owner角色,语义清晰、权限边界明确。
为什么应用账号单独用一套,不给开发账号同样的写权限?应用账号是生产环境直接跑流量的,给多了权限等于扩大了攻击面;开发账号在生产库最多只读,避免误操作影响线上数据。这个约束在技术上通过授权模型强制落地,从源头规避风险。
为什么序列和表分开授权?这是PG系数据库最容易踩的坑。表有INSERT权限但序列没有USAGE权限,业务插入自增主键时照样报错"permission denied for sequence"。所以凡是涉及自增主键的场景,一定要记得把序列权限也授出去。
7. 常见权限问题与排查技巧实录
7.1 四种最典型的权限报错与排查
| 报错信息 | 可能原因 | 解决方向 |
|---|---|---|
| permission denied for table xxx | 对表没有对应DML权限 | 查table_privileges确认授权,检查角色继承链 |
| permission denied for sequence xxx | 序列没有USAGE权限 | 单独GRANT USAGE ON SEQUENCE |
| permission denied for schema xxx | 用户没有模式USAGE权限 | GRANT USAGE ON SCHEMA,别只授表权限 |
| must be owner of table xxx | 需要对象owner身份 | 确认当前操作人是否对象owner或超级用户 |
其中"permission denied for schema xxx"是最容易让人懵的。很多时候表权限授得很完整,但SCHEMA没有USAGE权限,用户照样无法访问。这就像给了你办公室钥匙,但不让你进办公楼的大门。
7.2 排查流程:从权限视图到角色链路的递进分析
我的排查套路基本固定,分四步走:
第一步,确认当前用户身份:
SELECT current_user, session_user;注意current_user和session_user可能不同——如果你SET ROLE切换过角色,current_user会变化而session_user不变。先确认当前是谁在操作,能避免后面白忙活。
第二步,查询对象的授权情况:
SELECT grantee, privilege_type FROM information_schema.table_privileges WHERE table_name = 'orders';第三步,沿着角色继承链倒查:
WITH RECURSIVE role_tree AS ( SELECT oid, rolname, 0 AS depth FROM pg_roles WHERE rolname = current_user UNION ALL SELECT r.oid, r.rolname, rt.depth + 1 FROM pg_roles r JOIN pg_auth_members am ON r.oid = am.roleid JOIN role_tree rt ON am.member = rt.oid ) SELECT DISTINCT rolname FROM role_tree;第四步,检查是否被PUBLIC或默认权限影响:
查一下对象的ACL,看看有没有隐式的PUBLIC授权:
SELECT relacl, relowner::regrole FROM pg_class WHERE relname = 'orders';ACL字段里如果有=r这种片段,意味着PUBLIC有读权限。这也是有些时候"没显式授权但居然能读"的原因。
7.3 一个真实案例:开发账号"有权限却报错"的追踪
有一次生产环境,开发反馈说dev_backend账号能SELECT订单表,但某个函数执行报permission denied。同一套数据库,同样都是查订单表,一个是表一个是函数,行为却不一致。
排查过程很有意思:
- 查表权限:dev_backend确实有SELECT权限,没问题。
- 查函数权限:发现函数是SECURITY DEFINER,owner是table_owner,且函数的EXECUTE权限只授给了app_order,没授给dev_backend。所以dev_backend执行时被拒绝。
问题实际上不是"表权限错了",而是函数权限没有跟上。这种跨对象权限组合的排查,经验不足的人容易卡半天。所以我建议大家权限方案落地时,要检查"业务链路",而不是单独看某一个对象——如果一个业务操作涉及表+序列+函数,那这三个对象的权限需要一并确认。
7.4 独家避坑:别被"超级用户"掩盖了权限模型问题
再分享一个经验:开发环境为了省事,经常给开发账号直接赋予超级用户权限。这确实让业务跑得很顺,但一旦进入测试甚至生产环境,切换成最小权限账号时,各种问题就集中爆发了。
所以我现在的习惯是:开发环境也尽量按生产环境的权限模型搭建。成本不高,但能提前暴露很多权限配置问题,而不是留到生产环境去踩雷。如果你已经在用超级用户跑开发环境,建议至少抽时间把权限模型梳理一遍,至少让测试环境先严格起来。
另外还有一个小技巧:权限配置脚本一定要纳入版本管理。我见过不少团队,权限是靠DBA手动在数据库里敲的,时间一长根本说不清哪些权限是必要的、哪些是历史遗留。把授权脚本写成文件,跟着代码一起发布,既能审计也能快速在另一套环境重建相同的权限体系。
8. 实操中的几点体会与扩展建议
权限管理这件事,做得好的团队往往不觉得它存在,做不好的团队隔三差五就会被它绊一下。从我自己的经验来看,有几件事是投入产出比最高的:把账号体系从"人"抽象成"角色"、把授权脚本纳入代码仓库、把默认权限机制用起来减少人工操作。只要这三件事做到位,数据库权限治理的基本盘就稳了。
如果再往前一步,可以关注KingbaseES的行级安全(RLS)和列级加密特性。权限管理解决的是"谁能访问哪些对象"的问题,而RLS解决的是"即使能访问对象,也只能看到特定行"的问题。比如一个客服系统,所有客服都能访问客户表,但通过RLS只能看到自己负责的客户数据。这已经是权限管理的进阶玩法了,等基础权限体系稳固之后再上更合适。
最后留一个扩展方向:审计日志。权限再完善,也得有审计配合。KingbaseES的审计功能可以记录谁在什么时间通过什么角色执行了什么操作。把权限管理和审计日志结合起来,才能在发生安全事件时做到有据可查。条件允许的话,我建议从项目初期就把审计开起来,哪怕只是最基础的登录和DDL审计,后面回顾时也会感谢当时的自己。