大多数MySQL用户可能都知道MySQL中的权限系统。权限控制着一个特定的用户账户可以访问哪些数据库、表、列、程序和函数。例如,如果一个应用程序在数据库中存储了信用卡数据,那么这些数据可能应该被保护起来,不让大多数用户访问。为了实现这一点,DBA可能会禁止对存储这些敏感数据的表或列的访问。
然而,有时,权限系统并不足以保证数据的安全。行级安全(有时缩写为RLS)在一些场景下是必要的。譬如:一个政府机构只允许用户根据分类(机密、秘密、绝密)和其他因素查看某一行; 一个存储信用卡信息的电子商务网站可能只允许用户查看与其账户绑定的信用卡; 一个医院或诊所可能只允许员工查看他们被授权查看的病人记录;一个区域性的商店经理可能只能看到员工的就业记录和他们区域内商店的库存记录。
但是,MySQL的权限系统不支持行级权限,所以开发者和DBA需要找到另一种方法来实现行级安全。此时,行级安全逻辑是由应用程序负责的。其他数据库,譬如ORACLE/DB2等把行级安全逻辑放在数据库层级中,由数据库来处理安全问题。
此文我将展示在MySQL 8.0中使用以下功能实现行级安全的一个非常简单的方法。实现表数据的行级的权限控制,根据用户的所属角色权限级别去访问有相应权限的记录。
实现步骤:
1. 创建存取控制数据库
CREATE DATABASE accesses;
2. 创建安全标签信息表
CREATE TABLE accesses.sec_labels (
id INT AUTO_INCREMENT PRIMARY KEY,
security_label VARCHAR(50),
label_value BIT(4)
);
写入记录的分类标签,安全等级从小到大依次为 UNCLASSIFIED, CONFIDENTIAL, SECRET, TOP SECRET。
INSERT INTO accesses.sec_labels (security_label, label_value) VALUES
('TOP SECRET', b'0001'),
('SECRET', b'0010'),
('CONFIDENTIAL', b'0100'),
('UNCLASSIFIED', b'1000');
3. 创建用户账户的安全标签信息表
CREATE TABLE accesses.role_accesses (
id INT AUTO_INCREMENT PRIMARY KEY,
role VARCHAR(50),
label_value BIT(4)
);
INSERT INTO accesses.role_accesses (role, label_value) VALUES
('SYS', b'1111'),
('DBA', b'1110'),
('DEV', b'1100'),
('PUBLIC', b'1000')
;
4. 创建存储函数实现行级权限控制策略
CREATE FUNCTION accesses.access_policy (v_user VARCHAR(50), v_security_label VARCHAR(50))
RETURNS BOOLEAN
NOT DETERMINISTIC
READS SQL DATA
SQL SECURITY INVOKER
BEGIN
SELECT label_value INTO @v_label_value
FROM accesses.sec_labels
WHERE security_label = v_security_label;
SELECT @v_label_value & label_value INTO @v_label_check
FROM accesses.role_accesses
WHERE role = v_user;
IF @v_label_check = @v_label_value THEN
RETURN true;
ELSE
RETURN false;
END IF;
END
5. 准备测试数据
创建表,其记录都有安全标签列。以某公司员工表为例,其中有员工号、员工名、员工部门号、薪水、安全标签。拥有表访问权限的用户,其拥有的行级访问权限也不同。例如,经理可以访问特定部门员工的记录;大区经理能访问所有员工记录;员工只能访问未指定密级。
CREATE TABLE test.employee (
empno int,
lastname char(10),
deptno int,
payrank int ,
security_label VARCHAR(50),
PRIMARY KEY (empno)
);
6. 创建视图实现对数据的安全访问
首先创建用户函数得到当前用户的角色
CREATE FUNCTION accesses.getRole() returns CHAR(50) DETERMINISTIC
READS SQL DATA
SQL SECURITY INVOKER
BEGIN
SELECT distinct DEFAULT_ROLE_USER FROM mysql.default_roles dr,performance_schema.processlist p where dr.USER=p.USER and p.ID = connection_id()
and p.USER= SUBSTR(SESSION_USER(),1,instr(SESSION_USER(),'@')-1)
INTO @rl;
RETURN @rl;
END
然后,创建视图
CREATE OR replace SQL SECURITY DEFINER VIEW protected.employee
AS
SELECT * FROM test.employee uid
WHERE accesses.access_policy(accesses.getRole(), uid.security_label)
WITH CHECK OPTION;
7. 创建角色并给角色授权
CREATE ROLE 'DBA', 'DEV', 'PUBLIC','SYS';
GRANT ALL ON protected.* TO SYS;
GRANT SELECT, INSERT, UPDATE, DELETE ON protected.* TO DBA;
GRANT SELECT, INSERT, UPDATE ON protected.* TO DEV;
GRANT SELECT ON protected.* TO PUBLIC;
8. 创建用户并给用户赋予角色,并设置用户的默认角色
CREATE USER 'sys1'@'localhost' IDENTIFIED BY '123';
CREATE USER 'dev1'@'localhost' IDENTIFIED BY '123';
CREATE USER 'dba1'@'localhost' IDENTIFIED BY '123';
CREATE USER 'public1'@'localhost' IDENTIFIED BY '123';
GRANT 'SYS' TO 'sys1'@'localhost';
GRANT 'DEV' TO 'dev1'@'localhost';
GRANT 'DBA' TO 'dba1'@'localhost';
GRANT 'PUBLIC' TO 'public1'@'localhost';
SET DEFAULT ROLE SYS TO 'sys1'@'localhost';
SET DEFAULT ROLE DBA TO 'dba1'@'localhost';
SET DEFAULT ROLE DEV TO 'dev1'@'localhost';
SET DEFAULT ROLE PUBLIC TO 'public1'@'localhost';
至此,环境搭建完毕。下篇,我将演示如何在行级控制下实现增删改查。
TO BE CONT...