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 java.io.IOException;
23  import java.lang.reflect.UndeclaredThrowableException;
24  import java.security.PrivilegedAction;
25  import java.security.PrivilegedExceptionAction;
26  
27  import org.apache.hadoop.conf.Configuration;
28  import org.apache.hadoop.hbase.classification.InterfaceAudience;
29  import org.apache.hadoop.hbase.util.Methods;
30  import org.apache.hadoop.mapred.JobConf;
31  import org.apache.hadoop.mapreduce.Job;
32  import org.apache.hadoop.security.SecurityUtil;
33  import org.apache.hadoop.security.UserGroupInformation;
34  import org.apache.hadoop.security.token.Token;
35  
36  /**
37   * Wrapper to abstract out usage of user and group information in HBase.
38   *
39   * <p>
40   * This class provides a common interface for interacting with user and group
41   * information across changing APIs in different versions of Hadoop.  It only
42   * provides access to the common set of functionality in
43   * {@link org.apache.hadoop.security.UserGroupInformation} currently needed by
44   * HBase, but can be extended as needs change.
45   * </p>
46   */
47  @InterfaceAudience.Private
48  public abstract class User {
49    public static final String HBASE_SECURITY_CONF_KEY =
50        "hbase.security.authentication";
51  
52    protected UserGroupInformation ugi;
53  
54    public UserGroupInformation getUGI() {
55      return ugi;
56    }
57  
58    /**
59     * Returns the full user name.  For Kerberos principals this will include
60     * the host and realm portions of the principal name.
61     * @return User full name.
62     */
63    public String getName() {
64      return ugi.getUserName();
65    }
66  
67    /**
68     * Returns the list of groups of which this user is a member.  On secure
69     * Hadoop this returns the group information for the user as resolved on the
70     * server.  For 0.20 based Hadoop, the group names are passed from the client.
71     */
72    public String[] getGroupNames() {
73      return ugi.getGroupNames();
74    }
75  
76    /**
77     * Returns the shortened version of the user name -- the portion that maps
78     * to an operating system user name.
79     * @return Short name
80     */
81    public abstract String getShortName();
82  
83    /**
84     * Executes the given action within the context of this user.
85     */
86    public abstract <T> T runAs(PrivilegedAction<T> action);
87  
88    /**
89     * Executes the given action within the context of this user.
90     */
91    public abstract <T> T runAs(PrivilegedExceptionAction<T> action)
92        throws IOException, InterruptedException;
93  
94    /**
95     * Requests an authentication token for this user and stores it in the
96     * user's credentials.
97     *
98     * @throws IOException
99     */
100   public abstract void obtainAuthTokenForJob(Configuration conf, Job job)
101       throws IOException, InterruptedException;
102 
103   /**
104    * Requests an authentication token for this user and stores it in the
105    * user's credentials.
106    *
107    * @throws IOException
108    */
109   public abstract void obtainAuthTokenForJob(JobConf job)
110       throws IOException, InterruptedException;
111 
112   /**
113    * Returns the Token of the specified kind associated with this user,
114    * or null if the Token is not present.
115    *
116    * @param kind the kind of token
117    * @param service service on which the token is supposed to be used
118    * @return the token of the specified kind.
119    */
120   public Token<?> getToken(String kind, String service) throws IOException {
121     for (Token<?> token: ugi.getTokens()) {
122       if (token.getKind().toString().equals(kind) &&
123           (service != null && token.getService().toString().equals(service)))
124       {
125         return token;
126       }
127     }
128     return null;
129   }
130 
131   @Override
132   public boolean equals(Object o) {
133     if (this == o) {
134       return true;
135     }
136     if (o == null || getClass() != o.getClass()) {
137       return false;
138     }
139     return ugi.equals(((User) o).ugi);
140   }
141 
142   @Override
143   public int hashCode() {
144     return ugi.hashCode();
145   }
146 
147   @Override
148   public String toString() {
149     return ugi.toString();
150   }
151 
152   /**
153    * Returns the {@code User} instance within current execution context.
154    */
155   public static User getCurrent() throws IOException {
156     User user = new SecureHadoopUser();
157     if (user.getUGI() == null) {
158       return null;
159     }
160     return user;
161   }
162 
163   /**
164    * Executes the given action as the login user
165    * @param action
166    * @return the result of the action
167    * @throws IOException
168    * @throws InterruptedException
169    */
170   @SuppressWarnings({ "rawtypes", "unchecked" })
171   public static <T> T runAsLoginUser(PrivilegedExceptionAction<T> action) throws IOException {
172     try {
173       Class c = Class.forName("org.apache.hadoop.security.SecurityUtil");
174       Class [] types = new Class[]{PrivilegedExceptionAction.class};
175       Object[] args = new Object[]{action};
176       return (T) Methods.call(c, null, "doAsLoginUser", types, args);
177     } catch (Throwable e) {
178       throw new IOException(e);
179     }
180   }
181 
182   /**
183    * Wraps an underlying {@code UserGroupInformation} instance.
184    * @param ugi The base Hadoop user
185    * @return User
186    */
187   public static User create(UserGroupInformation ugi) {
188     if (ugi == null) {
189       return null;
190     }
191     return new SecureHadoopUser(ugi);
192   }
193 
194   /**
195    * Generates a new {@code User} instance specifically for use in test code.
196    * @param name the full username
197    * @param groups the group names to which the test user will belong
198    * @return a new <code>User</code> instance
199    */
200   public static User createUserForTesting(Configuration conf,
201       String name, String[] groups) {
202     return SecureHadoopUser.createUserForTesting(conf, name, groups);
203   }
204 
205   /**
206    * Log in the current process using the given configuration keys for the
207    * credential file and login principal.
208    *
209    * <p><strong>This is only applicable when
210    * running on secure Hadoop</strong> -- see
211    * org.apache.hadoop.security.SecurityUtil#login(Configuration,String,String,String).
212    * On regular Hadoop (without security features), this will safely be ignored.
213    * </p>
214    *
215    * @param conf The configuration data to use
216    * @param fileConfKey Property key used to configure path to the credential file
217    * @param principalConfKey Property key used to configure login principal
218    * @param localhost Current hostname to use in any credentials
219    * @throws IOException underlying exception from SecurityUtil.login() call
220    */
221   public static void login(Configuration conf, String fileConfKey,
222       String principalConfKey, String localhost) throws IOException {
223     SecureHadoopUser.login(conf, fileConfKey, principalConfKey, localhost);
224   }
225 
226   /**
227    * Returns whether or not Kerberos authentication is configured for Hadoop.
228    * For non-secure Hadoop, this always returns <code>false</code>.
229    * For secure Hadoop, it will return the value from
230    * {@code UserGroupInformation.isSecurityEnabled()}.
231    */
232   public static boolean isSecurityEnabled() {
233     return SecureHadoopUser.isSecurityEnabled();
234   }
235 
236   /**
237    * Returns whether or not secure authentication is enabled for HBase. Note that
238    * HBase security requires HDFS security to provide any guarantees, so it is
239    * recommended that secure HBase should run on secure HDFS.
240    */
241   public static boolean isHBaseSecurityEnabled(Configuration conf) {
242     return "kerberos".equalsIgnoreCase(conf.get(HBASE_SECURITY_CONF_KEY));
243   }
244 
245   /* Concrete implementations */
246 
247   /**
248    * Bridges {@code User} invocations to underlying calls to
249    * {@link org.apache.hadoop.security.UserGroupInformation} for secure Hadoop
250    * 0.20 and versions 0.21 and above.
251    */
252   private static class SecureHadoopUser extends User {
253     private String shortName;
254 
255     private SecureHadoopUser() throws IOException {
256       ugi = UserGroupInformation.getCurrentUser();
257     }
258 
259     private SecureHadoopUser(UserGroupInformation ugi) {
260       this.ugi = ugi;
261     }
262 
263     @Override
264     public String getShortName() {
265       if (shortName != null) return shortName;
266       try {
267         shortName = ugi.getShortUserName();
268         return shortName;
269       } catch (Exception e) {
270         throw new RuntimeException("Unexpected error getting user short name",
271           e);
272       }
273     }
274 
275     @Override
276     public <T> T runAs(PrivilegedAction<T> action) {
277       return ugi.doAs(action);
278     }
279 
280     @Override
281     public <T> T runAs(PrivilegedExceptionAction<T> action)
282         throws IOException, InterruptedException {
283       return ugi.doAs(action);
284     }
285 
286     @Override
287     public void obtainAuthTokenForJob(Configuration conf, Job job)
288         throws IOException, InterruptedException {
289       try {
290         Class<?> c = Class.forName(
291             "org.apache.hadoop.hbase.security.token.TokenUtil");
292         Methods.call(c, null, "obtainTokenForJob",
293             new Class[]{Configuration.class, UserGroupInformation.class,
294                 Job.class},
295             new Object[]{conf, ugi, job});
296       } catch (ClassNotFoundException cnfe) {
297         throw new RuntimeException("Failure loading TokenUtil class, "
298             +"is secure RPC available?", cnfe);
299       } catch (IOException ioe) {
300         throw ioe;
301       } catch (InterruptedException ie) {
302         throw ie;
303       } catch (RuntimeException re) {
304         throw re;
305       } catch (Exception e) {
306         throw new UndeclaredThrowableException(e,
307             "Unexpected error calling TokenUtil.obtainAndCacheToken()");
308       }
309     }
310 
311     @Override
312     public void obtainAuthTokenForJob(JobConf job)
313         throws IOException, InterruptedException {
314       try {
315         Class<?> c = Class.forName(
316             "org.apache.hadoop.hbase.security.token.TokenUtil");
317         Methods.call(c, null, "obtainTokenForJob",
318             new Class[]{JobConf.class, UserGroupInformation.class},
319             new Object[]{job, ugi});
320       } catch (ClassNotFoundException cnfe) {
321         throw new RuntimeException("Failure loading TokenUtil class, "
322             +"is secure RPC available?", cnfe);
323       } catch (IOException ioe) {
324         throw ioe;
325       } catch (InterruptedException ie) {
326         throw ie;
327       } catch (RuntimeException re) {
328         throw re;
329       } catch (Exception e) {
330         throw new UndeclaredThrowableException(e,
331             "Unexpected error calling TokenUtil.obtainAndCacheToken()");
332       }
333     }
334 
335     /** @see User#createUserForTesting(org.apache.hadoop.conf.Configuration, String, String[]) */
336     public static User createUserForTesting(Configuration conf,
337         String name, String[] groups) {
338       return new SecureHadoopUser(UserGroupInformation.createUserForTesting(name, groups));
339     }
340 
341     /**
342      * Obtain credentials for the current process using the configured
343      * Kerberos keytab file and principal.
344      * @see User#login(org.apache.hadoop.conf.Configuration, String, String, String)
345      *
346      * @param conf the Configuration to use
347      * @param fileConfKey Configuration property key used to store the path
348      * to the keytab file
349      * @param principalConfKey Configuration property key used to store the
350      * principal name to login as
351      * @param localhost the local hostname
352      */
353     public static void login(Configuration conf, String fileConfKey,
354         String principalConfKey, String localhost) throws IOException {
355       if (isSecurityEnabled()) {
356         SecurityUtil.login(conf, fileConfKey, principalConfKey, localhost);
357       }
358     }
359 
360     /**
361      * Returns the result of {@code UserGroupInformation.isSecurityEnabled()}.
362      */
363     public static boolean isSecurityEnabled() {
364       return UserGroupInformation.isSecurityEnabled();
365     }
366   }
367 }