93.【数据库】ClickHouse从入门到放弃-权限管理

341 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第29天,点击查看活动详情 

文档参考:《ClickHouse原理解析与应用实践(数据库技术丛书)(朱凯)》

90.【数据库】ClickHouse从入门到放弃-用户配置 - 掘金 (juejin.cn)

书接之前提到的clickhouse设置用户配置,本节继续分析clickhouse的权限管理

权限管理

权限管理是一个始终都绕不开的话题,ClickHouse分别从访问、查询和数据等角度出发,层层递进,为我们提供了一个较为立体的权限体系。

1 访问权限

访问层控制是整个权限体系的第一层防护,它又可进一步细分成两类权限。

1.网络访问权限

网络访问权限使用networks标签设置,用于限制某个用户登录的客户端地址,有IP地址、host主机名称以及正则匹配三种形式,可以任选其中一种进行设置。

(1)IP地址:直接使用IP地址进行设置。

<ip>127.0.0.1</ip>

(2)host主机名称:通过host主机名称设置。

<host>ch5.nauu.com</host>

(3)正则匹配:通过表达式来匹配host名称。

<host>^ch\d.nauu.com$</host>

现在用一个示例说明:

<user_normal>
    <password></password>
    <networks>
        <ip>10.37.129.13</ip>
    </networks>
    <profile>default</profile>
    <quota>default</quota>
</user_normal>

用户user_normal限制了客户端IP,在设置之后,该用户将只能从指定的地址登录。此时如果从非指定IP的地址进行登录,例如:

--从10.37.129.10登录
# clickhouse-client -u user_normal

则将会得到如下错误:

DB::Exception: User user_normal is not allowed to connect from address 10.37.129.10.

2.数据库与字典访问权限

在客户端连入服务之后,可以进一步限制某个用户数据库和字典的访问权限,它们分别通过allow_databases和allow_dictionaries标签进行设置。如果不进行任何定义,则表示不进行限制。现在继续在用户user_normal的定义中增加权限配置:

<user_normal>
    ……
    <allow_databases>
        <database>default</database>
        <database>test_dictionaries</database>
    </allow_databases>
    <allow_dictionaries>
        <dictionary>test_flat_dict</dictionary>
    </allow_dictionaries>
</user_normal>

通过上述操作,该用户在登录之后,将只能看到为其开放了访问权限的数据库和字典。

2 查询权限

查询权限是整个权限体系的第二层防护,它决定了一个用户能够执行的查询语句。查询权限可以分成以下四类:

·读权限:包括SELECT、EXISTS、SHOW和DESCRIBE查询。

·写权限:包括INSERT和OPTIMIZE查询。

·设置权限:包括SET查询。

·DDL权限:包括CREATE、DROP、ALTER、RENAME、ATTACH、DETACH和TRUNCATE查询。

·其他权限:包括KILL和USE查询,任何用户都可以执行这些查询。

上述这四类权限,通过以下两项配置标签控制:

(1)readonly:读权限、写权限和设置权限均由此标签控制,它有三种取值。

·当取值为0时,不进行任何限制(默认值)。

·当取值为1时,只拥有读权限(只能执行SELECT、EXISTS、SHOW和DESCRIBE)。

·当取值为2时,拥有读权限和设置权限(在读权限基础上,增加了SET查询)。

(2)allow_ddl:DDL权限由此标签控制,它有两种取值。

·当取值为0时,不允许DDL查询。

·当取值为1时,允许DDL查询(默认值)。

现在继续用一个示例说明。与刚才的配置项不同,readonly和allow_ddl需要定义在用户profiles中,例如:

<profiles>        
    <normal> <!-- 只有read读权限-->
        <readonly>1</readonly>
        <allow_ddl>0</allow_ddl>
    </normal>
    <normal_1> <!-- 有读和设置参数权限-->
        <readonly>2</readonly>
        <allow_ddl>0</allow_ddl>
    </normal_1>

继续在先前的profiles配置中追加了两个新角色。其中,normal只有读权限,而normal_1则有读和设置参数的权限,它们都没有DDL查询的权限。

再次修改用户的user_normal的定义,将它的profile设置为刚追加的normal,这意味着该用户只有读权限。现在开始验证权限的设置是否生效。使用user_normal登录后尝试写操作:

--登录
# clickhouse-client -h 10.37.129.10 -u user_normal
--写操作
:) INSERT INTO TABLE test_ddl VALUES (1)
DB::Exception: Cannot insert into table in readonly mode.

可以看到,写操作如期返回了异常。接着执行DDL查询,会发现该操作同样会被限制执行:

:) CREATE DATABASE test_3
DB::Exception: Cannot create database in readonly mode.

至此,权限设置已然生效。

数据行级权限

数据权限是整个权限体系中的第三层防护,它决定了一个用户能够看到什么数据。 数据权限使用databases标签定义,它是用户定义中的一项选填设置。database通过定义用户级别的查询过滤器来实现数据的行级粒度权限,它的定义规则如下所示:

<databases>
        <database_name><!--数据库名称-->
            <table_name><!--表名称-->
                <filter> id < 10</filter><!--数据过滤条件-->
            </table_name>
    </database_name>

其中,database_name表示数据库名称;table_name表示表名称;而filter则是权限过滤的关键所在,它等同于定义了一条WHERE条件子句,与WHERE子句类似,它支持组合条件。现在用一个示例说明。这里还是用user_normal,为它追加databases定义:

<user_normal>
    ……
    <databases>
            <default><!--默认数据库-->
                    <test_row_level><!—表名称-->
                        <filter>id < 10</filter>
                    </test_row_level>
 
                <!—支持组合条件 
                <test_query_all>
                    <filter>id <= 100 or repo >= 100</filter>
                </test_query_all> -->
            </default>
        </databases>

基于上述配置,通过为user_normal用户增加全局过滤条件,实现了该用户在数据表default.test_row_level的行级粒度数据上的权限设置。test_row_level的表结构如下所示:

CREATE TABLE test_row_level(
    id UInt64,
    name UInt64
)ENGINE = MergeTree()
ORDER BY id

下面验证权限是否生效。首先写入测试数据:

INSERT INTO TABLE test_row_level VALUES (1,100),(5,200),(20,200),(30,110)

写入之后,登录user_normal用户并查询这张数据表:

SELECT * FROM test_row_level
┌─id─┬─name─┐
│  1100  │
│  5200  │
└───┴─────┘

可以看到,在返回的结果数据中,只包含 id<10 的部分,证明权限设置生效了。

那么数据权限的设定是如何实现的呢?进一步分析它的执行日志:

Expression
    Expression
        Filter –增加了过滤的步骤
    MergeTreeThread

可以发现,上述代码在普通查询计划的基础之上自动附加了Filter过滤的步骤。

对于数据权限的使用有一点需要明确,在使用了这项功能之后,PREWHERE优化将不再生效,例如执行下面的查询语句:

SELECT * FROM test_row_level where name = 5

此时如果使用了数据权限,那么这条SQL将不会进行PREWHERE优化;反之,如果没有设置数据权限,则会进行PREWHERE优化,例如:

InterpreterSelectQuery: MergeTreeWhereOptimizer: condition "name = 5" moved to PREWHERE

所以,是直接利用ClickHouse的内置过滤器,还是通过拼接WHERE查询条件的方式实现行级数据权限,需要用户在具体的使用场景中进行权衡。