def feature(self, node="clickhouse1"): """Check grant query syntax. ```sql GRANT ON CLUSTER [cluster_name] role [,...] TO {user | another_role | CURRENT_USER} [,...] [WITH ADMIN OPTION] ``` """ node = self.context.cluster.node(node) @contextmanager def setup(users=0, roles=0): try: with Given("I have some users and roles"): for i in range(users): node.query(f"CREATE USER OR REPLACE user{i}") for j in range(roles): node.query(f"CREATE ROLE OR REPLACE role{j}") yield finally: with Finally("I drop the users and roles"): for i in range(users): node.query(f"DROP USER IF EXISTS user{i}") for j in range(roles): node.query(f"DROP ROLE IF EXISTS role{j}") with Scenario("I grant a role to a user", requirements=[RQ_SRS_006_RBAC_Grant_Role("1.0")]): with setup(1, 1): with When("I grant a role"): node.query("GRANT role0 TO user0") with Scenario( "I grant a nonexistent role to user", requirements=[RQ_SRS_006_RBAC_Grant_Role("1.0")], ): with setup(1, 0): with When("I grant nonexistent role to a user"): exitcode, message = errors.role_not_found_in_disk(name="role0") node.query("GRANT role0 TO user0", exitcode=exitcode, message=message) # with nonexistent object name, GRANT assumes type role (treats user0 as role) with Scenario( "I grant a role to a nonexistent user", requirements=[RQ_SRS_006_RBAC_Grant_Role("1.0")], ): with setup(0, 1): with When("I grant role to a nonexistent user"): exitcode, message = errors.role_not_found_in_disk(name="user0") node.query("GRANT role0 TO user0", exitcode=exitcode, message=message) with Scenario( "I grant a nonexistent role to a nonexistent user", requirements=[RQ_SRS_006_RBAC_Grant_Role("1.0")], ): with setup(0, 0): with When("I grant nonexistent role to a nonexistent user"): exitcode, message = (errors.role_not_found_in_disk( name="user0") if check_clickhouse_version(">=21.09")(self) else errors.role_not_found_in_disk( name="role0")) node.query("GRANT role0 TO user0", exitcode=exitcode, message=message) with Scenario( "I grant a role to multiple users", requirements=[RQ_SRS_006_RBAC_Grant_Role("1.0")], ): with setup(2, 1): with When("I grant role to a multiple users"): node.query("GRANT role0 TO user0, user1") with Scenario( "I grant multiple roles to multiple users", requirements=[RQ_SRS_006_RBAC_Grant_Role("1.0")], ): with setup(2, 2): with When("I grant multiple roles to multiple users"): node.query("GRANT role0, role1 TO user0, user1") with Scenario( "I grant role to current user", requirements=[RQ_SRS_006_RBAC_Grant_Role_CurrentUser("1.0")], ): with setup(1, 1): with Given("I have a user with access management privilege"): node.query("GRANT ACCESS MANAGEMENT ON *.* TO user0") with When("I grant role to current user"): node.query("GRANT role0 TO CURRENT_USER", settings=[("user", "user0")]) with Scenario( "I grant role to default user, throws exception", requirements=[RQ_SRS_006_RBAC_Grant_Role_CurrentUser("1.0")], ): with setup(1, 1): with When("I grant role to default user"): exitcode, message = errors.cannot_update_default() node.query("GRANT role0 TO CURRENT_USER", exitcode=exitcode, message=message) with Scenario( "I grant role to user with admin option", requirements=[RQ_SRS_006_RBAC_Grant_Role_AdminOption("1.0")], ): with setup(1, 1): with When("I grant role to a user with admin option"): node.query("GRANT role0 TO user0 WITH ADMIN OPTION") with Scenario( "I grant role to user on cluster", requirements=[RQ_SRS_006_RBAC_Grant_Role_OnCluster("1.0")], ): try: with Given("I have a user and a role on a cluster"): node.query( "CREATE USER OR REPLACE user0 ON CLUSTER sharded_cluster") node.query( "CREATE ROLE OR REPLACE role0 ON CLUSTER sharded_cluster") with When("I grant the role to the user"): node.query("GRANT ON CLUSTER sharded_cluster role0 TO user0") finally: with Finally("I drop the user and role"): node.query( "DROP USER IF EXISTS user0 ON CLUSTER sharded_cluster") node.query( "DROP ROLE IF EXISTS role0 ON CLUSTER sharded_cluster") with Scenario( "I grant role to user on fake cluster, throws exception", requirements=[RQ_SRS_006_RBAC_Grant_Role_OnCluster("1.0")], ): with setup(1, 1): with When("I grant the role to the user"): exitcode, message = errors.cluster_not_found("fake_cluster") node.query( "GRANT ON CLUSTER fake_cluster role0 TO user0", exitcode=exitcode, message=message, )
} xflags = { "privileges/alter index/table_type='ReplicatedVersionedCollapsingMergeTree-sharded_cluster'/role with privileges from role with grant option/granted=:/I try to ALTER INDEX with given privileges/I check order by when privilege is granted": ( SKIP, 0, ) } ffails = { "/clickhouse/rbac/privileges/:/table_type='ReplicatedReplacingMergeTree-sharded_cluster": ( Skip, "Causes clickhouse timeout on 21.10", (lambda test: check_clickhouse_version(">=21.10") (test) and check_clickhouse_version("<21.11")(test)), ), "/clickhouse/rbac/views": ( Skip, "Does not work on clickhouse 21.09", (lambda test: check_clickhouse_version(">=21.9") (test) and check_clickhouse_version("<21.10")(test)), ), } @TestModule @ArgumentParser(argparser) @XFails(xfails) @XFlags(xflags) @FFails(ffails)
# Cross-outs of known fails xfails = { "mapping/roles removed and added in parallel": [(Fail, "known bug")], "user dn detection/mapping/roles removed and added in parallel": [(Fail, "known bug")], "cluster secret/external user directory/:/:/cluster with secret/ldap user/:mapped True/select using mapped role/with privilege on source and distributed": [(Fail, "https://github.com/ClickHouse/ClickHouse/issues/34130")], } # Force results without running the test ffails = { "cluster secret": ( Skip, "feature available on 20.10+", check_clickhouse_version("<20.10"), ) } @TestFeature @Name("role mapping") @ArgumentParser(argparser) @Specifications(SRS_014_ClickHouse_LDAP_Role_Mapping) @Requirements(RQ_SRS_014_LDAP_RoleMapping("1.0")) @XFails(xfails) @FFails(ffails) def regression(self, local, clickhouse_binary_path, clickhouse_version=None,
from helpers.common import check_clickhouse_version # Cross-outs of known fails xfails = { "mapping/roles removed and added in parallel": [(Fail, "known bug")], "user dn detection/mapping/roles removed and added in parallel": [(Fail, "known bug")], "cluster secret/external user directory/:/:/cluster with secret/ldap user/:mapped True/select using mapped role/with privilege on source and distributed": [(Fail, "https://github.com/ClickHouse/ClickHouse/issues/34130")] } # Force results without running the test ffails={ "cluster secret": (Skip, "feature available on 20.10+", check_clickhouse_version("<20.10")) } @TestFeature @Name("role mapping") @ArgumentParser(argparser) @Specifications( SRS_014_ClickHouse_LDAP_Role_Mapping ) @Requirements( RQ_SRS_014_LDAP_RoleMapping("1.0") ) @XFails(xfails) @FFails(ffails) def regression(self, local, clickhouse_binary_path, clickhouse_version=None, stress=None): """ClickHouse LDAP role mapping regression module.
(Fail, "can't be tested with self-signed certificates") ], "connection protocols/tls require cert default demand": [ (Fail, "can't be tested with self-signed certificates") ], "connection protocols/starttls with custom port": [(Fail, "it seems that starttls is not enabled by default on custom plain-text ports in LDAP server" )], "connection protocols/tls cipher suite": [(Fail, "can't get it to work")] } ffails = { "user authentications/verification cooldown performance/:": (Skip, "causes timeout on 21.8", (lambda test: check_clickhouse_version(">=21.8") (test) and check_clickhouse_version("<21.9")(test))) } @TestFeature @Name("external user directory") @ArgumentParser(argparser) @Specifications(SRS_009_ClickHouse_LDAP_External_User_Directory) @Requirements(RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication("1.0")) @XFails(xfails) @FFails(ffails) def regression(self, local, clickhouse_binary_path, clickhouse_version=None, stress=None):
def feature(self, node="clickhouse1"): """Check revoke query syntax. ```sql REVOKE [ON CLUSTER cluster_name] [ADMIN OPTION FOR] role [,...] FROM {user | role | CURRENT_USER} [,...] | ALL | ALL EXCEPT {user_name | role_name | CURRENT_USER} [,...] ``` """ node = self.context.cluster.node(node) @contextmanager def setup(users=2, roles=2): try: with Given("I have some users"): for i in range(users): node.query(f"CREATE USER OR REPLACE user{i}") with And("I have some roles"): for i in range(roles): node.query(f"CREATE ROLE OR REPLACE role{i}") yield finally: with Finally("I drop the users"): for i in range(users): node.query(f"DROP USER IF EXISTS user{i}") with And("I drop the roles"): for i in range(roles): node.query(f"DROP ROLE IF EXISTS role{i}") with Scenario("I revoke a role from a user", requirements=[RQ_SRS_006_RBAC_Revoke_Role("1.0")]): with setup(): with When("I revoke a role"): node.query("REVOKE role0 FROM user0") with Scenario( "I revoke a nonexistent role from user", requirements=[RQ_SRS_006_RBAC_Revoke_Role("1.0")], ): with setup(1, 0): with When("I revoke nonexistent role from a user"): exitcode, message = errors.role_not_found_in_disk(name="role0") node.query("REVOKE role0 FROM user0", exitcode=exitcode, message=message) # with nonexistent object name, REVOKE assumes type role (treats user0 as role) with Scenario( "I revoke a role from a nonexistent user", requirements=[RQ_SRS_006_RBAC_Revoke_Role("1.0")], ): with setup(0, 1): with When("I revoke role from a nonexistent user"): exitcode, message = errors.role_not_found_in_disk(name="user0") node.query("REVOKE role0 FROM user0", exitcode=exitcode, message=message) # with nonexistent object name, REVOKE assumes type role (treats user0 as role) with Scenario( "I revoke a role from ALL EXCEPT nonexistent user", requirements=[RQ_SRS_006_RBAC_Revoke_Role("1.0")], ): with setup(0, 1): with When("I revoke role from a nonexistent user"): exitcode, message = errors.role_not_found_in_disk(name="user0") node.query( "REVOKE role0 FROM ALL EXCEPT user0", exitcode=exitcode, message=message, ) with Scenario( "I revoke a nonexistent role from a nonexistent user", requirements=[RQ_SRS_006_RBAC_Revoke_Role("1.0")], ): with setup(0, 0): with When("I revoke nonexistent role from a nonexistent user"): exitcode, message = (errors.role_not_found_in_disk( name="user0") if check_clickhouse_version(">=21.09")(self) else errors.role_not_found_in_disk( name="role0")) node.query("REVOKE role0 FROM user0", exitcode=exitcode, message=message) with Scenario( "I revoke a role from multiple users", requirements=[RQ_SRS_006_RBAC_Revoke_Role("1.0")], ): with setup(): with When("I revoke a role from multiple users"): node.query("REVOKE role0 FROM user0, user1") with Scenario( "I revoke multiple roles from multiple users", requirements=[RQ_SRS_006_RBAC_Revoke_Role("1.0")], ): with setup(): node.query("REVOKE role0, role1 FROM user0, user1") # user is default, expect exception with Scenario( "I revoke a role from default user", requirements=[ RQ_SRS_006_RBAC_Revoke_Role("1.0"), RQ_SRS_006_RBAC_Revoke_Role_Keywords("1.0"), ], ): with setup(): with When("I revoke a role from default user"): exitcode, message = errors.cannot_update_default() node.query("REVOKE role0 FROM CURRENT_USER", exitcode=exitcode, message=message) # user is user0 with Scenario( "I revoke a role from current user", requirements=[ RQ_SRS_006_RBAC_Revoke_Role("1.0"), RQ_SRS_006_RBAC_Revoke_Role_Keywords("1.0"), ], ): with setup(): with When("I revoke a role from current user"): node.query("REVOKE role0 FROM CURRENT_USER", settings=[("user", "user0")]) # user is default, expect exception with Scenario( "I revoke a role from all", requirements=[ RQ_SRS_006_RBAC_Revoke_Role("1.0"), RQ_SRS_006_RBAC_Revoke_Role_Keywords("1.0"), ], ): with setup(): with When("I revoke a role from all"): exitcode, message = errors.cannot_update_default() node.query("REVOKE role0 FROM ALL", exitcode=exitcode, message=message) # user is default, expect exception with Scenario( "I revoke multiple roles from all", requirements=[ RQ_SRS_006_RBAC_Revoke_Role("1.0"), RQ_SRS_006_RBAC_Revoke_Role_Keywords("1.0"), ], ): with setup(): with When("I revoke multiple roles from all"): exitcode, message = errors.cannot_update_default() node.query("REVOKE role0, role1 FROM ALL", exitcode=exitcode, message=message) with Scenario( "I revoke a role from all but current user", requirements=[ RQ_SRS_006_RBAC_Revoke_Role("1.0"), RQ_SRS_006_RBAC_Revoke_Role_Keywords("1.0"), ], ): with setup(): with When("I revoke a role from all except current"): node.query("REVOKE role0 FROM ALL EXCEPT CURRENT_USER") with Scenario( "I revoke a role from all but default user", requirements=[ RQ_SRS_006_RBAC_Revoke_Role("1.0"), RQ_SRS_006_RBAC_Revoke_Role_Keywords("1.0"), ], ): with setup(): with When("I revoke a role from all except default"): node.query("REVOKE role0 FROM ALL EXCEPT default", settings=[("user", "user0")]) with Scenario( "I revoke multiple roles from all but default user", requirements=[ RQ_SRS_006_RBAC_Revoke_Role("1.0"), RQ_SRS_006_RBAC_Revoke_Role_Keywords("1.0"), ], ): with setup(): with When("I revoke multiple roles from all except default"): node.query( "REVOKE role0, role1 FROM ALL EXCEPT default", settings=[("user", "user0")], ) with Scenario("I revoke a role from a role", requirements=[RQ_SRS_006_RBAC_Revoke_Role("1.0")]): with setup(): with When("I revoke a role from a role"): node.query("REVOKE role0 FROM role1") with Scenario( "I revoke a role from a role and a user", requirements=[RQ_SRS_006_RBAC_Revoke_Role("1.0")], ): with setup(): with When("I revoke a role from multiple roles"): node.query("REVOKE role0 FROM role1, user0") with Scenario( "I revoke a role from a user on cluster", requirements=[RQ_SRS_006_RBAC_Revoke_Role_Cluster("1.0")], ): with Given("I have a role and a user on a cluster"): node.query( "CREATE USER OR REPLACE user0 ON CLUSTER sharded_cluster") node.query( "CREATE ROLE OR REPLACE role0 ON CLUSTER sharded_cluster") with When("I revoke a role from user on a cluster"): node.query("REVOKE ON CLUSTER sharded_cluster role0 FROM user0") with Finally("I drop the user and role"): node.query("DROP USER IF EXISTS user0 ON CLUSTER sharded_cluster") node.query("DROP ROLE IF EXISTS role0 ON CLUSTER sharded_cluster") with Scenario( "I revoke a role on fake cluster, throws exception", requirements=[RQ_SRS_006_RBAC_Revoke_Role_Cluster("1.0")], ): with Given("I have a role and a user on a cluster"): node.query("CREATE USER OR REPLACE user0") node.query("CREATE ROLE OR REPLACE role0") with When("I revoke a role from user on a cluster"): exitcode, message = errors.cluster_not_found("fake_cluster") node.query( "REVOKE ON CLUSTER fake_cluster role0 FROM user0", exitcode=exitcode, message=message, ) with Finally("I drop the user and role"): node.query("DROP USER IF EXISTS user0") node.query("DROP ROLE IF EXISTS role0") with Scenario( "I revoke multiple roles from multiple users on cluster", requirements=[ RQ_SRS_006_RBAC_Revoke_Role("1.0"), RQ_SRS_006_RBAC_Revoke_Role_Cluster("1.0"), ], ): with Given("I have multiple roles and multiple users on a cluster"): for i in range(2): node.query( f"CREATE USER OR REPLACE user{i} ON CLUSTER sharded_cluster" ) node.query( f"CREATE ROLE OR REPLACE role{i} ON CLUSTER sharded_cluster" ) with When("I revoke multiple roles from multiple users on cluster"): node.query( "REVOKE ON CLUSTER sharded_cluster role0, role1 FROM user0, user1" ) with Finally("I drop the roles and users"): for i in range(2): node.query( f"DROP USER IF EXISTS user{i} ON CLUSTER sharded_cluster") node.query( f"DROP ROLE IF EXISTS role{i} ON CLUSTER sharded_cluster") with Scenario( "I revoke admin option for role from a user", requirements=[RQ_SRS_006_RBAC_Revoke_AdminOption("1.0")], ): with setup(): with When("I revoke admin option for role from a user"): node.query("REVOKE ADMIN OPTION FOR role0 FROM user0") with Scenario( "I revoke admin option for multiple roles from multiple users", requirements=[ RQ_SRS_006_RBAC_Revoke_Role("1.0"), RQ_SRS_006_RBAC_Revoke_AdminOption("1.0"), ], ): with setup(): with When( "I revoke admin option for multiple roles from multiple users" ): node.query( "REVOKE ADMIN OPTION FOR role0, role1 FROM user0, user1")