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