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