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