电科金仓权限管理深度解析:那些隐藏在默认权限和角色继承中的“坑”
电科金仓权限管理深度解析:那些隐藏在默认权限和角色继承中的“坑”
上周我们有个开发兄弟差点把生产库搞崩了——他在测试环境建了个表,结果所有业务用户都能查。问题就出在默认权限上。今天,我要把金仓权限管理里那些容易踩的坑一个个挖出来,这些经验可是我们团队用真金白银换来的。
开头:那个差点让生产库“裸奔”的下午
周三下午三点,我正在喝咖啡,开发部门的老王突然冲进我办公室,脸色煞白:“王哥,出大事了!我们新上线的订单表,客户信息好像被不该看的人看到了!”
我当时心里咯噔一下——客户数据泄露,这可不是闹着玩的。我们赶紧登录数据库检查,发现问题比想象中还复杂。
老王他们的新表放在orders schema里,按理说只有授权用户能查。但我们发现,好多没授权的用户居然也能查到这个表的数据!更离谱的是,有些用户连schema权限都没有,却能直接查询。
我们花了两个小时排查,最后发现问题出在三个月前的一个默认权限设置上。当时有个DBA为了省事,设置了一个“未来所有表自动授权”的规则,结果所有人都忘了这个事。
这个事件让我深刻认识到:金仓的权限体系很强大,但强大的背后是复杂性。一个不小心,就可能让整个数据库“裸奔”。
第一章:默认权限——那些“未来”的权限陷阱
真实案例:一张表引发的数据泄露危机
我们后来复盘,问题出在这段代码上:
-- 三个月前,某个DBA为了省事写了这个
ALTER DEFAULT PRIVILEGES IN SCHEMA orders
GRANT SELECT ON TABLES TO PUBLIC;
PUBLIC是个特殊角色,所有用户都属于这个角色。这个设置意味着:未来在orders schema下创建的任何表,所有用户都能查!
默认权限到底是什么?
简单说,默认权限就是给“未来的表”提前发通行证。你建表之前,先告诉金仓:“以后我(或者别人)在这个schema下建的表,应该给谁什么权限。”
这功能本来挺好用的,但用不好就是灾难。
四个容易踩的坑
坑一:作用范围搞不清
很多同事以为默认权限是全局的,其实它跟执行者绑定。
-- 用户A执行这个:
ALTER DEFAULT PRIVILEGES IN SCHEMA sales
GRANT SELECT ON TABLES TO viewer_role;
-- 然后用户B在sales下建个表
-- 猜猜viewer_role能查这个表吗?
-- 答案:不能!因为默认权限只对设置者创建的表有效
坑二:FOR USER语法用不对
如果你想让某个用户的建表行为都自动授权,得用FOR USER:
-- 这样设置后,sales_user建的表,viewer_role都能查
ALTER DEFAULT PRIVILEGES FOR USER sales_user IN SCHEMA sales
GRANT SELECT ON TABLES TO viewer_role;
-- 可以指定多个用户
ALTER DEFAULT PRIVILEGES FOR USER user1, user2 IN SCHEMA sales
GRANT SELECT ON TABLES TO viewer_role;
坑三:忘记旧表还要单独授权
默认权限只管未来的表,不管现有的表。这是个巨坑!
-- 设置了默认权限
ALTER DEFAULT PRIVILEGES IN SCHEMA sales
GRANT SELECT ON TABLES TO viewer_role;
-- 然后创建新表
CREATE TABLE sales.new_order (...); -- viewer_role能查
-- 但老表呢?
SELECT * FROM sales.old_order; -- viewer_role不能查!
坑四:权限撤销很麻烦
默认权限设错了,撤销起来也不省心:
-- 设置默认权限
ALTER DEFAULT PRIVILEGES IN SCHEMA sales
GRANT SELECT ON TABLES TO viewer_role;
-- 撤销(注意语法不一样!)
ALTER DEFAULT PRIVILEGES IN SCHEMA sales
REVOKE SELECT ON TABLES FROM viewer_role;
我们的最佳实践:三要三不要
三要:
- 要明确指定FOR USER:别用默认的,一定要写明对谁生效
- 要定期审查默认权限:每月检查一次
- 要文档化记录:每次设置默认权限都要写文档
三不要:
- 不要给PUBLIC授权:除非你知道自己在干什么
- 不要用ALL PRIVILEGES:权限要给得刚刚好
- 不要依赖默认权限管理现有表:老表要单独处理
我们还写了个检查脚本:
-- 检查所有默认权限
SELECT
pg_get_userbyid(defacluser) AS 设置者,
nspname AS schema名,
defaclobjtype AS 对象类型,
defaclacl AS 权限信息
FROM sys_default_acl a
JOIN sys_namespace n ON n.oid = a.defaclnamespace
WHERE nspname NOT LIKE 'pg_%';
-- 检查是否有给PUBLIC的默认权限
SELECT
pg_get_userbyid(defacluser) AS 危险设置者,
nspname AS 危险schema,
defaclobjtype AS 对象类型
FROM sys_default_acl a
JOIN sys_namespace n ON n.oid = a.defaclnamespace
WHERE defaclacl::text LIKE '%PUBLIC%'
AND nspname NOT LIKE 'pg_%';
第二章:角色管理——权限继承的“惊喜”与“惊吓”
真实故事:一个角色引发的权限混乱
我们公司有个财务系统,权限设计是这样的:
finance_read:只能查finance_write:能查能写finance_admin:所有权限
看起来挺合理对吧?结果出问题了。
有个新人小李,我们给他授了finance_read角色。后来他调岗到财务部,需要写权限,我们就给他加了finance_write角色。
结果呢?他不仅能写,还能删表!我们查了半天才发现,finance_write角色继承了finance_admin的部分权限——因为当初建角色的人图省事,让finance_write也成了finance_admin的成员。
金仓角色的核心概念
角色和用户是一回事
在金仓里,角色(ROLE)和用户(USER)本质上是一样的。唯一的区别是:能不能登录。
-- 这两个其实差不多
CREATE ROLE developer NOLOGIN; -- 不能登录的角色
CREATE USER developer WITH LOGIN; -- 能登录的用户(其实也是角色)
角色可以继承角色
这是最强大也最危险的功能:
-- 创建基础角色
CREATE ROLE basic_user;
-- 创建高级角色,继承基础角色
CREATE ROLE advanced_user INHERIT basic_user;
-- 给基础角色授权
GRANT SELECT ON TABLE sales_data TO basic_user;
-- 那么advanced_user能查sales_data吗?
-- 答案:能!因为继承
五个角色管理的“坑”
坑一:系统权限不继承
这是最让人困惑的地方。普通权限能继承,但系统权限(SUPERUSER、CREATEDB这些)不行。
-- 创建有特殊权限的角色
CREATE ROLE admin_role CREATEDB CREATEROLE;
-- 创建普通角色并继承
CREATE ROLE user_role INHERIT admin_role;
-- 把admin_role给user_role
GRANT admin_role TO user_role;
-- 猜猜user_role能建库吗?
-- 答案:不能!系统权限不继承
坑二:SET ROLE的魔力
这个功能很强大,但也很危险:
-- user_role是admin_role的成员
-- 平时user_role没有建库权限
-- 但可以这样临时获取
SET ROLE admin_role;
-- 现在user_role有了admin_role的所有权限!
-- 用完记得切回来
RESET ROLE;
我们有个开发兄弟,用这个功能临时获取权限后忘了切回来,结果用超级权限执行了普通操作,差点出大事。
坑三:WITH ADMIN OPTION的传播性
这个选项让权限管理变得复杂:
-- 这样授权后,user1可以把role1再给别人
GRANT role1 TO user1 WITH ADMIN OPTION;
-- user1可以这样操作
GRANT role1 TO user2; -- user1自己就能授权了!
我们曾经遇到过一个情况:A用户把角色给了B,B又给了C,C给了D……最后权限扩散得一塌糊涂,收都收不回来。
坑四:登录权限的特殊性
带LOGIN的角色,权限继承有点特殊:
CREATE ROLE parent_role;
CREATE ROLE child_role LOGIN INHERIT parent_role;
-- 给parent_role授权
GRANT SELECT ON sales_data TO parent_role;
-- child_role能查sales_data吗?
-- 能,因为INHERIT了
坑五:权限回收的连锁反应
回收权限时,如果用了CASCADE,效果很可怕:
-- A把角色给了B,B给了C
-- 这样回收,C的权限也没了
REVOKE role1 FROM B CASCADE;
我们吃过这个亏:本来只想回收一个人的权限,结果把整个部门的权限都收回了。
我们的角色管理规范
经过几次教训,我们定了套规矩:
1. 角色命名规范
-- 业务_权限级别_类型
CREATE ROLE sales_read_only; -- 销售只读
CREATE ROLE sales_read_write; -- 销售读写
CREATE ROLE sales_dba; -- 销售DBA
2. 权限继承原则
- 普通权限可以继承
- 系统权限绝不继承
- 每个角色最多两级继承
3. 授权审批流程
- 普通角色授权:组长审批
- 特殊权限授权:总监审批
- 超级权限授权:CTO审批
我们还开发了角色权限分析工具:
-- 查看角色权限树
CREATE OR REPLACE FUNCTION get_role_tree(role_name TEXT)
RETURNS TABLE(
level INT,
role_path TEXT,
has_login BOOLEAN,
is_superuser BOOLEAN
) AS $$
BEGIN
RETURN QUERY
WITH RECURSIVE role_tree AS (
SELECT
1 AS level,
rolname::TEXT AS role_path,
rolname,
rolcanlogin,
rolsuper
FROM sys_roles
WHERE rolname = role_name
UNION ALL
SELECT
rt.level + 1,
rt.role_path || ' -> ' || rm.rolname,
rm.rolname,
rm.rolcanlogin,
rm.rolsuper
FROM sys_roles rm
JOIN sys_auth_members am ON rm.oid = am.member
JOIN role_tree rt ON am.roleid = (SELECT oid FROM sys_roles WHERE rolname = rt.rolname)
)
SELECT * FROM role_tree;
END;
$$ LANGUAGE plpgsql;
-- 使用示例
SELECT * FROM get_role_tree('sales_read_write');
第三章:特殊权限——那些“碰不得”的权限
真实案例:一次VACUUM引发的性能灾难
去年双十一前,我们想优化一下数据库性能。有个DBA(已经离职了)给几个业务用户临时加了超级权限,让他们自己执行VACUUM清理表。
结果呢?有人在业务高峰期执行了全库VACUUM,数据库直接卡死,业务停了半小时。损失?别提了,反正那个月我们团队都没奖金。
金仓的特殊权限有哪些?
- SUPERUSER:无所不能,想干啥干啥
- CREATEDB:能建数据库
- CREATEROLE:能建角色
- REPLICATION:能做数据复制
- BYPASSRLS:能绕过行级安全策略
为什么这些权限危险?
SUPERUSER有多可怕?
-- 超级用户能干什么?
-- 1. 删库(包括系统库)
DROP DATABASE kingbase;
-- 2. 改系统表
UPDATE sys_authid SET rolsuper = true;
-- 3. 绕过所有权限检查
-- 基本上,想干啥干啥
CREATEROLE的隐患
有这个权限的人,可以给自己建个超级用户:
-- 先建个超级用户角色
CREATE ROLE my_super WITH SUPERUSER LOGIN;
-- 然后把密码设成自己知道的
ALTER ROLE my_super WITH PASSWORD '123456';
BYPASSRLS的风险
如果你们用了行级安全策略(RLS),这个权限能绕过所有策略:
-- 假设有个表,普通用户只能看自己的数据
SELECT * FROM my_data; -- 只能看到自己的
-- 但有BYPASSRLS权限的人
SET ROLE bypass_user;
SELECT * FROM my_data; -- 能看到所有人的!
我们的特殊权限管理方案
原则:能不给人就不给人,能给角色不给用户
-- 错误的做法
ALTER USER app_user WITH SUPERUSER;
-- 正确的做法
-- 1. 创建带特殊权限的角色
CREATE ROLE maintenance_dba WITH
CREATEDB
CREATEROLE
REPLICATION
BYPASSRLS
NOLOGIN;
-- 2. 需要时临时授权
GRANT maintenance_dba TO app_user;
-- 3. 用完马上回收
REVOKE maintenance_dba FROM app_user;
监控特殊权限的使用
-- 记录所有特殊权限操作
CREATE TABLE special_priv_log (
log_id SERIAL PRIMARY KEY,
username TEXT,
operation TEXT,
object_name TEXT,
execution_time TIMESTAMP,
client_ip INET
);
-- 创建审计触发器(简化版)
CREATE OR REPLACE FUNCTION audit_special_priv()
RETURNS event_trigger AS $$
DECLARE
r RECORD;
BEGIN
-- 这里可以记录各种特殊操作
-- 比如建库、建角色、授权超级权限等
INSERT INTO special_priv_log
(username, operation, execution_time)
VALUES (
CURRENT_USER,
TG_TAG,
CURRENT_TIMESTAMP
);
END;
$$ LANGUAGE plpgsql;
CREATE EVENT TRIGGER audit_special_priv_trigger
ON ddl_command_end
WHEN TAG IN ('CREATE DATABASE', 'CREATE ROLE', 'ALTER ROLE')
EXECUTE FUNCTION audit_special_priv();
第四章:实战案例——我们公司的权限体系设计
业务背景
我们公司有这些业务系统:
- 销售系统(订单、客户)
- 财务系统(账务、报表)
- 人力资源系统(员工、薪资)
- 数据分析平台
权限体系设计
第一层:基础角色
-- 各部门的基础角色
CREATE ROLE sales_base NOLOGIN;
CREATE ROLE finance_base NOLOGIN;
CREATE ROLE hr_base NOLOGIN;
-- 给基础角色授权(按schema)
GRANT USAGE ON SCHEMA sales TO sales_base;
GRANT USAGE ON SCHEMA finance TO finance_base;
GRANT USAGE ON SCHEMA hr TO hr_base;
第二层:权限级别角色
-- 只读角色
CREATE ROLE read_only NOLOGIN;
ALTER DEFAULT PRIVILEGES FOR ROLE sales_base IN SCHEMA sales
GRANT SELECT ON TABLES TO read_only;
-- 其他schema类似...
-- 读写角色(继承只读)
CREATE ROLE read_write NOLOGIN INHERIT read_only;
ALTER DEFAULT PRIVILEGES FOR ROLE sales_base IN SCHEMA sales
GRANT INSERT, UPDATE, DELETE ON TABLES TO read_write;
-- 管理角色
CREATE ROLE schema_admin NOLOGIN;
GRANT CREATE ON SCHEMA sales TO schema_admin;
GRANT ALL ON ALL TABLES IN SCHEMA sales TO schema_admin;
第三层:用户角色组合
-- 销售只读用户
CREATE ROLE sales_reader LOGIN INHERIT sales_base, read_only;
-- 销售读写用户
CREATE ROLE sales_writer LOGIN INHERIT sales_base, read_write;
-- 销售管理员
CREATE ROLE sales_manager LOGIN INHERIT sales_base, schema_admin;
权限审批流程
- 申请:用户在OA系统提申请
- 审批:直属领导审批
- 执行:DBA执行授权
- 验证:申请人验证权限
- 归档:记录到权限管理系统
定期审计
我们每月做一次权限审计:
-- 审计脚本
SELECT
'风险点:过度授权' AS 风险类型,
grantee AS 用户名,
table_schema || '.' || table_name AS 对象,
string_agg(privilege_type, ',') AS 权限列表
FROM information_schema.role_table_grants
WHERE grantee NOT IN ('system', 'sso', 'sao')
AND privilege_type IN ('DELETE', 'TRUNCATE', 'REFERENCES')
GROUP BY grantee, table_schema, table_name
HAVING COUNT(*) > 3;
写在最后:权限管理是门艺术
在金仓上做权限管理这三年,我最深的体会是:权限给出去容易,收回来难;给大了危险,给小了麻烦。
现在回想那些踩过的坑,虽然当时痛苦,但确实让我们成长了。我们现在建立的这套权限体系,不敢说完美,但至少安全、可控、易管理。
如果你也在用金仓,或者准备用金仓,我给你几个建议:
- 从简单开始:别一上来就搞复杂的角色继承
- 文档化:每个权限变动都要记录,记性再好不如烂笔头
- 定期审查:权限就像房间,久了不打扫会乱
- 最小权限原则:能只读就不给写,能写单表就不给全库
最后,如果你在权限管理上遇到了问题,或者有更好的实践经验,欢迎到金仓技术探索社区(https://kingbase.com.cn/explore)交流。那里有很多实战案例和技术文章,都是大家用真金白银换来的经验。
权限管理这条路,我们走过来了,踩过坑、加过班,但最终建成了一套让业务安心、让运维省心的权限体系。希望我们的经验,能帮你走得更稳、更快。

浙公网安备 33010602011771号