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  
19  package org.apache.hadoop.hbase.security.token;
20  
21  import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION;
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertNotNull;
25  import static org.junit.Assert.assertTrue;
26  
27  import java.io.IOException;
28  import java.net.InetSocketAddress;
29  import java.security.PrivilegedExceptionAction;
30  import java.util.ArrayList;
31  import java.util.List;
32  import java.util.concurrent.ConcurrentMap;
33  import java.util.concurrent.ExecutorService;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.hadoop.conf.Configuration;
38  import org.apache.hadoop.hbase.ClusterId;
39  import org.apache.hadoop.hbase.Coprocessor;
40  import org.apache.hadoop.hbase.HBaseTestingUtility;
41  import org.apache.hadoop.hbase.HConstants;
42  import org.apache.hadoop.hbase.HRegionInfo;
43  import org.apache.hadoop.hbase.testclassification.MediumTests;
44  import org.apache.hadoop.hbase.Server;
45  import org.apache.hadoop.hbase.ServerName;
46  import org.apache.hadoop.hbase.TableName;
47  import org.apache.hadoop.hbase.catalog.CatalogTracker;
48  import org.apache.hadoop.hbase.client.HConnection;
49  import org.apache.hadoop.hbase.client.HConnectionManager;
50  import org.apache.hadoop.hbase.client.HTableInterface;
51  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
52  import org.apache.hadoop.hbase.ipc.BlockingRpcCallback;
53  import org.apache.hadoop.hbase.ipc.FifoRpcScheduler;
54  import org.apache.hadoop.hbase.ipc.RpcClient;
55  import org.apache.hadoop.hbase.ipc.RpcServer;
56  import org.apache.hadoop.hbase.ipc.RpcServer.BlockingServiceAndInterface;
57  import org.apache.hadoop.hbase.ipc.RpcServerInterface;
58  import org.apache.hadoop.hbase.ipc.ServerRpcController;
59  import org.apache.hadoop.hbase.protobuf.generated.AuthenticationProtos;
60  import org.apache.hadoop.hbase.regionserver.HRegion;
61  import org.apache.hadoop.hbase.regionserver.RegionServerServices;
62  import org.apache.hadoop.hbase.security.SecurityInfo;
63  import org.apache.hadoop.hbase.security.User;
64  import org.apache.hadoop.hbase.util.Bytes;
65  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
66  import org.apache.hadoop.hbase.util.Sleeper;
67  import org.apache.hadoop.hbase.util.Strings;
68  import org.apache.hadoop.hbase.util.Threads;
69  import org.apache.hadoop.hbase.util.Writables;
70  import org.apache.hadoop.hbase.zookeeper.ZKClusterId;
71  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
72  import org.apache.hadoop.net.DNS;
73  import org.apache.hadoop.security.UserGroupInformation;
74  import org.apache.hadoop.security.authorize.PolicyProvider;
75  import org.apache.hadoop.security.authorize.Service;
76  import org.apache.hadoop.security.token.SecretManager;
77  import org.apache.hadoop.security.token.Token;
78  import org.apache.hadoop.security.token.TokenIdentifier;
79  import org.junit.AfterClass;
80  import org.junit.BeforeClass;
81  import org.junit.Test;
82  import org.junit.experimental.categories.Category;
83  
84  import com.google.protobuf.BlockingRpcChannel;
85  import com.google.protobuf.BlockingService;
86  import com.google.protobuf.RpcController;
87  import com.google.protobuf.ServiceException;
88  
89  /**
90   * Tests for authentication token creation and usage
91   */
92  @Category(MediumTests.class)
93  public class TestTokenAuthentication {
94    static {
95      // Setting whatever system properties after recommendation from
96      // http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/tutorials/KerberosReq.html
97      System.setProperty("java.security.krb5.realm", "hbase");
98      System.setProperty("java.security.krb5.kdc", "blah");
99    }
100   private static Log LOG = LogFactory.getLog(TestTokenAuthentication.class);
101 
102   public interface AuthenticationServiceSecurityInfo {}
103 
104   /**
105    * Basic server process for RPC authentication testing
106    */
107   private static class TokenServer extends TokenProvider
108   implements AuthenticationProtos.AuthenticationService.BlockingInterface, Runnable, Server {
109     private static Log LOG = LogFactory.getLog(TokenServer.class);
110     private Configuration conf;
111     private RpcServerInterface rpcServer;
112     private InetSocketAddress isa;
113     private ZooKeeperWatcher zookeeper;
114     private Sleeper sleeper;
115     private boolean started = false;
116     private boolean aborted = false;
117     private boolean stopped = false;
118     private long startcode;
119 
120     public TokenServer(Configuration conf) throws IOException {
121       this.conf = conf;
122       this.startcode = EnvironmentEdgeManager.currentTimeMillis();
123       // Server to handle client requests.
124       String hostname =
125         Strings.domainNamePointerToHostName(DNS.getDefaultHost("default", "default"));
126       int port = 0;
127       // Creation of an ISA will force a resolve.
128       InetSocketAddress initialIsa = new InetSocketAddress(hostname, port);
129       if (initialIsa.getAddress() == null) {
130         throw new IllegalArgumentException("Failed resolve of " + initialIsa);
131       }
132       final List<BlockingServiceAndInterface> sai =
133         new ArrayList<BlockingServiceAndInterface>(1);
134       BlockingService service =
135         AuthenticationProtos.AuthenticationService.newReflectiveBlockingService(this);
136       sai.add(new BlockingServiceAndInterface(service,
137         AuthenticationProtos.AuthenticationService.BlockingInterface.class));
138       this.rpcServer =
139         new RpcServer(this, "tokenServer", sai, initialIsa, conf, new FifoRpcScheduler(conf, 1));
140       InetSocketAddress address = rpcServer.getListenerAddress();
141       if (address == null) {
142         throw new IOException("Listener channel is closed");
143       }
144       this.isa = address;
145       this.sleeper = new Sleeper(1000, this);
146     }
147 
148     @Override
149     public Configuration getConfiguration() {
150       return conf;
151     }
152 
153     @Override
154     public CatalogTracker getCatalogTracker() {
155       return null;
156     }
157 
158     @Override
159     public ZooKeeperWatcher getZooKeeper() {
160       return zookeeper;
161     }
162 
163     @Override
164     public boolean isAborted() {
165       return aborted;
166     }
167 
168     @Override
169     public ServerName getServerName() {
170       return ServerName.valueOf(isa.getHostName(), isa.getPort(), startcode);
171     }
172 
173     @Override
174     public void abort(String reason, Throwable error) {
175       LOG.fatal("Aborting on: "+reason, error);
176       this.aborted = true;
177       this.stopped = true;
178       sleeper.skipSleepCycle();
179     }
180 
181     private void initialize() throws IOException {
182       // ZK configuration must _not_ have hbase.security.authentication or it will require SASL auth
183       Configuration zkConf = new Configuration(conf);
184       zkConf.set(User.HBASE_SECURITY_CONF_KEY, "simple");
185       this.zookeeper = new ZooKeeperWatcher(zkConf, TokenServer.class.getSimpleName(),
186           this, true);
187       this.rpcServer.start();
188 
189       // mock RegionServerServices to provide to coprocessor environment
190       final RegionServerServices mockServices = TEST_UTIL.createMockRegionServerService(rpcServer);
191 
192       // mock up coprocessor environment
193       super.start(new RegionCoprocessorEnvironment() {
194         @Override
195         public HRegion getRegion() { return null; }
196 
197         @Override
198         public RegionServerServices getRegionServerServices() {
199           return mockServices;
200         }
201 
202         @Override
203         public ConcurrentMap<String, Object> getSharedData() { return null; }
204 
205         @Override
206         public int getVersion() { return 0; }
207 
208         @Override
209         public String getHBaseVersion() { return null; }
210 
211         @Override
212         public Coprocessor getInstance() { return null; }
213 
214         @Override
215         public int getPriority() { return 0; }
216 
217         @Override
218         public int getLoadSequence() { return 0; }
219 
220         @Override
221         public Configuration getConfiguration() { return conf; }
222 
223         @Override
224         public HTableInterface getTable(TableName tableName) throws IOException
225           { return null; }
226 
227         @Override
228         public HTableInterface getTable(TableName tableName, ExecutorService service)
229             throws IOException {
230           return null;
231         }
232 
233         @Override
234         public ClassLoader getClassLoader() {
235           return Thread.currentThread().getContextClassLoader();
236         }
237 
238         @Override
239         public HRegionInfo getRegionInfo() {
240           return null;
241         }
242       });
243 
244       started = true;
245     }
246 
247     public void run() {
248       try {
249         initialize();
250         while (!stopped) {
251           this.sleeper.sleep();
252         }
253       } catch (Exception e) {
254         abort(e.getMessage(), e);
255       }
256       this.rpcServer.stop();
257     }
258 
259     public boolean isStarted() {
260       return started;
261     }
262 
263     @Override
264     public void stop(String reason) {
265       LOG.info("Stopping due to: "+reason);
266       this.stopped = true;
267       sleeper.skipSleepCycle();
268     }
269 
270     @Override
271     public boolean isStopped() {
272       return stopped;
273     }
274 
275     public InetSocketAddress getAddress() {
276       return isa;
277     }
278 
279     public SecretManager<? extends TokenIdentifier> getSecretManager() {
280       return ((RpcServer)rpcServer).getSecretManager();
281     }
282 
283     @Override
284     public AuthenticationProtos.GetAuthenticationTokenResponse getAuthenticationToken(
285         RpcController controller, AuthenticationProtos.GetAuthenticationTokenRequest request)
286       throws ServiceException {
287       LOG.debug("Authentication token request from "+ RpcServer.getRequestUserName());
288       // ignore passed in controller -- it's always null
289       ServerRpcController serverController = new ServerRpcController();
290       BlockingRpcCallback<AuthenticationProtos.GetAuthenticationTokenResponse> callback =
291           new BlockingRpcCallback<AuthenticationProtos.GetAuthenticationTokenResponse>();
292       getAuthenticationToken(serverController, request, callback);
293       try {
294         serverController.checkFailed();
295         return callback.get();
296       } catch (IOException ioe) {
297         throw new ServiceException(ioe);
298       }
299     }
300 
301     @Override
302     public AuthenticationProtos.WhoAmIResponse whoAmI(
303         RpcController controller, AuthenticationProtos.WhoAmIRequest request)
304       throws ServiceException {
305       LOG.debug("whoAmI() request from " + RpcServer.getRequestUserName());
306       // ignore passed in controller -- it's always null
307       ServerRpcController serverController = new ServerRpcController();
308       BlockingRpcCallback<AuthenticationProtos.WhoAmIResponse> callback =
309           new BlockingRpcCallback<AuthenticationProtos.WhoAmIResponse>();
310       whoAmI(serverController, request, callback);
311       try {
312         serverController.checkFailed();
313         return callback.get();
314       } catch (IOException ioe) {
315         throw new ServiceException(ioe);
316       }
317     }
318   }
319 
320 
321   private static HBaseTestingUtility TEST_UTIL;
322   private static TokenServer server;
323   private static Thread serverThread;
324   private static AuthenticationTokenSecretManager secretManager;
325   private static ClusterId clusterId = new ClusterId();
326 
327   @BeforeClass
328   public static void setupBeforeClass() throws Exception {
329     TEST_UTIL = new HBaseTestingUtility();
330     TEST_UTIL.startMiniZKCluster();
331     // register token type for protocol
332     SecurityInfo.addInfo(AuthenticationProtos.AuthenticationService.getDescriptor().getName(),
333       new SecurityInfo("hbase.test.kerberos.principal",
334         AuthenticationProtos.TokenIdentifier.Kind.HBASE_AUTH_TOKEN));
335     // security settings only added after startup so that ZK does not require SASL
336     Configuration conf = TEST_UTIL.getConfiguration();
337     conf.set("hadoop.security.authentication", "kerberos");
338     conf.set("hbase.security.authentication", "kerberos");
339     conf.setBoolean(HADOOP_SECURITY_AUTHORIZATION, true);
340     server = new TokenServer(conf);
341     serverThread = new Thread(server);
342     Threads.setDaemonThreadRunning(serverThread, "TokenServer:"+server.getServerName().toString());
343     // wait for startup
344     while (!server.isStarted() && !server.isStopped()) {
345       Thread.sleep(10);
346     }
347     server.rpcServer.refreshAuthManager(new PolicyProvider() {
348       @Override
349       public Service[] getServices() {
350         return new Service [] {
351           new Service("security.client.protocol.acl",
352             AuthenticationProtos.AuthenticationService.BlockingInterface.class)};
353       }
354     });
355     ZKClusterId.setClusterId(server.getZooKeeper(), clusterId);
356     secretManager = (AuthenticationTokenSecretManager)server.getSecretManager();
357     while(secretManager.getCurrentKey() == null) {
358       Thread.sleep(1);
359     }
360   }
361 
362   @AfterClass
363   public static void tearDownAfterClass() throws Exception {
364     server.stop("Test complete");
365     Threads.shutdown(serverThread);
366     TEST_UTIL.shutdownMiniZKCluster();
367   }
368 
369   @Test
370   public void testTokenCreation() throws Exception {
371     Token<AuthenticationTokenIdentifier> token =
372         secretManager.generateToken("testuser");
373 
374     AuthenticationTokenIdentifier ident = new AuthenticationTokenIdentifier();
375     Writables.getWritable(token.getIdentifier(), ident);
376     assertEquals("Token username should match", "testuser",
377         ident.getUsername());
378     byte[] passwd = secretManager.retrievePassword(ident);
379     assertTrue("Token password and password from secret manager should match",
380         Bytes.equals(token.getPassword(), passwd));
381   }
382 
383   @Test
384   public void testTokenAuthentication() throws Exception {
385     UserGroupInformation testuser =
386         UserGroupInformation.createUserForTesting("testuser", new String[]{"testgroup"});
387 
388     testuser.setAuthenticationMethod(
389         UserGroupInformation.AuthenticationMethod.TOKEN);
390     final Configuration conf = TEST_UTIL.getConfiguration();
391     UserGroupInformation.setConfiguration(conf);
392     Token<AuthenticationTokenIdentifier> token =
393         secretManager.generateToken("testuser");
394     LOG.debug("Got token: " + token.toString());
395     testuser.addToken(token);
396 
397     // verify the server authenticates us as this token user
398     testuser.doAs(new PrivilegedExceptionAction<Object>() {
399       public Object run() throws Exception {
400         Configuration c = server.getConfiguration();
401         RpcClient rpcClient = new RpcClient(c, clusterId.toString());
402         ServerName sn =
403             ServerName.valueOf(server.getAddress().getHostName(), server.getAddress().getPort(),
404                 System.currentTimeMillis());
405         try {
406           BlockingRpcChannel channel = rpcClient.createBlockingRpcChannel(sn,
407               User.getCurrent(), HConstants.DEFAULT_HBASE_RPC_TIMEOUT);
408           AuthenticationProtos.AuthenticationService.BlockingInterface stub =
409               AuthenticationProtos.AuthenticationService.newBlockingStub(channel);
410           AuthenticationProtos.WhoAmIResponse response =
411               stub.whoAmI(null, AuthenticationProtos.WhoAmIRequest.getDefaultInstance());
412           String myname = response.getUsername();
413           assertEquals("testuser", myname);
414           String authMethod = response.getAuthMethod();
415           assertEquals("TOKEN", authMethod);
416         } finally {
417           rpcClient.stop();
418         }
419         return null;
420       }
421     });
422   }
423 
424   @Test
425   public void testUseExistingToken() throws Exception {
426     User user = User.createUserForTesting(TEST_UTIL.getConfiguration(), "testuser2",
427         new String[]{"testgroup"});
428     Token<AuthenticationTokenIdentifier> token =
429         secretManager.generateToken(user.getName());
430     assertNotNull(token);
431     user.addToken(token);
432 
433     // make sure we got a token
434     Token<AuthenticationTokenIdentifier> firstToken =
435         new AuthenticationTokenSelector().selectToken(token.getService(), user.getTokens());
436     assertNotNull(firstToken);
437     assertEquals(token, firstToken);
438 
439     HConnection conn = HConnectionManager.createConnection(TEST_UTIL.getConfiguration());
440     try {
441       assertFalse(TokenUtil.addTokenIfMissing(conn, user));
442       // make sure we still have the same token
443       Token<AuthenticationTokenIdentifier> secondToken =
444           new AuthenticationTokenSelector().selectToken(token.getService(), user.getTokens());
445       assertEquals(firstToken, secondToken);
446     } finally {
447       conn.close();
448     }
449   }
450 }