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 static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023import static org.junit.Assert.fail; 024import static org.mockito.ArgumentMatchers.any; 025import static org.mockito.ArgumentMatchers.anyString; 026import static org.mockito.Mockito.mock; 027import static org.mockito.Mockito.verify; 028import static org.mockito.Mockito.when; 029 030import java.io.IOException; 031import java.net.InetAddress; 032import java.util.Map; 033import javax.security.auth.callback.Callback; 034import javax.security.auth.callback.NameCallback; 035import javax.security.auth.callback.PasswordCallback; 036import javax.security.auth.callback.TextOutputCallback; 037import javax.security.auth.callback.UnsupportedCallbackException; 038import javax.security.sasl.RealmCallback; 039import javax.security.sasl.Sasl; 040import javax.security.sasl.SaslClient; 041import org.apache.hadoop.conf.Configuration; 042import org.apache.hadoop.hbase.HBaseClassTestRule; 043import org.apache.hadoop.hbase.HBaseConfiguration; 044import org.apache.hadoop.hbase.security.provider.DigestSaslClientAuthenticationProvider; 045import org.apache.hadoop.hbase.security.provider.DigestSaslClientAuthenticationProvider.DigestSaslClientCallbackHandler; 046import org.apache.hadoop.hbase.security.provider.GssSaslClientAuthenticationProvider; 047import org.apache.hadoop.hbase.security.provider.SimpleSaslClientAuthenticationProvider; 048import org.apache.hadoop.hbase.testclassification.SecurityTests; 049import org.apache.hadoop.hbase.testclassification.SmallTests; 050import org.apache.hadoop.hbase.util.Bytes; 051import org.apache.hadoop.io.DataInputBuffer; 052import org.apache.hadoop.io.DataOutputBuffer; 053import org.apache.hadoop.security.token.Token; 054import org.apache.hadoop.security.token.TokenIdentifier; 055import org.junit.ClassRule; 056import org.junit.Test; 057import org.junit.experimental.categories.Category; 058import org.mockito.Mockito; 059import org.slf4j.Logger; 060import org.slf4j.LoggerFactory; 061 062import org.apache.hbase.thirdparty.com.google.common.base.Strings; 063 064@Category({ SecurityTests.class, SmallTests.class }) 065public class TestHBaseSaslRpcClient { 066 067 @ClassRule 068 public static final HBaseClassTestRule CLASS_RULE = 069 HBaseClassTestRule.forClass(TestHBaseSaslRpcClient.class); 070 071 static { 072 System.setProperty("java.security.krb5.realm", "DOMAIN.COM"); 073 System.setProperty("java.security.krb5.kdc", "DOMAIN.COM"); 074 } 075 076 static final String DEFAULT_USER_NAME = "principal"; 077 static final String DEFAULT_USER_PASSWORD = "password"; 078 079 private static final Logger LOG = LoggerFactory.getLogger(TestHBaseSaslRpcClient.class); 080 081 @Test 082 public void testSaslClientUsesGivenRpcProtection() throws Exception { 083 Token<? extends TokenIdentifier> token = 084 createTokenMockWithCredentials(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD); 085 DigestSaslClientAuthenticationProvider provider = new DigestSaslClientAuthenticationProvider(); 086 for (SaslUtil.QualityOfProtection qop : SaslUtil.QualityOfProtection.values()) { 087 String negotiatedQop = new HBaseSaslRpcClient(HBaseConfiguration.create(), provider, token, 088 Mockito.mock(InetAddress.class), "", false, qop.name(), false) { 089 public String getQop() { 090 return saslProps.get(Sasl.QOP); 091 } 092 }.getQop(); 093 assertEquals(negotiatedQop, qop.getSaslQop()); 094 } 095 } 096 097 @Test 098 public void testDigestSaslClientCallbackHandler() throws UnsupportedCallbackException { 099 final Token<? extends TokenIdentifier> token = createTokenMock(); 100 when(token.getIdentifier()).thenReturn(Bytes.toBytes(DEFAULT_USER_NAME)); 101 when(token.getPassword()).thenReturn(Bytes.toBytes(DEFAULT_USER_PASSWORD)); 102 103 final NameCallback nameCallback = mock(NameCallback.class); 104 final PasswordCallback passwordCallback = mock(PasswordCallback.class); 105 final RealmCallback realmCallback = mock(RealmCallback.class); 106 107 // We can provide a realmCallback, but HBase presently does nothing with it. 108 Callback[] callbackArray = { nameCallback, passwordCallback, realmCallback }; 109 final DigestSaslClientCallbackHandler saslClCallbackHandler = 110 new DigestSaslClientCallbackHandler(token); 111 saslClCallbackHandler.handle(callbackArray); 112 verify(nameCallback).setName(anyString()); 113 verify(passwordCallback).setPassword(any()); 114 } 115 116 @Test 117 public void testDigestSaslClientCallbackHandlerWithException() { 118 final Token<? extends TokenIdentifier> token = createTokenMock(); 119 when(token.getIdentifier()).thenReturn(Bytes.toBytes(DEFAULT_USER_NAME)); 120 when(token.getPassword()).thenReturn(Bytes.toBytes(DEFAULT_USER_PASSWORD)); 121 final DigestSaslClientCallbackHandler saslClCallbackHandler = 122 new DigestSaslClientCallbackHandler(token); 123 try { 124 saslClCallbackHandler.handle(new Callback[] { mock(TextOutputCallback.class) }); 125 } catch (UnsupportedCallbackException expEx) { 126 // expected 127 } catch (Exception ex) { 128 fail("testDigestSaslClientCallbackHandlerWithException error : " + ex.getMessage()); 129 } 130 } 131 132 @Test 133 public void testHBaseSaslRpcClientCreation() throws Exception { 134 // creation kerberos principal check section 135 // Note this is mocked in a way that doesn't care about principal names 136 assertFalse(assertSuccessCreationKerberos()); 137 138 // creation digest principal check section 139 assertFalse(assertSuccessCreationDigestPrincipal(null, null)); 140 assertFalse(assertSuccessCreationDigestPrincipal("", "")); 141 assertFalse(assertSuccessCreationDigestPrincipal("", null)); 142 assertFalse(assertSuccessCreationDigestPrincipal(null, "")); 143 assertTrue(assertSuccessCreationDigestPrincipal(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD)); 144 145 // creation simple principal check section 146 // Note this is mocked in a way that doesn't care about principal names 147 assertFalse(assertSuccessCreationSimple()); 148 149 // exceptions check section 150 assertTrue(assertIOExceptionThenSaslClientIsNull(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD)); 151 assertTrue( 152 assertIOExceptionWhenGetStreamsBeforeConnectCall(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD)); 153 } 154 155 @Test 156 public void testAuthMethodReadWrite() throws IOException { 157 DataInputBuffer in = new DataInputBuffer(); 158 DataOutputBuffer out = new DataOutputBuffer(); 159 160 assertAuthMethodRead(in, AuthMethod.SIMPLE); 161 assertAuthMethodRead(in, AuthMethod.KERBEROS); 162 assertAuthMethodRead(in, AuthMethod.DIGEST); 163 164 assertAuthMethodWrite(out, AuthMethod.SIMPLE); 165 assertAuthMethodWrite(out, AuthMethod.KERBEROS); 166 assertAuthMethodWrite(out, AuthMethod.DIGEST); 167 } 168 169 private void assertAuthMethodRead(DataInputBuffer in, AuthMethod authMethod) throws IOException { 170 in.reset(new byte[] { authMethod.code }, 1); 171 assertEquals(authMethod, AuthMethod.read(in)); 172 } 173 174 private void assertAuthMethodWrite(DataOutputBuffer out, AuthMethod authMethod) 175 throws IOException { 176 authMethod.write(out); 177 assertEquals(authMethod.code, out.getData()[0]); 178 out.reset(); 179 } 180 181 private boolean assertIOExceptionWhenGetStreamsBeforeConnectCall(String principal, 182 String password) throws IOException { 183 boolean inState = false; 184 boolean outState = false; 185 186 DigestSaslClientAuthenticationProvider provider = new DigestSaslClientAuthenticationProvider() { 187 @Override 188 public SaslClient createClient(Configuration conf, InetAddress serverAddress, 189 String serverPrincipal, Token<? extends TokenIdentifier> token, boolean fallbackAllowed, 190 Map<String, String> saslProps) { 191 return Mockito.mock(SaslClient.class); 192 } 193 }; 194 HBaseSaslRpcClient rpcClient = new HBaseSaslRpcClient(HBaseConfiguration.create(), provider, 195 createTokenMockWithCredentials(principal, password), Mockito.mock(InetAddress.class), "", 196 false); 197 198 try { 199 rpcClient.getInputStream(); 200 } catch (IOException ex) { 201 // Sasl authentication exchange hasn't completed yet 202 inState = true; 203 } 204 205 try { 206 rpcClient.getOutputStream(); 207 } catch (IOException ex) { 208 // Sasl authentication exchange hasn't completed yet 209 outState = true; 210 } 211 212 return inState && outState; 213 } 214 215 private boolean assertIOExceptionThenSaslClientIsNull(String principal, String password) { 216 try { 217 DigestSaslClientAuthenticationProvider provider = 218 new DigestSaslClientAuthenticationProvider() { 219 @Override 220 public SaslClient createClient(Configuration conf, InetAddress serverAddress, 221 String serverPrincipal, Token<? extends TokenIdentifier> token, boolean fallbackAllowed, 222 Map<String, String> saslProps) { 223 return null; 224 } 225 }; 226 new HBaseSaslRpcClient(HBaseConfiguration.create(), provider, 227 createTokenMockWithCredentials(principal, password), Mockito.mock(InetAddress.class), "", 228 false); 229 return false; 230 } catch (IOException ex) { 231 return true; 232 } 233 } 234 235 private boolean assertSuccessCreationKerberos() { 236 HBaseSaslRpcClient rpcClient = null; 237 try { 238 // createSaslRpcClientForKerberos is mocked in a way that doesn't care about principal names 239 rpcClient = createSaslRpcClientForKerberos(); 240 } catch (Exception ex) { 241 LOG.error(ex.getMessage(), ex); 242 } 243 return rpcClient != null; 244 } 245 246 private boolean assertSuccessCreationDigestPrincipal(String principal, String password) { 247 HBaseSaslRpcClient rpcClient = null; 248 try { 249 rpcClient = new HBaseSaslRpcClient(HBaseConfiguration.create(), 250 new DigestSaslClientAuthenticationProvider(), 251 createTokenMockWithCredentials(principal, password), Mockito.mock(InetAddress.class), "", 252 false); 253 } catch (Exception ex) { 254 LOG.error(ex.getMessage(), ex); 255 } 256 return rpcClient != null; 257 } 258 259 private boolean assertSuccessCreationSimple() { 260 HBaseSaslRpcClient rpcClient = null; 261 try { 262 rpcClient = createSaslRpcClientSimple(); 263 } catch (Exception ex) { 264 LOG.error(ex.getMessage(), ex); 265 } 266 return rpcClient != null; 267 } 268 269 private HBaseSaslRpcClient createSaslRpcClientForKerberos() throws IOException { 270 return new HBaseSaslRpcClient(HBaseConfiguration.create(), 271 new GssSaslClientAuthenticationProvider(), createTokenMock(), Mockito.mock(InetAddress.class), 272 "", false); 273 } 274 275 private Token<? extends TokenIdentifier> createTokenMockWithCredentials(String principal, 276 String password) throws IOException { 277 Token<? extends TokenIdentifier> token = createTokenMock(); 278 if (!Strings.isNullOrEmpty(principal) && !Strings.isNullOrEmpty(password)) { 279 when(token.getIdentifier()).thenReturn(Bytes.toBytes(DEFAULT_USER_NAME)); 280 when(token.getPassword()).thenReturn(Bytes.toBytes(DEFAULT_USER_PASSWORD)); 281 } 282 return token; 283 } 284 285 private HBaseSaslRpcClient createSaslRpcClientSimple() throws IOException { 286 return new HBaseSaslRpcClient(HBaseConfiguration.create(), 287 new SimpleSaslClientAuthenticationProvider(), createTokenMock(), 288 Mockito.mock(InetAddress.class), "", false); 289 } 290 291 @SuppressWarnings("unchecked") 292 private Token<? extends TokenIdentifier> createTokenMock() { 293 return mock(Token.class); 294 } 295}