知识库

创建和配置数据库本地角色

Neo4j 4.0 引入了高级安全功能,即基于角色的访问控制,这对于多数据库功能的引入尤其重要。

管理员可以在系统数据库上访问这些控制功能,系统数据库存在于每个 Neo4j 实例上。

系统数据库附带了几个内置角色,但如何将这些类型的权限授予受特定数据库限制的用户尚不完全清楚。

本文旨在解释这些角色的全局性,并提供一些示例,说明如何在本地数据库级别创建与这些内置角色等效的角色。

内置角色是全局的,适用于所有数据库

一个常见但错误的假设是,可以将角色分配给数据库上的用户,例如将 reader 角色分配给数据库 db1 上的 user_a。但角色及其适用的数据库并非相互独立。每个角色本身不仅包含其权限,还包含其有权访问的数据库。权限本身可以限定在特定数据库范围内。

因此,内置角色适用的数据库已设置且不可更改:它们包含全局数据库访问权限,其权限适用于所有数据库,无论过去、现在还是将来。被授予 reader 角色的用户是将在该 DBMS 上创建的每个数据库的 reader。

其他内置角色也是如此。它们不适合在本地数据库级别使用。

对于每个数据库的访问权限和角色,最好从权限的角度来思考,而不是内置角色。因此,不要考虑将 reader 角色授予数据库 db1 上的 user_a,而是考虑创建一个新角色,该角色拥有 db1 上的读权限和 db1 的访问权限,并将新角色授予 user_a。

我们可以复制内置角色并调整其数据库访问权限

我们不必从头创建新角色,而是可以将内置角色用作模板,当我们只需要一个限定在特定数据库或多个数据库范围内的内置角色等效功能时。

我们可以通过创建现有角色的副本来创建一个新角色。然后,我们可以撤销全局数据库访问权限,再授予该角色应有权访问的数据库或多个数据库的访问权限。

因此,要创建等效于 reader 角色但仅限于数据库 db1 的角色,并将其授予 user_a,我们可以从系统数据库执行以下操作:

CREATE ROLE db1_reader AS COPY OF reader;
REVOKE GRANT ACCESS ON DATABASES * FROM db1_reader;
GRANT ACCESS ON DATABASE db1 TO db1_reader;
GRANT ROLE db1_reader TO user_a;

当然,user_a 和数据库 db1 必须先存在才能成功执行此操作。

这授予与 reader 角色等效的权限,但将用户的访问权限限制在仅限于数据库 db1。

注意,来自多个角色的权限可以组合

需要注意的是,db1_reader 角色实际上并没有仅限于 db1 的 reader 权限。他们仍然拥有跨任何和所有数据库的完全读权限(继承自内置 reader 角色),只是目前他们的角色只允许访问 db1。我们仅撤销了所有数据库的 ACCESS 权限,而非 READ 权限。

如果该用户被授予了另一个角色,该角色授予了对不同数据库的访问权限,并且没有以某种方式限制其权限,那么 db1_reader 角色中固有的全局读权限将允许他们成为新数据库的 reader。

让我们通过添加新角色 db2_accessor 并将其授予 user_a 来演示这一点:

CREATE ROLE db2_accessor;
GRANT ACCESS ON DATABASE db2 TO db2_accessor;
GRANT ROLE db2_accessor TO user_a;

即使 db2_accessor 角色仅授予对数据库的访问权限,没有任何读、写或其他任何权限,由于 db1_reader 的全局读权限,user_a 仍可以读取 db2 上的所有内容。

让我们检查 user_a 的权限以验证:

SHOW USER user_a PRIVILEGES;
╒═════════╤══════════╤════════════════╤═══════╤═════════════════╤══════════════╤════════╕
│"access" │"action"  │"resource"      │"graph"│"segment"        │"role"        │"user"  │
╞═════════╪══════════╪════════════════╪═══════╪═════════════════╪══════════════╪════════╡
│"GRANTED"│"read"    │"all_properties"│"*"    │"NODE(*)"        │"db1_reader"  │"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"GRANTED"│"traverse"│"graph"         │"*"    │"NODE(*)"        │"db1_reader"  │"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"GRANTED"│"read"    │"all_properties"│"*"    │"RELATIONSHIP(*)"│"db1_reader"  │"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"GRANTED"│"traverse"│"graph"         │"*"    │"RELATIONSHIP(*)"│"db1_reader"  │"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"GRANTED"│"access"  │"database"      │"db1"  │"database"       │"db1_reader"  │"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"GRANTED"│"access"  │"database"      │"db2"  │"database"       │"db2_accessor"│"user_a"│
└─────────┴──────────┴────────────────┴───────┴─────────────────┴──────────────┴────────┘

我们可以使用 DENY 掩盖权限

权限组合的能力不一定是障碍。有时它会非常有用。

例如,如果我们对这种组合的读访问权限没意见,但想确保无论使用哪个数据库,该用户都无法读取或匹配 :Person 节点上的 SSN 属性。我们可以专门为此限制添加一个特殊角色。

CREATE ROLE ssn_blind;
DENY MATCH {ssn, SSN} ON GRAPH * NODES Person TO ssn_blind;
GRANT ROLE ssn_blind TO user_a;

狭窄地限定权限范围可保持权限的可预测性

如果我们想要最严格的安全级别,限定权限范围以避免授予新角色(和访问新数据库)时意外获得广泛权限,那么在创建新角色时,我们需要放弃复制内置角色的想法。它们的权限范围覆盖整个数据库,这对于我们想要的功能来说可能过于宽松。

相反,我们需要手动授予权限,并将其限定在相关的数据库或多个数据库范围内。

让我们删除 db1_reader 角色,并以更狭窄的范围权限重新创建它:

CREATE OR REPLACE ROLE db1_reader;
GRANT ACCESS ON DATABASE db1 TO db1_reader;
GRANT MATCH {*} ON GRAPH db1 to db1_reader;
GRANT ROLE db1_reader TO user_a;

https://neo4j.ac.cn/docs/cypher-manual/current/access-control/manage-privileges/#access-control-graph-privileges[MATCH 权限] 是 READ 和 TRAVERSE 权限的简写,这为我们节省了一行。

现在我们再次检查 user_a 的权限:

SHOW USER user_a PRIVILEGES;
╒═════════╤══════════╤════════════════╤═══════╤═════════════════╤══════════════╤════════╕
│"access" │"action"  │"resource"      │"graph"│"segment"        │"role"        │"user"  │
╞═════════╪══════════╪════════════════╪═══════╪═════════════════╪══════════════╪════════╡
│"GRANTED"│"read"    │"all_properties"│"db1"  │"NODE(*)"        │"db1_reader"  │"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"GRANTED"│"traverse"│"graph"         │"db1"  │"NODE(*)"        │"db1_reader"  │"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"GRANTED"│"read"    │"all_properties"│"db1"  │"RELATIONSHIP(*)"│"db1_reader"  │"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"GRANTED"│"traverse"│"graph"         │"db1"  │"RELATIONSHIP(*)"│"db1_reader"  │"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"GRANTED"│"access"  │"database"      │"db1"  │"database"       │"db1_reader"  │"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"GRANTED"│"access"  │"database"      │"db2"  │"database"       │"db2_accessor"│"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"DENIED" │"read"    │"property(SSN)" │"*"    │"NODE(Person)"   │"ssn_blind"   │"user_a"│
├─────────┼──────────┼────────────────┼───────┼─────────────────┼──────────────┼────────┤
│"DENIED" │"read"    │"property(ssn)" │"*"    │"NODE(Person)"   │"ssn_blind"   │"user_a"│
└─────────┴──────────┴────────────────┴───────┴─────────────────┴──────────────┴────────┘

我们可以看到,尽管 user_a 可以访问 db1 和 db2 两个数据库,但我们授予 db1_reader 的读和遍历权限仅限定在 db1 范围内。user_a 可以访问 db2,但在我们通过其现有角色或新角色授予更多权限之前,实际上无法在 db2 上执行任何操作。