1 /*
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
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
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.
17 */
18
19 package org.apache.hadoop.hbase;
20
21 import java.io.IOException;
22 import java.net.UnknownHostException;
23
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.apache.hadoop.conf.Configuration;
27 import org.apache.hadoop.hbase.classification.InterfaceAudience;
28 import org.apache.hadoop.hbase.classification.InterfaceStability;
29 import org.apache.hadoop.hbase.security.UserProvider;
30 import org.apache.hadoop.hbase.util.DNS;
31 import org.apache.hadoop.hbase.util.Strings;
32 import org.apache.hadoop.security.UserGroupInformation;
33
34 /**
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.
38 *
39 * Callers must ensure:
40 *
41 * <ul>
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
45 * </ul>
46 *
47 * <pre>
48 * {@code
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);
56 * }
57 * try {
58 * // do application work
59 * } finally {
60 * if (choreService != null) {
61 * choreService.shutdown();
62 * }
63 * }
64 * }
65 * </pre>
66 *
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.
69 */
70 @InterfaceAudience.Public
71 @InterfaceStability.Stable
72 public class AuthUtil {
73 private static final Log LOG = LogFactory.getLog(AuthUtil.class);
74
75 /** Prefix character to denote group names */
76 private static final String GROUP_PREFIX = "@";
77
78 private AuthUtil() {
79 super();
80 }
81
82 /**
83 * Checks if security is enabled and if so, launches chore for refreshing kerberos ticket.
84 * @param conf the hbase service configuration
85 * @return a ScheduledChore for renewals, if needed, and null otherwise.
86 */
87 public static ScheduledChore getAuthChore(Configuration conf) throws IOException {
88 UserProvider userProvider = UserProvider.instantiate(conf);
89 // login the principal (if using secure Hadoop)
90 boolean securityEnabled =
91 userProvider.isHadoopSecurityEnabled() && userProvider.isHBaseSecurityEnabled();
92 if (!securityEnabled) return null;
93 String host = null;
94 try {
95 host = Strings.domainNamePointerToHostName(DNS.getDefaultHost(
96 conf.get("hbase.client.dns.interface", "default"),
97 conf.get("hbase.client.dns.nameserver", "default")));
98 userProvider.login("hbase.client.keytab.file", "hbase.client.kerberos.principal", host);
99 } catch (UnknownHostException e) {
100 LOG.error("Error resolving host name: " + e.getMessage(), e);
101 throw e;
102 } catch (IOException e) {
103 LOG.error("Error while trying to perform the initial login: " + e.getMessage(), e);
104 throw e;
105 }
106
107 final UserGroupInformation ugi = userProvider.getCurrent().getUGI();
108 Stoppable stoppable = new Stoppable() {
109 private volatile boolean isStopped = false;
110
111 @Override
112 public void stop(String why) {
113 isStopped = true;
114 }
115
116 @Override
117 public boolean isStopped() {
118 return isStopped;
119 }
120 };
121
122 // if you're in debug mode this is useful to avoid getting spammed by the getTGT()
123 // you can increase this, keeping in mind that the default refresh window is 0.8
124 // e.g. 5min tgt * 0.8 = 4min refresh so interval is better be way less than 1min
125 final int CHECK_TGT_INTERVAL = 30 * 1000; // 30sec
126
127 ScheduledChore refreshCredentials =
128 new ScheduledChore("RefreshCredentials", stoppable, CHECK_TGT_INTERVAL) {
129 @Override
130 protected void chore() {
131 try {
132 ugi.checkTGTAndReloginFromKeytab();
133 } catch (IOException e) {
134 LOG.error("Got exception while trying to refresh credentials: " + e.getMessage(), e);
135 }
136 }
137 };
138
139 return refreshCredentials;
140 }
141
142 /**
143 * Returns whether or not the given name should be interpreted as a group
144 * principal. Currently this simply checks if the name starts with the
145 * special group prefix character ("@").
146 */
147 @InterfaceAudience.Private
148 public static boolean isGroupPrincipal(String name) {
149 return name != null && name.startsWith(GROUP_PREFIX);
150 }
151
152 /**
153 * Returns the actual name for a group principal (stripped of the
154 * group prefix).
155 */
156 @InterfaceAudience.Private
157 public static String getGroupName(String aclKey) {
158 if (!isGroupPrincipal(aclKey)) {
159 return aclKey;
160 }
161
162 return aclKey.substring(GROUP_PREFIX.length());
163 }
164
165 /**
166 * Returns the group entry with the group prefix for a group principal.
167 */
168 @InterfaceAudience.Private
169 public static String toGroupEntry(String name) {
170 return GROUP_PREFIX + name;
171 }
172 }