View Javadoc

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