2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 package org
.apache
.hadoop
.hbase
;
21 import java
.io
.IOException
;
22 import java
.net
.UnknownHostException
;
24 import org
.apache
.hadoop
.conf
.Configuration
;
25 import org
.apache
.hadoop
.hbase
.security
.User
;
26 import org
.apache
.hadoop
.hbase
.security
.UserProvider
;
27 import org
.apache
.hadoop
.hbase
.util
.DNS
;
28 import org
.apache
.hadoop
.hbase
.util
.Strings
;
29 import org
.apache
.hadoop
.security
.UserGroupInformation
;
30 import org
.apache
.yetus
.audience
.InterfaceAudience
;
31 import org
.slf4j
.Logger
;
32 import org
.slf4j
.LoggerFactory
;
35 * Utility methods for helping with security tasks. Downstream users
36 * may rely on this class to handle authenticating via keytab where
37 * long running services need access to a secure HBase cluster.
39 * Callers must ensure:
42 * <li>HBase configuration files are in the Classpath
43 * <li>hbase.client.keytab.file points to a valid keytab on the local filesystem
44 * <li>hbase.client.kerberos.principal gives the Kerberos principal to use
49 * ChoreService choreService = null;
50 * // Presumes HBase configuration files are on the classpath
51 * final Configuration conf = HBaseConfiguration.create();
52 * final ScheduledChore authChore = AuthUtil.getAuthChore(conf);
53 * if (authChore != null) {
54 * choreService = new ChoreService("MY_APPLICATION");
55 * choreService.scheduleChore(authChore);
58 * // do application work
60 * if (choreService != null) {
61 * choreService.shutdown();
67 * See the "Running Canary in a Kerberos-enabled Cluster" section of the HBase Reference Guide for
68 * an example of configuring a user of this Auth Chore to run on a secure cluster.
71 * This class will be internal used only from 2.2.0 version, and will transparently work
72 * for kerberized applications. For more, please refer
73 * <a href="http://hbase.apache.org/book.html#hbase.secure.configuration">Client-side Configuration for Secure Operation</a>
75 * @deprecated since 2.2.0, to be marked as
76 * {@link org.apache.yetus.audience.InterfaceAudience.Private} in 4.0.0.
77 * @see <a href="https://issues.apache.org/jira/browse/HBASE-20886">HBASE-20886</a>
80 @InterfaceAudience.Public
81 public final class AuthUtil
{
82 private static final Logger LOG
= LoggerFactory
.getLogger(AuthUtil
.class);
84 /** Prefix character to denote group names */
85 private static final String GROUP_PREFIX
= "@";
87 /** Client keytab file */
88 public static final String HBASE_CLIENT_KEYTAB_FILE
= "hbase.client.keytab.file";
90 /** Client principal */
91 public static final String HBASE_CLIENT_KERBEROS_PRINCIPAL
= "hbase.client.keytab.principal";
98 * For kerberized cluster, return login user (from kinit or from keytab if specified).
99 * For non-kerberized cluster, return system user.
100 * @param conf configuartion file
102 * @throws IOException login exception
104 @InterfaceAudience.Private
105 public static User
loginClient(Configuration conf
) throws IOException
{
106 UserProvider provider
= UserProvider
.instantiate(conf
);
107 User user
= provider
.getCurrent();
108 boolean securityOn
= provider
.isHBaseSecurityEnabled() && provider
.isHadoopSecurityEnabled();
111 boolean fromKeytab
= provider
.shouldLoginFromKeytab();
112 if (user
.getUGI().hasKerberosCredentials()) {
113 // There's already a login user.
114 // But we should avoid misuse credentials which is a dangerous security issue,
115 // so here check whether user specified a keytab and a principal:
116 // 1. Yes, check if user principal match.
117 // a. match, just return.
118 // b. mismatch, login using keytab.
119 // 2. No, user may login through kinit, this is the old way, also just return.
121 return checkPrincipalMatch(conf
, user
.getUGI().getUserName()) ? user
:
122 loginFromKeytabAndReturnUser(provider
);
125 } else if (fromKeytab
) {
126 // Kerberos is on and client specify a keytab and principal, but client doesn't login yet.
127 return loginFromKeytabAndReturnUser(provider
);
133 private static boolean checkPrincipalMatch(Configuration conf
, String loginUserName
) {
134 String configuredUserName
= conf
.get(HBASE_CLIENT_KERBEROS_PRINCIPAL
);
135 boolean match
= configuredUserName
.equals(loginUserName
);
137 LOG
.warn("Trying to login with a different user: {}, existed user is {}.",
138 configuredUserName
, loginUserName
);
143 private static User
loginFromKeytabAndReturnUser(UserProvider provider
) throws IOException
{
145 provider
.login(HBASE_CLIENT_KEYTAB_FILE
, HBASE_CLIENT_KERBEROS_PRINCIPAL
);
146 } catch (IOException ioe
) {
147 LOG
.error("Error while trying to login as user {} through {}, with message: {}.",
148 HBASE_CLIENT_KERBEROS_PRINCIPAL
, HBASE_CLIENT_KEYTAB_FILE
,
152 return provider
.getCurrent();
156 * For kerberized cluster, return login user (from kinit or from keytab).
157 * Principal should be the following format: name/fully.qualified.domain.name@REALM.
158 * For non-kerberized cluster, return system user.
160 * NOT recommend to use to method unless you're sure what you're doing, it is for canary only.
161 * Please use User#loginClient.
162 * @param conf configuration file
164 * @throws IOException login exception
166 private static User
loginClientAsService(Configuration conf
) throws IOException
{
167 UserProvider provider
= UserProvider
.instantiate(conf
);
168 if (provider
.isHBaseSecurityEnabled() && provider
.isHadoopSecurityEnabled()) {
170 if (provider
.shouldLoginFromKeytab()) {
171 String host
= Strings
.domainNamePointerToHostName(DNS
.getDefaultHost(
172 conf
.get("hbase.client.dns.interface", "default"),
173 conf
.get("hbase.client.dns.nameserver", "default")));
174 provider
.login(HBASE_CLIENT_KEYTAB_FILE
, HBASE_CLIENT_KERBEROS_PRINCIPAL
, host
);
176 } catch (UnknownHostException e
) {
177 LOG
.error("Error resolving host name: " + e
.getMessage(), e
);
179 } catch (IOException e
) {
180 LOG
.error("Error while trying to perform the initial login: " + e
.getMessage(), e
);
184 return provider
.getCurrent();
188 * Checks if security is enabled and if so, launches chore for refreshing kerberos ticket.
189 * @return a ScheduledChore for renewals.
191 @InterfaceAudience.Private
192 public static ScheduledChore
getAuthRenewalChore(final UserGroupInformation user
) {
193 if (!user
.hasKerberosCredentials()) {
197 Stoppable stoppable
= createDummyStoppable();
198 // if you're in debug mode this is useful to avoid getting spammed by the getTGT()
199 // you can increase this, keeping in mind that the default refresh window is 0.8
200 // e.g. 5min tgt * 0.8 = 4min refresh so interval is better be way less than 1min
201 final int CHECK_TGT_INTERVAL
= 30 * 1000; // 30sec
202 return new ScheduledChore("RefreshCredentials", stoppable
, CHECK_TGT_INTERVAL
) {
204 protected void chore() {
206 user
.checkTGTAndReloginFromKeytab();
207 } catch (IOException e
) {
208 LOG
.error("Got exception while trying to refresh credentials: " + e
.getMessage(), e
);
215 * Checks if security is enabled and if so, launches chore for refreshing kerberos ticket.
216 * @param conf the hbase service configuration
217 * @return a ScheduledChore for renewals, if needed, and null otherwise.
218 * @deprecated Deprecated since 2.2.0, this method will be
219 * {@link org.apache.yetus.audience.InterfaceAudience.Private} use only after 4.0.0.
220 * @see <a href="https://issues.apache.org/jira/browse/HBASE-20886">HBASE-20886</a>
223 public static ScheduledChore
getAuthChore(Configuration conf
) throws IOException
{
224 User user
= loginClientAsService(conf
);
225 return getAuthRenewalChore(user
.getUGI());
228 private static Stoppable
createDummyStoppable() {
229 return new Stoppable() {
230 private volatile boolean isStopped
= false;
233 public void stop(String why
) {
238 public boolean isStopped() {
245 * Returns whether or not the given name should be interpreted as a group
246 * principal. Currently this simply checks if the name starts with the
247 * special group prefix character ("@").
249 @InterfaceAudience.Private
250 public static boolean isGroupPrincipal(String name
) {
251 return name
!= null && name
.startsWith(GROUP_PREFIX
);
255 * Returns the actual name for a group principal (stripped of the
258 @InterfaceAudience.Private
259 public static String
getGroupName(String aclKey
) {
260 if (!isGroupPrincipal(aclKey
)) {
264 return aclKey
.substring(GROUP_PREFIX
.length());
268 * Returns the group entry with the group prefix for a group principal.
270 @InterfaceAudience.Private
271 public static String
toGroupEntry(String name
) {
272 return GROUP_PREFIX
+ name
;