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