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