View Javadoc

1   /*
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.security;
22  
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.hadoop.conf.Configuration;
25  import org.apache.hadoop.security.UserGroupInformation;
26  
27  import java.io.IOException;
28  import java.lang.reflect.Constructor;
29  import java.lang.reflect.Method;
30  import java.lang.reflect.UndeclaredThrowableException;
31  import java.security.PrivilegedAction;
32  import java.security.PrivilegedExceptionAction;
33  import org.apache.commons.logging.Log;
34  
35  /**
36   * Wrapper to abstract out usage of user and group information in HBase.
37   *
38   * <p>
39   * This class provides a common interface for interacting with user and group
40   * information across changing APIs in different versions of Hadoop.  It only
41   * provides access to the common set of functionality in
42   * {@link org.apache.hadoop.security.UserGroupInformation} currently needed by
43   * HBase, but can be extended as needs change.
44   * </p>
45   *
46   * <p>
47   * Note: this class does not attempt to support any of the Kerberos
48   * authentication methods exposed in security-enabled Hadoop (for the moment
49   * at least), as they're not yet needed.  Properly supporting
50   * authentication is left up to implementation in secure HBase.
51   * </p>
52   */
53  public abstract class User {
54    private static boolean IS_SECURE_HADOOP = true;
55    static {
56      try {
57        UserGroupInformation.class.getMethod("isSecurityEnabled");
58      } catch (NoSuchMethodException nsme) {
59        IS_SECURE_HADOOP = false;
60      }
61    }
62    private static Log LOG = LogFactory.getLog(User.class);
63    protected UserGroupInformation ugi;
64  
65    /**
66     * Returns the full user name.  For Kerberos principals this will include
67     * the host and realm portions of the principal name.
68     * @return User full name.
69     */
70    public String getName() {
71      return ugi.getUserName();
72    }
73  
74    /**
75     * Returns the shortened version of the user name -- the portion that maps
76     * to an operating system user name.
77     * @return Short name
78     */
79    public abstract String getShortName();
80  
81    /**
82     * Executes the given action within the context of this user.
83     */
84    public abstract <T> T runAs(PrivilegedAction<T> action);
85  
86    /**
87     * Executes the given action within the context of this user.
88     */
89    public abstract <T> T runAs(PrivilegedExceptionAction<T> action)
90        throws IOException, InterruptedException;
91  
92    public String toString() {
93      return ugi.toString();
94    }
95  
96    /**
97     * Returns the {@code User} instance within current execution context.
98     */
99    public static User getCurrent() {
100     if (IS_SECURE_HADOOP) {
101       return new SecureHadoopUser();
102     } else {
103       return new HadoopUser();
104     }
105   }
106 
107   /**
108    * Generates a new {@code User} instance specifically for use in test code.
109    * @param name the full username
110    * @param groups the group names to which the test user will belong
111    * @return a new <code>User</code> instance
112    */
113   public static User createUserForTesting(Configuration conf,
114       String name, String[] groups) {
115     if (IS_SECURE_HADOOP) {
116       return SecureHadoopUser.createUserForTesting(conf, name, groups);
117     }
118     return HadoopUser.createUserForTesting(conf, name, groups);
119   }
120 
121   /* Concrete implementations */
122 
123   /**
124    * Bridges {@link User} calls to invocations of the appropriate methods
125    * in {@link org.apache.hadoop.security.UserGroupInformation} in regular
126    * Hadoop 0.20 (ASF Hadoop and other versions without the backported security
127    * features).
128    */
129   private static class HadoopUser extends User {
130 
131     private HadoopUser() {
132       ugi = (UserGroupInformation) callStatic("getCurrentUGI");
133     }
134 
135     private HadoopUser(UserGroupInformation ugi) {
136       this.ugi = ugi;
137     }
138 
139     @Override
140     public String getShortName() {
141       return ugi.getUserName();
142     }
143 
144     @Override
145     public <T> T runAs(PrivilegedAction<T> action) {
146       UserGroupInformation previous =
147           (UserGroupInformation) callStatic("getCurrentUGI");
148       if (ugi != null) {
149         callStatic("setCurrentUser", new Class[]{UserGroupInformation.class},
150             new Object[]{ugi});
151       }
152       T result = action.run();
153       callStatic("setCurrentUser", new Class[]{UserGroupInformation.class},
154           new Object[]{previous});
155       return result;
156     }
157 
158     @Override
159     public <T> T runAs(PrivilegedExceptionAction<T> action)
160         throws IOException, InterruptedException {
161       UserGroupInformation previous =
162           (UserGroupInformation) callStatic("getCurrentUGI");
163       if (ugi != null) {
164         callStatic("setCurrentUGI", new Class[]{UserGroupInformation.class},
165             new Object[]{ugi});
166       }
167       T result = null;
168       try {
169         result = action.run();
170       } catch (Exception e) {
171         if (e instanceof IOException) {
172           throw (IOException)e;
173         } else if (e instanceof InterruptedException) {
174           throw (InterruptedException)e;
175         } else if (e instanceof RuntimeException) {
176           throw (RuntimeException)e;
177         } else {
178           throw new UndeclaredThrowableException(e, "Unknown exception in runAs()");
179         }
180       } finally {
181         callStatic("setCurrentUGI", new Class[]{UserGroupInformation.class},
182             new Object[]{previous});
183       }
184       return result;
185     }
186 
187     public static User createUserForTesting(Configuration conf,
188         String name, String[] groups) {
189       try {
190         Class c = Class.forName("org.apache.hadoop.security.UnixUserGroupInformation");
191         Constructor constructor = c.getConstructor(String.class, String[].class);
192         if (constructor == null) {
193           throw new NullPointerException(
194              );
195         }
196         UserGroupInformation newUser =
197             (UserGroupInformation)constructor.newInstance(name, groups);
198         // set user in configuration -- hack for regular hadoop
199         conf.set("hadoop.job.ugi", newUser.toString());
200         return new HadoopUser(newUser);
201       } catch (ClassNotFoundException cnfe) {
202         LOG.error("UnixUserGroupInformation not found, is this secure Hadoop?", cnfe);
203       } catch (NoSuchMethodException nsme) {
204         LOG.error("No valid constructor found for UnixUserGroupInformation!", nsme);
205       } catch (Exception e) {
206         LOG.error("Error instantiating new UnixUserGroupInformation", e);
207       }
208 
209       return null;
210     }
211   }
212 
213   /**
214    * Bridges {@code User} invocations to underlying calls to
215    * {@link org.apache.hadoop.security.UserGroupInformation} for secure Hadoop
216    * 0.20 and versions 0.21 and above.
217    */
218   private static class SecureHadoopUser extends User {
219     private SecureHadoopUser() {
220       ugi = (UserGroupInformation) callStatic("getCurrentUser");
221     }
222 
223     private SecureHadoopUser(UserGroupInformation ugi) {
224       this.ugi = ugi;
225     }
226 
227     @Override
228     public String getShortName() {
229       return (String)call(ugi, "getShortUserName", null, null);
230     }
231 
232     @Override
233     public <T> T runAs(PrivilegedAction<T> action) {
234       return (T) call(ugi, "doAs", new Class[]{PrivilegedAction.class},
235           new Object[]{action});
236     }
237 
238     @Override
239     public <T> T runAs(PrivilegedExceptionAction<T> action)
240         throws IOException, InterruptedException {
241       return (T) call(ugi, "doAs",
242           new Class[]{PrivilegedExceptionAction.class},
243           new Object[]{action});
244     }
245 
246     public static User createUserForTesting(Configuration conf,
247         String name, String[] groups) {
248       return new SecureHadoopUser(
249           (UserGroupInformation)callStatic("createUserForTesting",
250               new Class[]{String.class, String[].class},
251               new Object[]{name, groups})
252       );
253     }
254   }
255 
256   /* Reflection helper methods */
257   private static Object callStatic(String methodName) {
258     return call(null, methodName, null, null);
259   }
260 
261   private static Object callStatic(String methodName, Class[] types,
262       Object[] args) {
263     return call(null, methodName, types, args);
264   }
265 
266   private static Object call(UserGroupInformation instance, String methodName,
267       Class[] types, Object[] args) {
268     try {
269       Method m = UserGroupInformation.class.getMethod(methodName, types);
270       return m.invoke(instance, args);
271     } catch (NoSuchMethodException nsme) {
272       LOG.fatal("Can't find method "+methodName+" in UserGroupInformation!",
273           nsme);
274     } catch (Exception e) {
275       LOG.fatal("Error calling method "+methodName, e);
276     }
277     return null;
278   }
279 }