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";
93 /** Configuration to automatically try to renew keytab-based logins */
94 public static final String HBASE_CLIENT_AUTOMATIC_KEYTAB_RENEWAL_KEY
= "hbase.client.keytab.automatic.renewal";
95 public static final boolean HBASE_CLIENT_AUTOMATIC_KEYTAB_RENEWAL_DEFAULT
= true;
102 * For kerberized cluster, return login user (from kinit or from keytab if specified).
103 * For non-kerberized cluster, return system user.
104 * @param conf configuartion file
106 * @throws IOException login exception
108 @InterfaceAudience.Private
109 public static User
loginClient(Configuration conf
) throws IOException
{
110 UserProvider provider
= UserProvider
.instantiate(conf
);
111 User user
= provider
.getCurrent();
112 boolean securityOn
= provider
.isHBaseSecurityEnabled() && provider
.isHadoopSecurityEnabled();
115 boolean fromKeytab
= provider
.shouldLoginFromKeytab();
116 if (user
.getUGI().hasKerberosCredentials()) {
117 // There's already a login user.
118 // But we should avoid misuse credentials which is a dangerous security issue,
119 // so here check whether user specified a keytab and a principal:
120 // 1. Yes, check if user principal match.
121 // a. match, just return.
122 // b. mismatch, login using keytab.
123 // 2. No, user may login through kinit, this is the old way, also just return.
125 return checkPrincipalMatch(conf
, user
.getUGI().getUserName()) ? user
:
126 loginFromKeytabAndReturnUser(provider
);
129 } else if (fromKeytab
) {
130 // Kerberos is on and client specify a keytab and principal, but client doesn't login yet.
131 return loginFromKeytabAndReturnUser(provider
);
137 private static boolean checkPrincipalMatch(Configuration conf
, String loginUserName
) {
138 String configuredUserName
= conf
.get(HBASE_CLIENT_KERBEROS_PRINCIPAL
);
139 boolean match
= configuredUserName
.equals(loginUserName
);
141 LOG
.warn("Trying to login with a different user: {}, existed user is {}.",
142 configuredUserName
, loginUserName
);
147 private static User
loginFromKeytabAndReturnUser(UserProvider provider
) throws IOException
{
149 provider
.login(HBASE_CLIENT_KEYTAB_FILE
, HBASE_CLIENT_KERBEROS_PRINCIPAL
);
150 } catch (IOException ioe
) {
151 LOG
.error("Error while trying to login as user {} through {}, with message: {}.",
152 HBASE_CLIENT_KERBEROS_PRINCIPAL
, HBASE_CLIENT_KEYTAB_FILE
,
156 return provider
.getCurrent();
160 * For kerberized cluster, return login user (from kinit or from keytab).
161 * Principal should be the following format: name/fully.qualified.domain.name@REALM.
162 * For non-kerberized cluster, return system user.
164 * NOT recommend to use to method unless you're sure what you're doing, it is for canary only.
165 * Please use User#loginClient.
166 * @param conf configuration file
168 * @throws IOException login exception
170 private static User
loginClientAsService(Configuration conf
) throws IOException
{
171 UserProvider provider
= UserProvider
.instantiate(conf
);
172 if (provider
.isHBaseSecurityEnabled() && provider
.isHadoopSecurityEnabled()) {
174 if (provider
.shouldLoginFromKeytab()) {
175 String host
= Strings
.domainNamePointerToHostName(DNS
.getDefaultHost(
176 conf
.get("hbase.client.dns.interface", "default"),
177 conf
.get("hbase.client.dns.nameserver", "default")));
178 provider
.login(HBASE_CLIENT_KEYTAB_FILE
, HBASE_CLIENT_KERBEROS_PRINCIPAL
, host
);
180 } catch (UnknownHostException e
) {
181 LOG
.error("Error resolving host name: " + e
.getMessage(), e
);
183 } catch (IOException e
) {
184 LOG
.error("Error while trying to perform the initial login: " + e
.getMessage(), e
);
188 return provider
.getCurrent();
192 * Checks if security is enabled and if so, launches chore for refreshing kerberos ticket.
193 * @return a ScheduledChore for renewals.
195 @InterfaceAudience.Private
196 public static ScheduledChore
getAuthRenewalChore(final UserGroupInformation user
, Configuration conf
) {
197 if (!user
.hasKerberosCredentials() || !isAuthRenewalChoreEnabled(conf
)) {
201 Stoppable stoppable
= createDummyStoppable();
202 // if you're in debug mode this is useful to avoid getting spammed by the getTGT()
203 // you can increase this, keeping in mind that the default refresh window is 0.8
204 // e.g. 5min tgt * 0.8 = 4min refresh so interval is better be way less than 1min
205 final int CHECK_TGT_INTERVAL
= 30 * 1000; // 30sec
206 return new ScheduledChore("RefreshCredentials", stoppable
, CHECK_TGT_INTERVAL
) {
208 protected void chore() {
210 user
.checkTGTAndReloginFromKeytab();
211 } catch (IOException e
) {
212 LOG
.error("Got exception while trying to refresh credentials: " + e
.getMessage(), e
);
219 * Checks if security is enabled and if so, launches chore for refreshing kerberos ticket.
220 * @param conf the hbase service configuration
221 * @return a ScheduledChore for renewals, if needed, and null otherwise.
222 * @deprecated Deprecated since 2.2.0, this method will be
223 * {@link org.apache.yetus.audience.InterfaceAudience.Private} use only after 4.0.0.
224 * @see <a href="https://issues.apache.org/jira/browse/HBASE-20886">HBASE-20886</a>
227 public static ScheduledChore
getAuthChore(Configuration conf
) throws IOException
{
228 if (!isAuthRenewalChoreEnabled(conf
)) {
231 User user
= loginClientAsService(conf
);
232 return getAuthRenewalChore(user
.getUGI(), conf
);
235 private static Stoppable
createDummyStoppable() {
236 return new Stoppable() {
237 private volatile boolean isStopped
= false;
240 public void stop(String why
) {
245 public boolean isStopped() {
252 * Returns whether or not the given name should be interpreted as a group
253 * principal. Currently this simply checks if the name starts with the
254 * special group prefix character ("@").
256 @InterfaceAudience.Private
257 public static boolean isGroupPrincipal(String name
) {
258 return name
!= null && name
.startsWith(GROUP_PREFIX
);
262 * Returns the actual name for a group principal (stripped of the
265 @InterfaceAudience.Private
266 public static String
getGroupName(String aclKey
) {
267 if (!isGroupPrincipal(aclKey
)) {
271 return aclKey
.substring(GROUP_PREFIX
.length());
275 * Returns the group entry with the group prefix for a group principal.
277 @InterfaceAudience.Private
278 public static String
toGroupEntry(String name
) {
279 return GROUP_PREFIX
+ name
;
283 * Returns true if the chore to automatically renew Kerberos tickets (from
284 * keytabs) should be started. The default is true.
286 static boolean isAuthRenewalChoreEnabled(Configuration conf
) {
287 return conf
.getBoolean(HBASE_CLIENT_AUTOMATIC_KEYTAB_RENEWAL_KEY
,
288 HBASE_CLIENT_AUTOMATIC_KEYTAB_RENEWAL_DEFAULT
);