001package org.apache.hadoop.security.rpcauth; 002 003import java.io.ByteArrayInputStream; 004import java.io.DataInputStream; 005import java.io.IOException; 006import java.util.Map; 007 008import javax.security.auth.callback.Callback; 009import javax.security.auth.callback.CallbackHandler; 010import javax.security.auth.callback.NameCallback; 011import javax.security.auth.callback.PasswordCallback; 012import javax.security.auth.callback.UnsupportedCallbackException; 013import javax.security.sasl.AuthorizeCallback; 014import javax.security.sasl.RealmCallback; 015import javax.security.sasl.RealmChoiceCallback; 016import javax.security.sasl.Sasl; 017import javax.security.sasl.SaslClient; 018import javax.security.sasl.SaslServer; 019 020import org.apache.commons.codec.binary.Base64; 021import org.apache.commons.logging.Log; 022import org.apache.commons.logging.LogFactory; 023import org.apache.hadoop.ipc.Server; 024import org.apache.hadoop.security.AccessControlException; 025import org.apache.hadoop.security.SaslRpcServer; 026import org.apache.hadoop.security.UserGroupInformation; 027import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; 028import org.apache.hadoop.security.token.SecretManager; 029import org.apache.hadoop.security.token.SecretManager.InvalidToken; 030import org.apache.hadoop.security.token.Token; 031import org.apache.hadoop.security.token.TokenIdentifier; 032 033//TODO: rename this class to TokenAuthMethod and use the mechanism as "token" 034// This is because UGI.AuthenticationMethod doesn't have DIGEST anymore and 035// hadoop is deprecating SaslRpcServer.AuthMethod which has DIGEST already 036// deprecated. 037public final class DigestAuthMethod extends RpcAuthMethod { 038 private static final Log LOG = LogFactory.getLog(DigestAuthMethod.class); 039 040 static final RpcAuthMethod INSTANCE = new DigestAuthMethod(); 041 private DigestAuthMethod() { 042 super((byte) 82, "token", "DIGEST-MD5", AuthenticationMethod.TOKEN); 043 } 044 045 @Override 046 public boolean isProxyAllowed() { 047 return false; 048 } 049 050 @Override 051 @SuppressWarnings("unchecked") 052 public UserGroupInformation getAuthorizedUgi(String authorizedId, 053 SecretManager secretManager) throws IOException { 054 TokenIdentifier tokenId = getIdentifier(authorizedId, secretManager); 055 UserGroupInformation ugi = tokenId.getUser(); 056 if (ugi == null) { 057 throw new AccessControlException( 058 "Can't retrieve username from tokenIdentifier."); 059 } 060 ugi.addTokenIdentifier(tokenId); 061 return ugi; 062 } 063 064 @Override 065 public boolean isSasl() { 066 return true; 067 } 068 069 @Override 070 public String getProtocol() throws IOException { 071 return SaslRpcServer.SASL_DEFAULT_REALM; 072 } 073 074 @Override 075 public String getServerId() throws IOException { 076 return ""; 077 } 078 079 @Override 080 @SuppressWarnings("unchecked") 081 public SaslClient createSaslClient(final Map<String, Object> saslProperties) 082 throws IOException { 083 Token<? extends TokenIdentifier> token = (Token<? extends TokenIdentifier>) 084 saslProperties.get(SaslRpcServer.SASL_AUTH_TOKEN); 085 if (LOG.isDebugEnabled()) 086 LOG.debug("Creating SASL " + mechanismName 087 + " client to authenticate to service at " + token.getService()); 088 return Sasl.createSaslClient(new String[] { mechanismName }, 089 null, null, SaslRpcServer.SASL_DEFAULT_REALM, 090 saslProperties, new SaslClientCallbackHandler(token)); 091 } 092 093 @Override 094 @SuppressWarnings("unchecked") 095 public SaslServer createSaslServer(final Server.Connection connection, 096 final Map<String, Object> saslProperties) 097 throws IOException { 098 SecretManager<TokenIdentifier> secretManager = (SecretManager<TokenIdentifier>) 099 saslProperties.get(SaslRpcServer.SASL_AUTH_SECRET_MANAGER); 100 if (secretManager == null) { 101 throw new AccessControlException( 102 "Server is not configured to do DIGEST authentication."); 103 } 104 return Sasl.createSaslServer(mechanismName, null, 105 SaslRpcServer.SASL_DEFAULT_REALM, saslProperties, 106 new SaslDigestCallbackHandler(secretManager, connection)); 107 } 108 109 public static char[] encodePassword(byte[] password) { 110 return new String(Base64.encodeBase64(password)).toCharArray(); 111 } 112 113 public static <T extends TokenIdentifier> T getIdentifier(String id, 114 SecretManager<T> secretManager) throws InvalidToken { 115 byte[] tokenId = decodeIdentifier(id); 116 T tokenIdentifier = secretManager.createIdentifier(); 117 try { 118 tokenIdentifier.readFields(new DataInputStream(new ByteArrayInputStream( 119 tokenId))); 120 } catch (IOException e) { 121 throw (InvalidToken) new InvalidToken( 122 "Can't de-serialize tokenIdentifier").initCause(e); 123 } 124 return tokenIdentifier; 125 } 126 127 public static String encodeIdentifier(byte[] identifier) { 128 return new String(Base64.encodeBase64(identifier)); 129 } 130 131 public static byte[] decodeIdentifier(String identifier) { 132 return Base64.decodeBase64(identifier.getBytes()); 133 } 134 135 private static class SaslClientCallbackHandler implements CallbackHandler { 136 private final String userName; 137 private final char[] userPassword; 138 139 public SaslClientCallbackHandler(Token<? extends TokenIdentifier> token) { 140 this.userName = encodeIdentifier(token.getIdentifier()); 141 this.userPassword = encodePassword(token.getPassword()); 142 } 143 144 @Override 145 public void handle(Callback[] callbacks) 146 throws UnsupportedCallbackException { 147 NameCallback nc = null; 148 PasswordCallback pc = null; 149 RealmCallback rc = null; 150 for (Callback callback : callbacks) { 151 if (callback instanceof RealmChoiceCallback) { 152 continue; 153 } else if (callback instanceof NameCallback) { 154 nc = (NameCallback) callback; 155 } else if (callback instanceof PasswordCallback) { 156 pc = (PasswordCallback) callback; 157 } else if (callback instanceof RealmCallback) { 158 rc = (RealmCallback) callback; 159 } else { 160 throw new UnsupportedCallbackException(callback, 161 "Unrecognized SASL client callback"); 162 } 163 } 164 if (nc != null) { 165 if (LOG.isDebugEnabled()) 166 LOG.debug("SASL client callback: setting username: " + userName); 167 nc.setName(userName); 168 } 169 if (pc != null) { 170 if (LOG.isDebugEnabled()) 171 LOG.debug("SASL client callback: setting userPassword"); 172 pc.setPassword(userPassword); 173 } 174 if (rc != null) { 175 if (LOG.isDebugEnabled()) 176 LOG.debug("SASL client callback: setting realm: " 177 + rc.getDefaultText()); 178 rc.setText(rc.getDefaultText()); 179 } 180 } 181 } 182 183 /** CallbackHandler for SASL DIGEST-MD5 mechanism */ 184 public static class SaslDigestCallbackHandler implements CallbackHandler { 185 private SecretManager<TokenIdentifier> secretManager; 186 private Server.Connection connection; 187 188 public SaslDigestCallbackHandler(SecretManager<TokenIdentifier> secretManager, 189 Server.Connection connection) { 190 this.secretManager = secretManager; 191 this.connection = connection; 192 } 193 194 private char[] getPassword(TokenIdentifier tokenid) throws InvalidToken { 195 return encodePassword(secretManager.retrievePassword(tokenid)); 196 } 197 198 /** {@inheritDoc} */ 199 @Override 200 public void handle(Callback[] callbacks) throws InvalidToken, 201 UnsupportedCallbackException { 202 NameCallback nc = null; 203 PasswordCallback pc = null; 204 AuthorizeCallback ac = null; 205 for (Callback callback : callbacks) { 206 if (callback instanceof AuthorizeCallback) { 207 ac = (AuthorizeCallback) callback; 208 } else if (callback instanceof NameCallback) { 209 nc = (NameCallback) callback; 210 } else if (callback instanceof PasswordCallback) { 211 pc = (PasswordCallback) callback; 212 } else if (callback instanceof RealmCallback) { 213 continue; // realm is ignored 214 } else { 215 throw new UnsupportedCallbackException(callback, 216 "Unrecognized SASL DIGEST-MD5 Callback"); 217 } 218 } 219 if (pc != null) { 220 TokenIdentifier tokenIdentifier = getIdentifier(nc.getDefaultName(), secretManager); 221 char[] password = getPassword(tokenIdentifier); 222 UserGroupInformation user = null; 223 user = tokenIdentifier.getUser(); // may throw exception 224 connection.attemptingUser = user; 225 if (SaslRpcServer.LOG.isDebugEnabled()) { 226 SaslRpcServer.LOG.debug("SASL server DIGEST-MD5 callback: setting password " 227 + "for client: " + tokenIdentifier.getUser()); 228 } 229 pc.setPassword(password); 230 } 231 if (ac != null) { 232 String authid = ac.getAuthenticationID(); 233 String authzid = ac.getAuthorizationID(); 234 if (authid.equals(authzid)) { 235 ac.setAuthorized(true); 236 } else { 237 ac.setAuthorized(false); 238 } 239 if (ac.isAuthorized()) { 240 if (SaslRpcServer.LOG.isDebugEnabled()) { 241 String username = 242 getIdentifier(authzid, secretManager).getUser().getUserName(); 243 SaslRpcServer.LOG.debug("SASL server DIGEST-MD5 callback: setting " 244 + "canonicalized client ID: " + username); 245 } 246 ac.setAuthorizedID(authzid); 247 } 248 } 249 } 250 } 251}