001package org.apache.hadoop.security.rpcauth; 002 003import org.apache.commons.logging.Log; 004import org.apache.commons.logging.LogFactory; 005import org.apache.hadoop.ipc.Server; 006import org.apache.hadoop.ipc.protobuf.IpcConnectionContextProtos.UserInformationProto.Builder; 007import org.apache.hadoop.security.AccessControlException; 008import org.apache.hadoop.security.SaslRpcServer; 009import org.apache.hadoop.security.UserGroupInformation; 010import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; 011import org.apache.hadoop.security.authentication.util.KerberosUtil; 012 013import javax.security.auth.callback.Callback; 014import javax.security.auth.callback.CallbackHandler; 015import javax.security.auth.callback.UnsupportedCallbackException; 016import javax.security.sasl.AuthorizeCallback; 017import javax.security.sasl.Sasl; 018import javax.security.sasl.SaslClient; 019import javax.security.sasl.SaslException; 020import javax.security.sasl.SaslServer; 021import java.io.IOException; 022import java.security.PrivilegedExceptionAction; 023import java.util.Map; 024 025public final class KerberosAuthMethod extends RpcAuthMethod { 026 public static final Log LOG = LogFactory.getLog(KerberosAuthMethod.class); 027 028 static final RpcAuthMethod INSTANCE = new KerberosAuthMethod(); 029 private KerberosAuthMethod() { 030 super((byte) 81, "kerberos", "GSSAPI", 031 AuthenticationMethod.KERBEROS); 032 } 033 034 private static final String[] LOGIN_MODULES = { 035 KerberosUtil.getKrb5LoginModuleName(), 036 "com.sun.security.auth.module.Krb5LoginModule" 037 }; 038 039 @Override 040 public String[] loginModules() { 041 return LOGIN_MODULES; 042 } 043 044 @Override 045 public UserGroupInformation getUser(final UserGroupInformation ticket) { 046 return (ticket.getRealUser() != null) ? ticket.getRealUser() : ticket; 047 } 048 049 @Override 050 public void writeUGI(UserGroupInformation ugi, Builder ugiProto) { 051 // Send effective user for Kerberos auth 052 ugiProto.setEffectiveUser(ugi.getUserName()); 053 } 054 055 @Override 056 public boolean isSasl() { 057 return true; 058 } 059 060 public boolean isNegotiable() { 061 return true; 062 } 063 064 @Override 065 public String getProtocol() throws IOException { 066 String fullName = UserGroupInformation.getCurrentUser().getUserName(); 067 if (LOG.isDebugEnabled()) { 068 LOG.debug("Kerberos principal name is " + fullName); 069 } 070 String[] parts = fullName.split("[/@]", 3); 071 return parts.length > 1 ? parts[0] : ""; 072 } 073 074 @Override 075 public String getServerId() throws IOException { 076 String fullName = UserGroupInformation.getCurrentUser().getUserName(); 077 if (LOG.isDebugEnabled()) { 078 LOG.debug("Kerberos principal name is " + fullName); 079 } 080 String[] parts = fullName.split("[/@]", 3); 081 return (parts.length < 2) ? "" : parts[1]; 082 } 083 084 @Override 085 public SaslClient createSaslClient(final Map<String, Object> saslProperties) 086 throws IOException { 087 String serverPrincipal = (String) 088 saslProperties.get(SaslRpcServer.SASL_KERBEROS_PRINCIPAL); 089 090 if (LOG.isDebugEnabled()) { 091 LOG.debug("Creating SASL " + mechanismName 092 + " client. Server's Kerberos principal name is " 093 + serverPrincipal); 094 } 095 if (serverPrincipal == null || serverPrincipal.length() == 0) { 096 throw new IOException( 097 "Failed to specify server's Kerberos principal name"); 098 } 099 String names[] = splitKerberosName(serverPrincipal); 100 if (names.length != 3) { 101 throw new IOException( 102 "Kerberos principal name does NOT have the expected hostname part: " 103 + serverPrincipal); 104 } 105 return Sasl.createSaslClient(new String[] {mechanismName}, 106 null, names[0], names[1], saslProperties, null); 107 } 108 109 @Override 110 public SaslServer createSaslServer(final Server.Connection connection, 111 final Map<String, Object> saslProperties) 112 throws IOException, InterruptedException { 113 final UserGroupInformation current = UserGroupInformation.getCurrentUser(); 114 String fullName = current.getUserName(); 115 if (LOG.isDebugEnabled()) 116 LOG.debug("Kerberos principal name is " + fullName); 117 final String names[] = splitKerberosName(fullName); 118 if (names.length != 3) { 119 throw new AccessControlException( 120 "Kerberos principal name does NOT have the expected " 121 + "hostname part: " + fullName); 122 } 123 return current.doAs( 124 new PrivilegedExceptionAction<SaslServer>() { 125 @Override 126 public SaslServer run() throws SaslException { 127 return Sasl.createSaslServer(mechanismName, names[0], names[1], 128 saslProperties, new SaslGssCallbackHandler()); 129 } 130 }); 131 } 132 133 @Override 134 public synchronized boolean shouldReLogin() throws IOException { 135 UserGroupInformation loginUser = UserGroupInformation.getLoginUser(); 136 UserGroupInformation currentUser = 137 UserGroupInformation.getCurrentUser(); 138 UserGroupInformation realUser = currentUser.getRealUser(); 139 if (loginUser != null && 140 //Make sure user logged in using Kerberos either keytab or TGT 141 loginUser.hasKerberosCredentials() && 142 // relogin only in case it is the login user (e.g. JT) 143 // or superuser (like oozie). 144 (loginUser.equals(currentUser) || loginUser.equals(realUser)) 145 ) { 146 return true; 147 } 148 return false; 149 } 150 151 @Override 152 public void reLogin() throws IOException { 153 if (UserGroupInformation.isLoginKeytabBased()) { 154 UserGroupInformation.getLoginUser().reloginFromKeytab(); 155 } else { 156 UserGroupInformation.getLoginUser().reloginFromTicketCache(); 157 } 158 } 159 160 /** Splitting fully qualified Kerberos name into parts */ 161 public static String[] splitKerberosName(String fullName) { 162 return fullName.split("[/@]"); 163 } 164 165 /** CallbackHandler for SASL GSSAPI Kerberos mechanism */ 166 public static class SaslGssCallbackHandler implements CallbackHandler { 167 168 /** {@inheritDoc} */ 169 @Override 170 public void handle(Callback[] callbacks) throws 171 UnsupportedCallbackException { 172 AuthorizeCallback ac = null; 173 for (Callback callback : callbacks) { 174 if (callback instanceof AuthorizeCallback) { 175 ac = (AuthorizeCallback) callback; 176 } else { 177 throw new UnsupportedCallbackException(callback, 178 "Unrecognized SASL GSSAPI Callback"); 179 } 180 } 181 if (ac != null) { 182 String authid = ac.getAuthenticationID(); 183 String authzid = ac.getAuthorizationID(); 184 if (authid.equals(authzid)) { 185 ac.setAuthorized(true); 186 } else { 187 ac.setAuthorized(false); 188 } 189 if (ac.isAuthorized()) { 190 if (SaslRpcServer.LOG.isDebugEnabled()) 191 SaslRpcServer.LOG.debug("SASL server GSSAPI callback: setting " 192 + "canonicalized client ID: " + authzid); 193 ac.setAuthorizedID(authzid); 194 } 195 } 196 } 197 } 198}