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