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 /** 133 * @return true if user credentials are obtained from keytab. 134 */ 135 public boolean isLoginFromKeytab() { 136 return ugi.isFromKeytab(); 137 } 138 139 @Override 140 public boolean equals(Object o) { 141 if (this == o) { 142 return true; 143 } 144 if (o == null || getClass() != o.getClass()) { 145 return false; 146 } 147 return ugi.equals(((User) o).ugi); 148 } 149 150 @Override 151 public int hashCode() { 152 return ugi.hashCode(); 153 } 154 155 @Override 156 public String toString() { 157 return ugi.toString(); 158 } 159 160 /** 161 * Returns the {@code User} instance within current execution context. 162 */ 163 public static User getCurrent() throws IOException { 164 User user = new SecureHadoopUser(); 165 if (user.getUGI() == null) { 166 return null; 167 } 168 return user; 169 } 170 171 /** 172 * Executes the given action as the login user n * @return the result of the action n 173 */ 174 @SuppressWarnings({ "rawtypes", "unchecked" }) 175 public static <T> T runAsLoginUser(PrivilegedExceptionAction<T> action) throws IOException { 176 try { 177 Class c = Class.forName("org.apache.hadoop.security.SecurityUtil"); 178 Class[] types = new Class[] { PrivilegedExceptionAction.class }; 179 Object[] args = new Object[] { action }; 180 return (T) Methods.call(c, null, "doAsLoginUser", types, args); 181 } catch (Throwable e) { 182 throw new IOException(e); 183 } 184 } 185 186 /** 187 * Wraps an underlying {@code UserGroupInformation} instance. 188 * @param ugi The base Hadoop user n 189 */ 190 public static User create(UserGroupInformation ugi) { 191 if (ugi == null) { 192 return null; 193 } 194 return new SecureHadoopUser(ugi); 195 } 196 197 /** 198 * Generates a new {@code User} instance specifically for use in test code. 199 * @param name the full username 200 * @param groups the group names to which the test user will belong 201 * @return a new <code>User</code> instance 202 */ 203 public static User createUserForTesting(Configuration conf, String name, String[] groups) { 204 User userForTesting = SecureHadoopUser.createUserForTesting(conf, name, groups); 205 return userForTesting; 206 } 207 208 /** 209 * Log in the current process using the given configuration keys for the credential file and login 210 * principal. 211 * <p> 212 * <strong>This is only applicable when running on secure Hadoop</strong> -- see 213 * org.apache.hadoop.security.SecurityUtil#login(Configuration,String,String,String). On regular 214 * Hadoop (without security features), this will safely be ignored. 215 * </p> 216 * @param conf The configuration data to use 217 * @param fileConfKey Property key used to configure path to the credential file 218 * @param principalConfKey Property key used to configure login principal 219 * @param localhost Current hostname to use in any credentials 220 * @throws IOException underlying exception from SecurityUtil.login() call 221 */ 222 public static void login(Configuration conf, String fileConfKey, String principalConfKey, 223 String localhost) throws IOException { 224 SecureHadoopUser.login(conf, fileConfKey, principalConfKey, localhost); 225 } 226 227 /** 228 * Login with the given keytab and principal. 229 * @param keytabLocation path of keytab 230 * @param pricipalName login principal 231 * @throws IOException underlying exception from UserGroupInformation.loginUserFromKeytab 232 */ 233 public static void login(String keytabLocation, String pricipalName) throws IOException { 234 SecureHadoopUser.login(keytabLocation, pricipalName); 235 } 236 237 /** 238 * Returns whether or not Kerberos authentication is configured for Hadoop. For non-secure Hadoop, 239 * this always returns <code>false</code>. For secure Hadoop, it will return the value from 240 * {@code UserGroupInformation.isSecurityEnabled()}. 241 */ 242 public static boolean isSecurityEnabled() { 243 return SecureHadoopUser.isSecurityEnabled(); 244 } 245 246 /** 247 * Returns whether or not secure authentication is enabled for HBase. Note that HBase security 248 * requires HDFS security to provide any guarantees, so it is recommended that secure HBase should 249 * run on secure HDFS. 250 */ 251 public static boolean isHBaseSecurityEnabled(Configuration conf) { 252 return "kerberos".equalsIgnoreCase(conf.get(HBASE_SECURITY_CONF_KEY)); 253 } 254 255 /** 256 * In secure environment, if a user specified his keytab and principal, a hbase client will try to 257 * login with them. Otherwise, hbase client will try to obtain ticket(through kinit) from system. 258 * @param conf configuration file 259 * @return true if keytab and principal are configured 260 */ 261 public static boolean shouldLoginFromKeytab(Configuration conf) { 262 Optional<String> keytab = Optional.ofNullable(conf.get(AuthUtil.HBASE_CLIENT_KEYTAB_FILE)); 263 Optional<String> principal = 264 Optional.ofNullable(conf.get(AuthUtil.HBASE_CLIENT_KERBEROS_PRINCIPAL)); 265 return keytab.isPresent() && principal.isPresent(); 266 } 267 268 /* Concrete implementations */ 269 270 /** 271 * Bridges {@code User} invocations to underlying calls to 272 * {@link org.apache.hadoop.security.UserGroupInformation} for secure Hadoop 0.20 and versions 273 * 0.21 and above. 274 */ 275 @InterfaceAudience.Private 276 public static final class SecureHadoopUser extends User { 277 private String shortName; 278 private LoadingCache<String, String[]> cache; 279 280 public SecureHadoopUser() throws IOException { 281 ugi = UserGroupInformation.getCurrentUser(); 282 this.cache = null; 283 } 284 285 public SecureHadoopUser(UserGroupInformation ugi) { 286 this.ugi = ugi; 287 this.cache = null; 288 } 289 290 public SecureHadoopUser(UserGroupInformation ugi, LoadingCache<String, String[]> cache) { 291 this.ugi = ugi; 292 this.cache = cache; 293 } 294 295 @Override 296 public String getShortName() { 297 if (shortName != null) return shortName; 298 try { 299 shortName = ugi.getShortUserName(); 300 return shortName; 301 } catch (Exception e) { 302 throw new RuntimeException("Unexpected error getting user short name", e); 303 } 304 } 305 306 @Override 307 public String[] getGroupNames() { 308 if (cache != null) { 309 try { 310 return this.cache.get(getShortName()); 311 } catch (ExecutionException e) { 312 return new String[0]; 313 } 314 } 315 return ugi.getGroupNames(); 316 } 317 318 @Override 319 public <T> T runAs(PrivilegedAction<T> action) { 320 return ugi.doAs(action); 321 } 322 323 @Override 324 public <T> T runAs(PrivilegedExceptionAction<T> action) 325 throws IOException, InterruptedException { 326 return ugi.doAs(action); 327 } 328 329 /** @see User#createUserForTesting(org.apache.hadoop.conf.Configuration, String, String[]) */ 330 public static User createUserForTesting(Configuration conf, String name, String[] groups) { 331 synchronized (UserProvider.class) { 332 if (!(UserProvider.groups instanceof TestingGroups)) { 333 UserProvider.groups = new TestingGroups(UserProvider.groups); 334 } 335 } 336 337 ((TestingGroups) UserProvider.groups).setUserGroups(name, groups); 338 return new SecureHadoopUser(UserGroupInformation.createUserForTesting(name, groups)); 339 } 340 341 /** 342 * Obtain credentials for the current process using the configured Kerberos keytab file and 343 * principal. 344 * @see User#login(org.apache.hadoop.conf.Configuration, String, String, String) 345 * @param conf the Configuration to use 346 * @param fileConfKey Configuration property key used to store the path to the keytab file 347 * @param principalConfKey Configuration property key used to store the principal name to login 348 * as 349 * @param localhost the local hostname 350 */ 351 public static void login(Configuration conf, String fileConfKey, String principalConfKey, 352 String localhost) throws IOException { 353 if (isSecurityEnabled()) { 354 SecurityUtil.login(conf, fileConfKey, principalConfKey, localhost); 355 } 356 } 357 358 /** 359 * Login through configured keytab and pricipal. 360 * @param keytabLocation location of keytab 361 * @param principalName principal in keytab 362 * @throws IOException exception from UserGroupInformation.loginUserFromKeytab 363 */ 364 public static void login(String keytabLocation, String principalName) throws IOException { 365 if (isSecurityEnabled()) { 366 UserGroupInformation.loginUserFromKeytab(principalName, keytabLocation); 367 } 368 } 369 370 /** 371 * Returns the result of {@code UserGroupInformation.isSecurityEnabled()}. 372 */ 373 public static boolean isSecurityEnabled() { 374 return UserGroupInformation.isSecurityEnabled(); 375 } 376 } 377 378 public static class TestingGroups extends Groups { 379 public static final String TEST_CONF = "hbase.group.service.for.test.only"; 380 381 private final Map<String, List<String>> userToGroupsMapping = new HashMap<>(); 382 private Groups underlyingImplementation; 383 384 public TestingGroups(Groups underlyingImplementation) { 385 super(new Configuration()); 386 this.underlyingImplementation = underlyingImplementation; 387 } 388 389 @Override 390 public List<String> getGroups(String user) throws IOException { 391 List<String> result = userToGroupsMapping.get(user); 392 393 if (result == null) { 394 result = underlyingImplementation.getGroups(user); 395 } 396 397 return result; 398 } 399 400 private void setUserGroups(String user, String[] groups) { 401 userToGroupsMapping.put(user, Arrays.asList(groups)); 402 } 403 } 404}