| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| # Client tests to ensure object ownership functionality. |
| |
| import grp |
| import pytest |
| from getpass import getuser |
| from os import getenv |
| |
| from tests.common.sentry_cache_test_suite import SentryCacheTestSuite, TestObject |
| from tests.common.test_dimensions import create_uncompressed_text_dimension |
| |
| # Sentry long polling frequency to make Sentry refresh not run. |
| SENTRY_LONG_POLLING_FREQUENCY_S = 3600 |
| |
| SENTRY_CONFIG_DIR = getenv('IMPALA_HOME') + '/fe/src/test/resources/' |
| SENTRY_BASE_LOG_DIR = getenv('IMPALA_CLUSTER_LOGS_DIR') + "/sentry" |
| SENTRY_CONFIG_FILE_OO = SENTRY_CONFIG_DIR + 'sentry-site_oo.xml' |
| SENTRY_CONFIG_FILE_OO_NOGRANT = SENTRY_CONFIG_DIR + 'sentry-site_oo_nogrant.xml' |
| SENTRY_CONFIG_FILE_NO_OO = SENTRY_CONFIG_DIR + 'sentry-site_no_oo.xml' |
| |
| |
| class TestOwnerPrivileges(SentryCacheTestSuite): |
| @classmethod |
| def add_test_dimensions(cls): |
| super(TestOwnerPrivileges, cls).add_test_dimensions() |
| cls.ImpalaTestMatrix.add_dimension( |
| create_uncompressed_text_dimension(cls.get_workload())) |
| |
| def teardown_class(self): |
| super(self) |
| |
| def setup_method(self, method): |
| super(TestOwnerPrivileges, self).setup_method(method) |
| self._setup_admin() |
| |
| def teardown_method(self, method): |
| self._cleanup_admin() |
| super(TestOwnerPrivileges, self).teardown_method(method) |
| |
| def _setup_ownership_test(self): |
| self._cleanup_ownership_test() |
| # Base roles for enabling tests. |
| self.execute_query("create role owner_priv_test_oo_user1") |
| # Role for verifying grant. |
| self.execute_query("create role owner_priv_test_all_role") |
| # Role for verifying transfer to role. |
| self.execute_query("create role owner_priv_test_owner_role") |
| self.execute_query("grant role owner_priv_test_oo_user1 to group oo_group1") |
| self.execute_query("grant role owner_priv_test_owner_role to group oo_group1") |
| self.execute_query("grant create on server to owner_priv_test_oo_user1") |
| self.execute_query("grant select on database functional to owner_priv_test_oo_user1") |
| |
| def _cleanup_ownership_test(self): |
| # Clean up the test artifacts. |
| try: |
| self.cleanup_db("owner_priv_db", sync_ddl=0) |
| except Exception: |
| # Ignore this if we can't show tables. |
| pass |
| |
| # Clean up any old roles created by this test |
| for role_name in self.execute_query("show roles").data: |
| if "owner_priv_test" in role_name: |
| self.execute_query("drop role %s" % role_name) |
| |
| @staticmethod |
| def count_user_privileges(result): |
| """ |
| This method returns a new list of privileges that only contain user privileges. |
| """ |
| # results should have the following columns |
| # principal_name, principal_type, scope, database, table, column, uri, privilege, |
| # grant_option, create_time |
| total = 0 |
| for row in result.data: |
| col = row.split('\t') |
| if col[0] == 'USER': |
| total += 1 |
| return total |
| |
| def _validate_no_user_privileges(self, client, user, invalidate_metadata): |
| if invalidate_metadata: self.execute_query("invalidate metadata") |
| result = self.user_query(client, "show grant user %s" % user, user=user) |
| return TestOwnerPrivileges.count_user_privileges(result) == 0 |
| |
| def _setup_admin(self): |
| # Admin for manipulation and cleaning up. |
| try: |
| self.execute_query("drop role owner_priv_admin") |
| except Exception: |
| # Ignore in case it wasn't created yet. |
| pass |
| self.execute_query("create role owner_priv_admin") |
| self.execute_query("grant all on server to owner_priv_admin with grant option") |
| group_name = grp.getgrnam(getuser()).gr_name |
| self.execute_query("grant role owner_priv_admin to group `%s`" % group_name) |
| |
| def _cleanup_admin(self): |
| self.execute_query("drop role owner_priv_admin") |
| |
| @pytest.mark.execute_serially |
| @SentryCacheTestSuite.with_args( |
| impalad_args="--server_name=server1 --sentry_config={0} " |
| "--authorization_policy_provider_class=" |
| "org.apache.impala.service.CustomClusterResourceAuthorizationProvider" |
| .format(SENTRY_CONFIG_FILE_OO), |
| catalogd_args="--sentry_config={0} --sentry_catalog_polling_frequency_s={1} " |
| "--authorization_policy_provider_class=" |
| "org.apache.impala.service.CustomClusterResourceAuthorizationProvider" |
| .format(SENTRY_CONFIG_FILE_OO, SENTRY_LONG_POLLING_FREQUENCY_S), |
| sentry_config=SENTRY_CONFIG_FILE_OO, |
| sentry_log_dir="{0}/test_owner_privileges_with_grant".format(SENTRY_BASE_LOG_DIR)) |
| def test_owner_privileges_with_grant(self, vector, unique_database): |
| """Tests owner privileges with grant on database, table, and view. |
| - invalidate_metadata=True: With Sentry refresh to make sure privileges are really |
| stored in Sentry. |
| - invalidate_metadata=False: No Sentry refresh to make sure user can use owner |
| privileges right away without a Sentry refresh.""" |
| for invalidate in [True, False]: |
| try: |
| self._setup_ownership_test() |
| self._execute_owner_privilege_tests(TestObject(TestObject.DATABASE, |
| "owner_priv_db", |
| grant=True), |
| invalidate_metadata=invalidate) |
| self._execute_owner_privilege_tests(TestObject(TestObject.TABLE, |
| unique_database + |
| ".owner_priv_tbl", |
| grant=True), |
| invalidate_metadata=invalidate) |
| self._execute_owner_privilege_tests(TestObject(TestObject.VIEW, |
| unique_database + |
| ".owner_priv_view", |
| grant=True), |
| invalidate_metadata=invalidate) |
| finally: |
| self._cleanup_ownership_test() |
| |
| def _execute_owner_privilege_tests(self, test_obj, invalidate_metadata): |
| """ |
| Executes all the statements required to validate owner privileges work correctly |
| for a specific database, table, or view. |
| """ |
| # Create object and ensure oo_user1 gets owner privileges. |
| self.oo_user1_impalad_client = self.create_impala_client() |
| # oo_user2 is only used for transferring ownership. |
| self.oo_user2_impalad_client = self.create_impala_client() |
| self.user_query(self.oo_user1_impalad_client, "create %s if not exists %s %s %s" % |
| (test_obj.obj_type, test_obj.obj_name, test_obj.table_def, |
| test_obj.view_select), user="oo_user1") |
| self.validate_privileges(self.oo_user1_impalad_client, "show grant user oo_user1", |
| test_obj, user="oo_user1", |
| invalidate_metadata=invalidate_metadata) |
| |
| # Ensure grant works. |
| self.user_query(self.oo_user1_impalad_client, |
| "grant all on %s %s to role owner_priv_test_all_role" % |
| (test_obj.grant_name, test_obj.obj_name), user="oo_user1") |
| self.user_query(self.oo_user1_impalad_client, |
| "revoke all on %s %s from role owner_priv_test_all_role" % |
| (test_obj.grant_name, test_obj.obj_name), user="oo_user1") |
| |
| # Change the database owner and ensure oo_user1 does not have owner privileges. |
| self.user_query(self.oo_user1_impalad_client, "alter %s %s set owner user oo_user2" % |
| (test_obj.obj_type, test_obj.obj_name), user="oo_user1") |
| assert self._validate_no_user_privileges(self.oo_user1_impalad_client, |
| user="oo_user1", |
| invalidate_metadata=invalidate_metadata) |
| |
| # Ensure oo_user1 cannot drop database after owner change. |
| # Use a delay to avoid cache consistency issue that could occur after alter. |
| self.user_query(self.oo_user1_impalad_client, "drop %s %s" % |
| (test_obj.obj_type, test_obj.obj_name), user="oo_user1", |
| error_msg="does not have privileges to execute 'DROP'") |
| |
| # oo_user2 should have privileges for object now. |
| self.validate_privileges(self.oo_user2_impalad_client, "show grant user oo_user2", |
| test_obj, user="oo_user2", |
| invalidate_metadata=invalidate_metadata) |
| |
| # Change the owner to a role and ensure oo_user2 doesn't have privileges. |
| # Set the owner back to oo_user1 since for views, oo_user2 doesn't have select |
| # privileges on the underlying table. |
| self.execute_query("alter %s %s set owner user oo_user1" % |
| (test_obj.obj_type, test_obj.obj_name), |
| query_options={"sync_ddl": 1}) |
| assert self._validate_no_user_privileges(self.oo_user2_impalad_client, |
| user="oo_user2", |
| invalidate_metadata=invalidate_metadata) |
| self.user_query(self.oo_user1_impalad_client, |
| "alter %s %s set owner role owner_priv_test_owner_role" % |
| (test_obj.obj_type, test_obj.obj_name), user="oo_user1") |
| # Ensure oo_user1 does not have user privileges. |
| assert self._validate_no_user_privileges(self.oo_user1_impalad_client, |
| user="oo_user1", |
| invalidate_metadata=invalidate_metadata) |
| |
| # Ensure role has owner privileges. |
| self.validate_privileges(self.oo_user1_impalad_client, |
| "show grant role owner_priv_test_owner_role", test_obj, |
| user="oo_user1", invalidate_metadata=invalidate_metadata) |
| |
| # Drop the object and ensure no role privileges. |
| # Use a delay to avoid cache consistency issue that could occur after alter. |
| self.user_query(self.oo_user1_impalad_client, "drop %s %s " % |
| (test_obj.obj_type, test_obj.obj_name), user="oo_user1") |
| assert self._validate_no_user_privileges(self.oo_user1_impalad_client, |
| user="oo_user1", |
| invalidate_metadata=invalidate_metadata) |
| |
| # Ensure user privileges are gone after drop. |
| # Use a delay to avoid cache consistency issue that could occur after drop. |
| self.user_query(self.oo_user1_impalad_client, "create %s if not exists %s %s %s" % |
| (test_obj.obj_type, test_obj.obj_name, test_obj.table_def, |
| test_obj.view_select), user="oo_user1") |
| # Use a delay to avoid cache consistency issue that could occur after create. |
| self.user_query(self.oo_user1_impalad_client, "drop %s %s " % |
| (test_obj.obj_type, test_obj.obj_name), user="oo_user1") |
| assert self._validate_no_user_privileges(self.oo_user1_impalad_client, |
| user="oo_user1", |
| invalidate_metadata=invalidate_metadata) |
| |
| @pytest.mark.execute_serially |
| @SentryCacheTestSuite.with_args( |
| impalad_args="--server_name=server1 --sentry_config={0} " |
| "--authorization_policy_provider_class=" |
| "org.apache.impala.service.CustomClusterResourceAuthorizationProvider " |
| .format(SENTRY_CONFIG_FILE_NO_OO), |
| catalogd_args="--sentry_config={0} --authorization_policy_provider_class=" |
| "org.apache.impala.service.CustomClusterResourceAuthorizationProvider" |
| .format(SENTRY_CONFIG_FILE_NO_OO), |
| sentry_config=SENTRY_CONFIG_FILE_NO_OO, |
| sentry_log_dir="{0}/test_owner_privileges_disabled".format(SENTRY_BASE_LOG_DIR)) |
| def test_owner_privileges_disabled(self, vector, unique_database): |
| """Tests that there should not be owner privileges.""" |
| try: |
| self._setup_ownership_test() |
| self._execute_owner_privilege_tests_no_oo(TestObject(TestObject.DATABASE, |
| "owner_priv_db")) |
| self._execute_owner_privilege_tests_no_oo(TestObject(TestObject.TABLE, |
| unique_database + |
| ".owner_priv_tbl")) |
| self._execute_owner_privilege_tests_no_oo(TestObject(TestObject.VIEW, |
| unique_database + |
| ".owner_priv_view")) |
| finally: |
| self._cleanup_ownership_test() |
| |
| def _execute_owner_privilege_tests_no_oo(self, test_obj): |
| """ |
| Executes all the statements required to validate owner privileges work correctly |
| for a specific database, table, or view. |
| """ |
| # Create object and ensure oo_user1 gets owner privileges. |
| self.oo_user1_impalad_client = self.create_impala_client() |
| self.user_query(self.oo_user1_impalad_client, "create %s if not exists %s %s %s" |
| % (test_obj.obj_type, test_obj.obj_name, test_obj.table_def, |
| test_obj.view_select), user="oo_user1") |
| |
| # Ensure grant doesn't work. |
| self.user_query(self.oo_user1_impalad_client, |
| "grant all on %s %s to role owner_priv_test_all_role" % |
| (test_obj.grant_name, test_obj.obj_name), user="oo_user1", |
| error_msg="does not have privileges to execute: GRANT_PRIVILEGE") |
| |
| self.user_query(self.oo_user1_impalad_client, |
| "revoke all on %s %s from role owner_priv_test_all_role" % |
| (test_obj.grant_name, test_obj.obj_name), user="oo_user1", |
| error_msg="does not have privileges to execute: REVOKE_PRIVILEGE") |
| |
| # Ensure changing the database owner doesn't work. |
| self.user_query(self.oo_user1_impalad_client, |
| "alter %s %s set owner user oo_user2" % |
| (test_obj.obj_type, test_obj.obj_name), user="oo_user1", |
| error_msg="does not have privileges with 'GRANT OPTION'") |
| |
| # Ensure oo_user1 cannot drop database. |
| self.user_query(self.oo_user1_impalad_client, "drop %s %s" % |
| (test_obj.obj_type, test_obj.obj_name), user="oo_user1", |
| error_msg="does not have privileges to execute 'DROP'") |
| |
| @pytest.mark.execute_serially |
| @SentryCacheTestSuite.with_args( |
| impalad_args="--server_name=server1 --sentry_config={0} " |
| "--authorization_policy_provider_class=" |
| "org.apache.impala.service.CustomClusterResourceAuthorizationProvider" |
| .format(SENTRY_CONFIG_FILE_OO_NOGRANT), |
| catalogd_args="--sentry_config={0} --sentry_catalog_polling_frequency_s={1} " |
| "--authorization_policy_provider_class=" |
| "org.apache.impala.service.CustomClusterResourceAuthorizationProvider" |
| .format(SENTRY_CONFIG_FILE_OO_NOGRANT, |
| SENTRY_LONG_POLLING_FREQUENCY_S), |
| sentry_config=SENTRY_CONFIG_FILE_OO_NOGRANT, |
| sentry_log_dir="{0}/test_owner_privileges_without_grant" |
| .format(SENTRY_BASE_LOG_DIR)) |
| def test_owner_privileges_without_grant(self, vector, unique_database): |
| """Tests owner privileges without grant on database, table, and view. |
| - invalidate_metadata=True: With Sentry refresh to make sure privileges are really |
| stored in Sentry. |
| - invalidate_metadata=False: No Sentry refresh to make sure user can use owner |
| privileges right away without a Sentry refresh.""" |
| for invalidate in [True, False]: |
| try: |
| self._setup_ownership_test() |
| self._execute_owner_privilege_tests_oo_nogrant(TestObject(TestObject.DATABASE, |
| "owner_priv_db"), |
| invalidate_metadata=invalidate) |
| self._execute_owner_privilege_tests_oo_nogrant(TestObject(TestObject.TABLE, |
| unique_database + |
| ".owner_priv_tbl"), |
| invalidate_metadata=invalidate) |
| self._execute_owner_privilege_tests_oo_nogrant(TestObject(TestObject.VIEW, |
| unique_database + |
| ".owner_priv_view"), |
| invalidate_metadata=invalidate) |
| finally: |
| self._cleanup_ownership_test() |
| |
| def _execute_owner_privilege_tests_oo_nogrant(self, test_obj, invalidate_metadata): |
| """ |
| Executes all the statements required to validate owner privileges work correctly |
| for a specific database, table, or view. |
| """ |
| # Create object and ensure oo_user1 gets owner privileges. |
| self.oo_user1_impalad_client = self.create_impala_client() |
| self.user_query(self.oo_user1_impalad_client, "create %s if not exists %s %s %s" % |
| (test_obj.obj_type, test_obj.obj_name, test_obj.table_def, |
| test_obj.view_select), user="oo_user1") |
| self.validate_privileges(self.oo_user1_impalad_client, "show grant user oo_user1", |
| test_obj, user="oo_user1", |
| invalidate_metadata=invalidate_metadata) |
| |
| # Ensure grant doesn't work. |
| self.user_query(self.oo_user1_impalad_client, |
| "grant all on %s %s to role owner_priv_test_all_role" % |
| (test_obj.grant_name, test_obj.obj_name), user="oo_user1", |
| error_msg="does not have privileges to execute: GRANT_PRIVILEGE") |
| |
| self.user_query(self.oo_user1_impalad_client, |
| "revoke all on %s %s from role owner_priv_test_all_role" % |
| (test_obj.grant_name, test_obj.obj_name), user="oo_user1", |
| error_msg="does not have privileges to execute: REVOKE_PRIVILEGE") |
| |
| self.user_query(self.oo_user1_impalad_client, "alter %s %s set owner user oo_user2" % |
| (test_obj.obj_type, test_obj.obj_name), user="oo_user1", |
| error_msg="does not have privileges with 'GRANT OPTION'") |
| |
| # Use a delay to avoid cache consistency issue that could occur after alter. |
| self.user_query(self.oo_user1_impalad_client, "drop %s %s " % |
| (test_obj.obj_type, test_obj.obj_name), user="oo_user1") |
| assert self._validate_no_user_privileges(self.oo_user1_impalad_client, |
| user="oo_user1", |
| invalidate_metadata=invalidate_metadata) |
| |
| @pytest.mark.execute_serially |
| @SentryCacheTestSuite.with_args( |
| impalad_args="--server_name=server1 --sentry_config={0} " |
| "--authorization_policy_provider_class=" |
| "org.apache.impala.service.CustomClusterResourceAuthorizationProvider" |
| .format(SENTRY_CONFIG_FILE_OO), |
| catalogd_args="--sentry_config={0} " |
| "--authorization_policy_provider_class=" |
| "org.apache.impala.service.CustomClusterResourceAuthorizationProvider" |
| .format(SENTRY_CONFIG_FILE_OO), |
| sentry_config=SENTRY_CONFIG_FILE_OO, |
| sentry_log_dir="{0}/test_owner_privileges_different_cases" |
| .format(SENTRY_BASE_LOG_DIR)) |
| def test_owner_privileges_different_cases(self, vector, unique_database): |
| """IMPALA-7742: Tests that only user names that differ only in case are not |
| authorized to access the database/table/view unless the user is the owner.""" |
| # Use two different clients so that the sessions will use two different user names. |
| foobar_impalad_client = self.create_impala_client() |
| FOOBAR_impalad_client = self.create_impala_client() |
| role_name = "owner_priv_diff_cases_role" |
| try: |
| self.execute_query("create role %s" % role_name) |
| self.execute_query("grant role %s to group foobar" % role_name) |
| self.execute_query("grant all on server to role %s" % role_name) |
| |
| self.user_query(foobar_impalad_client, "create database %s_db" % |
| unique_database, user="foobar") |
| # FOOBAR user should not be allowed to create a table in the foobar's database. |
| self.user_query(FOOBAR_impalad_client, "create table %s_db.test_tbl(i int)" % |
| unique_database, user="FOOBAR", |
| error_msg="User 'FOOBAR' does not have privileges to execute " |
| "'CREATE' on: %s_db" % unique_database) |
| |
| self.user_query(foobar_impalad_client, "create table %s.owner_case_tbl(i int)" % |
| unique_database, user="foobar") |
| # FOOBAR user should not be allowed to select foobar's table. |
| self.user_query(FOOBAR_impalad_client, "select * from %s.owner_case_tbl" % |
| unique_database, user="FOOBAR", |
| error_msg="User 'FOOBAR' does not have privileges to execute " |
| "'SELECT' on: %s.owner_case_tbl" % unique_database) |
| |
| self.user_query(foobar_impalad_client, |
| "create view %s.owner_case_view as select 1" % unique_database, |
| user="foobar") |
| # FOOBAR user should not be allowed to select foobar's view. |
| self.user_query(FOOBAR_impalad_client, "select * from %s.owner_case_view" % |
| unique_database, user="FOOBAR", |
| error_msg="User 'FOOBAR' does not have privileges to execute " |
| "'SELECT' on: %s.owner_case_view" % unique_database) |
| |
| # FOOBAR user should not be allowed to see foobar's privileges. |
| self.user_query(FOOBAR_impalad_client, "show grant user foobar", user="FOOBAR", |
| error_msg="User 'FOOBAR' does not have privileges to access the " |
| "requested policy metadata") |
| finally: |
| self.user_query(foobar_impalad_client, "drop database %s_db cascade" % |
| unique_database, user="foobar") |
| self.execute_query("drop role %s" % role_name) |