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