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;
020
021import java.io.DataInput;
022import java.io.DataOutput;
023import java.io.IOException;
024import java.security.Security;
025import java.util.ArrayList;
026import java.util.Enumeration;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030import javax.security.auth.callback.CallbackHandler;
031import javax.security.sasl.Sasl;
032import javax.security.sasl.SaslException;
033import javax.security.sasl.SaslServer;
034import javax.security.sasl.SaslServerFactory;
035
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038import org.apache.hadoop.classification.InterfaceAudience;
039import org.apache.hadoop.classification.InterfaceStability;
040import org.apache.hadoop.conf.Configuration;
041import org.apache.hadoop.ipc.Server;
042import org.apache.hadoop.ipc.Server.Connection;
043import org.apache.hadoop.security.rpcauth.DigestAuthMethod;
044import org.apache.hadoop.security.rpcauth.KerberosAuthMethod;
045import org.apache.hadoop.security.rpcauth.RpcAuthMethod;
046import org.apache.hadoop.security.rpcauth.RpcAuthRegistry;
047import org.apache.hadoop.security.token.SecretManager;
048import org.apache.hadoop.security.token.SecretManager.InvalidToken;
049import org.apache.hadoop.security.token.TokenIdentifier;
050
051/**
052 * A utility class for dealing with SASL on RPC server
053 */
054@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
055@InterfaceStability.Evolving
056public class SaslRpcServer {
057  public static final Log LOG = LogFactory.getLog(SaslRpcServer.class);
058  public static final String SASL_DEFAULT_REALM = "default";
059  public static final String SASL_AUTH_SECRET_MANAGER = "org.apache.hadoop.auth.secret.manager";
060  public static final String SASL_KERBEROS_PRINCIPAL = "org.apache.hadoop.auth.kerberos.principal";
061  public static final String SASL_AUTH_TOKEN = "org.apache.hadoop.auth.token";
062
063  public static enum QualityOfProtection {
064    AUTHENTICATION("auth"),
065    INTEGRITY("auth-int"),
066    PRIVACY("auth-conf");
067    
068    public final String saslQop;
069    
070    private QualityOfProtection(String saslQop) {
071      this.saslQop = saslQop;
072    }
073    
074    public String getSaslQop() {
075      return saslQop;
076    }
077  }
078
079  public RpcAuthMethod authMethod;
080  public String mechanism;
081  public String protocol;
082  public String serverId;
083
084  @InterfaceAudience.Private
085  @InterfaceStability.Unstable
086  public SaslRpcServer(RpcAuthMethod authMethod) throws IOException {
087    this.authMethod = authMethod;
088    mechanism = authMethod.getMechanismName();
089    if (authMethod.equals(RpcAuthRegistry.SIMPLE)) {
090      return; // no sasl for simple
091    }
092    protocol = authMethod.getProtocol();
093    serverId = authMethod.getServerId();
094  }
095  
096  @InterfaceAudience.Private
097  @InterfaceStability.Unstable
098  public SaslServer create(final Connection connection,
099                           Map<String,Object> saslProperties,
100                           SecretManager<TokenIdentifier> secretManager
101      ) throws IOException, InterruptedException {
102    if (secretManager != null) {
103      saslProperties.put(SaslRpcServer.SASL_AUTH_SECRET_MANAGER, secretManager);
104    }
105    if (LOG.isDebugEnabled()) {
106      LOG.debug("creating SaslServer for authMethod: " + authMethod);
107    }
108    return authMethod.createSaslServer(connection, saslProperties);
109  }
110
111  public static void init(Configuration conf) {
112    Security.addProvider(new SaslPlainServer.SecurityProvider());
113  }
114  
115  static String encodeIdentifier(byte[] identifier) {
116    return DigestAuthMethod.encodeIdentifier(identifier);
117  }
118
119  static byte[] decodeIdentifier(String identifier) {
120    return DigestAuthMethod.decodeIdentifier(identifier);
121  }
122
123  public static <T extends TokenIdentifier> T getIdentifier(String id,
124      SecretManager<T> secretManager) throws InvalidToken {
125    return DigestAuthMethod.getIdentifier(id, secretManager);
126  }
127
128  static char[] encodePassword(byte[] password) {
129    return DigestAuthMethod.encodePassword(password);
130  }
131
132  /** Splitting fully qualified Kerberos name into parts */
133  public static String[] splitKerberosName(String fullName) {
134    return KerberosAuthMethod.splitKerberosName(fullName);
135  }
136
137  /** Authentication method */
138  //TODO : Deprecate this after moving all the tests to use UGI.AuthenticationMethod
139  @InterfaceStability.Evolving
140  public static enum AuthMethod {
141    SIMPLE((byte) 80, ""),
142    KERBEROS((byte) 81, "GSSAPI"),
143    @Deprecated
144    DIGEST((byte) 82, "DIGEST-MD5"),
145    TOKEN((byte) 82, "DIGEST-MD5"),
146    PLAIN((byte) 83, "PLAIN");
147
148    /** The code for this method. */
149    public final byte code;
150    public final String mechanismName;
151
152    private AuthMethod(byte code, String mechanismName) { 
153      this.code = code;
154      this.mechanismName = mechanismName;
155    }
156
157    private static final int FIRST_CODE = values()[0].code;
158
159    /** Return the object represented by the code. */
160    private static AuthMethod valueOf(byte code) {
161      final int i = (code & 0xff) - FIRST_CODE;
162      return i < 0 || i >= values().length ? null : values()[i];
163    }
164
165    /** Return the SASL mechanism name */
166    public String getMechanismName() {
167      return mechanismName;
168    }
169
170    /** Read from in */
171    public static AuthMethod read(DataInput in) throws IOException {
172      return valueOf(in.readByte());
173    }
174
175    /** Write to out */
176    public void write(DataOutput out) throws IOException {
177      out.write(code);
178    }
179  };
180
181  /** CallbackHandler for SASL DIGEST-MD5 mechanism */
182  @InterfaceStability.Evolving
183  public static class SaslDigestCallbackHandler
184      extends org.apache.hadoop.security.rpcauth.DigestAuthMethod.SaslDigestCallbackHandler {
185    public SaslDigestCallbackHandler(
186        SecretManager<TokenIdentifier> secretManager,
187        Server.Connection connection) {
188      super(secretManager, connection);
189    }
190  }
191
192  /** CallbackHandler for SASL GSSAPI Kerberos mechanism */
193  @InterfaceStability.Evolving
194  public static class SaslGssCallbackHandler
195      extends org.apache.hadoop.security.rpcauth.KerberosAuthMethod.SaslGssCallbackHandler {
196  }
197
198  // TODO: Consider using this inside RpcAuthMethod implementations.
199  // Sasl.createSaslServer is 100-200X slower than caching the factories!
200  private static class FastSaslServerFactory implements SaslServerFactory {
201    private final Map<String,List<SaslServerFactory>> factoryCache =
202        new HashMap<String,List<SaslServerFactory>>();
203
204    FastSaslServerFactory(Map<String,?> props) {
205      final Enumeration<SaslServerFactory> factories =
206          Sasl.getSaslServerFactories();
207      while (factories.hasMoreElements()) {
208        SaslServerFactory factory = factories.nextElement();
209        for (String mech : factory.getMechanismNames(props)) {
210          if (!factoryCache.containsKey(mech)) {
211            factoryCache.put(mech, new ArrayList<SaslServerFactory>());
212          }
213          factoryCache.get(mech).add(factory);
214        }
215      }
216    }
217
218    @Override
219    public SaslServer createSaslServer(String mechanism, String protocol,
220        String serverName, Map<String,?> props, CallbackHandler cbh)
221        throws SaslException {
222      SaslServer saslServer = null;
223      List<SaslServerFactory> factories = factoryCache.get(mechanism);
224      if (factories != null) {
225        for (SaslServerFactory factory : factories) {
226          saslServer = factory.createSaslServer(
227              mechanism, protocol, serverName, props, cbh);
228          if (saslServer != null) {
229            break;
230          }
231        }
232      }
233      return saslServer;
234    }
235
236    @Override
237    public String[] getMechanismNames(Map<String, ?> props) {
238      return factoryCache.keySet().toArray(new String[0]);
239    }
240  }
241}