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       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         @Override
235         public HRegionInfo getRegionInfo() {
236           return null;
237         }
238       });
239 
240       started = true;
241     }
242 
243     public void run() {
244       try {
245         initialize();
246         while (!stopped) {
247           this.sleeper.sleep();
248         }
249       } catch (Exception e) {
250         abort(e.getMessage(), e);
251       }
252       this.rpcServer.stop();
253     }
254 
255     public boolean isStarted() {
256       return started;
257     }
258 
259     @Override
260     public void stop(String reason) {
261       LOG.info("Stopping due to: "+reason);
262       this.stopped = true;
263       sleeper.skipSleepCycle();
264     }
265 
266     @Override
267     public boolean isStopped() {
268       return stopped;
269     }
270 
271     public InetSocketAddress getAddress() {
272       return isa;
273     }
274 
275     public SecretManager<? extends TokenIdentifier> getSecretManager() {
276       return ((RpcServer)rpcServer).getSecretManager();
277     }
278 
279     @Override
280     public AuthenticationProtos.GetAuthenticationTokenResponse getAuthenticationToken(
281         RpcController controller, AuthenticationProtos.GetAuthenticationTokenRequest request)
282       throws ServiceException {
283       LOG.debug("Authentication token request from "+ RpcServer.getRequestUserName());
284       // ignore passed in controller -- it's always null
285       ServerRpcController serverController = new ServerRpcController();
286       BlockingRpcCallback<AuthenticationProtos.GetAuthenticationTokenResponse> callback =
287           new BlockingRpcCallback<AuthenticationProtos.GetAuthenticationTokenResponse>();
288       getAuthenticationToken(serverController, request, callback);
289       try {
290         serverController.checkFailed();
291         return callback.get();
292       } catch (IOException ioe) {
293         throw new ServiceException(ioe);
294       }
295     }
296 
297     @Override
298     public AuthenticationProtos.WhoAmIResponse whoAmI(
299         RpcController controller, AuthenticationProtos.WhoAmIRequest request)
300       throws ServiceException {
301       LOG.debug("whoAmI() request from " + RpcServer.getRequestUserName());
302       // ignore passed in controller -- it's always null
303       ServerRpcController serverController = new ServerRpcController();
304       BlockingRpcCallback<AuthenticationProtos.WhoAmIResponse> callback =
305           new BlockingRpcCallback<AuthenticationProtos.WhoAmIResponse>();
306       whoAmI(serverController, request, callback);
307       try {
308         serverController.checkFailed();
309         return callback.get();
310       } catch (IOException ioe) {
311         throw new ServiceException(ioe);
312       }
313     }
314   }
315 
316 
317   private static HBaseTestingUtility TEST_UTIL;
318   private static TokenServer server;
319   private static Thread serverThread;
320   private static AuthenticationTokenSecretManager secretManager;
321   private static ClusterId clusterId = new ClusterId();
322 
323   @BeforeClass
324   public static void setupBeforeClass() throws Exception {
325     TEST_UTIL = new HBaseTestingUtility();
326     TEST_UTIL.startMiniZKCluster();
327     // register token type for protocol
328     SecurityInfo.addInfo(AuthenticationProtos.AuthenticationService.getDescriptor().getName(),
329       new SecurityInfo("hbase.test.kerberos.principal",
330         AuthenticationProtos.TokenIdentifier.Kind.HBASE_AUTH_TOKEN));
331     // security settings only added after startup so that ZK does not require SASL
332     Configuration conf = TEST_UTIL.getConfiguration();
333     conf.set("hadoop.security.authentication", "kerberos");
334     conf.set("hbase.security.authentication", "kerberos");
335     conf.setBoolean(HADOOP_SECURITY_AUTHORIZATION, true);
336     server = new TokenServer(conf);
337     serverThread = new Thread(server);
338     Threads.setDaemonThreadRunning(serverThread, "TokenServer:"+server.getServerName().toString());
339     // wait for startup
340     while (!server.isStarted() && !server.isStopped()) {
341       Thread.sleep(10);
342     }
343     server.rpcServer.refreshAuthManager(new PolicyProvider() {
344       @Override
345       public Service[] getServices() {
346         return new Service [] {
347           new Service("security.client.protocol.acl",
348             AuthenticationProtos.AuthenticationService.BlockingInterface.class)};
349       }
350     });
351     ZKClusterId.setClusterId(server.getZooKeeper(), clusterId);
352     secretManager = (AuthenticationTokenSecretManager)server.getSecretManager();
353     while(secretManager.getCurrentKey() == null) {
354       Thread.sleep(1);
355     }
356   }
357 
358   @AfterClass
359   public static void tearDownAfterClass() throws Exception {
360     server.stop("Test complete");
361     Threads.shutdown(serverThread);
362     TEST_UTIL.shutdownMiniZKCluster();
363   }
364 
365   @Test
366   public void testTokenCreation() throws Exception {
367     Token<AuthenticationTokenIdentifier> token =
368         secretManager.generateToken("testuser");
369 
370     AuthenticationTokenIdentifier ident = new AuthenticationTokenIdentifier();
371     Writables.getWritable(token.getIdentifier(), ident);
372     assertEquals("Token username should match", "testuser",
373         ident.getUsername());
374     byte[] passwd = secretManager.retrievePassword(ident);
375     assertTrue("Token password and password from secret manager should match",
376         Bytes.equals(token.getPassword(), passwd));
377   }
378 
379   @Test
380   public void testTokenAuthentication() throws Exception {
381     UserGroupInformation testuser =
382         UserGroupInformation.createUserForTesting("testuser", new String[]{"testgroup"});
383 
384     testuser.setAuthenticationMethod(
385         UserGroupInformation.AuthenticationMethod.TOKEN);
386     final Configuration conf = TEST_UTIL.getConfiguration();
387     UserGroupInformation.setConfiguration(conf);
388     Token<AuthenticationTokenIdentifier> token =
389         secretManager.generateToken("testuser");
390     LOG.debug("Got token: " + token.toString());
391     testuser.addToken(token);
392 
393     // verify the server authenticates us as this token user
394     testuser.doAs(new PrivilegedExceptionAction<Object>() {
395       public Object run() throws Exception {
396         Configuration c = server.getConfiguration();
397         RpcClient rpcClient = new RpcClient(c, clusterId.toString());
398         ServerName sn =
399             ServerName.valueOf(server.getAddress().getHostName(), server.getAddress().getPort(),
400                 System.currentTimeMillis());
401         try {
402           BlockingRpcChannel channel = rpcClient.createBlockingRpcChannel(sn,
403               User.getCurrent(), HConstants.DEFAULT_HBASE_RPC_TIMEOUT);
404           AuthenticationProtos.AuthenticationService.BlockingInterface stub =
405               AuthenticationProtos.AuthenticationService.newBlockingStub(channel);
406           AuthenticationProtos.WhoAmIResponse response =
407               stub.whoAmI(null, AuthenticationProtos.WhoAmIRequest.getDefaultInstance());
408           String myname = response.getUsername();
409           assertEquals("testuser", myname);
410           String authMethod = response.getAuthMethod();
411           assertEquals("TOKEN", authMethod);
412         } finally {
413           rpcClient.stop();
414         }
415         return null;
416       }
417     });
418   }
419 
420   @Test
421   public void testUseExistingToken() throws Exception {
422     User user = User.createUserForTesting(TEST_UTIL.getConfiguration(), "testuser2",
423         new String[]{"testgroup"});
424     Token<AuthenticationTokenIdentifier> token =
425         secretManager.generateToken(user.getName());
426     assertNotNull(token);
427     user.addToken(token);
428 
429     // make sure we got a token
430     Token<AuthenticationTokenIdentifier> firstToken =
431         new AuthenticationTokenSelector().selectToken(token.getService(), user.getTokens());
432     assertNotNull(firstToken);
433     assertEquals(token, firstToken);
434 
435     HConnection conn = HConnectionManager.createConnection(TEST_UTIL.getConfiguration());
436     try {
437       assertFalse(TokenUtil.addTokenIfMissing(conn, user));
438       // make sure we still have the same token
439       Token<AuthenticationTokenIdentifier> secondToken =
440           new AuthenticationTokenSelector().selectToken(token.getService(), user.getTokens());
441       assertEquals(firstToken, secondToken);
442     } finally {
443       conn.close();
444     }
445   }
446 }