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