马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?用户注册
×
数据库账号权限治理实战:从共享账号到最小权限与审计
一、具体的问题
接手一套跑了五六年的生产库,账号现状通常是这样的:mysql.user 里挂着四十多个账号,其中十几个是「app」「dev」「test」这类没人认领的共享账号;开发同学人手一份同一个写账号的密码,三年没换过;DBA 自己日常用 root 直连生产,顺手改表、重启实例;有人离职了,密码还在他个人微信收藏里躺着。出了事故想复盘是谁改的数据,打开 binlog 一看,User@Host: app[app]——等于什么都没查到。
更麻烦的是这类问题平时完全看不出来:连接正常、查询正常、业务正常。它只在你最需要追责、最需要止血的那一刻暴露——你不知道该冻结哪个账号,也不知道改坏数据的那次连接来自哪台机器、哪个人的操作。权限治理不是安全合规的纸面要求,是让「事后可追责、事中可止血」成立的唯一前提。
二、核心原理
账号体系的本质是一条责任链:每个连接必须能唯一归因到一个人或一个系统。共享账号把这条链直接剪断了——「app 账号干的」既不能定位人,也不能定位用途,审计和追责全部失效。
由此推出三条原则:
- 最小权限:一个账号只授予完成其职责所必需的权限。写账号不需要 DROP,报表账号只读即可,GRANT ALL ON *.* 只能属于 root 这类极少数的管理账号。
- 人机分离:人的账号和应用程序的账号必须分开。应用账号权限固定、密码走配置中心轮换;人的账号走个人实名授权,离职即回收。
- 角色化授权:不给人逐条 GRANT,而是定义 app_dev、app_readonly、app_dba 几个角色,人加入角色、权限随角色走。这样权限收敛有据可查,复查时对角色清单而不是对几十个账号逐个核对。
还有一个反直觉的点:治理的最大阻力往往不是技术,是「改了会不会出事」的担忧。所以整套动作必须可灰度、可回退——先锁定观察、再降权、最后删除,而不是一刀切 DROP USER。
三、实例参考(动手步骤)
以 MySQL 8.0 为主展开,Oracle / SQL Server 的对应动作放在各步骤末尾对照。
步骤 0:确认现状基线
先摸清家底,不要凭印象动手:
- -- 账号清单与主机来源
- SELECT user, host FROM mysql.user ORDER BY user;
- -- 逐个账号的权限快照(输出到文件做台账)
- -- shell 里循环执行
- mysql -N -e "SELECT CONCAT(user,'@',host) FROM mysql.user" | while read u h; do
- echo "=== $u@$h ==="
- mysql -N -e "SHOW GRANTS FOR '$u'@'$h'"
- done > account_grants_$(date +%F).txt
复制代码
再确认哪些是共享账号。两个证据:一是账号名与个人无关(app/dev/test/common);二是连接来源分散——
- SELECT user, host, COUNT(*) AS conns
- FROM information_schema.processlist
- GROUP BY user, host ORDER BY conns DESC;
复制代码
同一个 user 从 10 个不同 host 连进来的,基本就是共享账号。Oracle 对照:SELECT username, account_status, created, last_login FROM dba_users 看 last_login 是否长期为空;SQL Server 对照:sys.server_principals 配合 sys.dm_exec_sessions 按 login_name 聚合。
步骤 1:建账号台账,逐个认领
把基线结果整理成一张表,列固定五个:账号、负责人、用途、权限级别(读写/只读/管理)、最后登录时间。找不到负责人的账号直接标记为待处置,这是后面所有动作的输入。台账放代码仓库里维护,跟配置一样走 review。
步骤 2:逐人建个人账号 + 角色化授权
MySQL 8.0 支持角色,先把角色定义出来:
- CREATE ROLE 'app_dev', 'app_readonly', 'app_dba';
- -- 开发角色:目标库读写,不给 DROP/ALTER 之外的全局权限
- GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app_dev';
- -- 只读角色:全业务库 SELECT
- GRANT SELECT ON appdb.* TO 'app_readonly';
- -- DBA 角色:日常管理,仍不含 super 类高危权限
- GRANT ALL ON appdb.* TO 'app_dba';
- -- 按人开账号,挂角色
- CREATE USER 'zhangsan'@'10.0.%' IDENTIFIED BY '<初始密码>'
- PASSWORD EXPIRE INTERVAL 90 DAY
- FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 1;
- GRANT 'app_dev' TO 'zhangsan'@'10.0.%';
- SET DEFAULT ROLE 'app_dev' TO 'zhangsan'@'10.0.%';
复制代码
三个细节不能省:host 段收紧到办公网/跳板机网段,不要留 %;PASSWORD EXPIRE INTERVAL 90 让密码有生命周期;FAILED_LOGIN_ATTEMPTS 防爆破。Oracle 用 CREATE PROFILE 限 FAILED_LOGIN_ATTEMPTS/PASSWORD_LIFE_TIME 再分配给用户,权限挂 CREATE ROLE;SQL Server 用固定服务器角色/数据库角色 ALTER ROLE app_dev ADD MEMBER [zhangsan]。
步骤 3:应用账号降权收敛
应用账号和人的账号分开后,逐个检查应用账号权限。典型的过度授权:
- -- 收敛前:app 账号几乎全权
- SHOW GRANTS FOR 'app'@'10.1.%';
- -- GRANT ALL PRIVILEGES ON *.* TO ...
- -- 收敛后:只给目标库的 DML,DDL 走发布流程的专门账号
- REVOKE ALL PRIVILEGES ON *.* FROM 'app'@'10.1.%';
- GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'10.1.%';
- -- DDL 账号单独建,平时不配在应用连接串里
- CREATE USER 'app_ddl'@'10.1.%' IDENTIFIED BY '<密码>';
- GRANT ALTER, CREATE, DROP, INDEX, SELECT ON appdb.* TO 'app_ddl'@'10.1.%';
复制代码
报表、BI 这类纯读场景统一切到 app_readonly 角色下的账号。这一步要在业务低峰做,且提前和开发确认没有隐藏的 DDL 依赖(有些框架启动时自动 ALTER TABLE)。
步骤 4:共享账号灰度退役
不要直接 DROP。分三步走:
- -- 第一周:改名 + 锁定,观察有没有业务报认证失败
- RENAME USER 'dev'@'%' TO 'dev_retired'@'%';
- ALTER USER 'dev_retired'@'%' ACCOUNT LOCK;
- -- 观察两周无报错后:确认无会话残留
- SELECT * FROM information_schema.processlist WHERE user LIKE 'dev%';
- -- 最后删除
- DROP USER 'dev_retired'@'%';
复制代码
监控账号、备份账号、复制账号这类系统账号容易被误伤,处置前逐个在台账里确认用途。Oracle 对应 ALTER USER dev ACCOUNT LOCK,保留观察期后 DROP USER dev CASCADE。
步骤 5:审计落地,让归因成立
权限收干净后补上「谁连过、谁改了」的记录。MySQL 用审计插件(企业版 audit_log 或社区 mariadb 的 server_audit):
- INSTALL SONAME 'server_audit';
- SET GLOBAL server_audit_logging = ON;
- SET GLOBAL server_audit_events = 'CONNECT,QUERY_DDL,QUERY_DML';
- SET GLOBAL server_audit_file_rotate_size = 104857600; -- 100MB 轮转
复制代码
只记 CONNECT + DDL/DML,不记全量 QUERY——全量 DML 的日志量会把磁盘打满,也拖性能。配合 binlog 里的 User@Host,共享账号退役之后,每一次变更都能归因到具体个人或具体系统账号。
步骤 6:月度复查固化
治理不是一次性的。写个脚本每月一号跑:导出账号清单、和台账比对差异、列出 last_login 超过 90 天的账号、列出权限大于所属角色的账号,结果发到群里的告警频道。发现不一致,按台账流程处置,不让新共享账号有存活空间。
治理前后对比
| 指标 | 治理前 | 治理后 | | 可归因账号占比 | 40% | 100% | | 共享写账号 | 6 个 | 0 | | GRANT ON *.* 的人类账号 | 9 个 | 0 | | 密码超 180 天未换 | 60% | 0(90 天强制过期) | | 事故定位耗时 | 无法定位 | 平均 8 分钟(审计日志直查) | | 离职回收闭环 | 无流程 | 提交离职单次日强制过期 |
四、实操检查清单
- 账号全量清单已导出,逐个认领负责人,未认领账号标记待处置;
- SHOW GRANTS 快照已归档,ON *.* 的人类账号清零;
- 个人账号 host 段已收紧到指定网段,不存在 'x'@'%' 的实名账号;
- 应用账号、报表账号、DDL 账号三分离,连接串里无 DDL 账号;
- 共享账号按「改名锁定 → 观察两周 → 删除」灰度退役,无直接 DROP;
- 密码策略:90 天过期、连续失败 5 次锁定已生效;
- 审计插件开启,覆盖 CONNECT 与 DDL/DML,日志轮转配置完毕;
- 月度复查脚本上线,超 90 天未登录账号自动进告警清单;
- 复制、备份、监控等系统账号已在台账标注用途并豁免处置;
- 治理后的权限快照重新归档,与治理前对照留档。
几个容易踩的坑
- 直接 DROP 共享账号:应用侧重连风暴比权限风险来得更快。先改名锁定观察,是唯一的稳妥路径。
- REVOKE 误伤系统账号:全局 REVOKE ALL 之前先确认复制账号(REPLICATION SLAVE)、监控账号不在处理范围内,否则主从和告警一起瘫痪。
- Oracle PROFILE 设过严:给服务账号套了 FAILED_LOGIN_ATTEMPTS 3,应用配置错一次密码就锁定,故障扩大化。PROFILE 分级,服务账号放宽或豁免。
- 个人账号开完就算治理完:只要 root 密码还在小圈子里传,共享就只是换了个马甲。root 改走堡垒机/代理认证,人不再直接持有 root 密码,治理才算闭环。
- 审计只开不查:审计日志没人看等于没开。把「事故当天按 user/host 过滤审计日志」写进应急预案,工具才算用起来。
|