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