View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.security;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertTrue;
24  import static org.junit.Assert.fail;
25  import static org.mockito.Matchers.any;
26  import static org.mockito.Matchers.anyString;
27  import static org.mockito.Mockito.mock;
28  import static org.mockito.Mockito.verify;
29  import static org.mockito.Mockito.when;
30  
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.io.OutputStream;
34  
35  import javax.security.auth.callback.Callback;
36  import javax.security.auth.callback.CallbackHandler;
37  import javax.security.auth.callback.NameCallback;
38  import javax.security.auth.callback.PasswordCallback;
39  import javax.security.auth.callback.TextOutputCallback;
40  import javax.security.auth.callback.UnsupportedCallbackException;
41  import javax.security.sasl.Sasl;
42  import javax.security.sasl.RealmCallback;
43  import javax.security.sasl.RealmChoiceCallback;
44  import javax.security.sasl.SaslClient;
45  
46  import org.apache.hadoop.hbase.SmallTests;
47  import org.apache.hadoop.hbase.security.HBaseSaslRpcClient.SaslClientCallbackHandler;
48  import org.apache.hadoop.io.DataInputBuffer;
49  import org.apache.hadoop.io.DataOutputBuffer;
50  import org.apache.hadoop.security.token.Token;
51  import org.apache.hadoop.security.token.TokenIdentifier;
52  import org.apache.log4j.Level;
53  import org.apache.log4j.Logger;
54  import org.junit.BeforeClass;
55  import org.junit.Test;
56  import org.junit.experimental.categories.Category;
57  import org.mockito.Mockito;
58  
59  import com.google.common.base.Strings;
60  
61  @Category(SmallTests.class)
62  public class TestHBaseSaslRpcClient {
63    
64    static {
65      System.setProperty("java.security.krb5.realm", "DOMAIN.COM");
66      System.setProperty("java.security.krb5.kdc", "DOMAIN.COM");
67    }
68    
69    static final String DEFAULT_USER_NAME = "principal";
70    static final String DEFAULT_USER_PASSWORD = "password";
71  
72    private static final Logger LOG = Logger.getLogger(TestHBaseSaslRpcClient.class);
73  
74    @BeforeClass
75    public static void before() {
76      Logger.getRootLogger().setLevel(Level.DEBUG);
77    }
78  
79    @Test
80    public void testSaslQOPNotEmpty() throws Exception {
81      Token<? extends TokenIdentifier> token = createTokenMockWithCredentials(DEFAULT_USER_NAME,
82          DEFAULT_USER_PASSWORD);
83      // default QOP is authentication
84      new HBaseSaslRpcClient(AuthMethod.DIGEST, token, "principal/host@DOMAIN.COM", false);
85      assertTrue(SaslUtil.SASL_PROPS.get(Sasl.QOP).equals(SaslUtil.QualityOfProtection.
86          AUTHENTICATION.getSaslQop()));
87  
88      // check with specific QOPs
89      new HBaseSaslRpcClient(AuthMethod.DIGEST, token, "principal/host@DOMAIN.COM", false,
90          "authentication");
91      assertTrue(SaslUtil.SASL_PROPS.get(Sasl.QOP).equals(SaslUtil.QualityOfProtection.
92          AUTHENTICATION.getSaslQop()));
93  
94      new HBaseSaslRpcClient(AuthMethod.DIGEST, token, "principal/host@DOMAIN.COM", false,
95          "privacy");
96      assertTrue(SaslUtil.SASL_PROPS.get(Sasl.QOP).equals(SaslUtil.QualityOfProtection.
97          PRIVACY.getSaslQop()));
98  
99      new HBaseSaslRpcClient(AuthMethod.DIGEST, token, "principal/host@DOMAIN.COM", false,
100         "integrity");
101     assertTrue(SaslUtil.SASL_PROPS.get(Sasl.QOP).equals(SaslUtil.QualityOfProtection.
102         INTEGRITY.getSaslQop()));
103   }
104 
105   @Test
106   public void testSaslClientCallbackHandler() throws UnsupportedCallbackException {
107     final Token<? extends TokenIdentifier> token = createTokenMock();
108     when(token.getIdentifier()).thenReturn(DEFAULT_USER_NAME.getBytes());
109     when(token.getPassword()).thenReturn(DEFAULT_USER_PASSWORD.getBytes());
110 
111     final NameCallback nameCallback = mock(NameCallback.class);
112     final PasswordCallback passwordCallback = mock(PasswordCallback.class);
113     final RealmCallback realmCallback = mock(RealmCallback.class);
114     final RealmChoiceCallback realmChoiceCallback = mock(RealmChoiceCallback.class);
115 
116     Callback[] callbackArray = {nameCallback, passwordCallback,
117         realmCallback, realmChoiceCallback};
118     final SaslClientCallbackHandler saslClCallbackHandler = new SaslClientCallbackHandler(token);
119     saslClCallbackHandler.handle(callbackArray);
120     verify(nameCallback).setName(anyString());
121     verify(realmCallback).setText(anyString());
122     verify(passwordCallback).setPassword(any(char[].class));
123   }
124 
125   @Test
126   public void testSaslClientCallbackHandlerWithException() {
127     final Token<? extends TokenIdentifier> token = createTokenMock();
128     when(token.getIdentifier()).thenReturn(DEFAULT_USER_NAME.getBytes());
129     when(token.getPassword()).thenReturn(DEFAULT_USER_PASSWORD.getBytes());
130     final SaslClientCallbackHandler saslClCallbackHandler = new SaslClientCallbackHandler(token);
131     try {
132       saslClCallbackHandler.handle(new Callback[] { mock(TextOutputCallback.class) });
133     } catch (UnsupportedCallbackException expEx) {
134       //expected
135     } catch (Exception ex) {
136       fail("testSaslClientCallbackHandlerWithException error : " + ex.getMessage());
137     }
138   }
139 
140   @Test
141   public void testHBaseSaslRpcClientCreation() throws Exception {
142     //creation kerberos principal check section
143     assertFalse(assertSuccessCreationKerberosPrincipal(null));
144     assertFalse(assertSuccessCreationKerberosPrincipal("DOMAIN.COM"));
145     assertFalse(assertSuccessCreationKerberosPrincipal("principal/DOMAIN.COM"));
146     if (!assertSuccessCreationKerberosPrincipal("principal/localhost@DOMAIN.COM")) {
147       // XXX: This can fail if kerberos support in the OS is not sane, see HBASE-10107.
148       // For now, don't assert, just warn
149       LOG.warn("Could not create a SASL client with valid Kerberos credential");
150     }
151 
152     //creation digest principal check section
153     assertFalse(assertSuccessCreationDigestPrincipal(null, null));
154     assertFalse(assertSuccessCreationDigestPrincipal("", ""));
155     assertFalse(assertSuccessCreationDigestPrincipal("", null));
156     assertFalse(assertSuccessCreationDigestPrincipal(null, ""));
157     assertTrue(assertSuccessCreationDigestPrincipal(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
158 
159     //creation simple principal check section
160     assertFalse(assertSuccessCreationSimplePrincipal("", ""));
161     assertFalse(assertSuccessCreationSimplePrincipal(null, null));
162     assertFalse(assertSuccessCreationSimplePrincipal(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
163 
164     //exceptions check section
165     assertTrue(assertIOExceptionThenSaslClientIsNull(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
166     assertTrue(assertIOExceptionWhenGetStreamsBeforeConnectCall(
167         DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
168   }
169 
170   @Test
171   public void testAuthMethodReadWrite() throws IOException {
172     DataInputBuffer in = new DataInputBuffer();
173     DataOutputBuffer out = new DataOutputBuffer();
174 
175     assertAuthMethodRead(in, AuthMethod.SIMPLE);
176     assertAuthMethodRead(in, AuthMethod.KERBEROS);
177     assertAuthMethodRead(in, AuthMethod.DIGEST);
178 
179     assertAuthMethodWrite(out, AuthMethod.SIMPLE);
180     assertAuthMethodWrite(out, AuthMethod.KERBEROS);
181     assertAuthMethodWrite(out, AuthMethod.DIGEST);
182   }
183 
184   private void assertAuthMethodRead(DataInputBuffer in, AuthMethod authMethod)
185       throws IOException {
186     in.reset(new byte[] {authMethod.code}, 1);
187     assertEquals(authMethod, AuthMethod.read(in));
188   }
189 
190   private void assertAuthMethodWrite(DataOutputBuffer out, AuthMethod authMethod)
191       throws IOException {
192     authMethod.write(out);
193     assertEquals(authMethod.code, out.getData()[0]);
194     out.reset();
195   }
196 
197   private boolean assertIOExceptionWhenGetStreamsBeforeConnectCall(String principal,
198       String password) throws IOException {
199     boolean inState = false;
200     boolean outState = false;
201 
202     HBaseSaslRpcClient rpcClient = new HBaseSaslRpcClient(AuthMethod.DIGEST, 
203         createTokenMockWithCredentials(principal, password), principal, false) {
204       @Override
205       public SaslClient createDigestSaslClient(String[] mechanismNames,
206           String saslDefaultRealm, CallbackHandler saslClientCallbackHandler)
207               throws IOException {
208         return Mockito.mock(SaslClient.class);
209       }
210 
211       @Override
212       public SaslClient createKerberosSaslClient(String[] mechanismNames,
213           String userFirstPart, String userSecondPart) throws IOException {
214         return Mockito.mock(SaslClient.class);
215       }
216     };
217     
218     try {
219       rpcClient.getInputStream(Mockito.mock(InputStream.class));
220     } catch(IOException ex) {
221       //Sasl authentication exchange hasn't completed yet
222       inState = true;
223     }
224 
225     try {
226       rpcClient.getOutputStream(Mockito.mock(OutputStream.class));
227     } catch(IOException ex) {
228       //Sasl authentication exchange hasn't completed yet
229       outState = true;
230     }
231 
232     return inState && outState;
233   }
234 
235   private boolean assertIOExceptionThenSaslClientIsNull(String principal, String password) {
236     try {
237       new HBaseSaslRpcClient(AuthMethod.DIGEST, 
238           createTokenMockWithCredentials(principal, password), principal, false) {
239         @Override
240         public SaslClient createDigestSaslClient(String[] mechanismNames,
241             String saslDefaultRealm, CallbackHandler saslClientCallbackHandler)
242                 throws IOException {
243           return null;
244         }
245   
246         @Override
247         public SaslClient createKerberosSaslClient(String[] mechanismNames,
248             String userFirstPart, String userSecondPart) throws IOException {
249           return null;
250         }
251       };
252       return false;
253     } catch (IOException ex) {
254       return true;
255     }
256   }
257 
258   private boolean assertSuccessCreationKerberosPrincipal(String principal) {
259     HBaseSaslRpcClient rpcClient = null;
260     try {
261       rpcClient = createSaslRpcClientForKerberos(principal);
262     } catch(Exception ex) {
263       LOG.error(ex.getMessage(), ex);
264     }
265     return rpcClient != null;
266   }
267 
268   private boolean assertSuccessCreationDigestPrincipal(String principal, String password) {
269     HBaseSaslRpcClient rpcClient = null;
270     try {
271       rpcClient = new HBaseSaslRpcClient(AuthMethod.DIGEST, 
272           createTokenMockWithCredentials(principal, password), principal, false);
273     } catch(Exception ex) {
274       LOG.error(ex.getMessage(), ex);
275     }
276     return rpcClient != null;
277   }
278 
279   private boolean assertSuccessCreationSimplePrincipal(String principal, String password) {
280     HBaseSaslRpcClient rpcClient = null;
281     try {
282       rpcClient = createSaslRpcClientSimple(principal, password);
283     } catch(Exception ex) {
284       LOG.error(ex.getMessage(), ex);
285     }
286     return rpcClient != null;
287   }
288 
289   private HBaseSaslRpcClient createSaslRpcClientForKerberos(String principal)
290       throws IOException {
291     return new HBaseSaslRpcClient(AuthMethod.KERBEROS, createTokenMock(), principal, false);
292   }
293 
294   private Token<? extends TokenIdentifier> createTokenMockWithCredentials(
295       String principal, String password)
296       throws IOException {
297     Token<? extends TokenIdentifier> token = createTokenMock();
298     if (!Strings.isNullOrEmpty(principal) && !Strings.isNullOrEmpty(password)) {
299       when(token.getIdentifier()).thenReturn(DEFAULT_USER_NAME.getBytes());
300       when(token.getPassword()).thenReturn(DEFAULT_USER_PASSWORD.getBytes());
301     }
302     return token;
303   }
304 
305   private HBaseSaslRpcClient createSaslRpcClientSimple(String principal, String password)
306       throws IOException {
307     return new HBaseSaslRpcClient(AuthMethod.SIMPLE, createTokenMock(), principal, false);
308   }
309 
310   @SuppressWarnings("unchecked")
311   private Token<? extends TokenIdentifier> createTokenMock() {
312     return mock(Token.class);
313   }
314 }