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