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