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.UserProvider; 026import org.apache.hadoop.hbase.util.DNS; 027import org.apache.hadoop.hbase.util.Strings; 028import org.apache.hadoop.security.UserGroupInformation; 029import org.apache.yetus.audience.InterfaceAudience; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * Utility methods for helping with security tasks. Downstream users 035 * may rely on this class to handle authenticating via keytab where 036 * long running services need access to a secure HBase cluster. 037 * 038 * Callers must ensure: 039 * 040 * <ul> 041 * <li>HBase configuration files are in the Classpath 042 * <li>hbase.client.keytab.file points to a valid keytab on the local filesystem 043 * <li>hbase.client.kerberos.principal gives the Kerberos principal to use 044 * </ul> 045 * 046 * <pre> 047 * {@code 048 * ChoreService choreService = null; 049 * // Presumes HBase configuration files are on the classpath 050 * final Configuration conf = HBaseConfiguration.create(); 051 * final ScheduledChore authChore = AuthUtil.getAuthChore(conf); 052 * if (authChore != null) { 053 * choreService = new ChoreService("MY_APPLICATION"); 054 * choreService.scheduleChore(authChore); 055 * } 056 * try { 057 * // do application work 058 * } finally { 059 * if (choreService != null) { 060 * choreService.shutdown(); 061 * } 062 * } 063 * } 064 * </pre> 065 * 066 * See the "Running Canary in a Kerberos-enabled Cluster" section of the HBase Reference Guide for 067 * an example of configuring a user of this Auth Chore to run on a secure cluster. 068 */ 069@InterfaceAudience.Public 070public class AuthUtil { 071 private static final Logger LOG = LoggerFactory.getLogger(AuthUtil.class); 072 073 /** Prefix character to denote group names */ 074 private static final String GROUP_PREFIX = "@"; 075 076 private AuthUtil() { 077 super(); 078 } 079 080 /** 081 * Checks if security is enabled and if so, launches chore for refreshing kerberos ticket. 082 * @param conf the hbase service configuration 083 * @return a ScheduledChore for renewals, if needed, and null otherwise. 084 */ 085 public static ScheduledChore getAuthChore(Configuration conf) throws IOException { 086 UserProvider userProvider = UserProvider.instantiate(conf); 087 // login the principal (if using secure Hadoop) 088 boolean securityEnabled = 089 userProvider.isHadoopSecurityEnabled() && userProvider.isHBaseSecurityEnabled(); 090 if (!securityEnabled) return null; 091 String host = null; 092 try { 093 host = Strings.domainNamePointerToHostName(DNS.getDefaultHost( 094 conf.get("hbase.client.dns.interface", "default"), 095 conf.get("hbase.client.dns.nameserver", "default"))); 096 userProvider.login("hbase.client.keytab.file", "hbase.client.kerberos.principal", host); 097 } catch (UnknownHostException e) { 098 LOG.error("Error resolving host name: " + e.getMessage(), e); 099 throw e; 100 } catch (IOException e) { 101 LOG.error("Error while trying to perform the initial login: " + e.getMessage(), e); 102 throw e; 103 } 104 105 final UserGroupInformation ugi = userProvider.getCurrent().getUGI(); 106 Stoppable stoppable = new Stoppable() { 107 private volatile boolean isStopped = false; 108 109 @Override 110 public void stop(String why) { 111 isStopped = true; 112 } 113 114 @Override 115 public boolean isStopped() { 116 return isStopped; 117 } 118 }; 119 120 // if you're in debug mode this is useful to avoid getting spammed by the getTGT() 121 // you can increase this, keeping in mind that the default refresh window is 0.8 122 // e.g. 5min tgt * 0.8 = 4min refresh so interval is better be way less than 1min 123 final int CHECK_TGT_INTERVAL = 30 * 1000; // 30sec 124 125 ScheduledChore refreshCredentials = 126 new ScheduledChore("RefreshCredentials", stoppable, CHECK_TGT_INTERVAL) { 127 @Override 128 protected void chore() { 129 try { 130 ugi.checkTGTAndReloginFromKeytab(); 131 } catch (IOException e) { 132 LOG.error("Got exception while trying to refresh credentials: " + e.getMessage(), e); 133 } 134 } 135 }; 136 137 return refreshCredentials; 138 } 139 140 /** 141 * Returns whether or not the given name should be interpreted as a group 142 * principal. Currently this simply checks if the name starts with the 143 * special group prefix character ("@"). 144 */ 145 @InterfaceAudience.Private 146 public static boolean isGroupPrincipal(String name) { 147 return name != null && name.startsWith(GROUP_PREFIX); 148 } 149 150 /** 151 * Returns the actual name for a group principal (stripped of the 152 * group prefix). 153 */ 154 @InterfaceAudience.Private 155 public static String getGroupName(String aclKey) { 156 if (!isGroupPrincipal(aclKey)) { 157 return aclKey; 158 } 159 160 return aclKey.substring(GROUP_PREFIX.length()); 161 } 162 163 /** 164 * Returns the group entry with the group prefix for a group principal. 165 */ 166 @InterfaceAudience.Private 167 public static String toGroupEntry(String name) { 168 return GROUP_PREFIX + name; 169 } 170}