001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.hbase.security.access; 020 021import java.io.Closeable; 022import java.io.IOException; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.concurrent.ConcurrentHashMap; 029import java.util.concurrent.atomic.AtomicLong; 030 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.hbase.AuthUtil; 033import org.apache.hadoop.hbase.Cell; 034import org.apache.hadoop.hbase.TableName; 035import org.apache.hadoop.hbase.exceptions.DeserializationException; 036import org.apache.hadoop.hbase.log.HBaseMarkers; 037import org.apache.hadoop.hbase.security.Superusers; 038import org.apache.hadoop.hbase.security.User; 039import org.apache.hadoop.hbase.util.Bytes; 040import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 041import org.apache.yetus.audience.InterfaceAudience; 042import org.apache.zookeeper.KeeperException; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 047import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap; 048 049/** 050 * Performs authorization checks for a given user's assigned permissions. 051 * <p> 052 * There're following scopes: <b>Global</b>, <b>Namespace</b>, <b>Table</b>, <b>Family</b>, 053 * <b>Qualifier</b>, <b>Cell</b>. 054 * Generally speaking, higher scopes can overrides lower scopes, 055 * except for Cell permission can be granted even a user has not permission on specified table, 056 * which means the user can get/scan only those granted cells parts. 057 * </p> 058 * e.g, if user A has global permission R(ead), he can 059 * read table T without checking table scope permission, so authorization checks alway starts from 060 * Global scope. 061 * <p> 062 * For each scope, not only user but also groups he belongs to will be checked. 063 * </p> 064 */ 065@InterfaceAudience.Private 066public final class AuthManager implements Closeable { 067 068 /** 069 * Cache of permissions, it is thread safe. 070 * @param <T> T extends Permission 071 */ 072 private static class PermissionCache<T extends Permission> { 073 private final Object mutex = new Object(); 074 private Map<String, Set<T>> cache = new HashMap<>(); 075 076 void put(String name, T perm) { 077 synchronized (mutex) { 078 Set<T> perms = cache.getOrDefault(name, new HashSet<>()); 079 perms.add(perm); 080 cache.put(name, perms); 081 } 082 } 083 084 Set<T> get(String name) { 085 synchronized (mutex) { 086 return cache.get(name); 087 } 088 } 089 090 void clear() { 091 synchronized (mutex) { 092 for (Map.Entry<String, Set<T>> entry : cache.entrySet()) { 093 entry.getValue().clear(); 094 } 095 cache.clear(); 096 } 097 } 098 } 099 PermissionCache<NamespacePermission> NS_NO_PERMISSION = new PermissionCache<>(); 100 PermissionCache<TablePermission> TBL_NO_PERMISSION = new PermissionCache<>(); 101 102 /** 103 * Cache for global permission excluding superuser and supergroup. 104 * Since every user/group can only have one global permission, no need to use PermissionCache. 105 */ 106 private Map<String, GlobalPermission> globalCache = new ConcurrentHashMap<>(); 107 /** Cache for namespace permission. */ 108 private ConcurrentHashMap<String, PermissionCache<NamespacePermission>> namespaceCache = 109 new ConcurrentHashMap<>(); 110 /** Cache for table permission. */ 111 private ConcurrentHashMap<TableName, PermissionCache<TablePermission>> tableCache = 112 new ConcurrentHashMap<>(); 113 114 private static final Logger LOG = LoggerFactory.getLogger(AuthManager.class); 115 116 private Configuration conf; 117 private ZKPermissionWatcher zkperms; 118 private final AtomicLong mtime = new AtomicLong(0L); 119 120 private AuthManager(ZKWatcher watcher, Configuration conf) 121 throws IOException { 122 this.conf = conf; 123 124 this.zkperms = new ZKPermissionWatcher(watcher, this, conf); 125 try { 126 this.zkperms.start(); 127 } catch (KeeperException ke) { 128 LOG.error("ZooKeeper initialization failed", ke); 129 } 130 } 131 132 @Override 133 public void close() { 134 this.zkperms.close(); 135 } 136 137 public ZKPermissionWatcher getZKPermissionWatcher() { 138 return this.zkperms; 139 } 140 141 /** 142 * Update acl info for table. 143 * @param table name of table 144 * @param data updated acl data 145 * @throws IOException exception when deserialize data 146 */ 147 public void refreshTableCacheFromWritable(TableName table, byte[] data) throws IOException { 148 if (data != null && data.length > 0) { 149 try { 150 ListMultimap<String, Permission> perms = 151 AccessControlLists.readPermissions(data, conf); 152 if (perms != null) { 153 if (Bytes.equals(table.getName(), AccessControlLists.ACL_GLOBAL_NAME)) { 154 updateGlobalCache(perms); 155 } else { 156 updateTableCache(table, perms); 157 } 158 } 159 } catch (DeserializationException e) { 160 throw new IOException(e); 161 } 162 } else { 163 LOG.info("Skipping permission cache refresh because writable data is empty"); 164 } 165 } 166 167 /** 168 * Update acl info for namespace. 169 * @param namespace namespace 170 * @param data updated acl data 171 * @throws IOException exception when deserialize data 172 */ 173 public void refreshNamespaceCacheFromWritable(String namespace, byte[] data) throws IOException { 174 if (data != null && data.length > 0) { 175 try { 176 ListMultimap<String, Permission> perms = 177 AccessControlLists.readPermissions(data, conf); 178 if (perms != null) { 179 updateNamespaceCache(namespace, perms); 180 } 181 } catch (DeserializationException e) { 182 throw new IOException(e); 183 } 184 } else { 185 LOG.debug("Skipping permission cache refresh because writable data is empty"); 186 } 187 } 188 189 /** 190 * Updates the internal global permissions cache. 191 * @param globalPerms new global permissions 192 */ 193 private void updateGlobalCache(ListMultimap<String, Permission> globalPerms) { 194 globalCache.clear(); 195 for (String name : globalPerms.keySet()) { 196 for (Permission permission : globalPerms.get(name)) { 197 // Before 2.2, the global permission which storage in zk is not right. It was saved as a 198 // table permission. So here need to handle this for compatibility. See HBASE-22503. 199 if (permission instanceof TablePermission) { 200 globalCache.put(name, new GlobalPermission(permission.getActions())); 201 } else { 202 globalCache.put(name, (GlobalPermission) permission); 203 } 204 } 205 } 206 mtime.incrementAndGet(); 207 } 208 209 /** 210 * Updates the internal table permissions cache for specified table. 211 * @param table updated table name 212 * @param tablePerms new table permissions 213 */ 214 private void updateTableCache(TableName table, ListMultimap<String, Permission> tablePerms) { 215 PermissionCache<TablePermission> cacheToUpdate = 216 tableCache.getOrDefault(table, new PermissionCache<>()); 217 clearCache(cacheToUpdate); 218 updateCache(tablePerms, cacheToUpdate); 219 tableCache.put(table, cacheToUpdate); 220 mtime.incrementAndGet(); 221 } 222 223 /** 224 * Updates the internal namespace permissions cache for specified namespace. 225 * @param namespace updated namespace 226 * @param nsPerms new namespace permissions 227 */ 228 private void updateNamespaceCache(String namespace, 229 ListMultimap<String, Permission> nsPerms) { 230 PermissionCache<NamespacePermission> cacheToUpdate = 231 namespaceCache.getOrDefault(namespace, new PermissionCache<>()); 232 clearCache(cacheToUpdate); 233 updateCache(nsPerms, cacheToUpdate); 234 namespaceCache.put(namespace, cacheToUpdate); 235 mtime.incrementAndGet(); 236 } 237 238 private void clearCache(PermissionCache cacheToUpdate) { 239 cacheToUpdate.clear(); 240 } 241 242 @SuppressWarnings("unchecked") 243 private void updateCache(ListMultimap<String, ? extends Permission> newPermissions, 244 PermissionCache cacheToUpdate) { 245 for (String name : newPermissions.keySet()) { 246 for (Permission permission : newPermissions.get(name)) { 247 cacheToUpdate.put(name, permission); 248 } 249 } 250 } 251 252 /** 253 * Check if user has given action privilige in global scope. 254 * @param user user name 255 * @param action one of action in [Read, Write, Create, Exec, Admin] 256 * @return true if user has, false otherwise 257 */ 258 public boolean authorizeUserGlobal(User user, Permission.Action action) { 259 if (user == null) { 260 return false; 261 } 262 if (Superusers.isSuperUser(user)) { 263 return true; 264 } 265 if (authorizeGlobal(globalCache.get(user.getShortName()), action)) { 266 return true; 267 } 268 for (String group : user.getGroupNames()) { 269 if (authorizeGlobal(globalCache.get(AuthUtil.toGroupEntry(group)), action)) { 270 return true; 271 } 272 } 273 return false; 274 } 275 276 private boolean authorizeGlobal(GlobalPermission permissions, Permission.Action action) { 277 return permissions != null && permissions.implies(action); 278 } 279 280 /** 281 * Check if user has given action privilige in namespace scope. 282 * @param user user name 283 * @param namespace namespace 284 * @param action one of action in [Read, Write, Create, Exec, Admin] 285 * @return true if user has, false otherwise 286 */ 287 public boolean authorizeUserNamespace(User user, String namespace, Permission.Action action) { 288 if (user == null) { 289 return false; 290 } 291 if (authorizeUserGlobal(user, action)) { 292 return true; 293 } 294 PermissionCache<NamespacePermission> nsPermissions = namespaceCache.getOrDefault(namespace, 295 NS_NO_PERMISSION); 296 if (authorizeNamespace(nsPermissions.get(user.getShortName()), namespace, action)) { 297 return true; 298 } 299 for (String group : user.getGroupNames()) { 300 if (authorizeNamespace(nsPermissions.get(AuthUtil.toGroupEntry(group)), namespace, action)) { 301 return true; 302 } 303 } 304 return false; 305 } 306 307 private boolean authorizeNamespace(Set<NamespacePermission> permissions, 308 String namespace, Permission.Action action) { 309 if (permissions == null) { 310 return false; 311 } 312 for (NamespacePermission permission : permissions) { 313 if (permission.implies(namespace, action)) { 314 return true; 315 } 316 } 317 return false; 318 } 319 320 /** 321 * Checks if the user has access to the full table or at least a family/qualifier 322 * for the specified action. 323 * @param user user name 324 * @param table table name 325 * @param action action in one of [Read, Write, Create, Exec, Admin] 326 * @return true if the user has access to the table, false otherwise 327 */ 328 public boolean accessUserTable(User user, TableName table, Permission.Action action) { 329 if (user == null) { 330 return false; 331 } 332 if (table == null) { 333 table = AccessControlLists.ACL_TABLE_NAME; 334 } 335 if (authorizeUserNamespace(user, table.getNamespaceAsString(), action)) { 336 return true; 337 } 338 PermissionCache<TablePermission> tblPermissions = tableCache.getOrDefault(table, 339 TBL_NO_PERMISSION); 340 if (hasAccessTable(tblPermissions.get(user.getShortName()), action)) { 341 return true; 342 } 343 for (String group : user.getGroupNames()) { 344 if (hasAccessTable(tblPermissions.get(AuthUtil.toGroupEntry(group)), action)) { 345 return true; 346 } 347 } 348 return false; 349 } 350 351 private boolean hasAccessTable(Set<TablePermission> permissions, Permission.Action action) { 352 if (permissions == null) { 353 return false; 354 } 355 for (TablePermission permission : permissions) { 356 if (permission.implies(action)) { 357 return true; 358 } 359 } 360 return false; 361 } 362 363 /** 364 * Check if user has given action privilige in table scope. 365 * @param user user name 366 * @param table table name 367 * @param action one of action in [Read, Write, Create, Exec, Admin] 368 * @return true if user has, false otherwise 369 */ 370 public boolean authorizeUserTable(User user, TableName table, Permission.Action action) { 371 return authorizeUserTable(user, table, null, null, action); 372 } 373 374 /** 375 * Check if user has given action privilige in table:family scope. 376 * @param user user name 377 * @param table table name 378 * @param family family name 379 * @param action one of action in [Read, Write, Create, Exec, Admin] 380 * @return true if user has, false otherwise 381 */ 382 public boolean authorizeUserTable(User user, TableName table, byte[] family, 383 Permission.Action action) { 384 return authorizeUserTable(user, table, family, null, action); 385 } 386 387 /** 388 * Check if user has given action privilige in table:family:qualifier scope. 389 * @param user user name 390 * @param table table name 391 * @param family family name 392 * @param qualifier qualifier name 393 * @param action one of action in [Read, Write, Create, Exec, Admin] 394 * @return true if user has, false otherwise 395 */ 396 public boolean authorizeUserTable(User user, TableName table, byte[] family, 397 byte[] qualifier, Permission.Action action) { 398 if (user == null) { 399 return false; 400 } 401 if (table == null) { 402 table = AccessControlLists.ACL_TABLE_NAME; 403 } 404 if (authorizeUserNamespace(user, table.getNamespaceAsString(), action)) { 405 return true; 406 } 407 PermissionCache<TablePermission> tblPermissions = tableCache.getOrDefault(table, 408 TBL_NO_PERMISSION); 409 if (authorizeTable(tblPermissions.get(user.getShortName()), table, family, qualifier, action)) { 410 return true; 411 } 412 for (String group : user.getGroupNames()) { 413 if (authorizeTable(tblPermissions.get(AuthUtil.toGroupEntry(group)), 414 table, family, qualifier, action)) { 415 return true; 416 } 417 } 418 return false; 419 } 420 421 private boolean authorizeTable(Set<TablePermission> permissions, 422 TableName table, byte[] family, byte[] qualifier, Permission.Action action) { 423 if (permissions == null) { 424 return false; 425 } 426 for (TablePermission permission : permissions) { 427 if (permission.implies(table, family, qualifier, action)) { 428 return true; 429 } 430 } 431 return false; 432 } 433 434 /** 435 * Check if user has given action privilige in table:family scope. 436 * This method is for backward compatibility. 437 * @param user user name 438 * @param table table name 439 * @param family family names 440 * @param action one of action in [Read, Write, Create, Exec, Admin] 441 * @return true if user has, false otherwise 442 */ 443 public boolean authorizeUserFamily(User user, TableName table, 444 byte[] family, Permission.Action action) { 445 PermissionCache<TablePermission> tblPermissions = tableCache.getOrDefault(table, 446 TBL_NO_PERMISSION); 447 if (authorizeFamily(tblPermissions.get(user.getShortName()), table, family, action)) { 448 return true; 449 } 450 for (String group : user.getGroupNames()) { 451 if (authorizeFamily(tblPermissions.get(AuthUtil.toGroupEntry(group)), 452 table, family, action)) { 453 return true; 454 } 455 } 456 return false; 457 } 458 459 private boolean authorizeFamily(Set<TablePermission> permissions, 460 TableName table, byte[] family, Permission.Action action) { 461 if (permissions == null) { 462 return false; 463 } 464 for (TablePermission permission : permissions) { 465 if (permission.implies(table, family, action)) { 466 return true; 467 } 468 } 469 return false; 470 } 471 472 /** 473 * Check if user has given action privilige in cell scope. 474 * @param user user name 475 * @param table table name 476 * @param cell cell to be checked 477 * @param action one of action in [Read, Write, Create, Exec, Admin] 478 * @return true if user has, false otherwise 479 */ 480 public boolean authorizeCell(User user, TableName table, Cell cell, Permission.Action action) { 481 try { 482 List<Permission> perms = AccessControlLists.getCellPermissionsForUser(user, cell); 483 if (LOG.isTraceEnabled()) { 484 LOG.trace("Perms for user {} in table {} in cell {}: {}", 485 user.getShortName(), table, cell, (perms != null ? perms : "")); 486 } 487 if (perms != null) { 488 for (Permission p: perms) { 489 if (p.implies(action)) { 490 return true; 491 } 492 } 493 } 494 } catch (IOException e) { 495 // We failed to parse the KV tag 496 LOG.error("Failed parse of ACL tag in cell " + cell); 497 // Fall through to check with the table and CF perms we were able 498 // to collect regardless 499 } 500 return false; 501 } 502 503 /** 504 * Remove given namespace from AuthManager's namespace cache. 505 * @param ns namespace 506 */ 507 public void removeNamespace(byte[] ns) { 508 namespaceCache.remove(Bytes.toString(ns)); 509 } 510 511 /** 512 * Remove given table from AuthManager's table cache. 513 * @param table table name 514 */ 515 public void removeTable(TableName table) { 516 tableCache.remove(table); 517 } 518 519 /** 520 * Last modification logical time 521 * @return time 522 */ 523 public long getMTime() { 524 return mtime.get(); 525 } 526 527 private static Map<ZKWatcher, AuthManager> managerMap = new HashMap<>(); 528 529 private static Map<AuthManager, Integer> refCount = new HashMap<>(); 530 531 /** 532 * Returns a AuthManager from the cache. If not cached, constructs a new one. 533 * Returned instance should be released back by calling {@link #release(AuthManager)}. 534 * @param watcher zk watcher 535 * @param conf configuration 536 * @return an AuthManager 537 * @throws IOException zookeeper initialization failed 538 */ 539 public synchronized static AuthManager getOrCreate( 540 ZKWatcher watcher, Configuration conf) throws IOException { 541 AuthManager instance = managerMap.get(watcher); 542 if (instance == null) { 543 instance = new AuthManager(watcher, conf); 544 managerMap.put(watcher, instance); 545 } 546 int ref = refCount.get(instance) == null ? 0 : refCount.get(instance); 547 refCount.put(instance, ref + 1); 548 return instance; 549 } 550 551 @VisibleForTesting 552 public static int getTotalRefCount() { 553 int total = 0; 554 for (int count : refCount.values()) { 555 total += count; 556 } 557 return total; 558 } 559 560 /** 561 * Releases the resources for the given AuthManager if the reference count is down to 0. 562 * @param instance AuthManager to be released 563 */ 564 public synchronized static void release(AuthManager instance) { 565 if (refCount.get(instance) == null || refCount.get(instance) < 1) { 566 String msg = "Something wrong with the AuthManager reference counting: " + instance 567 + " whose count is " + refCount.get(instance); 568 LOG.error(HBaseMarkers.FATAL, msg); 569 instance.close(); 570 managerMap.remove(instance.getZKPermissionWatcher().getWatcher()); 571 instance.getZKPermissionWatcher().getWatcher().abort(msg, null); 572 } else { 573 int ref = refCount.get(instance); 574 --ref; 575 refCount.put(instance, ref); 576 if (ref == 0) { 577 instance.close(); 578 managerMap.remove(instance.getZKPermissionWatcher().getWatcher()); 579 refCount.remove(instance); 580 } 581 } 582 } 583}