001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.hbase; 020 021import java.io.IOException; 022import java.net.UnknownHostException; 023 024import org.apache.hadoop.conf.Configuration; 025import org.apache.hadoop.hbase.security.User; 026import org.apache.hadoop.hbase.security.UserProvider; 027import org.apache.hadoop.hbase.util.DNS; 028import org.apache.hadoop.hbase.util.Strings; 029import org.apache.hadoop.security.UserGroupInformation; 030import org.apache.yetus.audience.InterfaceAudience; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034/** 035 * Utility methods for helping with security tasks. Downstream users 036 * may rely on this class to handle authenticating via keytab where 037 * long running services need access to a secure HBase cluster. 038 * 039 * Callers must ensure: 040 * 041 * <ul> 042 * <li>HBase configuration files are in the Classpath 043 * <li>hbase.client.keytab.file points to a valid keytab on the local filesystem 044 * <li>hbase.client.kerberos.principal gives the Kerberos principal to use 045 * </ul> 046 * 047 * <pre> 048 * {@code 049 * ChoreService choreService = null; 050 * // Presumes HBase configuration files are on the classpath 051 * final Configuration conf = HBaseConfiguration.create(); 052 * final ScheduledChore authChore = AuthUtil.getAuthChore(conf); 053 * if (authChore != null) { 054 * choreService = new ChoreService("MY_APPLICATION"); 055 * choreService.scheduleChore(authChore); 056 * } 057 * try { 058 * // do application work 059 * } finally { 060 * if (choreService != null) { 061 * choreService.shutdown(); 062 * } 063 * } 064 * } 065 * </pre> 066 * 067 * See the "Running Canary in a Kerberos-enabled Cluster" section of the HBase Reference Guide for 068 * an example of configuring a user of this Auth Chore to run on a secure cluster. 069 * <pre> 070 * </pre> 071 * This class will be internal used only from 2.2.0 version, and will transparently work 072 * for kerberized applications. For more, please refer 073 * <a href="http://hbase.apache.org/book.html#hbase.secure.configuration">Client-side Configuration for Secure Operation</a> 074 * 075 * @deprecated since 2.2.0, to be marked as 076 * {@link org.apache.yetus.audience.InterfaceAudience.Private} in 4.0.0. 077 * @see <a href="https://issues.apache.org/jira/browse/HBASE-20886">HBASE-20886</a> 078 */ 079@Deprecated 080@InterfaceAudience.Public 081public final class AuthUtil { 082 private static final Logger LOG = LoggerFactory.getLogger(AuthUtil.class); 083 084 /** Prefix character to denote group names */ 085 private static final String GROUP_PREFIX = "@"; 086 087 /** Client keytab file */ 088 public static final String HBASE_CLIENT_KEYTAB_FILE = "hbase.client.keytab.file"; 089 090 /** Client principal */ 091 public static final String HBASE_CLIENT_KERBEROS_PRINCIPAL = "hbase.client.keytab.principal"; 092 093 private AuthUtil() { 094 super(); 095 } 096 097 /** 098 * For kerberized cluster, return login user (from kinit or from keytab if specified). 099 * For non-kerberized cluster, return system user. 100 * @param conf configuartion file 101 * @return user 102 * @throws IOException login exception 103 */ 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(); 109 110 if (securityOn) { 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. 120 if (fromKeytab) { 121 return checkPrincipalMatch(conf, user.getUGI().getUserName()) ? user : 122 loginFromKeytabAndReturnUser(provider); 123 } 124 return user; 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); 128 } 129 } 130 return user; 131 } 132 133 private static boolean checkPrincipalMatch(Configuration conf, String loginUserName) { 134 String configuredUserName = conf.get(HBASE_CLIENT_KERBEROS_PRINCIPAL); 135 boolean match = configuredUserName.equals(loginUserName); 136 if (!match) { 137 LOG.warn("Trying to login with a different user: {}, existed user is {}.", 138 configuredUserName, loginUserName); 139 } 140 return match; 141 } 142 143 private static User loginFromKeytabAndReturnUser(UserProvider provider) throws IOException { 144 try { 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, 149 ioe.getMessage()); 150 throw ioe; 151 } 152 return provider.getCurrent(); 153 } 154 155 /** 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. 159 * <p> 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 163 * @return user 164 * @throws IOException login exception 165 */ 166 private static User loginClientAsService(Configuration conf) throws IOException { 167 UserProvider provider = UserProvider.instantiate(conf); 168 if (provider.isHBaseSecurityEnabled() && provider.isHadoopSecurityEnabled()) { 169 try { 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); 175 } 176 } catch (UnknownHostException e) { 177 LOG.error("Error resolving host name: " + e.getMessage(), e); 178 throw e; 179 } catch (IOException e) { 180 LOG.error("Error while trying to perform the initial login: " + e.getMessage(), e); 181 throw e; 182 } 183 } 184 return provider.getCurrent(); 185 } 186 187 /** 188 * Checks if security is enabled and if so, launches chore for refreshing kerberos ticket. 189 * @return a ScheduledChore for renewals. 190 */ 191 @InterfaceAudience.Private 192 public static ScheduledChore getAuthRenewalChore(final UserGroupInformation user) { 193 if (!user.hasKerberosCredentials()) { 194 return null; 195 } 196 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) { 203 @Override 204 protected void chore() { 205 try { 206 user.checkTGTAndReloginFromKeytab(); 207 } catch (IOException e) { 208 LOG.error("Got exception while trying to refresh credentials: " + e.getMessage(), e); 209 } 210 } 211 }; 212 } 213 214 /** 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> 221 */ 222 @Deprecated 223 public static ScheduledChore getAuthChore(Configuration conf) throws IOException { 224 User user = loginClientAsService(conf); 225 return getAuthRenewalChore(user.getUGI()); 226 } 227 228 private static Stoppable createDummyStoppable() { 229 return new Stoppable() { 230 private volatile boolean isStopped = false; 231 232 @Override 233 public void stop(String why) { 234 isStopped = true; 235 } 236 237 @Override 238 public boolean isStopped() { 239 return isStopped; 240 } 241 }; 242 } 243 244 /** 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 ("@"). 248 */ 249 @InterfaceAudience.Private 250 public static boolean isGroupPrincipal(String name) { 251 return name != null && name.startsWith(GROUP_PREFIX); 252 } 253 254 /** 255 * Returns the actual name for a group principal (stripped of the 256 * group prefix). 257 */ 258 @InterfaceAudience.Private 259 public static String getGroupName(String aclKey) { 260 if (!isGroupPrincipal(aclKey)) { 261 return aclKey; 262 } 263 264 return aclKey.substring(GROUP_PREFIX.length()); 265 } 266 267 /** 268 * Returns the group entry with the group prefix for a group principal. 269 */ 270 @InterfaceAudience.Private 271 public static String toGroupEntry(String name) { 272 return GROUP_PREFIX + name; 273 } 274}