电科金仓权限管理实战:那些让我加班到凌晨的权限“陷阱”
上周五晚上10点,我正准备下班,开发组长小王火急火燎地跑过来:“哥,我们的报表系统崩了,用户说查不到数据!”我一看,又是权限问题——视图访问授权没做对。这已经是我们迁移到电科金仓后遇到的第N个权限问题了。今天,我把这些坑一个个挖出来,填平了给大家看。
开头:那个让我加班到凌晨的周五
说实话,我干了八年数据库运维,从Oracle到MySQL,啥场面没见过。但去年公司决定用电科金仓KES替换Oracle,我在权限管理上真是栽了不少跟头。
就说上周五那个事吧。我们的报表系统里有个核心视图,业务部门每天都要用。结果那天下午,突然所有用户都报“权限不足”。我查了半天,发现是有人误操作,把schema权限给改了。
这个经历让我意识到:金仓的权限体系很强大,但也很“较真”,一个细节没注意,整个系统都可能出问题。今天我就讲讲三个最常见的权限“陷阱”,都是我们团队用真金白银换来的经验。
陷阱一:视图访问授权——你以为授权了,其实没有
问题现场:“这个视图我能建,为啥别人不能查?”
我们有个数据分析师小张,他在自己的schema里建了个视图,然后想让其他部门的同事也能查。他觉得很简单:“不就是给个查询权限嘛!”结果操作完,同事还是报错。
我让他演示一下操作过程:
-- 小张(ud用户)建了个视图\c-udCREATEVIEWud.sales_viewASSELECT*FROMsales_dataWHEREregion='华东';-- 他觉得自己已经授权了GRANTSELECTONud.sales_viewTOreport_user;看起来没问题对吧?但report_user还是查不了。错误信息是:
ERROR: permission denied for schema ud问题根源:schema权限是道“门”
我告诉小张:“你给了人家进房间的钥匙(视图权限),但没开门啊!”
在金仓里,schema就像一个个房间。你要进房间拿东西(查询视图),需要两把钥匙:
- 房间的门禁卡(schema的USAGE权限)
- 具体物品的取用权(视图的SELECT权限)
正确操作:两步授权法
我教小张正确的授权流程:
第一步:先开门(授权schema访问)
-- 需要schema的owner或者有授权权限的人来操作\c-system-- 用system用户,因为schema owner是system-- 给report_user发门禁卡GRANTUSAGEONSCHEMAudTOreport_user;第二步:再授权物品(授权视图查询)
-- 小张可以自己操作,因为他是视图owner\c-udGRANTSELECTONud.sales_viewTOreport_user;这两步都做完,report_user才能正常查询。
进阶技巧:批量授权和角色管理
后来我们发现,这种需求很常见。与其每次都手动操作,不如建立一套权限管理体系。
方案一:创建只读角色
-- 创建一个专门用于查询的角色CREATEROLE report_viewer NOLOGIN;-- 批量授权所有视图GRANTSELECTONALLTABLESINSCHEMAudTOreport_viewer;-- 注意:这个ALL TABLES包括视图!-- 把角色赋给用户GRANTreport_viewerTOreport_user1,report_user2,report_user3;方案二:设置默认权限
如果我们希望未来新建的视图也能自动授权,可以设置默认权限:
-- 设置默认权限,以后ud用户在ud schema下建的所有表/视图,-- report_viewer角色都能查ALTERDEFAULTPRIVILEGESFORUSERudINSCHEMAudGRANTSELECTONTABLESTOreport_viewer;这个功能特别有用,设好之后就不用每次手动授权了。
真实案例:我们搞砸过一次
有一次我们给一个新员工授权,忘了给schema权限。结果这位同事试了半天都查不了数据,还以为系统坏了,差点提了紧急工单。
后来我们做了个授权检查清单,每次授权都按这个来:
- 检查用户是否需要schema权限
- 检查用户是否需要具体对象权限
- 验证授权是否生效
- 通知用户测试
陷阱二:权限简写——那些神秘字母的含义
问题现场:“arwdDxt是啥?外星语吗?”
刚用金仓的时候,我最头疼的就是看权限信息。比如这样的:
ud=arwdDxt/ud role01=r/ud我问团队里的小李:“你能看懂这啥意思吗?”小李一脸懵:“王哥,这比摩斯密码还难懂啊!”
解密:每个字母都有含义
其实这是金仓的权限简写系统。我后来专门研究了文档,搞明白了每个字母的意思:
- a- INSERT(追加)
- r- SELECT(读取)
- w- UPDATE(写入)
- d- DELETE(删除)
- D- TRUNCATE(清空)
- x- REFERENCES(外键引用)
- t- TRIGGER(触发器)
所以arwdDxt就是拥有所有权限,r就是只有查询权限。
实战:如何查看和分析权限
查看表权限:
-- 最简单的查法\dp 表名-- 或者用SQL查SELECTrelnameAS表名,relaclAS权限信息FROMsys_classWHERErelname='sales_data';查看schema权限:
SELECTnspnameASschema名,sys_catalog.sys_get_userbyid(nspowner)AS所有者,nspaclAS权限信息FROMsys_namespaceWHEREnspname='ud';查看数据库权限:
SELECTdatnameAS数据库名,sys_catalog.sys_get_userbyid(datdba)AS所有者,dataclAS权限信息FROMsys_database;小技巧:写个解析函数
为了让团队都能看懂,我写了个权限解析函数:
CREATEORREPLACEFUNCTIONdecode_privileges(priv_textTEXT)RETURNSTABLE(privilege_nameTEXT,privilege_descTEXT)AS$$DECLAREpriv_mappingTEXT[][]:=ARRAY[['a','INSERT(插入数据)'],['r','SELECT(查询数据)'],['w','UPDATE(更新数据)'],['d','DELETE(删除数据)'],['D','TRUNCATE(清空表)'],['x','REFERENCES(外键引用)'],['t','TRIGGER(触发器操作)']];iINT;chCHAR;BEGIN-- 遍历权限字符串的每个字符FORiIN1..length(priv_text)LOOPch :=substring(priv_textFROMiFOR1);-- 查找对应的权限描述FORjIN1..array_length(priv_mapping,1)LOOPIFpriv_mapping[j][1]=chTHENprivilege_name :=ch;privilege_desc :=priv_mapping[j][2];RETURNNEXT;EXIT;ENDIF;ENDLOOP;ENDLOOP;END;$$LANGUAGEplpgsql;-- 使用示例SELECT*FROMdecode_privileges('arwd');现在团队里谁看不懂权限简写,就跑一下这个函数,一目了然。
经验教训:权限审计很重要
我们曾经遇到过权限泄露问题。有人给一个临时账户授予了过大权限,后来忘了回收。幸亏我们定期做权限审计:
-- 每月一次的权限审计脚本SELECT'高风险:过度授权'AS问题类型,grantee::regroleAS用户,table_schema||'.'||table_nameAS对象,privilege_typeAS权限FROMinformation_schema.role_table_grantsWHEREgranteeNOTIN('system','sso','sao')-- 排除管理员ANDprivilege_typeIN('DELETE','TRUNCATE','REFERENCES','TRIGGER')ANDtable_schemaNOTLIKE'pg_%'ANDtable_schema!='information_schema'ORDERBYgrantee,table_schema,table_name;陷阱三:VACUUM权限——普通用户的“禁区”
问题现场:“我连清理表都不行吗?”
这是我们运维小刘遇到的问题。他发现某个表膨胀得厉害,想手动VACUUM一下,结果:
\c-app_user VACUUM sales_data;-- 结果:WARNING: skipping"sales_data"--- only table or database owner can vacuum it小刘很郁闷:“我好歹也是这个表的owner,怎么连清理都不行?”
真相:VACUUM是个特权操作
我告诉小刘:“VACUUM在金仓里是个特殊操作,普通用户确实干不了。”
为什么呢?因为VACUUM涉及到底层存储结构的调整,有一定风险。金仓设计时,只允许两种人执行VACUUM:
- 表的owner
- 超级用户(superuser)
解决方案:几种变通方法
方法一:让表的owner来执行
这是最简单的办法。如果app_user是表的owner,理论上可以执行。但如果还是不行,可能是因为有其他限制。
方法二:临时提权(谨慎使用)
-- 用system用户临时授权\c-systemGRANTsystemTOapp_user;-- 让app_user临时拥有system权限-- app_user执行VACUUM\c-app_user VACUUM sales_data;-- 用完马上回收\c-systemREVOKEsystemFROMapp_user;方法三:设置超级用户(不推荐)
ALTERUSERapp_userWITHSUPERUSER;但这个方法太危险了!一旦给用户超级权限,他就能为所欲为,包括删除整个数据库。
我们的实践:建立VACUUM管理流程
我们公司最后定了一套规矩:
- 普通用户不准手动VACUUM:全部交给自动VACUUM机制
- 紧急情况走流程:确实需要手动VACUUM时,提工单给DBA团队
- DBA统一操作:每周一早上,DBA检查表膨胀情况,统一执行VACUUM
我们还写了个监控脚本,自动发现需要VACUUM的表:
-- 监控表膨胀的脚本CREATEORREPLACEFUNCTIONcheck_table_bloat()RETURNSTABLE(schema_nameTEXT,table_nameTEXT,row_countBIGINT,dead_row_countBIGINT,bloat_ratioNUMERIC(5,2),need_vacuumBOOLEAN)AS$$BEGINRETURNQUERYSELECTschemaname::TEXT,tablename::TEXT,n_live_tup,n_dead_tup,CASEWHENn_live_tup=0THEN0ELSEROUND(n_dead_tup*100.0/n_live_tup,2)ENDASbloat_ratio,(n_dead_tup>1000ANDn_dead_tup>n_live_tup*0.2)ASneed_vacuumFROMsys_stat_user_tablesWHEREn_dead_tup>0ORDERBYbloat_ratioDESC;END;$$LANGUAGEplpgsql;-- 每天检查一次SELECTcron.schedule('check-bloat-daily','0 2 * * *',$$INSERTINTOvacuum_logSELECT*,CURRENT_TIMESTAMPFROMcheck_table_bloat()WHEREneed_vacuum=true$$);血的教训:一次VACUUM事故
去年我们有个新来的DBA,想提高性能,给一个业务用户临时加了超级权限执行VACUUM。结果完事后忘了回收权限。
一个月后,这个用户误操作删除了一个重要表。虽然最后从备份恢复了,但业务停了两个小时,损失不小。
从此以后,我们立下铁规:绝不给人超级权限,VACUUM必须走流程。
权限管理最佳实践:我们总结的“三要三不要”
通过这三年的实践,我们总结了金仓权限管理的“三要三不要”:
三要
1. 要分层授权
-- 不好的做法:一次性给所有权限GRANTALLONSCHEMAsalesTOsales_user;-- 好的做法:分层授权GRANTUSAGEONSCHEMAsalesTOsales_user;-- 基础权限GRANTSELECTONsales.customerTOsales_user;-- 具体表权限GRANTINSERT,UPDATEONsales.orderTOsales_user;-- 特定操作的权限2. 要用角色管理
不要直接给用户授权,而是通过角色:
-- 创建角色CREATEROLE sales_read_only;CREATEROLE sales_read_write;-- 给角色授权GRANTSELECTONALLTABLESINSCHEMAsalesTOsales_read_only;GRANTSELECT,INSERT,UPDATEONALLTABLESINSCHEMAsalesTOsales_read_write;-- 把角色给用户GRANTsales_read_onlyTOuser1;GRANTsales_read_writeTOuser2;3. 要定期审计
每月做一次权限审计:
-- 审计脚本CREATETABLEpermission_audit_log(audit_idSERIALPRIMARYKEY,audit_timeTIMESTAMPDEFAULTCURRENT_TIMESTAMP,object_typeTEXT,object_nameTEXT,granteeTEXT,privilegesTEXT,auditorTEXT);-- 每月1号自动运行SELECTcron.schedule('permission-audit','0 0 1 * *',$$INSERTINTOpermission_audit_log(object_type,object_name,grantee,privileges,auditor)SELECT'TABLE',schemaname||'.'||tablename,grantee,privilege_type,CURRENT_USERFROMinformation_schema.role_table_grantsWHEREgranteeNOTIN('system','sso','sao');$$);三不要
1. 不要给人超级权限
-- 千万别这么干!ALTERUSERapp_userWITHSUPERUSER;2. 不要用ALL PRIVILEGES
-- 太危险了!GRANTALLPRIVILEGESONDATABASEapp_dbTOapp_user;-- 应该细化授权GRANTCONNECTONDATABASEapp_dbTOapp_user;GRANTCREATEONSCHEMAapp_schemaTOapp_admin;3. 不要忘记回收权限
-- 员工离职时一定要做REVOKEALLPRIVILEGESONALLTABLESINSCHEMAsalesFROMformer_employee;REVOKEALLPRIVILEGESONSCHEMAsalesFROMformer_employee;REVOKECONNECTONDATABASEapp_dbFROMformer_employee;DROPUSERformer_employee;写在最后:权限管理是门学问
从Oracle转到电科金仓,我在权限管理上确实花了些时间适应。但说实话,适应之后发现金仓的权限体系设计得挺合理的——严格但清晰,复杂但可控。
现在回想那些加班到凌晨的日子,虽然辛苦,但值得。因为我们建立了一套安全、可控、易管理的权限体系。
如果你也在用金仓,或者在考虑用金仓,我建议你:
- 一定要理解三权分立:system、sso、sao各司其职
- 一定要从简单开始:先给最小权限,不够再加
- 一定要有文档:每个权限变动都要记录
- 一定要定期审查:权限就像房间,久了不打扫会乱
最后,如果你在权限管理上遇到了问题,或者有更好的实践经验,欢迎到金仓技术探索社区(https://kingbase.com.cn/explore)交流。那里有很多实战案例和技术文章,说不定能帮你少走弯路。
权限管理这条路,我们走过来了。虽然踩过坑、加过班,但最终建成了一套让业务安心、让运维省心的权限体系。希望我们的经验,能帮你走得更稳、更快。