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}