Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

volume_permissions.py 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. """ClickZetta Volume权限管理机制
  2. 该模块提供Volume权限检查、验证和管理功能。
  3. 根据ClickZetta的权限模型,不同Volume类型有不同的权限要求。
  4. """
  5. import logging
  6. from enum import Enum
  7. from typing import Optional
  8. logger = logging.getLogger(__name__)
  9. class VolumePermission(Enum):
  10. """Volume权限类型枚举"""
  11. READ = "SELECT" # 对应ClickZetta的SELECT权限
  12. WRITE = "INSERT,UPDATE,DELETE" # 对应ClickZetta的写权限
  13. LIST = "SELECT" # 列出文件需要SELECT权限
  14. DELETE = "INSERT,UPDATE,DELETE" # 删除文件需要写权限
  15. USAGE = "USAGE" # External Volume需要的基本权限
  16. class VolumePermissionManager:
  17. """Volume权限管理器"""
  18. def __init__(self, connection_or_config, volume_type: str | None = None, volume_name: Optional[str] = None):
  19. """初始化权限管理器
  20. Args:
  21. connection_or_config: ClickZetta连接对象或配置字典
  22. volume_type: Volume类型 (user|table|external)
  23. volume_name: Volume名称 (用于external volume)
  24. """
  25. # 支持两种初始化方式:连接对象或配置字典
  26. if isinstance(connection_or_config, dict):
  27. # 从配置字典创建连接
  28. import clickzetta # type: ignore[import-untyped]
  29. config = connection_or_config
  30. self._connection = clickzetta.connect(
  31. username=config.get("username"),
  32. password=config.get("password"),
  33. instance=config.get("instance"),
  34. service=config.get("service"),
  35. workspace=config.get("workspace"),
  36. vcluster=config.get("vcluster"),
  37. schema=config.get("schema") or config.get("database"),
  38. )
  39. self._volume_type = config.get("volume_type", volume_type)
  40. self._volume_name = config.get("volume_name", volume_name)
  41. else:
  42. # 直接使用连接对象
  43. self._connection = connection_or_config
  44. self._volume_type = volume_type
  45. self._volume_name = volume_name
  46. if not self._connection:
  47. raise ValueError("Valid connection or config is required")
  48. if not self._volume_type:
  49. raise ValueError("volume_type is required")
  50. self._permission_cache: dict[str, set[str]] = {}
  51. self._current_username = None # 将从连接中获取当前用户名
  52. def check_permission(self, operation: VolumePermission, dataset_id: Optional[str] = None) -> bool:
  53. """检查用户是否有执行特定操作的权限
  54. Args:
  55. operation: 要执行的操作类型
  56. dataset_id: 数据集ID (用于table volume)
  57. Returns:
  58. True if user has permission, False otherwise
  59. """
  60. try:
  61. if self._volume_type == "user":
  62. return self._check_user_volume_permission(operation)
  63. elif self._volume_type == "table":
  64. return self._check_table_volume_permission(operation, dataset_id)
  65. elif self._volume_type == "external":
  66. return self._check_external_volume_permission(operation)
  67. else:
  68. logger.warning("Unknown volume type: %s", self._volume_type)
  69. return False
  70. except Exception as e:
  71. logger.exception("Permission check failed")
  72. return False
  73. def _check_user_volume_permission(self, operation: VolumePermission) -> bool:
  74. """检查User Volume权限
  75. User Volume权限规则:
  76. - 用户对自己的User Volume有全部权限
  77. - 只要用户能够连接到ClickZetta,就默认具有User Volume的基本权限
  78. - 更注重连接身份验证,而不是复杂的权限检查
  79. """
  80. try:
  81. # 获取当前用户名
  82. current_user = self._get_current_username()
  83. # 检查基本连接状态
  84. with self._connection.cursor() as cursor:
  85. # 简单的连接测试,如果能执行查询说明用户有基本权限
  86. cursor.execute("SELECT 1")
  87. result = cursor.fetchone()
  88. if result:
  89. logger.debug(
  90. "User Volume permission check for %s, operation %s: granted (basic connection verified)",
  91. current_user,
  92. operation.name,
  93. )
  94. return True
  95. else:
  96. logger.warning(
  97. "User Volume permission check failed: cannot verify basic connection for %s", current_user
  98. )
  99. return False
  100. except Exception as e:
  101. logger.exception("User Volume permission check failed")
  102. # 对于User Volume,如果权限检查失败,可能是配置问题,给出更友好的错误提示
  103. logger.info("User Volume permission check failed, but permission checking is disabled in this version")
  104. return False
  105. def _check_table_volume_permission(self, operation: VolumePermission, dataset_id: Optional[str]) -> bool:
  106. """检查Table Volume权限
  107. Table Volume权限规则:
  108. - Table Volume权限继承对应表的权限
  109. - SELECT权限 -> 可以READ/LIST文件
  110. - INSERT,UPDATE,DELETE权限 -> 可以WRITE/DELETE文件
  111. """
  112. if not dataset_id:
  113. logger.warning("dataset_id is required for table volume permission check")
  114. return False
  115. table_name = f"dataset_{dataset_id}" if not dataset_id.startswith("dataset_") else dataset_id
  116. try:
  117. # 检查表权限
  118. permissions = self._get_table_permissions(table_name)
  119. required_permissions = set(operation.value.split(","))
  120. # 检查是否有所需的所有权限
  121. has_permission = required_permissions.issubset(permissions)
  122. logger.debug(
  123. "Table Volume permission check for %s, operation %s: required=%s, has=%s, granted=%s",
  124. table_name,
  125. operation.name,
  126. required_permissions,
  127. permissions,
  128. has_permission,
  129. )
  130. return has_permission
  131. except Exception as e:
  132. logger.exception("Table volume permission check failed for %s", table_name)
  133. return False
  134. def _check_external_volume_permission(self, operation: VolumePermission) -> bool:
  135. """检查External Volume权限
  136. External Volume权限规则:
  137. - 尝试获取对External Volume的权限
  138. - 如果权限检查失败,进行备选验证
  139. - 对于开发环境,提供更宽松的权限检查
  140. """
  141. if not self._volume_name:
  142. logger.warning("volume_name is required for external volume permission check")
  143. return False
  144. try:
  145. # 检查External Volume权限
  146. permissions = self._get_external_volume_permissions(self._volume_name)
  147. # External Volume权限映射:根据操作类型确定所需权限
  148. required_permissions = set()
  149. if operation in [VolumePermission.READ, VolumePermission.LIST]:
  150. required_permissions.add("read")
  151. elif operation in [VolumePermission.WRITE, VolumePermission.DELETE]:
  152. required_permissions.add("write")
  153. # 检查是否有所需的所有权限
  154. has_permission = required_permissions.issubset(permissions)
  155. logger.debug(
  156. "External Volume permission check for %s, operation %s: required=%s, has=%s, granted=%s",
  157. self._volume_name,
  158. operation.name,
  159. required_permissions,
  160. permissions,
  161. has_permission,
  162. )
  163. # 如果权限检查失败,尝试备选验证
  164. if not has_permission:
  165. logger.info("Direct permission check failed for %s, trying fallback verification", self._volume_name)
  166. # 备选验证:尝试列出Volume来验证基本访问权限
  167. try:
  168. with self._connection.cursor() as cursor:
  169. cursor.execute("SHOW VOLUMES")
  170. volumes = cursor.fetchall()
  171. for volume in volumes:
  172. if len(volume) > 0 and volume[0] == self._volume_name:
  173. logger.info("Fallback verification successful for %s", self._volume_name)
  174. return True
  175. except Exception as fallback_e:
  176. logger.warning("Fallback verification failed for %s: %s", self._volume_name, fallback_e)
  177. return has_permission
  178. except Exception as e:
  179. logger.exception("External volume permission check failed for %s", self._volume_name)
  180. logger.info("External Volume permission check failed, but permission checking is disabled in this version")
  181. return False
  182. def _get_table_permissions(self, table_name: str) -> set[str]:
  183. """获取用户对指定表的权限
  184. Args:
  185. table_name: 表名
  186. Returns:
  187. 用户对该表的权限集合
  188. """
  189. cache_key = f"table:{table_name}"
  190. if cache_key in self._permission_cache:
  191. return self._permission_cache[cache_key]
  192. permissions = set()
  193. try:
  194. with self._connection.cursor() as cursor:
  195. # 使用正确的ClickZetta语法检查当前用户权限
  196. cursor.execute("SHOW GRANTS")
  197. grants = cursor.fetchall()
  198. # 解析权限结果,查找对该表的权限
  199. for grant in grants:
  200. if len(grant) >= 3: # 典型格式: (privilege, object_type, object_name, ...)
  201. privilege = grant[0].upper()
  202. object_type = grant[1].upper() if len(grant) > 1 else ""
  203. object_name = grant[2] if len(grant) > 2 else ""
  204. # 检查是否是对该表的权限
  205. if (
  206. object_type == "TABLE"
  207. and object_name == table_name
  208. or object_type == "SCHEMA"
  209. and object_name in table_name
  210. ):
  211. if privilege in ["SELECT", "INSERT", "UPDATE", "DELETE", "ALL"]:
  212. if privilege == "ALL":
  213. permissions.update(["SELECT", "INSERT", "UPDATE", "DELETE"])
  214. else:
  215. permissions.add(privilege)
  216. # 如果没有找到明确的权限,尝试执行一个简单的查询来验证权限
  217. if not permissions:
  218. try:
  219. cursor.execute(f"SELECT COUNT(*) FROM {table_name} LIMIT 1")
  220. permissions.add("SELECT")
  221. except Exception:
  222. logger.debug("Cannot query table %s, no SELECT permission", table_name)
  223. except Exception as e:
  224. logger.warning("Could not check table permissions for %s: %s", table_name, e)
  225. # 安全默认:权限检查失败时拒绝访问
  226. pass
  227. # 缓存权限信息
  228. self._permission_cache[cache_key] = permissions
  229. return permissions
  230. def _get_current_username(self) -> str:
  231. """获取当前用户名"""
  232. if self._current_username:
  233. return self._current_username
  234. try:
  235. with self._connection.cursor() as cursor:
  236. cursor.execute("SELECT CURRENT_USER()")
  237. result = cursor.fetchone()
  238. if result:
  239. self._current_username = result[0]
  240. return str(self._current_username)
  241. except Exception as e:
  242. logger.exception("Failed to get current username")
  243. return "unknown"
  244. def _get_user_permissions(self, username: str) -> set[str]:
  245. """获取用户的基本权限集合"""
  246. cache_key = f"user_permissions:{username}"
  247. if cache_key in self._permission_cache:
  248. return self._permission_cache[cache_key]
  249. permissions = set()
  250. try:
  251. with self._connection.cursor() as cursor:
  252. # 使用正确的ClickZetta语法检查当前用户权限
  253. cursor.execute("SHOW GRANTS")
  254. grants = cursor.fetchall()
  255. # 解析权限结果,查找用户的基本权限
  256. for grant in grants:
  257. if len(grant) >= 3: # 典型格式: (privilege, object_type, object_name, ...)
  258. privilege = grant[0].upper()
  259. object_type = grant[1].upper() if len(grant) > 1 else ""
  260. # 收集所有相关权限
  261. if privilege in ["SELECT", "INSERT", "UPDATE", "DELETE", "ALL"]:
  262. if privilege == "ALL":
  263. permissions.update(["SELECT", "INSERT", "UPDATE", "DELETE"])
  264. else:
  265. permissions.add(privilege)
  266. except Exception as e:
  267. logger.warning("Could not check user permissions for %s: %s", username, e)
  268. # 安全默认:权限检查失败时拒绝访问
  269. pass
  270. # 缓存权限信息
  271. self._permission_cache[cache_key] = permissions
  272. return permissions
  273. def _get_external_volume_permissions(self, volume_name: str) -> set[str]:
  274. """获取用户对指定External Volume的权限
  275. Args:
  276. volume_name: External Volume名称
  277. Returns:
  278. 用户对该Volume的权限集合
  279. """
  280. cache_key = f"external_volume:{volume_name}"
  281. if cache_key in self._permission_cache:
  282. return self._permission_cache[cache_key]
  283. permissions = set()
  284. try:
  285. with self._connection.cursor() as cursor:
  286. # 使用正确的ClickZetta语法检查Volume权限
  287. logger.info("Checking permissions for volume: %s", volume_name)
  288. cursor.execute(f"SHOW GRANTS ON VOLUME {volume_name}")
  289. grants = cursor.fetchall()
  290. logger.info("Raw grants result for %s: %s", volume_name, grants)
  291. # 解析权限结果
  292. # 格式: (granted_type, privilege, conditions, granted_on, object_name, granted_to,
  293. # grantee_name, grantor_name, grant_option, granted_time)
  294. for grant in grants:
  295. logger.info("Processing grant: %s", grant)
  296. if len(grant) >= 5:
  297. granted_type = grant[0]
  298. privilege = grant[1].upper()
  299. granted_on = grant[3]
  300. object_name = grant[4]
  301. logger.info(
  302. "Grant details - type: %s, privilege: %s, granted_on: %s, object_name: %s",
  303. granted_type,
  304. privilege,
  305. granted_on,
  306. object_name,
  307. )
  308. # 检查是否是对该Volume的权限或者是层级权限
  309. if (
  310. granted_type == "PRIVILEGE" and granted_on == "VOLUME" and object_name.endswith(volume_name)
  311. ) or (granted_type == "OBJECT_HIERARCHY" and granted_on == "VOLUME"):
  312. logger.info("Matching grant found for %s", volume_name)
  313. if "READ" in privilege:
  314. permissions.add("read")
  315. logger.info("Added READ permission for %s", volume_name)
  316. if "WRITE" in privilege:
  317. permissions.add("write")
  318. logger.info("Added WRITE permission for %s", volume_name)
  319. if "ALTER" in privilege:
  320. permissions.add("alter")
  321. logger.info("Added ALTER permission for %s", volume_name)
  322. if privilege == "ALL":
  323. permissions.update(["read", "write", "alter"])
  324. logger.info("Added ALL permissions for %s", volume_name)
  325. logger.info("Final permissions for %s: %s", volume_name, permissions)
  326. # 如果没有找到明确的权限,尝试查看Volume列表来验证基本权限
  327. if not permissions:
  328. try:
  329. cursor.execute("SHOW VOLUMES")
  330. volumes = cursor.fetchall()
  331. for volume in volumes:
  332. if len(volume) > 0 and volume[0] == volume_name:
  333. permissions.add("read") # 至少有读权限
  334. logger.debug("Volume %s found in SHOW VOLUMES, assuming read permission", volume_name)
  335. break
  336. except Exception:
  337. logger.debug("Cannot access volume %s, no basic permission", volume_name)
  338. except Exception as e:
  339. logger.warning("Could not check external volume permissions for %s: %s", volume_name, e)
  340. # 在权限检查失败时,尝试基本的Volume访问验证
  341. try:
  342. with self._connection.cursor() as cursor:
  343. cursor.execute("SHOW VOLUMES")
  344. volumes = cursor.fetchall()
  345. for volume in volumes:
  346. if len(volume) > 0 and volume[0] == volume_name:
  347. logger.info("Basic volume access verified for %s", volume_name)
  348. permissions.add("read")
  349. permissions.add("write") # 假设有写权限
  350. break
  351. except Exception as basic_e:
  352. logger.warning("Basic volume access check failed for %s: %s", volume_name, basic_e)
  353. # 最后的备选方案:假设有基本权限
  354. permissions.add("read")
  355. # 缓存权限信息
  356. self._permission_cache[cache_key] = permissions
  357. return permissions
  358. def clear_permission_cache(self):
  359. """清空权限缓存"""
  360. self._permission_cache.clear()
  361. logger.debug("Permission cache cleared")
  362. def get_permission_summary(self, dataset_id: Optional[str] = None) -> dict[str, bool]:
  363. """获取权限摘要
  364. Args:
  365. dataset_id: 数据集ID (用于table volume)
  366. Returns:
  367. 权限摘要字典
  368. """
  369. summary = {}
  370. for operation in VolumePermission:
  371. summary[operation.name.lower()] = self.check_permission(operation, dataset_id)
  372. return summary
  373. def check_inherited_permission(self, file_path: str, operation: VolumePermission) -> bool:
  374. """检查文件路径的权限继承
  375. Args:
  376. file_path: 文件路径
  377. operation: 要执行的操作
  378. Returns:
  379. True if user has permission, False otherwise
  380. """
  381. try:
  382. # 解析文件路径
  383. path_parts = file_path.strip("/").split("/")
  384. if not path_parts:
  385. logger.warning("Invalid file path for permission inheritance check")
  386. return False
  387. # 对于Table Volume,第一层是dataset_id
  388. if self._volume_type == "table":
  389. if len(path_parts) < 1:
  390. return False
  391. dataset_id = path_parts[0]
  392. # 检查对dataset的权限
  393. has_dataset_permission = self.check_permission(operation, dataset_id)
  394. if not has_dataset_permission:
  395. logger.debug("Permission denied for dataset %s", dataset_id)
  396. return False
  397. # 检查路径遍历攻击
  398. if self._contains_path_traversal(file_path):
  399. logger.warning("Path traversal attack detected: %s", file_path)
  400. return False
  401. # 检查是否访问敏感目录
  402. if self._is_sensitive_path(file_path):
  403. logger.warning("Access to sensitive path denied: %s", file_path)
  404. return False
  405. logger.debug("Permission inherited for path %s", file_path)
  406. return True
  407. elif self._volume_type == "user":
  408. # User Volume的权限继承
  409. current_user = self._get_current_username()
  410. # 检查是否试图访问其他用户的目录
  411. if len(path_parts) > 1 and path_parts[0] != current_user:
  412. logger.warning("User %s attempted to access %s's directory", current_user, path_parts[0])
  413. return False
  414. # 检查基本权限
  415. return self.check_permission(operation)
  416. elif self._volume_type == "external":
  417. # External Volume的权限继承
  418. # 检查对External Volume的权限
  419. return self.check_permission(operation)
  420. else:
  421. logger.warning("Unknown volume type for permission inheritance: %s", self._volume_type)
  422. return False
  423. except Exception as e:
  424. logger.exception("Permission inheritance check failed")
  425. return False
  426. def _contains_path_traversal(self, file_path: str) -> bool:
  427. """检查路径是否包含路径遍历攻击"""
  428. # 检查常见的路径遍历模式
  429. traversal_patterns = [
  430. "../",
  431. "..\\",
  432. "..%2f",
  433. "..%2F",
  434. "..%5c",
  435. "..%5C",
  436. "%2e%2e%2f",
  437. "%2e%2e%5c",
  438. "....//",
  439. "....\\\\",
  440. ]
  441. file_path_lower = file_path.lower()
  442. for pattern in traversal_patterns:
  443. if pattern in file_path_lower:
  444. return True
  445. # 检查绝对路径
  446. if file_path.startswith("/") or file_path.startswith("\\"):
  447. return True
  448. # 检查Windows驱动器路径
  449. if len(file_path) >= 2 and file_path[1] == ":":
  450. return True
  451. return False
  452. def _is_sensitive_path(self, file_path: str) -> bool:
  453. """检查路径是否为敏感路径"""
  454. sensitive_patterns = [
  455. "passwd",
  456. "shadow",
  457. "hosts",
  458. "config",
  459. "secrets",
  460. "private",
  461. "key",
  462. "certificate",
  463. "cert",
  464. "ssl",
  465. "database",
  466. "backup",
  467. "dump",
  468. "log",
  469. "tmp",
  470. ]
  471. file_path_lower = file_path.lower()
  472. return any(pattern in file_path_lower for pattern in sensitive_patterns)
  473. def validate_operation(self, operation: str, dataset_id: Optional[str] = None) -> bool:
  474. """验证操作权限
  475. Args:
  476. operation: 操作名称 (save|load|exists|delete|scan)
  477. dataset_id: 数据集ID
  478. Returns:
  479. True if operation is allowed, False otherwise
  480. """
  481. operation_mapping = {
  482. "save": VolumePermission.WRITE,
  483. "load": VolumePermission.READ,
  484. "load_once": VolumePermission.READ,
  485. "load_stream": VolumePermission.READ,
  486. "download": VolumePermission.READ,
  487. "exists": VolumePermission.READ,
  488. "delete": VolumePermission.DELETE,
  489. "scan": VolumePermission.LIST,
  490. }
  491. if operation not in operation_mapping:
  492. logger.warning("Unknown operation: %s", operation)
  493. return False
  494. volume_permission = operation_mapping[operation]
  495. return self.check_permission(volume_permission, dataset_id)
  496. class VolumePermissionError(Exception):
  497. """Volume权限错误异常"""
  498. def __init__(self, message: str, operation: str, volume_type: str, dataset_id: Optional[str] = None):
  499. self.operation = operation
  500. self.volume_type = volume_type
  501. self.dataset_id = dataset_id
  502. super().__init__(message)
  503. def check_volume_permission(
  504. permission_manager: VolumePermissionManager, operation: str, dataset_id: Optional[str] = None
  505. ) -> None:
  506. """权限检查装饰器函数
  507. Args:
  508. permission_manager: 权限管理器
  509. operation: 操作名称
  510. dataset_id: 数据集ID
  511. Raises:
  512. VolumePermissionError: 如果没有权限
  513. """
  514. if not permission_manager.validate_operation(operation, dataset_id):
  515. error_message = f"Permission denied for operation '{operation}' on {permission_manager._volume_type} volume"
  516. if dataset_id:
  517. error_message += f" (dataset: {dataset_id})"
  518. raise VolumePermissionError(
  519. error_message,
  520. operation=operation,
  521. volume_type=permission_manager._volume_type or "unknown",
  522. dataset_id=dataset_id,
  523. )