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 */ 018package org.apache.hadoop.hbase.security; 019 020import java.io.ByteArrayInputStream; 021import java.io.DataInputStream; 022import java.io.IOException; 023import java.security.PrivilegedExceptionAction; 024import java.util.Map; 025 026import javax.security.auth.callback.Callback; 027import javax.security.auth.callback.CallbackHandler; 028import javax.security.auth.callback.NameCallback; 029import javax.security.auth.callback.PasswordCallback; 030import javax.security.auth.callback.UnsupportedCallbackException; 031import javax.security.sasl.AuthorizeCallback; 032import javax.security.sasl.RealmCallback; 033import javax.security.sasl.Sasl; 034import javax.security.sasl.SaslException; 035import javax.security.sasl.SaslServer; 036 037import org.apache.yetus.audience.InterfaceAudience; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040import org.apache.hadoop.security.UserGroupInformation; 041import org.apache.hadoop.security.token.SecretManager; 042import org.apache.hadoop.security.token.SecretManager.InvalidToken; 043import org.apache.hadoop.security.token.TokenIdentifier; 044 045/** 046 * A utility class that encapsulates SASL logic for RPC server. Copied from 047 * <code>org.apache.hadoop.security</code> 048 */ 049@InterfaceAudience.Private 050public class HBaseSaslRpcServer { 051 052 private static final Logger LOG = LoggerFactory.getLogger(HBaseSaslRpcServer.class); 053 054 private final SaslServer saslServer; 055 056 private UserGroupInformation attemptingUser; // user name before auth 057 058 public HBaseSaslRpcServer(AuthMethod method, Map<String, String> saslProps, 059 SecretManager<TokenIdentifier> secretManager) throws IOException { 060 switch (method) { 061 case DIGEST: 062 if (secretManager == null) { 063 throw new AccessDeniedException("Server is not configured to do DIGEST authentication."); 064 } 065 saslServer = Sasl.createSaslServer(AuthMethod.DIGEST.getMechanismName(), null, 066 SaslUtil.SASL_DEFAULT_REALM, saslProps, new SaslDigestCallbackHandler(secretManager)); 067 break; 068 case KERBEROS: 069 UserGroupInformation current = UserGroupInformation.getCurrentUser(); 070 String fullName = current.getUserName(); 071 if (LOG.isDebugEnabled()) { 072 LOG.debug("Kerberos principal name is " + fullName); 073 } 074 String[] names = SaslUtil.splitKerberosName(fullName); 075 if (names.length != 3) { 076 throw new AccessDeniedException( 077 "Kerberos principal name does NOT have the expected " + "hostname part: " + fullName); 078 } 079 try { 080 saslServer = current.doAs(new PrivilegedExceptionAction<SaslServer>() { 081 @Override 082 public SaslServer run() throws SaslException { 083 return Sasl.createSaslServer(AuthMethod.KERBEROS.getMechanismName(), names[0], 084 names[1], saslProps, new SaslGssCallbackHandler()); 085 } 086 }); 087 } catch (InterruptedException e) { 088 // should not happen 089 throw new AssertionError(e); 090 } 091 break; 092 default: 093 throw new IOException("Unknown authentication method " + method); 094 } 095 } 096 097 public boolean isComplete() { 098 return saslServer.isComplete(); 099 } 100 101 public byte[] evaluateResponse(byte[] response) throws SaslException { 102 return saslServer.evaluateResponse(response); 103 } 104 105 /** Release resources used by wrapped saslServer */ 106 public void dispose() { 107 SaslUtil.safeDispose(saslServer); 108 } 109 110 public UserGroupInformation getAttemptingUser() { 111 return attemptingUser; 112 } 113 114 public byte[] wrap(byte[] buf, int off, int len) throws SaslException { 115 return saslServer.wrap(buf, off, len); 116 } 117 118 public byte[] unwrap(byte[] buf, int off, int len) throws SaslException { 119 return saslServer.unwrap(buf, off, len); 120 } 121 122 public String getNegotiatedQop() { 123 return (String) saslServer.getNegotiatedProperty(Sasl.QOP); 124 } 125 126 public String getAuthorizationID() { 127 return saslServer.getAuthorizationID(); 128 } 129 130 public static <T extends TokenIdentifier> T getIdentifier(String id, 131 SecretManager<T> secretManager) throws InvalidToken { 132 byte[] tokenId = SaslUtil.decodeIdentifier(id); 133 T tokenIdentifier = secretManager.createIdentifier(); 134 try { 135 tokenIdentifier.readFields(new DataInputStream(new ByteArrayInputStream(tokenId))); 136 } catch (IOException e) { 137 throw (InvalidToken) new InvalidToken("Can't de-serialize tokenIdentifier").initCause(e); 138 } 139 return tokenIdentifier; 140 } 141 142 /** CallbackHandler for SASL DIGEST-MD5 mechanism */ 143 private class SaslDigestCallbackHandler implements CallbackHandler { 144 private SecretManager<TokenIdentifier> secretManager; 145 146 public SaslDigestCallbackHandler(SecretManager<TokenIdentifier> secretManager) { 147 this.secretManager = secretManager; 148 } 149 150 private char[] getPassword(TokenIdentifier tokenid) throws InvalidToken { 151 return SaslUtil.encodePassword(secretManager.retrievePassword(tokenid)); 152 } 153 154 /** {@inheritDoc} */ 155 @Override 156 public void handle(Callback[] callbacks) throws InvalidToken, UnsupportedCallbackException { 157 NameCallback nc = null; 158 PasswordCallback pc = null; 159 AuthorizeCallback ac = null; 160 for (Callback callback : callbacks) { 161 if (callback instanceof AuthorizeCallback) { 162 ac = (AuthorizeCallback) callback; 163 } else if (callback instanceof NameCallback) { 164 nc = (NameCallback) callback; 165 } else if (callback instanceof PasswordCallback) { 166 pc = (PasswordCallback) callback; 167 } else if (callback instanceof RealmCallback) { 168 continue; // realm is ignored 169 } else { 170 throw new UnsupportedCallbackException(callback, "Unrecognized SASL DIGEST-MD5 Callback"); 171 } 172 } 173 if (pc != null) { 174 TokenIdentifier tokenIdentifier = getIdentifier(nc.getDefaultName(), secretManager); 175 char[] password = getPassword(tokenIdentifier); 176 UserGroupInformation user = tokenIdentifier.getUser(); // may throw exception 177 attemptingUser = user; 178 if (LOG.isTraceEnabled()) { 179 LOG.trace("SASL server DIGEST-MD5 callback: setting password " + "for client: " + 180 tokenIdentifier.getUser()); 181 } 182 pc.setPassword(password); 183 } 184 if (ac != null) { 185 String authid = ac.getAuthenticationID(); 186 String authzid = ac.getAuthorizationID(); 187 if (authid.equals(authzid)) { 188 ac.setAuthorized(true); 189 } else { 190 ac.setAuthorized(false); 191 } 192 if (ac.isAuthorized()) { 193 if (LOG.isTraceEnabled()) { 194 String username = getIdentifier(authzid, secretManager).getUser().getUserName(); 195 LOG.trace( 196 "SASL server DIGEST-MD5 callback: setting " + "canonicalized client ID: " + username); 197 } 198 ac.setAuthorizedID(authzid); 199 } 200 } 201 } 202 } 203 204 /** CallbackHandler for SASL GSSAPI Kerberos mechanism */ 205 private static class SaslGssCallbackHandler implements CallbackHandler { 206 207 /** {@inheritDoc} */ 208 @Override 209 public void handle(Callback[] callbacks) throws UnsupportedCallbackException { 210 AuthorizeCallback ac = null; 211 for (Callback callback : callbacks) { 212 if (callback instanceof AuthorizeCallback) { 213 ac = (AuthorizeCallback) callback; 214 } else { 215 throw new UnsupportedCallbackException(callback, "Unrecognized SASL GSSAPI Callback"); 216 } 217 } 218 if (ac != null) { 219 String authid = ac.getAuthenticationID(); 220 String authzid = ac.getAuthorizationID(); 221 if (authid.equals(authzid)) { 222 ac.setAuthorized(true); 223 } else { 224 ac.setAuthorized(false); 225 } 226 if (ac.isAuthorized()) { 227 if (LOG.isDebugEnabled()) { 228 LOG.debug( 229 "SASL server GSSAPI callback: setting " + "canonicalized client ID: " + authzid); 230 } 231 ac.setAuthorizedID(authzid); 232 } 233 } 234 } 235 } 236}