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.testclassification.MediumTests;
43  import org.apache.hadoop.hbase.Server;
44  import org.apache.hadoop.hbase.ServerName;
45  import org.apache.hadoop.hbase.TableName;
46  import org.apache.hadoop.hbase.catalog.CatalogTracker;
47  import org.apache.hadoop.hbase.client.HConnection;
48  import org.apache.hadoop.hbase.client.HConnectionManager;
49  import org.apache.hadoop.hbase.client.HTableInterface;
50  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
51  import org.apache.hadoop.hbase.ipc.BlockingRpcCallback;
52  import org.apache.hadoop.hbase.ipc.FifoRpcScheduler;
53  import org.apache.hadoop.hbase.ipc.RequestContext;
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       this.isa = this.rpcServer.getListenerAddress();
141       this.sleeper = new Sleeper(1000, this);
142     }
143 
144     @Override
145     public Configuration getConfiguration() {
146       return conf;
147     }
148 
149     @Override
150     public CatalogTracker getCatalogTracker() {
151       return null;
152     }
153 
154     @Override
155     public ZooKeeperWatcher getZooKeeper() {
156       return zookeeper;
157     }
158 
159     @Override
160     public boolean isAborted() {
161       return aborted;
162     }
163 
164     @Override
165     public ServerName getServerName() {
166       return ServerName.valueOf(isa.getHostName(), isa.getPort(), startcode);
167     }
168 
169     @Override
170     public void abort(String reason, Throwable error) {
171       LOG.fatal("Aborting on: "+reason, error);
172       this.aborted = true;
173       this.stopped = true;
174       sleeper.skipSleepCycle();
175     }
176 
177     private void initialize() throws IOException {
178       // ZK configuration must _not_ have hbase.security.authentication or it will require SASL auth
179       Configuration zkConf = new Configuration(conf);
180       zkConf.set(User.HBASE_SECURITY_CONF_KEY, "simple");
181       this.zookeeper = new ZooKeeperWatcher(zkConf, TokenServer.class.getSimpleName(),
182           this, true);
183       this.rpcServer.start();
184 
185       // mock RegionServerServices to provide to coprocessor environment
186       final RegionServerServices mockServices = TEST_UTIL.createMockRegionServerService(rpcServer);
187 
188       // mock up coprocessor environment
189       super.start(new RegionCoprocessorEnvironment() {
190         @Override
191         public HRegion getRegion() { return null; }
192 
193         @Override
194         public RegionServerServices getRegionServerServices() {
195           return mockServices;
196         }
197 
198         @Override
199         public ConcurrentMap<String, Object> getSharedData() { return null; }
200 
201         @Override
202         public int getVersion() { return 0; }
203 
204         @Override
205         public String getHBaseVersion() { return null; }
206 
207         @Override
208         public Coprocessor getInstance() { return null; }
209 
210         @Override
211         public int getPriority() { return 0; }
212 
213         @Override
214         public int getLoadSequence() { return 0; }
215 
216         @Override
217         public Configuration getConfiguration() { return conf; }
218 
219         @Override
220         public HTableInterface getTable(TableName tableName) throws IOException
221           { return null; }
222 
223         @Override
224         public HTableInterface getTable(TableName tableName, ExecutorService service)
225             throws IOException {
226           return null;
227         }
228 
229         @Override
230         public ClassLoader getClassLoader() {
231           return Thread.currentThread().getContextClassLoader();
232         }
233       });
234 
235       started = true;
236     }
237 
238     public void run() {
239       try {
240         initialize();
241         while (!stopped) {
242           this.sleeper.sleep();
243         }
244       } catch (Exception e) {
245         abort(e.getMessage(), e);
246       }
247       this.rpcServer.stop();
248     }
249 
250     public boolean isStarted() {
251       return started;
252     }
253 
254     @Override
255     public void stop(String reason) {
256       LOG.info("Stopping due to: "+reason);
257       this.stopped = true;
258       sleeper.skipSleepCycle();
259     }
260 
261     @Override
262     public boolean isStopped() {
263       return stopped;
264     }
265 
266     public InetSocketAddress getAddress() {
267       return isa;
268     }
269 
270     public SecretManager<? extends TokenIdentifier> getSecretManager() {
271       return ((RpcServer)rpcServer).getSecretManager();
272     }
273 
274     @Override
275     public AuthenticationProtos.GetAuthenticationTokenResponse getAuthenticationToken(
276         RpcController controller, AuthenticationProtos.GetAuthenticationTokenRequest request)
277       throws ServiceException {
278       LOG.debug("Authentication token request from "+RequestContext.getRequestUserName());
279       // ignore passed in controller -- it's always null
280       ServerRpcController serverController = new ServerRpcController();
281       BlockingRpcCallback<AuthenticationProtos.GetAuthenticationTokenResponse> callback =
282           new BlockingRpcCallback<AuthenticationProtos.GetAuthenticationTokenResponse>();
283       getAuthenticationToken(serverController, request, callback);
284       try {
285         serverController.checkFailed();
286         return callback.get();
287       } catch (IOException ioe) {
288         throw new ServiceException(ioe);
289       }
290     }
291 
292     @Override
293     public AuthenticationProtos.WhoAmIResponse whoAmI(
294         RpcController controller, AuthenticationProtos.WhoAmIRequest request)
295       throws ServiceException {
296       LOG.debug("whoAmI() request from "+RequestContext.getRequestUserName());
297       // ignore passed in controller -- it's always null
298       ServerRpcController serverController = new ServerRpcController();
299       BlockingRpcCallback<AuthenticationProtos.WhoAmIResponse> callback =
300           new BlockingRpcCallback<AuthenticationProtos.WhoAmIResponse>();
301       whoAmI(serverController, request, callback);
302       try {
303         serverController.checkFailed();
304         return callback.get();
305       } catch (IOException ioe) {
306         throw new ServiceException(ioe);
307       }
308     }
309   }
310 
311 
312   private static HBaseTestingUtility TEST_UTIL;
313   private static TokenServer server;
314   private static Thread serverThread;
315   private static AuthenticationTokenSecretManager secretManager;
316   private static ClusterId clusterId = new ClusterId();
317 
318   @BeforeClass
319   public static void setupBeforeClass() throws Exception {
320     TEST_UTIL = new HBaseTestingUtility();
321     TEST_UTIL.startMiniZKCluster();
322     // register token type for protocol
323     SecurityInfo.addInfo(AuthenticationProtos.AuthenticationService.getDescriptor().getName(),
324       new SecurityInfo("hbase.test.kerberos.principal",
325         AuthenticationProtos.TokenIdentifier.Kind.HBASE_AUTH_TOKEN));
326     // security settings only added after startup so that ZK does not require SASL
327     Configuration conf = TEST_UTIL.getConfiguration();
328     conf.set("hadoop.security.authentication", "kerberos");
329     conf.set("hbase.security.authentication", "kerberos");
330     conf.setBoolean(HADOOP_SECURITY_AUTHORIZATION, true);
331     server = new TokenServer(conf);
332     serverThread = new Thread(server);
333     Threads.setDaemonThreadRunning(serverThread, "TokenServer:"+server.getServerName().toString());
334     // wait for startup
335     while (!server.isStarted() && !server.isStopped()) {
336       Thread.sleep(10);
337     }
338     server.rpcServer.refreshAuthManager(new PolicyProvider() {
339       @Override
340       public Service[] getServices() {
341         return new Service [] {
342           new Service("security.client.protocol.acl",
343             AuthenticationProtos.AuthenticationService.BlockingInterface.class)};
344       }
345     });
346     ZKClusterId.setClusterId(server.getZooKeeper(), clusterId);
347     secretManager = (AuthenticationTokenSecretManager)server.getSecretManager();
348     while(secretManager.getCurrentKey() == null) {
349       Thread.sleep(1);
350     }
351   }
352 
353   @AfterClass
354   public static void tearDownAfterClass() throws Exception {
355     server.stop("Test complete");
356     Threads.shutdown(serverThread);
357     TEST_UTIL.shutdownMiniZKCluster();
358   }
359 
360   @Test
361   public void testTokenCreation() throws Exception {
362     Token<AuthenticationTokenIdentifier> token =
363         secretManager.generateToken("testuser");
364 
365     AuthenticationTokenIdentifier ident = new AuthenticationTokenIdentifier();
366     Writables.getWritable(token.getIdentifier(), ident);
367     assertEquals("Token username should match", "testuser",
368         ident.getUsername());
369     byte[] passwd = secretManager.retrievePassword(ident);
370     assertTrue("Token password and password from secret manager should match",
371         Bytes.equals(token.getPassword(), passwd));
372   }
373 
374   @Test
375   public void testTokenAuthentication() throws Exception {
376     UserGroupInformation testuser =
377         UserGroupInformation.createUserForTesting("testuser", new String[]{"testgroup"});
378 
379     testuser.setAuthenticationMethod(
380         UserGroupInformation.AuthenticationMethod.TOKEN);
381     final Configuration conf = TEST_UTIL.getConfiguration();
382     UserGroupInformation.setConfiguration(conf);
383     Token<AuthenticationTokenIdentifier> token =
384         secretManager.generateToken("testuser");
385     LOG.debug("Got token: " + token.toString());
386     testuser.addToken(token);
387 
388     // verify the server authenticates us as this token user
389     testuser.doAs(new PrivilegedExceptionAction<Object>() {
390       public Object run() throws Exception {
391         Configuration c = server.getConfiguration();
392         RpcClient rpcClient = new RpcClient(c, clusterId.toString());
393         ServerName sn =
394             ServerName.valueOf(server.getAddress().getHostName(), server.getAddress().getPort(),
395                 System.currentTimeMillis());
396         try {
397           BlockingRpcChannel channel = rpcClient.createBlockingRpcChannel(sn,
398               User.getCurrent(), HConstants.DEFAULT_HBASE_RPC_TIMEOUT);
399           AuthenticationProtos.AuthenticationService.BlockingInterface stub =
400               AuthenticationProtos.AuthenticationService.newBlockingStub(channel);
401           AuthenticationProtos.WhoAmIResponse response =
402               stub.whoAmI(null, AuthenticationProtos.WhoAmIRequest.getDefaultInstance());
403           String myname = response.getUsername();
404           assertEquals("testuser", myname);
405           String authMethod = response.getAuthMethod();
406           assertEquals("TOKEN", authMethod);
407         } finally {
408           rpcClient.stop();
409         }
410         return null;
411       }
412     });
413   }
414 
415   @Test
416   public void testUseExistingToken() throws Exception {
417     User user = User.createUserForTesting(TEST_UTIL.getConfiguration(), "testuser2",
418         new String[]{"testgroup"});
419     Token<AuthenticationTokenIdentifier> token =
420         secretManager.generateToken(user.getName());
421     assertNotNull(token);
422     user.addToken(token);
423 
424     // make sure we got a token
425     Token<AuthenticationTokenIdentifier> firstToken =
426         new AuthenticationTokenSelector().selectToken(token.getService(), user.getTokens());
427     assertNotNull(firstToken);
428     assertEquals(token, firstToken);
429 
430     HConnection conn = HConnectionManager.createConnection(TEST_UTIL.getConfiguration());
431     try {
432       assertFalse(TokenUtil.addTokenIfMissing(conn, user));
433       // make sure we still have the same token
434       Token<AuthenticationTokenIdentifier> secondToken =
435           new AuthenticationTokenSelector().selectToken(token.getService(), user.getTokens());
436       assertEquals(firstToken, secondToken);
437     } finally {
438       conn.close();
439     }
440   }
441 }