001/* 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020package org.apache.hadoop.hbase.security; 021 022import java.io.IOException; 023import java.security.PrivilegedAction; 024import java.security.PrivilegedExceptionAction; 025import java.util.Arrays; 026import java.util.Collection; 027import java.util.HashMap; 028import java.util.List; 029import java.util.Map; 030import java.util.Optional; 031import java.util.concurrent.ExecutionException; 032 033import org.apache.hadoop.conf.Configuration; 034import org.apache.hadoop.hbase.AuthUtil; 035import org.apache.hadoop.hbase.util.Methods; 036import org.apache.hadoop.security.Groups; 037import org.apache.hadoop.security.SecurityUtil; 038import org.apache.hadoop.security.UserGroupInformation; 039import org.apache.hadoop.security.token.Token; 040import org.apache.hadoop.security.token.TokenIdentifier; 041import org.apache.yetus.audience.InterfaceAudience; 042 043import org.apache.hbase.thirdparty.com.google.common.cache.LoadingCache; 044 045/** 046 * Wrapper to abstract out usage of user and group information in HBase. 047 * 048 * <p> 049 * This class provides a common interface for interacting with user and group 050 * information across changing APIs in different versions of Hadoop. It only 051 * provides access to the common set of functionality in 052 * {@link org.apache.hadoop.security.UserGroupInformation} currently needed by 053 * HBase, but can be extended as needs change. 054 * </p> 055 */ 056@InterfaceAudience.Public 057public abstract class User { 058 public static final String HBASE_SECURITY_CONF_KEY = 059 "hbase.security.authentication"; 060 public static final String HBASE_SECURITY_AUTHORIZATION_CONF_KEY = 061 "hbase.security.authorization"; 062 063 protected UserGroupInformation ugi; 064 065 public UserGroupInformation getUGI() { 066 return ugi; 067 } 068 069 /** 070 * Returns the full user name. For Kerberos principals this will include 071 * the host and realm portions of the principal name. 072 * 073 * @return User full name. 074 */ 075 public String getName() { 076 return ugi.getUserName(); 077 } 078 079 /** 080 * Returns the list of groups of which this user is a member. On secure 081 * Hadoop this returns the group information for the user as resolved on the 082 * server. For 0.20 based Hadoop, the group names are passed from the client. 083 */ 084 public String[] getGroupNames() { 085 return ugi.getGroupNames(); 086 } 087 088 /** 089 * Returns the shortened version of the user name -- the portion that maps 090 * to an operating system user name. 091 * 092 * @return Short name 093 */ 094 public abstract String getShortName(); 095 096 /** 097 * Executes the given action within the context of this user. 098 */ 099 public abstract <T> T runAs(PrivilegedAction<T> action); 100 101 /** 102 * Executes the given action within the context of this user. 103 */ 104 public abstract <T> T runAs(PrivilegedExceptionAction<T> action) 105 throws IOException, InterruptedException; 106 107 /** 108 * Returns the Token of the specified kind associated with this user, 109 * or null if the Token is not present. 110 * 111 * @param kind the kind of token 112 * @param service service on which the token is supposed to be used 113 * @return the token of the specified kind. 114 */ 115 public Token<?> getToken(String kind, String service) throws IOException { 116 for (Token<?> token : ugi.getTokens()) { 117 if (token.getKind().toString().equals(kind) && 118 (service != null && token.getService().toString().equals(service))) { 119 return token; 120 } 121 } 122 return null; 123 } 124 125 /** 126 * Returns all the tokens stored in the user's credentials. 127 */ 128 public Collection<Token<? extends TokenIdentifier>> getTokens() { 129 return ugi.getTokens(); 130 } 131 132 /** 133 * Adds the given Token to the user's credentials. 134 * 135 * @param token the token to add 136 */ 137 public void addToken(Token<? extends TokenIdentifier> token) { 138 ugi.addToken(token); 139 } 140 141 /** 142 * @return true if user credentials are obtained from keytab. 143 */ 144 public boolean isLoginFromKeytab() { 145 return ugi.isFromKeytab(); 146 } 147 148 @Override 149 public boolean equals(Object o) { 150 if (this == o) { 151 return true; 152 } 153 if (o == null || getClass() != o.getClass()) { 154 return false; 155 } 156 return ugi.equals(((User) o).ugi); 157 } 158 159 @Override 160 public int hashCode() { 161 return ugi.hashCode(); 162 } 163 164 @Override 165 public String toString() { 166 return ugi.toString(); 167 } 168 169 /** 170 * Returns the {@code User} instance within current execution context. 171 */ 172 public static User getCurrent() throws IOException { 173 User user = new SecureHadoopUser(); 174 if (user.getUGI() == null) { 175 return null; 176 } 177 return user; 178 } 179 180 /** 181 * Executes the given action as the login user 182 * @param action 183 * @return the result of the action 184 * @throws IOException 185 */ 186 @SuppressWarnings({ "rawtypes", "unchecked" }) 187 public static <T> T runAsLoginUser(PrivilegedExceptionAction<T> action) throws IOException { 188 try { 189 Class c = Class.forName("org.apache.hadoop.security.SecurityUtil"); 190 Class [] types = new Class[]{PrivilegedExceptionAction.class}; 191 Object[] args = new Object[]{action}; 192 return (T) Methods.call(c, null, "doAsLoginUser", types, args); 193 } catch (Throwable e) { 194 throw new IOException(e); 195 } 196 } 197 198 /** 199 * Wraps an underlying {@code UserGroupInformation} instance. 200 * @param ugi The base Hadoop user 201 * @return User 202 */ 203 public static User create(UserGroupInformation ugi) { 204 if (ugi == null) { 205 return null; 206 } 207 return new SecureHadoopUser(ugi); 208 } 209 210 /** 211 * Generates a new {@code User} instance specifically for use in test code. 212 * @param name the full username 213 * @param groups the group names to which the test user will belong 214 * @return a new <code>User</code> instance 215 */ 216 public static User createUserForTesting(Configuration conf, 217 String name, String[] groups) { 218 User userForTesting = SecureHadoopUser.createUserForTesting(conf, name, groups); 219 return userForTesting; 220 } 221 222 /** 223 * Log in the current process using the given configuration keys for the 224 * credential file and login principal. 225 * 226 * <p><strong>This is only applicable when 227 * running on secure Hadoop</strong> -- see 228 * org.apache.hadoop.security.SecurityUtil#login(Configuration,String,String,String). 229 * On regular Hadoop (without security features), this will safely be ignored. 230 * </p> 231 * 232 * @param conf The configuration data to use 233 * @param fileConfKey Property key used to configure path to the credential file 234 * @param principalConfKey Property key used to configure login principal 235 * @param localhost Current hostname to use in any credentials 236 * @throws IOException underlying exception from SecurityUtil.login() call 237 */ 238 public static void login(Configuration conf, String fileConfKey, 239 String principalConfKey, String localhost) throws IOException { 240 SecureHadoopUser.login(conf, fileConfKey, principalConfKey, localhost); 241 } 242 243 /** 244 * Login with the given keytab and principal. 245 * @param keytabLocation path of keytab 246 * @param pricipalName login principal 247 * @throws IOException underlying exception from UserGroupInformation.loginUserFromKeytab 248 */ 249 public static void login(String keytabLocation, String pricipalName) throws IOException { 250 SecureHadoopUser.login(keytabLocation, pricipalName); 251 } 252 253 /** 254 * Returns whether or not Kerberos authentication is configured for Hadoop. 255 * For non-secure Hadoop, this always returns <code>false</code>. 256 * For secure Hadoop, it will return the value from 257 * {@code UserGroupInformation.isSecurityEnabled()}. 258 */ 259 public static boolean isSecurityEnabled() { 260 return SecureHadoopUser.isSecurityEnabled(); 261 } 262 263 /** 264 * Returns whether or not secure authentication is enabled for HBase. Note that 265 * HBase security requires HDFS security to provide any guarantees, so it is 266 * recommended that secure HBase should run on secure HDFS. 267 */ 268 public static boolean isHBaseSecurityEnabled(Configuration conf) { 269 return "kerberos".equalsIgnoreCase(conf.get(HBASE_SECURITY_CONF_KEY)); 270 } 271 272 /** 273 * In secure environment, if a user specified his keytab and principal, 274 * a hbase client will try to login with them. Otherwise, hbase client will try to obtain 275 * ticket(through kinit) from system. 276 * @param conf configuration file 277 * @return true if keytab and principal are configured 278 */ 279 public static boolean shouldLoginFromKeytab(Configuration conf) { 280 Optional<String> keytab = 281 Optional.ofNullable(conf.get(AuthUtil.HBASE_CLIENT_KEYTAB_FILE)); 282 Optional<String> principal = 283 Optional.ofNullable(conf.get(AuthUtil.HBASE_CLIENT_KERBEROS_PRINCIPAL)); 284 return keytab.isPresent() && principal.isPresent(); 285 } 286 287 /* Concrete implementations */ 288 289 /** 290 * Bridges {@code User} invocations to underlying calls to 291 * {@link org.apache.hadoop.security.UserGroupInformation} for secure Hadoop 292 * 0.20 and versions 0.21 and above. 293 */ 294 @InterfaceAudience.Private 295 public static final class SecureHadoopUser extends User { 296 private String shortName; 297 private LoadingCache<String, String[]> cache; 298 299 public SecureHadoopUser() throws IOException { 300 ugi = UserGroupInformation.getCurrentUser(); 301 this.cache = null; 302 } 303 304 public SecureHadoopUser(UserGroupInformation ugi) { 305 this.ugi = ugi; 306 this.cache = null; 307 } 308 309 public SecureHadoopUser(UserGroupInformation ugi, 310 LoadingCache<String, String[]> cache) { 311 this.ugi = ugi; 312 this.cache = cache; 313 } 314 315 @Override 316 public String getShortName() { 317 if (shortName != null) return shortName; 318 try { 319 shortName = ugi.getShortUserName(); 320 return shortName; 321 } catch (Exception e) { 322 throw new RuntimeException("Unexpected error getting user short name", 323 e); 324 } 325 } 326 327 @Override 328 public String[] getGroupNames() { 329 if (cache != null) { 330 try { 331 return this.cache.get(getShortName()); 332 } catch (ExecutionException e) { 333 return new String[0]; 334 } 335 } 336 return ugi.getGroupNames(); 337 } 338 339 @Override 340 public <T> T runAs(PrivilegedAction<T> action) { 341 return ugi.doAs(action); 342 } 343 344 @Override 345 public <T> T runAs(PrivilegedExceptionAction<T> action) 346 throws IOException, InterruptedException { 347 return ugi.doAs(action); 348 } 349 350 /** @see User#createUserForTesting(org.apache.hadoop.conf.Configuration, String, String[]) */ 351 public static User createUserForTesting(Configuration conf, 352 String name, String[] groups) { 353 synchronized (UserProvider.class) { 354 if (!(UserProvider.groups instanceof TestingGroups) || 355 conf.getBoolean(TestingGroups.TEST_CONF, false)) { 356 UserProvider.groups = new TestingGroups(UserProvider.groups); 357 } 358 } 359 360 ((TestingGroups)UserProvider.groups).setUserGroups(name, groups); 361 return new SecureHadoopUser(UserGroupInformation.createUserForTesting(name, groups)); 362 } 363 364 /** 365 * Obtain credentials for the current process using the configured 366 * Kerberos keytab file and principal. 367 * @see User#login(org.apache.hadoop.conf.Configuration, String, String, String) 368 * 369 * @param conf the Configuration to use 370 * @param fileConfKey Configuration property key used to store the path 371 * to the keytab file 372 * @param principalConfKey Configuration property key used to store the 373 * principal name to login as 374 * @param localhost the local hostname 375 */ 376 public static void login(Configuration conf, String fileConfKey, 377 String principalConfKey, String localhost) throws IOException { 378 if (isSecurityEnabled()) { 379 SecurityUtil.login(conf, fileConfKey, principalConfKey, localhost); 380 } 381 } 382 383 /** 384 * Login through configured keytab and pricipal. 385 * @param keytabLocation location of keytab 386 * @param principalName principal in keytab 387 * @throws IOException exception from UserGroupInformation.loginUserFromKeytab 388 */ 389 public static void login(String keytabLocation, String principalName) 390 throws IOException { 391 if (isSecurityEnabled()) { 392 UserGroupInformation.loginUserFromKeytab(principalName, keytabLocation); 393 } 394 } 395 396 /** 397 * Returns the result of {@code UserGroupInformation.isSecurityEnabled()}. 398 */ 399 public static boolean isSecurityEnabled() { 400 return UserGroupInformation.isSecurityEnabled(); 401 } 402 } 403 404 public static class TestingGroups extends Groups { 405 public static final String TEST_CONF = "hbase.group.service.for.test.only"; 406 407 private final Map<String, List<String>> userToGroupsMapping = new HashMap<>(); 408 private Groups underlyingImplementation; 409 410 public TestingGroups(Groups underlyingImplementation) { 411 super(new Configuration()); 412 this.underlyingImplementation = underlyingImplementation; 413 } 414 415 @Override 416 public List<String> getGroups(String user) throws IOException { 417 List<String> result = userToGroupsMapping.get(user); 418 419 if (result == null) { 420 result = underlyingImplementation.getGroups(user); 421 } 422 423 return result; 424 } 425 426 private void setUserGroups(String user, String[] groups) { 427 userToGroupsMapping.put(user, Arrays.asList(groups)); 428 } 429 } 430}