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 org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 021 022import java.io.IOException; 023import java.util.Map; 024 025import javax.security.auth.callback.Callback; 026import javax.security.auth.callback.CallbackHandler; 027import javax.security.auth.callback.NameCallback; 028import javax.security.auth.callback.PasswordCallback; 029import javax.security.auth.callback.UnsupportedCallbackException; 030import javax.security.sasl.RealmCallback; 031import javax.security.sasl.RealmChoiceCallback; 032import javax.security.sasl.Sasl; 033import javax.security.sasl.SaslClient; 034import javax.security.sasl.SaslException; 035 036import org.apache.yetus.audience.InterfaceAudience; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039import org.apache.hadoop.security.token.Token; 040import org.apache.hadoop.security.token.TokenIdentifier; 041 042/** 043 * A utility class that encapsulates SASL logic for RPC client. Copied from 044 * <code>org.apache.hadoop.security</code> 045 * @since 2.0.0 046 */ 047@InterfaceAudience.Private 048public abstract class AbstractHBaseSaslRpcClient { 049 050 private static final Logger LOG = LoggerFactory.getLogger(AbstractHBaseSaslRpcClient.class); 051 052 private static final byte[] EMPTY_TOKEN = new byte[0]; 053 054 protected final SaslClient saslClient; 055 056 protected final boolean fallbackAllowed; 057 058 protected final Map<String, String> saslProps; 059 060 /** 061 * Create a HBaseSaslRpcClient for an authentication method 062 * @param method the requested authentication method 063 * @param token token to use if needed by the authentication method 064 * @param serverPrincipal the server principal that we are trying to set the connection up to 065 * @param fallbackAllowed does the client allow fallback to simple authentication 066 * @throws IOException 067 */ 068 protected AbstractHBaseSaslRpcClient(AuthMethod method, Token<? extends TokenIdentifier> token, 069 String serverPrincipal, boolean fallbackAllowed) throws IOException { 070 this(method, token, serverPrincipal, fallbackAllowed, "authentication"); 071 } 072 073 /** 074 * Create a HBaseSaslRpcClient for an authentication method 075 * @param method the requested authentication method 076 * @param token token to use if needed by the authentication method 077 * @param serverPrincipal the server principal that we are trying to set the connection up to 078 * @param fallbackAllowed does the client allow fallback to simple authentication 079 * @param rpcProtection the protection level ("authentication", "integrity" or "privacy") 080 * @throws IOException 081 */ 082 protected AbstractHBaseSaslRpcClient(AuthMethod method, Token<? extends TokenIdentifier> token, 083 String serverPrincipal, boolean fallbackAllowed, String rpcProtection) throws IOException { 084 this.fallbackAllowed = fallbackAllowed; 085 saslProps = SaslUtil.initSaslProperties(rpcProtection); 086 switch (method) { 087 case DIGEST: 088 if (LOG.isDebugEnabled()) LOG.debug("Creating SASL " + AuthMethod.DIGEST.getMechanismName() 089 + " client to authenticate to service at " + token.getService()); 090 saslClient = createDigestSaslClient(new String[] { AuthMethod.DIGEST.getMechanismName() }, 091 SaslUtil.SASL_DEFAULT_REALM, new SaslClientCallbackHandler(token)); 092 break; 093 case KERBEROS: 094 if (LOG.isDebugEnabled()) { 095 LOG.debug("Creating SASL " + AuthMethod.KERBEROS.getMechanismName() 096 + " client. Server's Kerberos principal name is " + serverPrincipal); 097 } 098 if (serverPrincipal == null || serverPrincipal.length() == 0) { 099 throw new IOException("Failed to specify server's Kerberos principal name"); 100 } 101 String[] names = SaslUtil.splitKerberosName(serverPrincipal); 102 if (names.length != 3) { 103 throw new IOException( 104 "Kerberos principal does not have the expected format: " + serverPrincipal); 105 } 106 saslClient = createKerberosSaslClient( 107 new String[] { AuthMethod.KERBEROS.getMechanismName() }, names[0], names[1]); 108 break; 109 default: 110 throw new IOException("Unknown authentication method " + method); 111 } 112 if (saslClient == null) { 113 throw new IOException("Unable to find SASL client implementation"); 114 } 115 } 116 117 protected SaslClient createDigestSaslClient(String[] mechanismNames, String saslDefaultRealm, 118 CallbackHandler saslClientCallbackHandler) throws IOException { 119 return Sasl.createSaslClient(mechanismNames, null, null, saslDefaultRealm, saslProps, 120 saslClientCallbackHandler); 121 } 122 123 protected SaslClient createKerberosSaslClient(String[] mechanismNames, String userFirstPart, 124 String userSecondPart) throws IOException { 125 return Sasl.createSaslClient(mechanismNames, null, userFirstPart, userSecondPart, saslProps, 126 null); 127 } 128 129 public byte[] getInitialResponse() throws SaslException { 130 if (saslClient.hasInitialResponse()) { 131 return saslClient.evaluateChallenge(EMPTY_TOKEN); 132 } else { 133 return EMPTY_TOKEN; 134 } 135 } 136 137 public boolean isComplete() { 138 return saslClient.isComplete(); 139 } 140 141 public byte[] evaluateChallenge(byte[] challenge) throws SaslException { 142 return saslClient.evaluateChallenge(challenge); 143 } 144 145 /** Release resources used by wrapped saslClient */ 146 public void dispose() { 147 SaslUtil.safeDispose(saslClient); 148 } 149 150 @VisibleForTesting 151 static class SaslClientCallbackHandler implements CallbackHandler { 152 private final String userName; 153 private final char[] userPassword; 154 155 public SaslClientCallbackHandler(Token<? extends TokenIdentifier> token) { 156 this.userName = SaslUtil.encodeIdentifier(token.getIdentifier()); 157 this.userPassword = SaslUtil.encodePassword(token.getPassword()); 158 } 159 160 @Override 161 public void handle(Callback[] callbacks) throws UnsupportedCallbackException { 162 NameCallback nc = null; 163 PasswordCallback pc = null; 164 RealmCallback rc = null; 165 for (Callback callback : callbacks) { 166 if (callback instanceof RealmChoiceCallback) { 167 continue; 168 } else if (callback instanceof NameCallback) { 169 nc = (NameCallback) callback; 170 } else if (callback instanceof PasswordCallback) { 171 pc = (PasswordCallback) callback; 172 } else if (callback instanceof RealmCallback) { 173 rc = (RealmCallback) callback; 174 } else { 175 throw new UnsupportedCallbackException(callback, "Unrecognized SASL client callback"); 176 } 177 } 178 if (nc != null) { 179 if (LOG.isDebugEnabled()) { 180 LOG.debug("SASL client callback: setting username: " + userName); 181 } 182 nc.setName(userName); 183 } 184 if (pc != null) { 185 if (LOG.isDebugEnabled()) { 186 LOG.debug("SASL client callback: setting userPassword"); 187 } 188 pc.setPassword(userPassword); 189 } 190 if (rc != null) { 191 if (LOG.isDebugEnabled()) { 192 LOG.debug("SASL client callback: setting realm: " + rc.getDefaultText()); 193 } 194 rc.setText(rc.getDefaultText()); 195 } 196 } 197 } 198}