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