View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.security;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.hadoop.classification.InterfaceAudience;
25  import org.apache.hadoop.classification.InterfaceStability;
26  import org.apache.hadoop.conf.Configuration;
27  import org.apache.hadoop.fs.CommonConfigurationKeys;
28  import org.apache.hadoop.hbase.util.Methods;
29  import org.apache.hadoop.mapred.JobConf;
30  import org.apache.hadoop.mapreduce.Job;
31  import org.apache.hadoop.security.UserGroupInformation;
32  
33  import java.io.IOException;
34  import java.lang.reflect.UndeclaredThrowableException;
35  import java.security.PrivilegedAction;
36  import java.security.PrivilegedExceptionAction;
37  
38  /**
39   * Wrapper to abstract out usage of user and group information in HBase.
40   *
41   * <p>
42   * This class provides a common interface for interacting with user and group
43   * information across changing APIs in different versions of Hadoop.  It only
44   * provides access to the common set of functionality in
45   * {@link org.apache.hadoop.security.UserGroupInformation} currently needed by
46   * HBase, but can be extended as needs change.
47   * </p>
48   */
49  @InterfaceAudience.Public
50  @InterfaceStability.Evolving
51  public abstract class User {
52    public static final String HBASE_SECURITY_CONF_KEY =
53        "hbase.security.authentication";
54  
55    private static Log LOG = LogFactory.getLog(User.class);
56  
57    protected UserGroupInformation ugi;
58  
59    public UserGroupInformation getUGI() {
60      return ugi;
61    }
62  
63    /**
64     * Returns the full user name.  For Kerberos principals this will include
65     * the host and realm portions of the principal name.
66     * @return User full name.
67     */
68    public String getName() {
69      return ugi.getUserName();
70    }
71  
72    /**
73     * Returns the list of groups of which this user is a member.  On secure
74     * Hadoop this returns the group information for the user as resolved on the
75     * server.  For 0.20 based Hadoop, the group names are passed from the client.
76     */
77    public String[] getGroupNames() {
78      return ugi.getGroupNames();
79    }
80  
81    /**
82     * Returns the shortened version of the user name -- the portion that maps
83     * to an operating system user name.
84     * @return Short name
85     */
86    public abstract String getShortName();
87  
88    /**
89     * Executes the given action within the context of this user.
90     */
91    public abstract <T> T runAs(PrivilegedAction<T> action);
92  
93    /**
94     * Executes the given action within the context of this user.
95     */
96    public abstract <T> T runAs(PrivilegedExceptionAction<T> action)
97        throws IOException, InterruptedException;
98  
99    /**
100    * Requests an authentication token for this user and stores it in the
101    * user's credentials.
102    *
103    * @throws IOException
104    */
105   public abstract void obtainAuthTokenForJob(Configuration conf, Job job)
106       throws IOException, InterruptedException;
107 
108   /**
109    * Requests an authentication token for this user and stores it in the
110    * user's credentials.
111    *
112    * @throws IOException
113    */
114   public abstract void obtainAuthTokenForJob(JobConf job)
115       throws IOException, InterruptedException;
116 
117   @Override
118   public boolean equals(Object o) {
119     if (this == o) {
120       return true;
121     }
122     if (o == null || getClass() != o.getClass()) {
123       return false;
124     }
125     return ugi.equals(((User) o).ugi);
126   }
127   
128   @Override
129   public int hashCode() {
130     return ugi.hashCode();
131   }
132   
133   @Override
134   public String toString() {
135     return ugi.toString();
136   }
137 
138   /**
139    * Returns the {@code User} instance within current execution context.
140    */
141   public static User getCurrent() throws IOException {
142     User user = new SecureHadoopUser();
143     if (user.getUGI() == null) {
144       return null;
145     }
146     return user;
147   }
148 
149   /**
150    * Wraps an underlying {@code UserGroupInformation} instance.
151    * @param ugi The base Hadoop user
152    * @return User
153    */
154   public static User create(UserGroupInformation ugi) {
155     if (ugi == null) {
156       return null;
157     }
158     return new SecureHadoopUser(ugi);
159   }
160 
161   /**
162    * Generates a new {@code User} instance specifically for use in test code.
163    * @param name the full username
164    * @param groups the group names to which the test user will belong
165    * @return a new <code>User</code> instance
166    */
167   public static User createUserForTesting(Configuration conf,
168       String name, String[] groups) {
169     return SecureHadoopUser.createUserForTesting(conf, name, groups);
170   }
171 
172   /**
173    * Log in the current process using the given configuration keys for the
174    * credential file and login principal.
175    *
176    * <p><strong>This is only applicable when
177    * running on secure Hadoop</strong> -- see
178    * org.apache.hadoop.security.SecurityUtil#login(Configuration,String,String,String).
179    * On regular Hadoop (without security features), this will safely be ignored.
180    * </p>
181    *
182    * @param conf The configuration data to use
183    * @param fileConfKey Property key used to configure path to the credential file
184    * @param principalConfKey Property key used to configure login principal
185    * @param localhost Current hostname to use in any credentials
186    * @throws IOException underlying exception from SecurityUtil.login() call
187    */
188   public static void login(Configuration conf, String fileConfKey,
189       String principalConfKey, String localhost) throws IOException {
190     SecureHadoopUser.login(conf, fileConfKey, principalConfKey, localhost);
191   }
192 
193   /**
194    * Returns whether or not Kerberos authentication is configured for Hadoop.
195    * For non-secure Hadoop, this always returns <code>false</code>.
196    * For secure Hadoop, it will return the value from
197    * {@code UserGroupInformation.isSecurityEnabled()}.
198    */
199   public static boolean isSecurityEnabled() {
200     return SecureHadoopUser.isSecurityEnabled();
201   }
202 
203   /**
204    * Returns whether or not secure authentication is enabled for HBase.  Note that
205    * HBase security requires HDFS security to provide any guarantees, so this requires that
206    * both <code>hbase.security.authentication</code> and <code>hadoop.security.authentication</code>
207    * are set to <code>kerberos</code>.
208    */
209   public static boolean isHBaseSecurityEnabled(Configuration conf) {
210     return "kerberos".equalsIgnoreCase(conf.get(HBASE_SECURITY_CONF_KEY)) &&
211         "kerberos".equalsIgnoreCase(
212             conf.get(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION));
213   }
214 
215   /* Concrete implementations */
216 
217   /**
218    * Bridges {@code User} invocations to underlying calls to
219    * {@link org.apache.hadoop.security.UserGroupInformation} for secure Hadoop
220    * 0.20 and versions 0.21 and above.
221    */
222   private static class SecureHadoopUser extends User {
223     private String shortName;
224 
225     private SecureHadoopUser() throws IOException {
226       try {
227         ugi = (UserGroupInformation) callStatic("getCurrentUser");
228       } catch (IOException ioe) {
229         throw ioe;
230       } catch (RuntimeException re) {
231         throw re;
232       } catch (Exception e) {
233         throw new UndeclaredThrowableException(e,
234             "Unexpected exception getting current secure user");
235       }
236     }
237 
238     private SecureHadoopUser(UserGroupInformation ugi) {
239       this.ugi = ugi;
240     }
241 
242     @Override
243     public String getShortName() {
244       if (shortName != null) return shortName;
245 
246       try {
247         shortName = (String)call(ugi, "getShortUserName", null, null);
248         return shortName;
249       } catch (RuntimeException re) {
250         throw re;
251       } catch (Exception e) {
252         throw new UndeclaredThrowableException(e,
253             "Unexpected error getting user short name");
254       }
255     }
256 
257     @Override
258     public <T> T runAs(PrivilegedAction<T> action) {
259       try {
260         return (T) call(ugi, "doAs", new Class[]{PrivilegedAction.class},
261             new Object[]{action});
262       } catch (RuntimeException re) {
263         throw re;
264       } catch (Exception e) {
265         throw new UndeclaredThrowableException(e,
266             "Unexpected exception in runAs()");
267       }
268     }
269 
270     @Override
271     public <T> T runAs(PrivilegedExceptionAction<T> action)
272         throws IOException, InterruptedException {
273       try {
274         return (T) call(ugi, "doAs",
275             new Class[]{PrivilegedExceptionAction.class},
276             new Object[]{action});
277       } catch (IOException ioe) {
278         throw ioe;
279       } catch (InterruptedException ie) {
280         throw ie;
281       } catch (RuntimeException re) {
282         throw re;
283       } catch (Exception e) {
284         throw new UndeclaredThrowableException(e,
285             "Unexpected exception in runAs(PrivilegedExceptionAction)");
286       }
287     }
288 
289     @Override
290     public void obtainAuthTokenForJob(Configuration conf, Job job)
291         throws IOException, InterruptedException {
292       try {
293         Class c = Class.forName(
294             "org.apache.hadoop.hbase.security.token.TokenUtil");
295         Methods.call(c, null, "obtainTokenForJob",
296             new Class[]{Configuration.class, UserGroupInformation.class,
297                 Job.class},
298             new Object[]{conf, ugi, job});
299       } catch (ClassNotFoundException cnfe) {
300         throw new RuntimeException("Failure loading TokenUtil class, "
301             +"is secure RPC available?", cnfe);
302       } catch (IOException ioe) {
303         throw ioe;
304       } catch (InterruptedException ie) {
305         throw ie;
306       } catch (RuntimeException re) {
307         throw re;
308       } catch (Exception e) {
309         throw new UndeclaredThrowableException(e,
310             "Unexpected error calling TokenUtil.obtainAndCacheToken()");
311       }
312     }
313 
314     @Override
315     public void obtainAuthTokenForJob(JobConf job)
316         throws IOException, InterruptedException {
317       try {
318         Class c = Class.forName(
319             "org.apache.hadoop.hbase.security.token.TokenUtil");
320         Methods.call(c, null, "obtainTokenForJob",
321             new Class[]{JobConf.class, UserGroupInformation.class},
322             new Object[]{job, ugi});
323       } catch (ClassNotFoundException cnfe) {
324         throw new RuntimeException("Failure loading TokenUtil class, "
325             +"is secure RPC available?", cnfe);
326       } catch (IOException ioe) {
327         throw ioe;
328       } catch (InterruptedException ie) {
329         throw ie;
330       } catch (RuntimeException re) {
331         throw re;
332       } catch (Exception e) {
333         throw new UndeclaredThrowableException(e,
334             "Unexpected error calling TokenUtil.obtainAndCacheToken()");
335       }
336     }
337 
338     /** @see User#createUserForTesting(org.apache.hadoop.conf.Configuration, String, String[]) */
339     public static User createUserForTesting(Configuration conf,
340         String name, String[] groups) {
341       try {
342         return new SecureHadoopUser(
343             (UserGroupInformation)callStatic("createUserForTesting",
344                 new Class[]{String.class, String[].class},
345                 new Object[]{name, groups})
346         );
347       } catch (RuntimeException re) {
348         throw re;
349       } catch (Exception e) {
350         throw new UndeclaredThrowableException(e,
351             "Error creating secure test user");
352       }
353     }
354 
355     /**
356      * Obtain credentials for the current process using the configured
357      * Kerberos keytab file and principal.
358      * @see User#login(org.apache.hadoop.conf.Configuration, String, String, String)
359      *
360      * @param conf the Configuration to use
361      * @param fileConfKey Configuration property key used to store the path
362      * to the keytab file
363      * @param principalConfKey Configuration property key used to store the
364      * principal name to login as
365      * @param localhost the local hostname
366      */
367     public static void login(Configuration conf, String fileConfKey,
368         String principalConfKey, String localhost) throws IOException {
369       if (isSecurityEnabled()) {
370         // check for SecurityUtil class
371         try {
372           Class c = Class.forName("org.apache.hadoop.security.SecurityUtil");
373           Class[] types = new Class[]{
374               Configuration.class, String.class, String.class, String.class };
375           Object[] args = new Object[]{
376               conf, fileConfKey, principalConfKey, localhost };
377           Methods.call(c, null, "login", types, args);
378         } catch (ClassNotFoundException cnfe) {
379           throw new RuntimeException("Unable to login using " +
380               "org.apache.hadoop.security.SecurityUtil.login(). SecurityUtil class " +
381               "was not found!  Is this a version of secure Hadoop?", cnfe);
382         } catch (IOException ioe) {
383           throw ioe;
384         } catch (RuntimeException re) {
385           throw re;
386         } catch (Exception e) {
387           throw new UndeclaredThrowableException(e,
388               "Unhandled exception in User.login()");
389         }
390       }
391     }
392 
393     /**
394      * Returns the result of {@code UserGroupInformation.isSecurityEnabled()}.
395      */
396     public static boolean isSecurityEnabled() {
397       try {
398         return (Boolean)callStatic("isSecurityEnabled");
399       } catch (RuntimeException re) {
400         throw re;
401       } catch (Exception e) {
402         throw new UndeclaredThrowableException(e,
403             "Unexpected exception calling UserGroupInformation.isSecurityEnabled()");
404       }
405     }
406   }
407 
408   /* Reflection helper methods */
409   private static Object callStatic(String methodName) throws Exception {
410     return call(null, methodName, null, null);
411   }
412 
413   private static Object callStatic(String methodName, Class[] types,
414       Object[] args) throws Exception {
415     return call(null, methodName, types, args);
416   }
417 
418   private static Object call(UserGroupInformation instance, String methodName,
419       Class[] types, Object[] args) throws Exception {
420     return Methods.call(UserGroupInformation.class, instance, methodName, types,
421         args);
422   }
423 }