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