View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.security;
19  
20  import java.io.IOException;
21  import java.util.LinkedHashSet;
22  import java.util.Set;
23  import java.util.concurrent.Callable;
24  import java.util.concurrent.Executors;
25  import java.util.concurrent.TimeUnit;
26  
27  import org.apache.hadoop.hbase.classification.InterfaceAudience;
28  import com.google.common.cache.CacheBuilder;
29  import com.google.common.cache.CacheLoader;
30  import com.google.common.cache.LoadingCache;
31  import com.google.common.util.concurrent.ListenableFuture;
32  import com.google.common.util.concurrent.ListeningExecutorService;
33  import com.google.common.util.concurrent.MoreExecutors;
34  import com.google.common.util.concurrent.ThreadFactoryBuilder;
35  import org.apache.hadoop.conf.Configuration;
36  import org.apache.hadoop.fs.CommonConfigurationKeys;
37  import org.apache.hadoop.hbase.BaseConfigurable;
38  import org.apache.hadoop.security.Groups;
39  import org.apache.hadoop.security.UserGroupInformation;
40  import org.apache.hadoop.util.ReflectionUtils;
41  
42  /**
43   * Provide an instance of a user. Allows custom {@link User} creation.
44   */
45  @InterfaceAudience.Private
46  public class UserProvider extends BaseConfigurable {
47  
48    private static final String USER_PROVIDER_CONF_KEY = "hbase.client.userprovider.class";
49    private static final ListeningExecutorService executor = MoreExecutors.listeningDecorator(
50        Executors.newScheduledThreadPool(
51            1,
52            new ThreadFactoryBuilder().setDaemon(true).setNameFormat("group-cache-%d").build()));
53  
54    private LoadingCache<String, String[]> groupCache = null;
55  
56    static Groups groups = Groups.getUserToGroupsMappingService();
57  
58    @Override
59    public void setConf(final Configuration conf) {
60      super.setConf(conf);
61  
62      synchronized (UserProvider.class) {
63        if (!(groups instanceof User.TestingGroups)) {
64          groups = Groups.getUserToGroupsMappingService(conf);
65        }
66      }
67  
68      long cacheTimeout =
69          getConf().getLong(CommonConfigurationKeys.HADOOP_SECURITY_GROUPS_CACHE_SECS,
70              CommonConfigurationKeys.HADOOP_SECURITY_GROUPS_CACHE_SECS_DEFAULT) * 1000;
71  
72      this.groupCache = CacheBuilder.newBuilder()
73          // This is the same timeout that hadoop uses. So we'll follow suit.
74          .refreshAfterWrite(cacheTimeout, TimeUnit.MILLISECONDS)
75          .expireAfterWrite(10 * cacheTimeout, TimeUnit.MILLISECONDS)
76              // Set concurrency level equal to the default number of handlers that
77              // the simple handler spins up.
78          .concurrencyLevel(20)
79              // create the loader
80              // This just delegates to UGI.
81          .build(new CacheLoader<String, String[]>() {
82  
83            // Since UGI's don't hash based on the user id
84            // The cache needs to be keyed on the same thing that Hadoop's Groups class
85            // uses. So this cache uses shortname.
86            @Override
87            public String[] load(String ugi) throws Exception {
88              return getGroupStrings(ugi);
89            }
90  
91            private String[] getGroupStrings(String ugi) {
92              try {
93                Set<String> result = new LinkedHashSet<String>(groups.getGroups(ugi));
94                return result.toArray(new String[result.size()]);
95              } catch (Exception e) {
96                return new String[0];
97              }
98            }
99  
100           // Provide the reload function that uses the executor thread.
101           public ListenableFuture<String[]> reload(final String k,
102                                                    String[] oldValue) throws Exception {
103 
104             return executor.submit(new Callable<String[]>() {
105 
106               @Override
107               public String[] call() throws Exception {
108                 return getGroupStrings(k);
109               }
110             });
111           }
112         });
113   }
114 
115   /**
116    * Instantiate the {@link UserProvider} specified in the configuration and set the passed
117    * configuration via {@link UserProvider#setConf(Configuration)}
118    * @param conf to read and set on the created {@link UserProvider}
119    * @return a {@link UserProvider} ready for use.
120    */
121   public static UserProvider instantiate(Configuration conf) {
122     Class<? extends UserProvider> clazz =
123         conf.getClass(USER_PROVIDER_CONF_KEY, UserProvider.class, UserProvider.class);
124     return ReflectionUtils.newInstance(clazz, conf);
125   }
126 
127   /**
128    * Set the {@link UserProvider} in the given configuration that should be instantiated
129    * @param conf to update
130    * @param provider class of the provider to set
131    */
132   public static void setUserProviderForTesting(Configuration conf,
133       Class<? extends UserProvider> provider) {
134     conf.set(USER_PROVIDER_CONF_KEY, provider.getName());
135   }
136 
137   /**
138    * @return the userName for the current logged-in user.
139    * @throws IOException if the underlying user cannot be obtained
140    */
141   public String getCurrentUserName() throws IOException {
142     User user = getCurrent();
143     return user == null ? null : user.getName();
144   }
145 
146   /**
147    * @return <tt>true</tt> if security is enabled, <tt>false</tt> otherwise
148    */
149   public boolean isHBaseSecurityEnabled() {
150     return User.isHBaseSecurityEnabled(this.getConf());
151   }
152 
153   /**
154    * @return whether or not Kerberos authentication is configured for Hadoop. For non-secure Hadoop,
155    *         this always returns <code>false</code>. For secure Hadoop, it will return the value
156    *         from {@code UserGroupInformation.isSecurityEnabled()}.
157    */
158   public boolean isHadoopSecurityEnabled() {
159     return User.isSecurityEnabled();
160   }
161 
162   /**
163    * @return the current user within the current execution context
164    * @throws IOException if the user cannot be loaded
165    */
166   public User getCurrent() throws IOException {
167     return User.getCurrent();
168   }
169 
170   /**
171    * Wraps an underlying {@code UserGroupInformation} instance.
172    * @param ugi The base Hadoop user
173    * @return User
174    */
175   public User create(UserGroupInformation ugi) {
176     if (ugi == null) {
177       return null;
178     }
179     return new User.SecureHadoopUser(ugi, groupCache);
180   }
181 
182   /**
183    * Log in the current process using the given configuration keys for the credential file and login
184    * principal.
185    * <p>
186    * <strong>This is only applicable when running on secure Hadoop</strong> -- see
187    * org.apache.hadoop.security.SecurityUtil#login(Configuration,String,String,String). On regular
188    * Hadoop (without security features), this will safely be ignored.
189    * </p>
190    * @param fileConfKey Property key used to configure path to the credential file
191    * @param principalConfKey Property key used to configure login principal
192    * @param localhost Current hostname to use in any credentials
193    * @throws IOException underlying exception from SecurityUtil.login() call
194    */
195   public void login(String fileConfKey, String principalConfKey, String localhost)
196       throws IOException {
197     User.login(getConf(), fileConfKey, principalConfKey, localhost);
198   }
199 }