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}