|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +import logging |
| 3 | +from ydb.tests.oss.ydb_sdk_import import ydb |
| 4 | + |
| 5 | +logger = logging.getLogger(__name__) |
| 6 | + |
| 7 | + |
| 8 | +# local configuration for the ydb cluster (fetched by ydb_cluster_configuration fixture) |
| 9 | +CLUSTER_CONFIG = dict( |
| 10 | + additional_log_configs={ |
| 11 | + # 'TX_PROXY': LogLevels.DEBUG, |
| 12 | + } |
| 13 | +) |
| 14 | +DATABASE = "/Root/test" |
| 15 | +TABLE_NAME = "table" |
| 16 | +TABLE_PATH = f"{DATABASE}/{TABLE_NAME}" |
| 17 | +CREATE_TABLE_GRANTS = ['ydb.granular.create_table'] |
| 18 | +ALTER_TABLE_ADD_COLUMN_GRANTS = ['ydb.granular.alter_schema', 'ydb.granular.describe_schema'] |
| 19 | +ALTER_TABLE_DROP_COLUMN_GRANTS = ['ydb.granular.alter_schema', 'ydb.granular.describe_schema'] |
| 20 | +DROP_TABLE_GRANTS = ['ydb.granular.remove_schema', 'ydb.granular.describe_schema'] |
| 21 | +ALTER_TABLE_ADD_CHANGEFEED_GRANTS = ['ydb.granular.alter_schema', 'ydb.granular.describe_schema'] |
| 22 | +ALTER_TOPIC_ADD_CONSUMER_GRANTS = ['ydb.granular.alter_schema', 'ydb.granular.describe_schema'] |
| 23 | +READ_TOPIC_GRANTS = [] |
| 24 | +ALTER_TABLE_DROP_CHANGEFEED_GRANTS = ['ydb.granular.alter_schema', 'ydb.granular.describe_schema'] |
| 25 | +ALL_USED_GRANS = set( |
| 26 | + CREATE_TABLE_GRANTS |
| 27 | + + ALTER_TABLE_ADD_COLUMN_GRANTS |
| 28 | + + ALTER_TABLE_DROP_COLUMN_GRANTS |
| 29 | + + DROP_TABLE_GRANTS |
| 30 | + + ALTER_TABLE_ADD_CHANGEFEED_GRANTS |
| 31 | + + ALTER_TOPIC_ADD_CONSUMER_GRANTS |
| 32 | + + READ_TOPIC_GRANTS |
| 33 | + + ALTER_TABLE_DROP_CHANGEFEED_GRANTS |
| 34 | +) |
| 35 | + |
| 36 | + |
| 37 | +def run_query(config, query): |
| 38 | + with ydb.Driver(config) as driver: |
| 39 | + with ydb.QuerySessionPool(driver, size=1) as pool: |
| 40 | + pool.execute_with_retries(query) |
| 41 | + |
| 42 | + |
| 43 | +def run_with_assert(config, query, expected_err=None): |
| 44 | + if not expected_err: |
| 45 | + run_query(config, query) |
| 46 | + return |
| 47 | + |
| 48 | + try: |
| 49 | + run_query(config, query) |
| 50 | + assert False, 'Error expected' |
| 51 | + except Exception as e: |
| 52 | + assert expected_err in str(e) |
| 53 | + |
| 54 | + |
| 55 | +def create_user(ydb_cluster, admin_config, user_name): |
| 56 | + run_with_assert(admin_config, f"CREATE USER {user_name};") |
| 57 | + return ydb.DriverConfig( |
| 58 | + endpoint="%s:%s" % (ydb_cluster.nodes[1].host, ydb_cluster.nodes[1].port), |
| 59 | + database=DATABASE, |
| 60 | + credentials=ydb.StaticCredentials.from_user_password(user_name, ""), |
| 61 | + ) |
| 62 | + |
| 63 | + |
| 64 | +def revoke_grants(admin_config, user_name, object_name, all_grants): |
| 65 | + if all_grants is None or len(all_grants) == 0: |
| 66 | + return |
| 67 | + |
| 68 | + revoke_grants_query = '' |
| 69 | + for grant in all_grants: |
| 70 | + revoke_grants_query += f"REVOKE '{grant}' ON `{object_name}` FROM {user_name};" |
| 71 | + run_with_assert(admin_config, revoke_grants_query) |
| 72 | + |
| 73 | + |
| 74 | +def provide_grants(admin_config, user_name, object_name, required_grants): |
| 75 | + if required_grants is None or len(required_grants) == 0: |
| 76 | + return |
| 77 | + |
| 78 | + provide_grants_query = '' |
| 79 | + for grant in required_grants: |
| 80 | + assert grant in ALL_USED_GRANS, 'Keep ALL_USED_GRANS updated with all used grants' |
| 81 | + provide_grants_query += f"GRANT '{grant}' ON `{object_name}` TO {user_name};" |
| 82 | + run_with_assert(admin_config, provide_grants_query) |
| 83 | + |
| 84 | + |
| 85 | +def _test_grants(admin_config, user_config, user_name, query, object_name, required_grants, expected_err): |
| 86 | + revoke_grants(admin_config, user_name, object_name, ALL_USED_GRANS) |
| 87 | + if required_grants is not None and len(required_grants) > 0: # means the query does not require any grants |
| 88 | + run_with_assert(user_config, query, expected_err=expected_err) |
| 89 | + provide_grants(admin_config, user_name, object_name, required_grants) |
| 90 | + run_with_assert(user_config, query) |
| 91 | + |
| 92 | + |
| 93 | +def test_granular_grants_for_tables(ydb_cluster): |
| 94 | + ydb_cluster.create_database(DATABASE, storage_pool_units_count={"hdd": 1}) |
| 95 | + database_nodes = ydb_cluster.register_and_start_slots(DATABASE, count=1) |
| 96 | + ydb_cluster.wait_tenant_up(DATABASE) |
| 97 | + |
| 98 | + tenant_admin_config = ydb.DriverConfig( |
| 99 | + endpoint="%s:%s" % (ydb_cluster.nodes[1].host, ydb_cluster.nodes[1].port), |
| 100 | + database=DATABASE, |
| 101 | + ) |
| 102 | + |
| 103 | + # CREATE TABLE |
| 104 | + user1_config = create_user(ydb_cluster, tenant_admin_config, "user1") |
| 105 | + create_table_query = f"CREATE TABLE {TABLE_NAME} (a Uint64, b Uint64, PRIMARY KEY (a));" |
| 106 | + _test_grants( |
| 107 | + tenant_admin_config, |
| 108 | + user1_config, |
| 109 | + 'user1', |
| 110 | + create_table_query, |
| 111 | + DATABASE, |
| 112 | + CREATE_TABLE_GRANTS, |
| 113 | + "Access denied for scheme request", |
| 114 | + ) |
| 115 | + |
| 116 | + # ALTER TABLE ... ADD COLUMN |
| 117 | + user2_config = create_user(ydb_cluster, tenant_admin_config, "user2") |
| 118 | + add_column_query = f"ALTER TABLE `{TABLE_PATH}` ADD COLUMN `d` Uint64;" |
| 119 | + _test_grants( |
| 120 | + tenant_admin_config, |
| 121 | + user2_config, |
| 122 | + 'user2', |
| 123 | + add_column_query, |
| 124 | + TABLE_PATH, |
| 125 | + ALTER_TABLE_ADD_COLUMN_GRANTS, |
| 126 | + "you do not have access permissions", |
| 127 | + ) |
| 128 | + |
| 129 | + # ALTER TABLE ... DROP COLUMN |
| 130 | + drop_column_query = f"ALTER TABLE `{TABLE_PATH}` DROP COLUMN `d`;" |
| 131 | + _test_grants( |
| 132 | + tenant_admin_config, |
| 133 | + user2_config, |
| 134 | + 'user2', |
| 135 | + drop_column_query, |
| 136 | + TABLE_PATH, |
| 137 | + ALTER_TABLE_DROP_COLUMN_GRANTS, |
| 138 | + "you do not have access permissions", |
| 139 | + ) |
| 140 | + |
| 141 | + # DROP TABLE |
| 142 | + drop_table_query = f"DROP TABLE `{TABLE_PATH}`;" |
| 143 | + _test_grants( |
| 144 | + tenant_admin_config, |
| 145 | + user2_config, |
| 146 | + 'user2', |
| 147 | + drop_table_query, |
| 148 | + TABLE_PATH, |
| 149 | + DROP_TABLE_GRANTS, |
| 150 | + "you do not have access permissions", |
| 151 | + ) |
| 152 | + |
| 153 | + ydb_cluster.remove_database(DATABASE) |
| 154 | + ydb_cluster.unregister_and_stop_slots(database_nodes) |
| 155 | + |
| 156 | + |
| 157 | +def test_cdc_grants(ydb_cluster): |
| 158 | + ydb_cluster.create_database(DATABASE, storage_pool_units_count={"hdd": 1}) |
| 159 | + database_nodes = ydb_cluster.register_and_start_slots(DATABASE, count=1) |
| 160 | + ydb_cluster.wait_tenant_up(DATABASE) |
| 161 | + |
| 162 | + tenant_admin_config = ydb.DriverConfig( |
| 163 | + endpoint="%s:%s" % (ydb_cluster.nodes[1].host, ydb_cluster.nodes[1].port), |
| 164 | + database=DATABASE, |
| 165 | + ) |
| 166 | + |
| 167 | + # setup table |
| 168 | + user1_config = create_user(ydb_cluster, tenant_admin_config, "user1") |
| 169 | + run_with_assert(tenant_admin_config, f"GRANT CREATE TABLE ON `{DATABASE}` TO user1;") |
| 170 | + run_with_assert(user1_config, f"CREATE TABLE {TABLE_NAME} (a Uint64, b Uint64, PRIMARY KEY (a));") |
| 171 | + run_with_assert(user1_config, f"INSERT INTO `{TABLE_PATH}` (a, b) VALUES (1, 1);") |
| 172 | + |
| 173 | + # ALTER TABLE ... ADD CHANGEFEED |
| 174 | + user2_config = create_user(ydb_cluster, tenant_admin_config, "user2") |
| 175 | + create_changefeed_query = f"ALTER TABLE `{TABLE_PATH}` ADD CHANGEFEED updates WITH (FORMAT = 'JSON', MODE = 'NEW_AND_OLD_IMAGES', INITIAL_SCAN = TRUE);" |
| 176 | + _test_grants( |
| 177 | + tenant_admin_config, |
| 178 | + user2_config, |
| 179 | + 'user2', |
| 180 | + create_changefeed_query, |
| 181 | + TABLE_PATH, |
| 182 | + ALTER_TABLE_ADD_CHANGEFEED_GRANTS, |
| 183 | + "you do not have access permissions", |
| 184 | + ) |
| 185 | + |
| 186 | + # ALTER TOPIC ... ADD CONSUMER |
| 187 | + user3_config = create_user(ydb_cluster, tenant_admin_config, "user3") |
| 188 | + add_consumer_query = f"ALTER TOPIC `{TABLE_PATH}/updates` ADD CONSUMER consumer;" |
| 189 | + _test_grants( |
| 190 | + tenant_admin_config, |
| 191 | + user3_config, |
| 192 | + 'user3', |
| 193 | + add_consumer_query, |
| 194 | + TABLE_PATH, |
| 195 | + ALTER_TOPIC_ADD_CONSUMER_GRANTS, |
| 196 | + "you do not have access rights", |
| 197 | + ) |
| 198 | + |
| 199 | + # READ CHANGEFEED |
| 200 | + user4_config = create_user(ydb_cluster, tenant_admin_config, "user4") |
| 201 | + with ydb.Driver(user4_config) as driver: |
| 202 | + with driver.topic_client.reader(f"{TABLE_PATH}/updates", consumer="consumer", buffer_size_bytes=1000) as reader: |
| 203 | + message = reader.receive_message(timeout=5) |
| 204 | + assert '"newImage"' in message.data.decode("utf-8") |
| 205 | + |
| 206 | + # ALTER TABLE ... DROP CHANGEFEED |
| 207 | + drop_changefeed_query = f"ALTER TABLE `{TABLE_PATH}` DROP CHANGEFEED updates;" |
| 208 | + _test_grants( |
| 209 | + tenant_admin_config, |
| 210 | + user4_config, |
| 211 | + 'user4', |
| 212 | + drop_changefeed_query, |
| 213 | + TABLE_PATH, |
| 214 | + ALTER_TABLE_DROP_CHANGEFEED_GRANTS, |
| 215 | + "you do not have access permissions", |
| 216 | + ) |
| 217 | + |
| 218 | + ydb_cluster.remove_database(DATABASE) |
| 219 | + ydb_cluster.unregister_and_stop_slots(database_nodes) |
0 commit comments