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.testclassification.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.Rule;
56  import org.junit.Test;
57  import org.junit.experimental.categories.Category;
58  import org.junit.rules.ExpectedException;
59  import org.mockito.Mockito;
60  
61  import com.google.common.base.Strings;
62  
63  @Category(SmallTests.class)
64  public class TestHBaseSaslRpcClient {
65    
66    static {
67      System.setProperty("java.security.krb5.realm", "DOMAIN.COM");
68      System.setProperty("java.security.krb5.kdc", "DOMAIN.COM");
69    }
70    
71    static final String DEFAULT_USER_NAME = "principal";
72    static final String DEFAULT_USER_PASSWORD = "password";
73  
74    private static final Logger LOG = Logger.getLogger(TestHBaseSaslRpcClient.class);
75  
76  
77    @Rule
78    public ExpectedException exception = ExpectedException.none();
79  
80    @BeforeClass
81    public static void before() {
82      Logger.getRootLogger().setLevel(Level.DEBUG);
83    }
84  
85    @Test
86    public void testSaslQOPNotEmpty() throws Exception {
87      Token<? extends TokenIdentifier> token = createTokenMockWithCredentials(DEFAULT_USER_NAME,
88          DEFAULT_USER_PASSWORD);
89      // default QOP is authentication
90      new HBaseSaslRpcClient(AuthMethod.DIGEST, token, "principal/host@DOMAIN.COM", false);
91      assertTrue(SaslUtil.SASL_PROPS.get(Sasl.QOP).equals(SaslUtil.QualityOfProtection.
92          AUTHENTICATION.getSaslQop()));
93  
94      // check with specific QOPs
95      new HBaseSaslRpcClient(AuthMethod.DIGEST, token, "principal/host@DOMAIN.COM", false,
96          "authentication");
97      assertTrue(SaslUtil.SASL_PROPS.get(Sasl.QOP).equals(SaslUtil.QualityOfProtection.
98          AUTHENTICATION.getSaslQop()));
99  
100     new HBaseSaslRpcClient(AuthMethod.DIGEST, token, "principal/host@DOMAIN.COM", false,
101         "privacy");
102     assertTrue(SaslUtil.SASL_PROPS.get(Sasl.QOP).equals(SaslUtil.QualityOfProtection.
103         PRIVACY.getSaslQop()));
104 
105     new HBaseSaslRpcClient(AuthMethod.DIGEST, token, "principal/host@DOMAIN.COM", false,
106         "integrity");
107     assertTrue(SaslUtil.SASL_PROPS.get(Sasl.QOP).equals(SaslUtil.QualityOfProtection.
108         INTEGRITY.getSaslQop()));
109 
110     exception.expect(IllegalArgumentException.class);
111     new HBaseSaslRpcClient(AuthMethod.DIGEST, token, "principal/host@DOMAIN.COM", false,
112         "wrongvalue");
113   }
114 
115   @Test
116   public void testSaslClientCallbackHandler() throws UnsupportedCallbackException {
117     final Token<? extends TokenIdentifier> token = createTokenMock();
118     when(token.getIdentifier()).thenReturn(DEFAULT_USER_NAME.getBytes());
119     when(token.getPassword()).thenReturn(DEFAULT_USER_PASSWORD.getBytes());
120 
121     final NameCallback nameCallback = mock(NameCallback.class);
122     final PasswordCallback passwordCallback = mock(PasswordCallback.class);
123     final RealmCallback realmCallback = mock(RealmCallback.class);
124     final RealmChoiceCallback realmChoiceCallback = mock(RealmChoiceCallback.class);
125 
126     Callback[] callbackArray = {nameCallback, passwordCallback,
127         realmCallback, realmChoiceCallback};
128     final SaslClientCallbackHandler saslClCallbackHandler = new SaslClientCallbackHandler(token);
129     saslClCallbackHandler.handle(callbackArray);
130     verify(nameCallback).setName(anyString());
131     verify(realmCallback).setText(anyString());
132     verify(passwordCallback).setPassword(any(char[].class));
133   }
134 
135   @Test
136   public void testSaslClientCallbackHandlerWithException() {
137     final Token<? extends TokenIdentifier> token = createTokenMock();
138     when(token.getIdentifier()).thenReturn(DEFAULT_USER_NAME.getBytes());
139     when(token.getPassword()).thenReturn(DEFAULT_USER_PASSWORD.getBytes());
140     final SaslClientCallbackHandler saslClCallbackHandler = new SaslClientCallbackHandler(token);
141     try {
142       saslClCallbackHandler.handle(new Callback[] { mock(TextOutputCallback.class) });
143     } catch (UnsupportedCallbackException expEx) {
144       //expected
145     } catch (Exception ex) {
146       fail("testSaslClientCallbackHandlerWithException error : " + ex.getMessage());
147     }
148   }
149 
150   @Test
151   public void testHBaseSaslRpcClientCreation() throws Exception {
152     //creation kerberos principal check section
153     assertFalse(assertSuccessCreationKerberosPrincipal(null));
154     assertFalse(assertSuccessCreationKerberosPrincipal("DOMAIN.COM"));
155     assertFalse(assertSuccessCreationKerberosPrincipal("principal/DOMAIN.COM"));
156     if (!assertSuccessCreationKerberosPrincipal("principal/localhost@DOMAIN.COM")) {
157       // XXX: This can fail if kerberos support in the OS is not sane, see HBASE-10107.
158       // For now, don't assert, just warn
159       LOG.warn("Could not create a SASL client with valid Kerberos credential");
160     }
161 
162     //creation digest principal check section
163     assertFalse(assertSuccessCreationDigestPrincipal(null, null));
164     assertFalse(assertSuccessCreationDigestPrincipal("", ""));
165     assertFalse(assertSuccessCreationDigestPrincipal("", null));
166     assertFalse(assertSuccessCreationDigestPrincipal(null, ""));
167     assertTrue(assertSuccessCreationDigestPrincipal(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
168 
169     //creation simple principal check section
170     assertFalse(assertSuccessCreationSimplePrincipal("", ""));
171     assertFalse(assertSuccessCreationSimplePrincipal(null, null));
172     assertFalse(assertSuccessCreationSimplePrincipal(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
173 
174     //exceptions check section
175     assertTrue(assertIOExceptionThenSaslClientIsNull(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
176     assertTrue(assertIOExceptionWhenGetStreamsBeforeConnectCall(
177         DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
178   }
179 
180   @Test
181   public void testAuthMethodReadWrite() throws IOException {
182     DataInputBuffer in = new DataInputBuffer();
183     DataOutputBuffer out = new DataOutputBuffer();
184 
185     assertAuthMethodRead(in, AuthMethod.SIMPLE);
186     assertAuthMethodRead(in, AuthMethod.KERBEROS);
187     assertAuthMethodRead(in, AuthMethod.DIGEST);
188 
189     assertAuthMethodWrite(out, AuthMethod.SIMPLE);
190     assertAuthMethodWrite(out, AuthMethod.KERBEROS);
191     assertAuthMethodWrite(out, AuthMethod.DIGEST);
192   }
193 
194   private void assertAuthMethodRead(DataInputBuffer in, AuthMethod authMethod)
195       throws IOException {
196     in.reset(new byte[] {authMethod.code}, 1);
197     assertEquals(authMethod, AuthMethod.read(in));
198   }
199 
200   private void assertAuthMethodWrite(DataOutputBuffer out, AuthMethod authMethod)
201       throws IOException {
202     authMethod.write(out);
203     assertEquals(authMethod.code, out.getData()[0]);
204     out.reset();
205   }
206 
207   private boolean assertIOExceptionWhenGetStreamsBeforeConnectCall(String principal,
208       String password) throws IOException {
209     boolean inState = false;
210     boolean outState = false;
211 
212     HBaseSaslRpcClient rpcClient = new HBaseSaslRpcClient(AuthMethod.DIGEST, 
213         createTokenMockWithCredentials(principal, password), principal, false) {
214       @Override
215       public SaslClient createDigestSaslClient(String[] mechanismNames,
216           String saslDefaultRealm, CallbackHandler saslClientCallbackHandler)
217               throws IOException {
218         return Mockito.mock(SaslClient.class);
219       }
220 
221       @Override
222       public SaslClient createKerberosSaslClient(String[] mechanismNames,
223           String userFirstPart, String userSecondPart) throws IOException {
224         return Mockito.mock(SaslClient.class);
225       }
226     };
227     
228     try {
229       rpcClient.getInputStream(Mockito.mock(InputStream.class));
230     } catch(IOException ex) {
231       //Sasl authentication exchange hasn't completed yet
232       inState = true;
233     }
234 
235     try {
236       rpcClient.getOutputStream(Mockito.mock(OutputStream.class));
237     } catch(IOException ex) {
238       //Sasl authentication exchange hasn't completed yet
239       outState = true;
240     }
241 
242     return inState && outState;
243   }
244 
245   private boolean assertIOExceptionThenSaslClientIsNull(String principal, String password) {
246     try {
247       new HBaseSaslRpcClient(AuthMethod.DIGEST, 
248           createTokenMockWithCredentials(principal, password), principal, false) {
249         @Override
250         public SaslClient createDigestSaslClient(String[] mechanismNames,
251             String saslDefaultRealm, CallbackHandler saslClientCallbackHandler)
252                 throws IOException {
253           return null;
254         }
255   
256         @Override
257         public SaslClient createKerberosSaslClient(String[] mechanismNames,
258             String userFirstPart, String userSecondPart) throws IOException {
259           return null;
260         }
261       };
262       return false;
263     } catch (IOException ex) {
264       return true;
265     }
266   }
267 
268   private boolean assertSuccessCreationKerberosPrincipal(String principal) {
269     HBaseSaslRpcClient rpcClient = null;
270     try {
271       rpcClient = createSaslRpcClientForKerberos(principal);
272     } catch(Exception ex) {
273       LOG.error(ex.getMessage(), ex);
274     }
275     return rpcClient != null;
276   }
277 
278   private boolean assertSuccessCreationDigestPrincipal(String principal, String password) {
279     HBaseSaslRpcClient rpcClient = null;
280     try {
281       rpcClient = new HBaseSaslRpcClient(AuthMethod.DIGEST, 
282           createTokenMockWithCredentials(principal, password), principal, false);
283     } catch(Exception ex) {
284       LOG.error(ex.getMessage(), ex);
285     }
286     return rpcClient != null;
287   }
288 
289   private boolean assertSuccessCreationSimplePrincipal(String principal, String password) {
290     HBaseSaslRpcClient rpcClient = null;
291     try {
292       rpcClient = createSaslRpcClientSimple(principal, password);
293     } catch(Exception ex) {
294       LOG.error(ex.getMessage(), ex);
295     }
296     return rpcClient != null;
297   }
298 
299   private HBaseSaslRpcClient createSaslRpcClientForKerberos(String principal)
300       throws IOException {
301     return new HBaseSaslRpcClient(AuthMethod.KERBEROS, createTokenMock(), principal, false);
302   }
303 
304   private Token<? extends TokenIdentifier> createTokenMockWithCredentials(
305       String principal, String password)
306       throws IOException {
307     Token<? extends TokenIdentifier> token = createTokenMock();
308     if (!Strings.isNullOrEmpty(principal) && !Strings.isNullOrEmpty(password)) {
309       when(token.getIdentifier()).thenReturn(DEFAULT_USER_NAME.getBytes());
310       when(token.getPassword()).thenReturn(DEFAULT_USER_PASSWORD.getBytes());
311     }
312     return token;
313   }
314 
315   private HBaseSaslRpcClient createSaslRpcClientSimple(String principal, String password)
316       throws IOException {
317     return new HBaseSaslRpcClient(AuthMethod.SIMPLE, createTokenMock(), principal, false);
318   }
319 
320   @SuppressWarnings("unchecked")
321   private Token<? extends TokenIdentifier> createTokenMock() {
322     return mock(Token.class);
323   }
324 }