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.net.InetAddress; 022import java.security.PrivilegedAction; 023import java.security.PrivilegedExceptionAction; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import java.util.TreeMap; 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.hbase.AuthUtil; 032import org.apache.hadoop.hbase.Cell; 033import org.apache.hadoop.hbase.CellUtil; 034import org.apache.hadoop.hbase.DoNotRetryIOException; 035import org.apache.hadoop.hbase.HBaseInterfaceAudience; 036import org.apache.hadoop.hbase.NamespaceDescriptor; 037import org.apache.hadoop.hbase.TableName; 038import org.apache.hadoop.hbase.client.RegionInfo; 039import org.apache.hadoop.hbase.ipc.RpcServer; 040import org.apache.hadoop.hbase.security.AccessDeniedException; 041import org.apache.hadoop.hbase.security.Superusers; 042import org.apache.hadoop.hbase.security.User; 043import org.apache.hadoop.hbase.security.UserProvider; 044import org.apache.hadoop.hbase.security.access.Permission.Action; 045import org.apache.hadoop.hbase.util.Bytes; 046import org.apache.hadoop.security.Groups; 047import org.apache.hadoop.security.HadoopKerberosName; 048import org.apache.hadoop.security.UserGroupInformation; 049import org.apache.yetus.audience.InterfaceAudience; 050import org.apache.yetus.audience.InterfaceStability; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableSet; 055 056@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC) 057@InterfaceStability.Evolving 058public class AccessChecker { 059 private static final Logger LOG = LoggerFactory.getLogger(AccessChecker.class); 060 private static final Logger AUDITLOG = 061 LoggerFactory.getLogger("SecurityLogger." + AccessChecker.class.getName()); 062 private final AuthManager authManager; 063 064 /** Group service to retrieve the user group information */ 065 private static Groups groupService; 066 067 public static boolean isAuthorizationSupported(Configuration conf) { 068 return conf.getBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, false); 069 } 070 071 /** 072 * Constructor with existing configuration 073 * @param conf Existing configuration to use 074 */ 075 public AccessChecker(final Configuration conf) { 076 this.authManager = new AuthManager(conf); 077 initGroupService(conf); 078 } 079 080 public AuthManager getAuthManager() { 081 return authManager; 082 } 083 084 /** 085 * Authorizes that the current user has any of the given permissions to access the table. 086 * @param user Active user to which authorization checks should be applied 087 * @param request Request type. 088 * @param tableName Table requested 089 * @param permissions Actions being requested 090 * @throws IOException if obtaining the current user fails 091 * @throws AccessDeniedException if user has no authorization 092 */ 093 public void requireAccess(User user, String request, TableName tableName, Action... permissions) 094 throws IOException { 095 AuthResult result = null; 096 097 for (Action permission : permissions) { 098 if (authManager.accessUserTable(user, tableName, permission)) { 099 result = AuthResult.allow(request, "Table permission granted", user, permission, tableName, 100 null, null); 101 break; 102 } else { 103 // rest of the world 104 result = AuthResult.deny(request, "Insufficient permissions", user, permission, tableName, 105 null, null); 106 } 107 } 108 logResult(result); 109 if (!result.isAllowed()) { 110 throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); 111 } 112 } 113 114 /** 115 * Authorizes that the current user has global privileges for the given action. 116 * @param user Active user to which authorization checks should be applied 117 * @param request Request type 118 * @param filterUser User name to be filtered from permission as requested 119 * @param perm The action being requested 120 * @throws IOException if obtaining the current user fails 121 * @throws AccessDeniedException if authorization is denied 122 */ 123 public void requirePermission(User user, String request, String filterUser, Action perm) 124 throws IOException { 125 requireGlobalPermission(user, request, perm, null, null, filterUser); 126 } 127 128 /** 129 * Checks that the user has the given global permission. The generated audit log message will 130 * contain context information for the operation being authorized, based on the given parameters. 131 * @param user Active user to which authorization checks should be applied 132 * @param request Request type 133 * @param perm Action being requested 134 * @param tableName Affected table name. 135 * @param familyMap Affected column families. 136 * @param filterUser User name to be filtered from permission as requested 137 */ 138 public void requireGlobalPermission(User user, String request, Action perm, TableName tableName, 139 Map<byte[], ? extends Collection<byte[]>> familyMap, String filterUser) throws IOException { 140 AuthResult result; 141 if (authManager.authorizeUserGlobal(user, perm)) { 142 result = AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap); 143 } else { 144 result = AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap); 145 } 146 result.getParams().setTableName(tableName).setFamilies(familyMap); 147 result.getParams().addExtraParam("filterUser", filterUser); 148 logResult(result); 149 if (!result.isAllowed()) { 150 throw new AccessDeniedException( 151 "Insufficient permissions for user '" + (user != null ? user.getShortName() : "null") 152 + "' (global, action=" + perm.toString() + ")"); 153 } 154 } 155 156 /** 157 * Checks that the user has the given global permission. The generated audit log message will 158 * contain context information for the operation being authorized, based on the given parameters. 159 * @param user Active user to which authorization checks should be applied 160 * @param request Request type 161 * @param perm Action being requested 162 * @param namespace The given namespace 163 */ 164 public void requireGlobalPermission(User user, String request, Action perm, String namespace) 165 throws IOException { 166 AuthResult authResult; 167 if (authManager.authorizeUserGlobal(user, perm)) { 168 authResult = AuthResult.allow(request, "Global check allowed", user, perm, null); 169 authResult.getParams().setNamespace(namespace); 170 logResult(authResult); 171 } else { 172 authResult = AuthResult.deny(request, "Global check failed", user, perm, null); 173 authResult.getParams().setNamespace(namespace); 174 logResult(authResult); 175 throw new AccessDeniedException( 176 "Insufficient permissions for user '" + (user != null ? user.getShortName() : "null") 177 + "' (global, action=" + perm.toString() + ")"); 178 } 179 } 180 181 /** 182 * Checks that the user has the given global or namespace permission. 183 * @param user Active user to which authorization checks should be applied 184 * @param request Request type 185 * @param namespace Name space as requested 186 * @param filterUser User name to be filtered from permission as requested 187 * @param permissions Actions being requested 188 */ 189 public void requireNamespacePermission(User user, String request, String namespace, 190 String filterUser, Action... permissions) throws IOException { 191 AuthResult result = null; 192 193 for (Action permission : permissions) { 194 if (authManager.authorizeUserNamespace(user, namespace, permission)) { 195 result = 196 AuthResult.allow(request, "Namespace permission granted", user, permission, namespace); 197 break; 198 } else { 199 // rest of the world 200 result = AuthResult.deny(request, "Insufficient permissions", user, permission, namespace); 201 } 202 } 203 result.getParams().addExtraParam("filterUser", filterUser); 204 logResult(result); 205 if (!result.isAllowed()) { 206 throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); 207 } 208 } 209 210 /** 211 * Checks that the user has the given global or namespace permission. 212 * @param user Active user to which authorization checks should be applied 213 * @param request Request type 214 * @param namespace The given namespace 215 * @param tableName Table requested 216 * @param familyMap Column family map requested 217 * @param permissions Actions being requested 218 */ 219 public void requireNamespacePermission(User user, String request, String namespace, 220 TableName tableName, Map<byte[], ? extends Collection<byte[]>> familyMap, Action... permissions) 221 throws IOException { 222 AuthResult result = null; 223 224 for (Action permission : permissions) { 225 if (authManager.authorizeUserNamespace(user, namespace, permission)) { 226 result = 227 AuthResult.allow(request, "Namespace permission granted", user, permission, namespace); 228 result.getParams().setTableName(tableName).setFamilies(familyMap); 229 break; 230 } else { 231 // rest of the world 232 result = AuthResult.deny(request, "Insufficient permissions", user, permission, namespace); 233 result.getParams().setTableName(tableName).setFamilies(familyMap); 234 } 235 } 236 logResult(result); 237 if (!result.isAllowed()) { 238 throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); 239 } 240 } 241 242 /** 243 * Authorizes that the current user has any of the given permissions for the given table, column 244 * family and column qualifier. 245 * @param user Active user to which authorization checks should be applied 246 * @param request Request type 247 * @param tableName Table requested 248 * @param family Column family requested 249 * @param qualifier Column qualifier requested 250 * @param filterUser User name to be filtered from permission as requested 251 * @param permissions Actions being requested 252 * @throws IOException if obtaining the current user fails 253 * @throws AccessDeniedException if user has no authorization 254 */ 255 public void requirePermission(User user, String request, TableName tableName, byte[] family, 256 byte[] qualifier, String filterUser, Action... permissions) throws IOException { 257 AuthResult result = null; 258 259 for (Action permission : permissions) { 260 if (authManager.authorizeUserTable(user, tableName, family, qualifier, permission)) { 261 result = AuthResult.allow(request, "Table permission granted", user, permission, tableName, 262 family, qualifier); 263 break; 264 } else { 265 // rest of the world 266 result = AuthResult.deny(request, "Insufficient permissions", user, permission, tableName, 267 family, qualifier); 268 } 269 } 270 result.getParams().addExtraParam("filterUser", filterUser); 271 logResult(result); 272 if (!result.isAllowed()) { 273 throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); 274 } 275 } 276 277 /** 278 * Authorizes that the current user has any of the given permissions for the given table, column 279 * family and column qualifier. 280 * @param user Active user to which authorization checks should be applied 281 * @param request Request type 282 * @param tableName Table requested 283 * @param family Column family param 284 * @param qualifier Column qualifier param 285 * @throws IOException if obtaining the current user fails 286 * @throws AccessDeniedException if user has no authorization 287 */ 288 public void requireTablePermission(User user, String request, TableName tableName, byte[] family, 289 byte[] qualifier, Action... permissions) throws IOException { 290 AuthResult result = null; 291 292 for (Action permission : permissions) { 293 if (authManager.authorizeUserTable(user, tableName, permission)) { 294 result = AuthResult.allow(request, "Table permission granted", user, permission, tableName, 295 null, null); 296 result.getParams().setFamily(family).setQualifier(qualifier); 297 break; 298 } else { 299 // rest of the world 300 result = AuthResult.deny(request, "Insufficient permissions", user, permission, tableName, 301 family, qualifier); 302 result.getParams().setFamily(family).setQualifier(qualifier); 303 } 304 } 305 logResult(result); 306 if (!result.isAllowed()) { 307 throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); 308 } 309 } 310 311 /** 312 * Check if caller is granting or revoking superusers's or supergroups's permissions. 313 * @param request request name 314 * @param caller caller 315 * @param userToBeChecked target user or group 316 * @throws IOException AccessDeniedException if target user is superuser 317 */ 318 public void performOnSuperuser(String request, User caller, String userToBeChecked) 319 throws IOException { 320 List<String> userGroups = new ArrayList<>(); 321 userGroups.add(userToBeChecked); 322 if (!AuthUtil.isGroupPrincipal(userToBeChecked)) { 323 for (String group : getUserGroups(userToBeChecked)) { 324 userGroups.add(AuthUtil.toGroupEntry(group)); 325 } 326 } 327 for (String name : userGroups) { 328 if (Superusers.isSuperUser(name)) { 329 AuthResult result = AuthResult.deny(request, 330 "Granting or revoking superusers's or supergroups's permissions is not allowed", caller, 331 Action.ADMIN, NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR); 332 logResult(result); 333 throw new AccessDeniedException(result.getReason()); 334 } 335 } 336 } 337 338 public void checkLockPermissions(User user, String namespace, TableName tableName, 339 RegionInfo[] regionInfos, String reason) throws IOException { 340 if (namespace != null && !namespace.isEmpty()) { 341 requireNamespacePermission(user, reason, namespace, null, Action.ADMIN, Action.CREATE); 342 } else if (tableName != null || (regionInfos != null && regionInfos.length > 0)) { 343 // So, either a table or regions op. If latter, check perms ons table. 344 TableName tn = tableName != null ? tableName : regionInfos[0].getTable(); 345 requireTablePermission(user, reason, tn, null, null, Action.ADMIN, Action.CREATE); 346 } else { 347 throw new DoNotRetryIOException("Invalid lock level when requesting permissions."); 348 } 349 } 350 351 public static void logResult(AuthResult result) { 352 if (AUDITLOG.isTraceEnabled()) { 353 User user = result.getUser(); 354 UserGroupInformation ugi = user != null ? user.getUGI() : null; 355 AUDITLOG.trace( 356 "Access {} for user {}; reason: {}; remote address: {}; request: {}; context: {};" 357 + "auth method: {}", 358 (result.isAllowed() ? "allowed" : "denied"), 359 (user != null ? user.getShortName() : "UNKNOWN"), result.getReason(), 360 RpcServer.getRemoteAddress().map(InetAddress::toString).orElse(""), result.getRequest(), 361 result.toContextString(), ugi != null ? ugi.getAuthenticationMethod() : "UNKNOWN"); 362 } 363 } 364 365 /* 366 * Validate the hasPermission operation caller with the filter user. Self check doesn't require 367 * any privilege but for others caller must have ADMIN privilege. 368 */ 369 public User validateCallerWithFilterUser(User caller, TablePermission tPerm, String inputUserName) 370 throws IOException { 371 User filterUser = null; 372 if (!caller.getShortName().equals(inputUserName)) { 373 // User should have admin privilege if checking permission for other users 374 requirePermission(caller, "hasPermission", tPerm.getTableName(), tPerm.getFamily(), 375 tPerm.getQualifier(), inputUserName, Action.ADMIN); 376 // Initialize user instance for the input user name 377 List<String> groups = getUserGroups(inputUserName); 378 filterUser = new InputUser(inputUserName, groups.toArray(new String[groups.size()])); 379 } else { 380 // User don't need ADMIN privilege for self check. 381 // Setting action as null in AuthResult to display empty action in audit log 382 AuthResult result = AuthResult.allow("hasPermission", "Self user validation allowed", caller, 383 null, tPerm.getTableName(), tPerm.getFamily(), tPerm.getQualifier()); 384 logResult(result); 385 filterUser = caller; 386 } 387 return filterUser; 388 } 389 390 /** 391 * A temporary user class to instantiate User instance based on the name and groups. 392 */ 393 public static class InputUser extends User { 394 private String name; 395 private String shortName = null; 396 private String[] groups; 397 398 public InputUser(String name, String[] groups) { 399 this.name = name; 400 this.groups = groups; 401 } 402 403 @Override 404 public String getShortName() { 405 if (this.shortName == null) { 406 try { 407 this.shortName = new HadoopKerberosName(this.name).getShortName(); 408 } catch (IOException ioe) { 409 throw new IllegalArgumentException( 410 "Illegal principal name " + this.name + ": " + ioe.toString(), ioe); 411 } 412 } 413 return shortName; 414 } 415 416 @Override 417 public String getName() { 418 return this.name; 419 } 420 421 @Override 422 public String[] getGroupNames() { 423 return this.groups; 424 } 425 426 @Override 427 public <T> T runAs(PrivilegedAction<T> action) { 428 throw new UnsupportedOperationException( 429 "Method not supported, this class has limited implementation"); 430 } 431 432 @Override 433 public <T> T runAs(PrivilegedExceptionAction<T> action) 434 throws IOException, InterruptedException { 435 throw new UnsupportedOperationException( 436 "Method not supported, this class has limited implementation"); 437 } 438 439 @Override 440 public String toString() { 441 return this.name; 442 } 443 } 444 445 /* 446 * Initialize the group service. 447 */ 448 private void initGroupService(Configuration conf) { 449 if (groupService == null) { 450 if (conf.getBoolean(User.TestingGroups.TEST_CONF, false)) { 451 UserProvider.setGroups(new User.TestingGroups(UserProvider.getGroups())); 452 groupService = UserProvider.getGroups(); 453 } else { 454 groupService = Groups.getUserToGroupsMappingService(conf); 455 } 456 } 457 } 458 459 /** 460 * Retrieve the groups of the given user. 461 * @param user User name n 462 */ 463 public static List<String> getUserGroups(String user) { 464 try { 465 return groupService.getGroups(user); 466 } catch (IOException e) { 467 LOG.error("Error occurred while retrieving group for " + user, e); 468 return new ArrayList<>(); 469 } 470 } 471 472 /** 473 * Authorizes that if the current user has the given permissions. 474 * @param user Active user to which authorization checks should be applied 475 * @param request Request type 476 * @param permission Actions being requested 477 * @return True if the user has the specific permission 478 */ 479 public boolean hasUserPermission(User user, String request, Permission permission) { 480 if (permission instanceof TablePermission) { 481 TablePermission tPerm = (TablePermission) permission; 482 for (Permission.Action action : permission.getActions()) { 483 AuthResult authResult = permissionGranted(request, user, action, tPerm.getTableName(), 484 tPerm.getFamily(), tPerm.getQualifier()); 485 AccessChecker.logResult(authResult); 486 if (!authResult.isAllowed()) { 487 return false; 488 } 489 } 490 } else if (permission instanceof NamespacePermission) { 491 NamespacePermission nsPerm = (NamespacePermission) permission; 492 AuthResult authResult; 493 for (Action action : nsPerm.getActions()) { 494 if (getAuthManager().authorizeUserNamespace(user, nsPerm.getNamespace(), action)) { 495 authResult = 496 AuthResult.allow(request, "Namespace action allowed", user, action, null, null); 497 } else { 498 authResult = 499 AuthResult.deny(request, "Namespace action denied", user, action, null, null); 500 } 501 AccessChecker.logResult(authResult); 502 if (!authResult.isAllowed()) { 503 return false; 504 } 505 } 506 } else { 507 AuthResult authResult; 508 for (Permission.Action action : permission.getActions()) { 509 if (getAuthManager().authorizeUserGlobal(user, action)) { 510 authResult = AuthResult.allow(request, "Global action allowed", user, action, null, null); 511 } else { 512 authResult = AuthResult.deny(request, "Global action denied", user, action, null, null); 513 } 514 AccessChecker.logResult(authResult); 515 if (!authResult.isAllowed()) { 516 return false; 517 } 518 } 519 } 520 return true; 521 } 522 523 private AuthResult permissionGranted(String request, User user, Action permRequest, 524 TableName tableName, byte[] family, byte[] qualifier) { 525 Map<byte[], ? extends Collection<byte[]>> map = makeFamilyMap(family, qualifier); 526 return permissionGranted(request, user, permRequest, tableName, map); 527 } 528 529 /** 530 * Check the current user for authorization to perform a specific action against the given set of 531 * row data. 532 * <p> 533 * Note: Ordering of the authorization checks has been carefully optimized to short-circuit the 534 * most common requests and minimize the amount of processing required. 535 * </p> 536 * @param request User request 537 * @param user User name 538 * @param permRequest the action being requested 539 * @param tableName Table name 540 * @param families the map of column families to qualifiers present in the request 541 * @return an authorization result 542 */ 543 public AuthResult permissionGranted(String request, User user, Action permRequest, 544 TableName tableName, Map<byte[], ? extends Collection<?>> families) { 545 // 1. All users need read access to hbase:meta table. 546 // this is a very common operation, so deal with it quickly. 547 if (TableName.META_TABLE_NAME.equals(tableName)) { 548 if (permRequest == Action.READ) { 549 return AuthResult.allow(request, "All users allowed", user, permRequest, tableName, 550 families); 551 } 552 } 553 554 if (user == null) { 555 return AuthResult.deny(request, "No user associated with request!", null, permRequest, 556 tableName, families); 557 } 558 559 // 2. check for the table-level, if successful we can short-circuit 560 if (getAuthManager().authorizeUserTable(user, tableName, permRequest)) { 561 return AuthResult.allow(request, "Table permission granted", user, permRequest, tableName, 562 families); 563 } 564 565 // 3. check permissions against the requested families 566 if (families != null && families.size() > 0) { 567 // all families must pass 568 for (Map.Entry<byte[], ? extends Collection<?>> family : families.entrySet()) { 569 // a) check for family level access 570 if (getAuthManager().authorizeUserTable(user, tableName, family.getKey(), permRequest)) { 571 continue; // family-level permission overrides per-qualifier 572 } 573 574 // b) qualifier level access can still succeed 575 if ((family.getValue() != null) && (family.getValue().size() > 0)) { 576 if (family.getValue() instanceof Set) { 577 // for each qualifier of the family 578 Set<byte[]> familySet = (Set<byte[]>) family.getValue(); 579 for (byte[] qualifier : familySet) { 580 if ( 581 !getAuthManager().authorizeUserTable(user, tableName, family.getKey(), qualifier, 582 permRequest) 583 ) { 584 return AuthResult.deny(request, "Failed qualifier check", user, permRequest, 585 tableName, makeFamilyMap(family.getKey(), qualifier)); 586 } 587 } 588 } else if (family.getValue() instanceof List) { // List<Cell> 589 List<Cell> cellList = (List<Cell>) family.getValue(); 590 for (Cell cell : cellList) { 591 if ( 592 !getAuthManager().authorizeUserTable(user, tableName, family.getKey(), 593 CellUtil.cloneQualifier(cell), permRequest) 594 ) { 595 return AuthResult.deny(request, "Failed qualifier check", user, permRequest, 596 tableName, makeFamilyMap(family.getKey(), CellUtil.cloneQualifier(cell))); 597 } 598 } 599 } 600 } else { 601 // no qualifiers and family-level check already failed 602 return AuthResult.deny(request, "Failed family check", user, permRequest, tableName, 603 makeFamilyMap(family.getKey(), null)); 604 } 605 } 606 607 // all family checks passed 608 return AuthResult.allow(request, "All family checks passed", user, permRequest, tableName, 609 families); 610 } 611 612 // 4. no families to check and table level access failed 613 return AuthResult.deny(request, "No families to check and table permission failed", user, 614 permRequest, tableName, families); 615 } 616 617 private Map<byte[], ? extends Collection<byte[]>> makeFamilyMap(byte[] family, byte[] qualifier) { 618 if (family == null) { 619 return null; 620 } 621 622 Map<byte[], Collection<byte[]>> familyMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); 623 familyMap.put(family, qualifier != null ? ImmutableSet.of(qualifier) : null); 624 return familyMap; 625 } 626}