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}