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