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