3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 package org
.apache
.hadoop
.hbase
.security
;
22 import java
.io
.IOException
;
23 import java
.security
.PrivilegedAction
;
24 import java
.security
.PrivilegedExceptionAction
;
25 import java
.util
.Arrays
;
26 import java
.util
.Collection
;
27 import java
.util
.HashMap
;
28 import java
.util
.List
;
30 import java
.util
.Optional
;
31 import java
.util
.concurrent
.ExecutionException
;
33 import org
.apache
.hadoop
.conf
.Configuration
;
34 import org
.apache
.hadoop
.hbase
.AuthUtil
;
35 import org
.apache
.hadoop
.hbase
.util
.Methods
;
36 import org
.apache
.hadoop
.security
.Groups
;
37 import org
.apache
.hadoop
.security
.SecurityUtil
;
38 import org
.apache
.hadoop
.security
.UserGroupInformation
;
39 import org
.apache
.hadoop
.security
.token
.Token
;
40 import org
.apache
.hadoop
.security
.token
.TokenIdentifier
;
41 import org
.apache
.yetus
.audience
.InterfaceAudience
;
43 import org
.apache
.hbase
.thirdparty
.com
.google
.common
.cache
.LoadingCache
;
46 * Wrapper to abstract out usage of user and group information in HBase.
49 * This class provides a common interface for interacting with user and group
50 * information across changing APIs in different versions of Hadoop. It only
51 * provides access to the common set of functionality in
52 * {@link org.apache.hadoop.security.UserGroupInformation} currently needed by
53 * HBase, but can be extended as needs change.
56 @InterfaceAudience.Public
57 public abstract class User
{
58 public static final String HBASE_SECURITY_CONF_KEY
=
59 "hbase.security.authentication";
60 public static final String HBASE_SECURITY_AUTHORIZATION_CONF_KEY
=
61 "hbase.security.authorization";
63 protected UserGroupInformation ugi
;
65 public UserGroupInformation
getUGI() {
70 * Returns the full user name. For Kerberos principals this will include
71 * the host and realm portions of the principal name.
73 * @return User full name.
75 public String
getName() {
76 return ugi
.getUserName();
80 * Returns the list of groups of which this user is a member. On secure
81 * Hadoop this returns the group information for the user as resolved on the
82 * server. For 0.20 based Hadoop, the group names are passed from the client.
84 public String
[] getGroupNames() {
85 return ugi
.getGroupNames();
89 * Returns the shortened version of the user name -- the portion that maps
90 * to an operating system user name.
94 public abstract String
getShortName();
97 * Executes the given action within the context of this user.
99 public abstract <T
> T
runAs(PrivilegedAction
<T
> action
);
102 * Executes the given action within the context of this user.
104 public abstract <T
> T
runAs(PrivilegedExceptionAction
<T
> action
)
105 throws IOException
, InterruptedException
;
108 * Returns the Token of the specified kind associated with this user,
109 * or null if the Token is not present.
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.
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
))) {
126 * Returns all the tokens stored in the user's credentials.
128 public Collection
<Token
<?
extends TokenIdentifier
>> getTokens() {
129 return ugi
.getTokens();
133 * Adds the given Token to the user's credentials.
135 * @param token the token to add
137 public void addToken(Token
<?
extends TokenIdentifier
> token
) {
142 * @return true if user credentials are obtained from keytab.
144 public boolean isLoginFromKeytab() {
145 return ugi
.isFromKeytab();
149 public boolean equals(Object o
) {
153 if (o
== null || getClass() != o
.getClass()) {
156 return ugi
.equals(((User
) o
).ugi
);
160 public int hashCode() {
161 return ugi
.hashCode();
165 public String
toString() {
166 return ugi
.toString();
170 * Returns the {@code User} instance within current execution context.
172 public static User
getCurrent() throws IOException
{
173 User user
= new SecureHadoopUser();
174 if (user
.getUGI() == null) {
181 * Executes the given action as the login user
183 * @return the result of the action
184 * @throws IOException
186 @SuppressWarnings({ "rawtypes", "unchecked" })
187 public static <T
> T
runAsLoginUser(PrivilegedExceptionAction
<T
> action
) throws IOException
{
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
);
199 * Wraps an underlying {@code UserGroupInformation} instance.
200 * @param ugi The base Hadoop user
203 public static User
create(UserGroupInformation ugi
) {
207 return new SecureHadoopUser(ugi
);
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
216 public static User
createUserForTesting(Configuration conf
,
217 String name
, String
[] groups
) {
218 User userForTesting
= SecureHadoopUser
.createUserForTesting(conf
, name
, groups
);
219 return userForTesting
;
223 * Log in the current process using the given configuration keys for the
224 * credential file and login principal.
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.
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
238 public static void login(Configuration conf
, String fileConfKey
,
239 String principalConfKey
, String localhost
) throws IOException
{
240 SecureHadoopUser
.login(conf
, fileConfKey
, principalConfKey
, localhost
);
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
249 public static void login(String keytabLocation
, String pricipalName
) throws IOException
{
250 SecureHadoopUser
.login(keytabLocation
, pricipalName
);
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()}.
259 public static boolean isSecurityEnabled() {
260 return SecureHadoopUser
.isSecurityEnabled();
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.
268 public static boolean isHBaseSecurityEnabled(Configuration conf
) {
269 return "kerberos".equalsIgnoreCase(conf
.get(HBASE_SECURITY_CONF_KEY
));
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
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();
287 /* Concrete implementations */
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.
294 @InterfaceAudience.Private
295 public static final class SecureHadoopUser
extends User
{
296 private String shortName
;
297 private LoadingCache
<String
, String
[]> cache
;
299 public SecureHadoopUser() throws IOException
{
300 ugi
= UserGroupInformation
.getCurrentUser();
304 public SecureHadoopUser(UserGroupInformation ugi
) {
309 public SecureHadoopUser(UserGroupInformation ugi
,
310 LoadingCache
<String
, String
[]> cache
) {
316 public String
getShortName() {
317 if (shortName
!= null) return shortName
;
319 shortName
= ugi
.getShortUserName();
321 } catch (Exception e
) {
322 throw new RuntimeException("Unexpected error getting user short name",
328 public String
[] getGroupNames() {
331 return this.cache
.get(getShortName());
332 } catch (ExecutionException e
) {
333 return new String
[0];
336 return ugi
.getGroupNames();
340 public <T
> T
runAs(PrivilegedAction
<T
> action
) {
341 return ugi
.doAs(action
);
345 public <T
> T
runAs(PrivilegedExceptionAction
<T
> action
)
346 throws IOException
, InterruptedException
{
347 return ugi
.doAs(action
);
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 UserProvider
.groups
= new TestingGroups(UserProvider
.groups
);
359 ((TestingGroups
)UserProvider
.groups
).setUserGroups(name
, groups
);
360 return new SecureHadoopUser(UserGroupInformation
.createUserForTesting(name
, groups
));
364 * Obtain credentials for the current process using the configured
365 * Kerberos keytab file and principal.
366 * @see User#login(org.apache.hadoop.conf.Configuration, String, String, String)
368 * @param conf the Configuration to use
369 * @param fileConfKey Configuration property key used to store the path
371 * @param principalConfKey Configuration property key used to store the
372 * principal name to login as
373 * @param localhost the local hostname
375 public static void login(Configuration conf
, String fileConfKey
,
376 String principalConfKey
, String localhost
) throws IOException
{
377 if (isSecurityEnabled()) {
378 SecurityUtil
.login(conf
, fileConfKey
, principalConfKey
, localhost
);
383 * Login through configured keytab and pricipal.
384 * @param keytabLocation location of keytab
385 * @param principalName principal in keytab
386 * @throws IOException exception from UserGroupInformation.loginUserFromKeytab
388 public static void login(String keytabLocation
, String principalName
)
390 if (isSecurityEnabled()) {
391 UserGroupInformation
.loginUserFromKeytab(principalName
, keytabLocation
);
396 * Returns the result of {@code UserGroupInformation.isSecurityEnabled()}.
398 public static boolean isSecurityEnabled() {
399 return UserGroupInformation
.isSecurityEnabled();
403 public static class TestingGroups
extends Groups
{
404 public static final String TEST_CONF
= "hbase.group.service.for.test.only";
406 private final Map
<String
, List
<String
>> userToGroupsMapping
= new HashMap
<>();
407 private Groups underlyingImplementation
;
409 public TestingGroups(Groups underlyingImplementation
) {
410 super(new Configuration());
411 this.underlyingImplementation
= underlyingImplementation
;
415 public List
<String
> getGroups(String user
) throws IOException
{
416 List
<String
> result
= userToGroupsMapping
.get(user
);
418 if (result
== null) {
419 result
= underlyingImplementation
.getGroups(user
);
425 private void setUserGroups(String user
, String
[] groups
) {
426 userToGroupsMapping
.put(user
, Arrays
.asList(groups
));