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 */ 018package org.apache.hadoop.hbase.security.access; 019 020import java.io.IOException; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.concurrent.ConcurrentHashMap; 026import java.util.concurrent.atomic.AtomicLong; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.hbase.AuthUtil; 029import org.apache.hadoop.hbase.Cell; 030import org.apache.hadoop.hbase.TableName; 031import org.apache.hadoop.hbase.exceptions.DeserializationException; 032import org.apache.hadoop.hbase.security.Superusers; 033import org.apache.hadoop.hbase.security.User; 034import org.apache.hadoop.hbase.util.Bytes; 035import org.apache.yetus.audience.InterfaceAudience; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap; 040 041/** 042 * Performs authorization checks for a given user's assigned permissions. 043 * <p> 044 * There're following scopes: <b>Global</b>, <b>Namespace</b>, <b>Table</b>, <b>Family</b>, 045 * <b>Qualifier</b>, <b>Cell</b>. Generally speaking, higher scopes can overrides lower scopes, 046 * except for Cell permission can be granted even a user has not permission on specified table, 047 * which means the user can get/scan only those granted cells parts. 048 * </p> 049 * e.g, if user A has global permission R(ead), he can read table T without checking table scope 050 * permission, so authorization checks alway starts from Global scope. 051 * <p> 052 * For each scope, not only user but also groups he belongs to will be checked. 053 * </p> 054 */ 055@InterfaceAudience.Private 056public final class AuthManager { 057 058 /** 059 * Cache of permissions, it is thread safe. 060 * @param <T> T extends Permission 061 */ 062 private static class PermissionCache<T extends Permission> { 063 private final Object mutex = new Object(); 064 private Map<String, Set<T>> cache = new HashMap<>(); 065 066 void put(String name, T perm) { 067 synchronized (mutex) { 068 Set<T> perms = cache.getOrDefault(name, ConcurrentHashMap.newKeySet()); 069 perms.add(perm); 070 cache.put(name, perms); 071 } 072 } 073 074 Set<T> get(String name) { 075 synchronized (mutex) { 076 return cache.get(name); 077 } 078 } 079 080 void clear() { 081 synchronized (mutex) { 082 for (Map.Entry<String, Set<T>> entry : cache.entrySet()) { 083 entry.getValue().clear(); 084 } 085 cache.clear(); 086 } 087 } 088 } 089 090 PermissionCache<NamespacePermission> NS_NO_PERMISSION = new PermissionCache<>(); 091 PermissionCache<TablePermission> TBL_NO_PERMISSION = new PermissionCache<>(); 092 093 /** 094 * Cache for global permission excluding superuser and supergroup. Since every user/group can only 095 * have one global permission, no need to use PermissionCache. 096 */ 097 private Map<String, GlobalPermission> globalCache = new ConcurrentHashMap<>(); 098 /** Cache for namespace permission. */ 099 private ConcurrentHashMap<String, PermissionCache<NamespacePermission>> namespaceCache = 100 new ConcurrentHashMap<>(); 101 /** Cache for table permission. */ 102 private ConcurrentHashMap<TableName, PermissionCache<TablePermission>> tableCache = 103 new ConcurrentHashMap<>(); 104 105 private static final Logger LOG = LoggerFactory.getLogger(AuthManager.class); 106 107 private Configuration conf; 108 private final AtomicLong mtime = new AtomicLong(0L); 109 110 AuthManager(Configuration conf) { 111 this.conf = conf; 112 } 113 114 /** 115 * Update acl info for table. 116 * @param table name of table 117 * @param data updated acl data 118 * @throws IOException exception when deserialize data 119 */ 120 public void refreshTableCacheFromWritable(TableName table, byte[] data) throws IOException { 121 if (data != null && data.length > 0) { 122 try { 123 ListMultimap<String, Permission> perms = PermissionStorage.readPermissions(data, conf); 124 if (perms != null) { 125 if (Bytes.equals(table.getName(), PermissionStorage.ACL_GLOBAL_NAME)) { 126 updateGlobalCache(perms); 127 } else { 128 updateTableCache(table, perms); 129 } 130 } 131 } catch (DeserializationException e) { 132 throw new IOException(e); 133 } 134 } else { 135 LOG.info("Skipping permission cache refresh because writable data is empty"); 136 } 137 } 138 139 /** 140 * Update acl info for namespace. 141 * @param namespace namespace 142 * @param data updated acl data 143 * @throws IOException exception when deserialize data 144 */ 145 public void refreshNamespaceCacheFromWritable(String namespace, byte[] data) throws IOException { 146 if (data != null && data.length > 0) { 147 try { 148 ListMultimap<String, Permission> perms = PermissionStorage.readPermissions(data, conf); 149 if (perms != null) { 150 updateNamespaceCache(namespace, perms); 151 } 152 } catch (DeserializationException e) { 153 throw new IOException(e); 154 } 155 } else { 156 LOG.debug("Skipping permission cache refresh because writable data is empty"); 157 } 158 } 159 160 /** 161 * Updates the internal global permissions cache. 162 * @param globalPerms new global permissions 163 */ 164 private void updateGlobalCache(ListMultimap<String, Permission> globalPerms) { 165 globalCache.clear(); 166 for (String name : globalPerms.keySet()) { 167 for (Permission permission : globalPerms.get(name)) { 168 // Before 2.2, the global permission which storage in zk is not right. It was saved as a 169 // table permission. So here need to handle this for compatibility. See HBASE-22503. 170 if (permission instanceof TablePermission) { 171 globalCache.put(name, new GlobalPermission(permission.getActions())); 172 } else { 173 globalCache.put(name, (GlobalPermission) permission); 174 } 175 } 176 } 177 mtime.incrementAndGet(); 178 } 179 180 /** 181 * Updates the internal table permissions cache for specified table. 182 * @param table updated table name 183 * @param tablePerms new table permissions 184 */ 185 private void updateTableCache(TableName table, ListMultimap<String, Permission> tablePerms) { 186 PermissionCache<TablePermission> cacheToUpdate = 187 tableCache.getOrDefault(table, new PermissionCache<>()); 188 clearCache(cacheToUpdate); 189 updateCache(tablePerms, cacheToUpdate); 190 tableCache.put(table, cacheToUpdate); 191 mtime.incrementAndGet(); 192 } 193 194 /** 195 * Updates the internal namespace permissions cache for specified namespace. 196 * @param namespace updated namespace 197 * @param nsPerms new namespace permissions 198 */ 199 private void updateNamespaceCache(String namespace, ListMultimap<String, Permission> nsPerms) { 200 PermissionCache<NamespacePermission> cacheToUpdate = 201 namespaceCache.getOrDefault(namespace, new PermissionCache<>()); 202 clearCache(cacheToUpdate); 203 updateCache(nsPerms, cacheToUpdate); 204 namespaceCache.put(namespace, cacheToUpdate); 205 mtime.incrementAndGet(); 206 } 207 208 private void clearCache(PermissionCache cacheToUpdate) { 209 cacheToUpdate.clear(); 210 } 211 212 @SuppressWarnings("unchecked") 213 private void updateCache(ListMultimap<String, ? extends Permission> newPermissions, 214 PermissionCache cacheToUpdate) { 215 for (String name : newPermissions.keySet()) { 216 for (Permission permission : newPermissions.get(name)) { 217 cacheToUpdate.put(name, permission); 218 } 219 } 220 } 221 222 /** 223 * Check if user has given action privilige in global scope. 224 * @param user user name 225 * @param action one of action in [Read, Write, Create, Exec, Admin] 226 * @return true if user has, false otherwise 227 */ 228 public boolean authorizeUserGlobal(User user, Permission.Action action) { 229 if (user == null) { 230 return false; 231 } 232 if (Superusers.isSuperUser(user)) { 233 return true; 234 } 235 if (authorizeGlobal(globalCache.get(user.getShortName()), action)) { 236 return true; 237 } 238 for (String group : user.getGroupNames()) { 239 if (authorizeGlobal(globalCache.get(AuthUtil.toGroupEntry(group)), action)) { 240 return true; 241 } 242 } 243 return false; 244 } 245 246 private boolean authorizeGlobal(GlobalPermission permissions, Permission.Action action) { 247 return permissions != null && permissions.implies(action); 248 } 249 250 /** 251 * Check if user has given action privilige in namespace scope. 252 * @param user user name 253 * @param namespace namespace 254 * @param action one of action in [Read, Write, Create, Exec, Admin] 255 * @return true if user has, false otherwise 256 */ 257 public boolean authorizeUserNamespace(User user, String namespace, Permission.Action action) { 258 if (user == null) { 259 return false; 260 } 261 if (authorizeUserGlobal(user, action)) { 262 return true; 263 } 264 PermissionCache<NamespacePermission> nsPermissions = 265 namespaceCache.getOrDefault(namespace, NS_NO_PERMISSION); 266 if (authorizeNamespace(nsPermissions.get(user.getShortName()), namespace, action)) { 267 return true; 268 } 269 for (String group : user.getGroupNames()) { 270 if (authorizeNamespace(nsPermissions.get(AuthUtil.toGroupEntry(group)), namespace, action)) { 271 return true; 272 } 273 } 274 return false; 275 } 276 277 private boolean authorizeNamespace(Set<NamespacePermission> permissions, String namespace, 278 Permission.Action action) { 279 if (permissions == null) { 280 return false; 281 } 282 for (NamespacePermission permission : permissions) { 283 if (permission.implies(namespace, action)) { 284 return true; 285 } 286 } 287 return false; 288 } 289 290 /** 291 * Checks if the user has access to the full table or at least a family/qualifier for the 292 * specified action. 293 * @param user user name 294 * @param table table name 295 * @param action action in one of [Read, Write, Create, Exec, Admin] 296 * @return true if the user has access to the table, false otherwise 297 */ 298 public boolean accessUserTable(User user, TableName table, Permission.Action action) { 299 if (user == null) { 300 return false; 301 } 302 if (table == null) { 303 table = PermissionStorage.ACL_TABLE_NAME; 304 } 305 if (authorizeUserNamespace(user, table.getNamespaceAsString(), action)) { 306 return true; 307 } 308 PermissionCache<TablePermission> tblPermissions = 309 tableCache.getOrDefault(table, TBL_NO_PERMISSION); 310 if (hasAccessTable(tblPermissions.get(user.getShortName()), action)) { 311 return true; 312 } 313 for (String group : user.getGroupNames()) { 314 if (hasAccessTable(tblPermissions.get(AuthUtil.toGroupEntry(group)), action)) { 315 return true; 316 } 317 } 318 return false; 319 } 320 321 private boolean hasAccessTable(Set<TablePermission> permissions, Permission.Action action) { 322 if (permissions == null) { 323 return false; 324 } 325 for (TablePermission permission : permissions) { 326 if (permission.implies(action)) { 327 return true; 328 } 329 } 330 return false; 331 } 332 333 /** 334 * Check if user has given action privilige in table scope. 335 * @param user user name 336 * @param table table name 337 * @param action one of action in [Read, Write, Create, Exec, Admin] 338 * @return true if user has, false otherwise 339 */ 340 public boolean authorizeUserTable(User user, TableName table, Permission.Action action) { 341 return authorizeUserTable(user, table, null, null, action); 342 } 343 344 /** 345 * Check if user has given action privilige in table:family scope. 346 * @param user user name 347 * @param table table name 348 * @param family family name 349 * @param action one of action in [Read, Write, Create, Exec, Admin] 350 * @return true if user has, false otherwise 351 */ 352 public boolean authorizeUserTable(User user, TableName table, byte[] family, 353 Permission.Action action) { 354 return authorizeUserTable(user, table, family, null, action); 355 } 356 357 /** 358 * Check if user has given action privilige in table:family:qualifier scope. 359 * @param user user name 360 * @param table table name 361 * @param family family name 362 * @param qualifier qualifier name 363 * @param action one of action in [Read, Write, Create, Exec, Admin] 364 * @return true if user has, false otherwise 365 */ 366 public boolean authorizeUserTable(User user, TableName table, byte[] family, byte[] qualifier, 367 Permission.Action action) { 368 if (user == null) { 369 return false; 370 } 371 if (table == null) { 372 table = PermissionStorage.ACL_TABLE_NAME; 373 } 374 if (authorizeUserNamespace(user, table.getNamespaceAsString(), action)) { 375 return true; 376 } 377 PermissionCache<TablePermission> tblPermissions = 378 tableCache.getOrDefault(table, TBL_NO_PERMISSION); 379 if (authorizeTable(tblPermissions.get(user.getShortName()), table, family, qualifier, action)) { 380 return true; 381 } 382 for (String group : user.getGroupNames()) { 383 if ( 384 authorizeTable(tblPermissions.get(AuthUtil.toGroupEntry(group)), table, family, qualifier, 385 action) 386 ) { 387 return true; 388 } 389 } 390 return false; 391 } 392 393 private boolean authorizeTable(Set<TablePermission> permissions, TableName table, byte[] family, 394 byte[] qualifier, Permission.Action action) { 395 if (permissions == null) { 396 return false; 397 } 398 for (TablePermission permission : permissions) { 399 if (permission.implies(table, family, qualifier, action)) { 400 return true; 401 } 402 } 403 return false; 404 } 405 406 /** 407 * Check if user has given action privilige in table:family scope. This method is for backward 408 * compatibility. 409 * @param user user name 410 * @param table table name 411 * @param family family names 412 * @param action one of action in [Read, Write, Create, Exec, Admin] 413 * @return true if user has, false otherwise 414 */ 415 public boolean authorizeUserFamily(User user, TableName table, byte[] family, 416 Permission.Action action) { 417 PermissionCache<TablePermission> tblPermissions = 418 tableCache.getOrDefault(table, TBL_NO_PERMISSION); 419 if (authorizeFamily(tblPermissions.get(user.getShortName()), table, family, action)) { 420 return true; 421 } 422 for (String group : user.getGroupNames()) { 423 if ( 424 authorizeFamily(tblPermissions.get(AuthUtil.toGroupEntry(group)), table, family, action) 425 ) { 426 return true; 427 } 428 } 429 return false; 430 } 431 432 private boolean authorizeFamily(Set<TablePermission> permissions, TableName table, byte[] family, 433 Permission.Action action) { 434 if (permissions == null) { 435 return false; 436 } 437 for (TablePermission permission : permissions) { 438 if (permission.implies(table, family, action)) { 439 return true; 440 } 441 } 442 return false; 443 } 444 445 /** 446 * Check if user has given action privilige in cell scope. 447 * @param user user name 448 * @param table table name 449 * @param cell cell to be checked 450 * @param action one of action in [Read, Write, Create, Exec, Admin] 451 * @return true if user has, false otherwise 452 */ 453 public boolean authorizeCell(User user, TableName table, Cell cell, Permission.Action action) { 454 try { 455 List<Permission> perms = PermissionStorage.getCellPermissionsForUser(user, cell); 456 if (LOG.isTraceEnabled()) { 457 LOG.trace("Perms for user {} in table {} in cell {}: {}", user.getShortName(), table, cell, 458 (perms != null ? perms : "")); 459 } 460 if (perms != null) { 461 for (Permission p : perms) { 462 if (p.implies(action)) { 463 return true; 464 } 465 } 466 } 467 } catch (IOException e) { 468 // We failed to parse the KV tag 469 LOG.error("Failed parse of ACL tag in cell " + cell); 470 // Fall through to check with the table and CF perms we were able 471 // to collect regardless 472 } 473 return false; 474 } 475 476 /** 477 * Remove given namespace from AuthManager's namespace cache. 478 * @param ns namespace 479 */ 480 public void removeNamespace(byte[] ns) { 481 namespaceCache.remove(Bytes.toString(ns)); 482 } 483 484 /** 485 * Remove given table from AuthManager's table cache. 486 * @param table table name 487 */ 488 public void removeTable(TableName table) { 489 tableCache.remove(table); 490 } 491 492 /** 493 * Last modification logical time 494 */ 495 public long getMTime() { 496 return mtime.get(); 497 } 498}