电科金仓权限管理深度解析:那些隐藏在默认权限和角色继承中的“坑”

电科金仓权限管理深度解析:那些隐藏在默认权限和角色继承中的“坑”

上周我们有个开发兄弟差点把生产库搞崩了——他在测试环境建了个表,结果所有业务用户都能查。问题就出在默认权限上。今天,我要把金仓权限管理里那些容易踩的坑一个个挖出来,这些经验可是我们团队用真金白银换来的。

开头:那个差点让生产库“裸奔”的下午

周三下午三点,我正在喝咖啡,开发部门的老王突然冲进我办公室,脸色煞白:“王哥,出大事了!我们新上线的订单表,客户信息好像被不该看的人看到了!”

我当时心里咯噔一下——客户数据泄露,这可不是闹着玩的。我们赶紧登录数据库检查,发现问题比想象中还复杂。

老王他们的新表放在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;

我们的最佳实践:三要三不要

三要:

  1. 要明确指定FOR USER:别用默认的,一定要写明对谁生效
  2. 要定期审查默认权限:每月检查一次
  3. 要文档化记录:每次设置默认权限都要写文档

三不要:

  1. 不要给PUBLIC授权:除非你知道自己在干什么
  2. 不要用ALL PRIVILEGES:权限要给得刚刚好
  3. 不要依赖默认权限管理现有表:老表要单独处理

我们还写了个检查脚本:

-- 检查所有默认权限
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,数据库直接卡死,业务停了半小时。损失?别提了,反正那个月我们团队都没奖金。

金仓的特殊权限有哪些?

  1. SUPERUSER:无所不能,想干啥干啥
  2. CREATEDB:能建数据库
  3. CREATEROLE:能建角色
  4. REPLICATION:能做数据复制
  5. 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();

第四章:实战案例——我们公司的权限体系设计

业务背景

我们公司有这些业务系统:

  1. 销售系统(订单、客户)
  2. 财务系统(账务、报表)
  3. 人力资源系统(员工、薪资)
  4. 数据分析平台

权限体系设计

第一层:基础角色

-- 各部门的基础角色
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;

权限审批流程

  1. 申请:用户在OA系统提申请
  2. 审批:直属领导审批
  3. 执行:DBA执行授权
  4. 验证:申请人验证权限
  5. 归档:记录到权限管理系统

定期审计

我们每月做一次权限审计:

-- 审计脚本
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;

写在最后:权限管理是门艺术

在金仓上做权限管理这三年,我最深的体会是:权限给出去容易,收回来难;给大了危险,给小了麻烦

现在回想那些踩过的坑,虽然当时痛苦,但确实让我们成长了。我们现在建立的这套权限体系,不敢说完美,但至少安全、可控、易管理。

如果你也在用金仓,或者准备用金仓,我给你几个建议:

  1. 从简单开始:别一上来就搞复杂的角色继承
  2. 文档化:每个权限变动都要记录,记性再好不如烂笔头
  3. 定期审查:权限就像房间,久了不打扫会乱
  4. 最小权限原则:能只读就不给写,能写单表就不给全库

最后,如果你在权限管理上遇到了问题,或者有更好的实践经验,欢迎到金仓技术探索社区(https://kingbase.com.cn/explore)交流。那里有很多实战案例和技术文章,都是大家用真金白银换来的经验。

权限管理这条路,我们走过来了,踩过坑、加过班,但最终建成了一套让业务安心、让运维省心的权限体系。希望我们的经验,能帮你走得更稳、更快。

posted @ 2026-01-16 09:56  性感的猴子  阅读(0)  评论(0)    收藏  举报  来源