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.Matchers.any; 025import static org.mockito.Matchers.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 javax.security.auth.callback.Callback; 032import javax.security.auth.callback.CallbackHandler; 033import javax.security.auth.callback.NameCallback; 034import javax.security.auth.callback.PasswordCallback; 035import javax.security.auth.callback.TextOutputCallback; 036import javax.security.auth.callback.UnsupportedCallbackException; 037import javax.security.sasl.RealmCallback; 038import javax.security.sasl.RealmChoiceCallback; 039import javax.security.sasl.Sasl; 040import javax.security.sasl.SaslClient; 041import org.apache.hadoop.hbase.HBaseClassTestRule; 042import org.apache.hadoop.hbase.security.AbstractHBaseSaslRpcClient.SaslClientCallbackHandler; 043import org.apache.hadoop.hbase.testclassification.SecurityTests; 044import org.apache.hadoop.hbase.testclassification.SmallTests; 045import org.apache.hadoop.hbase.util.Bytes; 046import org.apache.hadoop.io.DataInputBuffer; 047import org.apache.hadoop.io.DataOutputBuffer; 048import org.apache.hadoop.security.token.Token; 049import org.apache.hadoop.security.token.TokenIdentifier; 050import org.apache.log4j.Level; 051import org.apache.log4j.Logger; 052import org.junit.BeforeClass; 053import org.junit.ClassRule; 054import org.junit.Rule; 055import org.junit.Test; 056import org.junit.experimental.categories.Category; 057import org.junit.rules.ExpectedException; 058import org.mockito.Mockito; 059 060import org.apache.hbase.thirdparty.com.google.common.base.Strings; 061 062@Category({SecurityTests.class, SmallTests.class}) 063public class TestHBaseSaslRpcClient { 064 065 @ClassRule 066 public static final HBaseClassTestRule CLASS_RULE = 067 HBaseClassTestRule.forClass(TestHBaseSaslRpcClient.class); 068 069 static { 070 System.setProperty("java.security.krb5.realm", "DOMAIN.COM"); 071 System.setProperty("java.security.krb5.kdc", "DOMAIN.COM"); 072 } 073 074 static final String DEFAULT_USER_NAME = "principal"; 075 static final String DEFAULT_USER_PASSWORD = "password"; 076 077 private static final Logger LOG = Logger.getLogger(TestHBaseSaslRpcClient.class); 078 079 080 @Rule 081 public ExpectedException exception = ExpectedException.none(); 082 083 @BeforeClass 084 public static void before() { 085 Logger.getRootLogger().setLevel(Level.DEBUG); 086 } 087 088 @Test 089 public void testSaslClientUsesGivenRpcProtection() throws Exception { 090 Token<? extends TokenIdentifier> token = createTokenMockWithCredentials(DEFAULT_USER_NAME, 091 DEFAULT_USER_PASSWORD); 092 for (SaslUtil.QualityOfProtection qop : SaslUtil.QualityOfProtection.values()) { 093 String negotiatedQop = new HBaseSaslRpcClient(AuthMethod.DIGEST, token, 094 "principal/host@DOMAIN.COM", false, qop.name(), 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 testSaslClientCallbackHandler() 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 final RealmChoiceCallback realmChoiceCallback = mock(RealmChoiceCallback.class); 113 114 Callback[] callbackArray = {nameCallback, passwordCallback, realmCallback, realmChoiceCallback}; 115 final SaslClientCallbackHandler saslClCallbackHandler = new SaslClientCallbackHandler(token); 116 saslClCallbackHandler.handle(callbackArray); 117 verify(nameCallback).setName(anyString()); 118 verify(realmCallback).setText(any()); 119 verify(passwordCallback).setPassword(any()); 120 } 121 122 @Test 123 public void testSaslClientCallbackHandlerWithException() { 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 SaslClientCallbackHandler saslClCallbackHandler = new SaslClientCallbackHandler(token); 128 try { 129 saslClCallbackHandler.handle(new Callback[] { mock(TextOutputCallback.class) }); 130 } catch (UnsupportedCallbackException expEx) { 131 //expected 132 } catch (Exception ex) { 133 fail("testSaslClientCallbackHandlerWithException error : " + ex.getMessage()); 134 } 135 } 136 137 @Test 138 public void testHBaseSaslRpcClientCreation() throws Exception { 139 //creation kerberos principal check section 140 assertFalse(assertSuccessCreationKerberosPrincipal(null)); 141 assertFalse(assertSuccessCreationKerberosPrincipal("DOMAIN.COM")); 142 assertFalse(assertSuccessCreationKerberosPrincipal("principal/DOMAIN.COM")); 143 if (!assertSuccessCreationKerberosPrincipal("principal/localhost@DOMAIN.COM")) { 144 // XXX: This can fail if kerberos support in the OS is not sane, see HBASE-10107. 145 // For now, don't assert, just warn 146 LOG.warn("Could not create a SASL client with valid Kerberos credential"); 147 } 148 149 //creation digest principal check section 150 assertFalse(assertSuccessCreationDigestPrincipal(null, null)); 151 assertFalse(assertSuccessCreationDigestPrincipal("", "")); 152 assertFalse(assertSuccessCreationDigestPrincipal("", null)); 153 assertFalse(assertSuccessCreationDigestPrincipal(null, "")); 154 assertTrue(assertSuccessCreationDigestPrincipal(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD)); 155 156 //creation simple principal check section 157 assertFalse(assertSuccessCreationSimplePrincipal("", "")); 158 assertFalse(assertSuccessCreationSimplePrincipal(null, null)); 159 assertFalse(assertSuccessCreationSimplePrincipal(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD)); 160 161 //exceptions check section 162 assertTrue(assertIOExceptionThenSaslClientIsNull(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD)); 163 assertTrue(assertIOExceptionWhenGetStreamsBeforeConnectCall( 164 DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD)); 165 } 166 167 @Test 168 public void testAuthMethodReadWrite() throws IOException { 169 DataInputBuffer in = new DataInputBuffer(); 170 DataOutputBuffer out = new DataOutputBuffer(); 171 172 assertAuthMethodRead(in, AuthMethod.SIMPLE); 173 assertAuthMethodRead(in, AuthMethod.KERBEROS); 174 assertAuthMethodRead(in, AuthMethod.DIGEST); 175 176 assertAuthMethodWrite(out, AuthMethod.SIMPLE); 177 assertAuthMethodWrite(out, AuthMethod.KERBEROS); 178 assertAuthMethodWrite(out, AuthMethod.DIGEST); 179 } 180 181 private void assertAuthMethodRead(DataInputBuffer in, AuthMethod authMethod) 182 throws IOException { 183 in.reset(new byte[] {authMethod.code}, 1); 184 assertEquals(authMethod, AuthMethod.read(in)); 185 } 186 187 private void assertAuthMethodWrite(DataOutputBuffer out, AuthMethod authMethod) 188 throws IOException { 189 authMethod.write(out); 190 assertEquals(authMethod.code, out.getData()[0]); 191 out.reset(); 192 } 193 194 private boolean assertIOExceptionWhenGetStreamsBeforeConnectCall(String principal, 195 String password) throws IOException { 196 boolean inState = false; 197 boolean outState = false; 198 199 HBaseSaslRpcClient rpcClient = new HBaseSaslRpcClient(AuthMethod.DIGEST, 200 createTokenMockWithCredentials(principal, password), principal, false) { 201 @Override 202 public SaslClient createDigestSaslClient(String[] mechanismNames, 203 String saslDefaultRealm, CallbackHandler saslClientCallbackHandler) 204 throws IOException { 205 return Mockito.mock(SaslClient.class); 206 } 207 208 @Override 209 public SaslClient createKerberosSaslClient(String[] mechanismNames, 210 String userFirstPart, String userSecondPart) throws IOException { 211 return Mockito.mock(SaslClient.class); 212 } 213 }; 214 215 try { 216 rpcClient.getInputStream(); 217 } catch(IOException ex) { 218 //Sasl authentication exchange hasn't completed yet 219 inState = true; 220 } 221 222 try { 223 rpcClient.getOutputStream(); 224 } catch(IOException ex) { 225 //Sasl authentication exchange hasn't completed yet 226 outState = true; 227 } 228 229 return inState && outState; 230 } 231 232 private boolean assertIOExceptionThenSaslClientIsNull(String principal, String password) { 233 try { 234 new HBaseSaslRpcClient(AuthMethod.DIGEST, 235 createTokenMockWithCredentials(principal, password), principal, false) { 236 @Override 237 public SaslClient createDigestSaslClient(String[] mechanismNames, 238 String saslDefaultRealm, CallbackHandler saslClientCallbackHandler) 239 throws IOException { 240 return null; 241 } 242 243 @Override 244 public SaslClient createKerberosSaslClient(String[] mechanismNames, 245 String userFirstPart, String userSecondPart) throws IOException { 246 return null; 247 } 248 }; 249 return false; 250 } catch (IOException ex) { 251 return true; 252 } 253 } 254 255 private boolean assertSuccessCreationKerberosPrincipal(String principal) { 256 HBaseSaslRpcClient rpcClient = null; 257 try { 258 rpcClient = createSaslRpcClientForKerberos(principal); 259 } catch(Exception ex) { 260 LOG.error(ex.getMessage(), ex); 261 } 262 return rpcClient != null; 263 } 264 265 private boolean assertSuccessCreationDigestPrincipal(String principal, String password) { 266 HBaseSaslRpcClient rpcClient = null; 267 try { 268 rpcClient = new HBaseSaslRpcClient(AuthMethod.DIGEST, 269 createTokenMockWithCredentials(principal, password), principal, false); 270 } catch(Exception ex) { 271 LOG.error(ex.getMessage(), ex); 272 } 273 return rpcClient != null; 274 } 275 276 private boolean assertSuccessCreationSimplePrincipal(String principal, String password) { 277 HBaseSaslRpcClient rpcClient = null; 278 try { 279 rpcClient = createSaslRpcClientSimple(principal, password); 280 } catch(Exception ex) { 281 LOG.error(ex.getMessage(), ex); 282 } 283 return rpcClient != null; 284 } 285 286 private HBaseSaslRpcClient createSaslRpcClientForKerberos(String principal) 287 throws IOException { 288 return new HBaseSaslRpcClient(AuthMethod.KERBEROS, createTokenMock(), principal, false); 289 } 290 291 private Token<? extends TokenIdentifier> createTokenMockWithCredentials( 292 String principal, String password) 293 throws IOException { 294 Token<? extends TokenIdentifier> token = createTokenMock(); 295 if (!Strings.isNullOrEmpty(principal) && !Strings.isNullOrEmpty(password)) { 296 when(token.getIdentifier()).thenReturn(Bytes.toBytes(DEFAULT_USER_NAME)); 297 when(token.getPassword()).thenReturn(Bytes.toBytes(DEFAULT_USER_PASSWORD)); 298 } 299 return token; 300 } 301 302 private HBaseSaslRpcClient createSaslRpcClientSimple(String principal, String password) 303 throws IOException { 304 return new HBaseSaslRpcClient(AuthMethod.SIMPLE, createTokenMock(), principal, false); 305 } 306 307 @SuppressWarnings("unchecked") 308 private Token<? extends TokenIdentifier> createTokenMock() { 309 return mock(Token.class); 310 } 311}