001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.security.provider;
019
020import java.lang.reflect.InvocationTargetException;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.Optional;
025import java.util.ServiceLoader;
026import java.util.concurrent.atomic.AtomicReference;
027import java.util.stream.Collectors;
028
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.HBaseInterfaceAudience;
031import org.apache.hadoop.hbase.security.User;
032import org.apache.hadoop.hbase.util.Pair;
033import org.apache.hadoop.security.token.Token;
034import org.apache.hadoop.security.token.TokenIdentifier;
035import org.apache.yetus.audience.InterfaceAudience;
036import org.apache.yetus.audience.InterfaceStability;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040/**
041 * Accessor for all SaslAuthenticationProvider instances.
042 */
043@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION)
044@InterfaceStability.Evolving
045public final class SaslClientAuthenticationProviders {
046  private static final Logger LOG = LoggerFactory.getLogger(
047      SaslClientAuthenticationProviders.class);
048
049  public static final String SELECTOR_KEY = "hbase.client.sasl.provider.class";
050  public static final String EXTRA_PROVIDERS_KEY = "hbase.client.sasl.provider.extras";
051
052  private static final AtomicReference<SaslClientAuthenticationProviders> providersRef =
053      new AtomicReference<>();
054
055  private final Collection<SaslClientAuthenticationProvider> providers;
056  private final AuthenticationProviderSelector selector;
057
058  private SaslClientAuthenticationProviders(
059      Collection<SaslClientAuthenticationProvider> providers,
060      AuthenticationProviderSelector selector) {
061    this.providers = providers;
062    this.selector = selector;
063  }
064
065  /**
066   * Returns the number of providers that have been registered.
067   */
068  public int getNumRegisteredProviders() {
069    return providers.size();
070  }
071
072  /**
073   * Returns a singleton instance of {@link SaslClientAuthenticationProviders}.
074   */
075  public static synchronized SaslClientAuthenticationProviders getInstance(Configuration conf) {
076    SaslClientAuthenticationProviders providers = providersRef.get();
077    if (providers == null) {
078      providers = instantiate(conf);
079      providersRef.set(providers);
080    }
081
082    return providers;
083  }
084
085  /**
086   * Removes the cached singleton instance of {@link SaslClientAuthenticationProviders}.
087   */
088  public static synchronized void reset() {
089    providersRef.set(null);
090  }
091
092  /**
093   * Adds the given {@code provider} to the set, only if an equivalent provider does not
094   * already exist in the set.
095   */
096  static void addProviderIfNotExists(SaslClientAuthenticationProvider provider,
097      HashMap<Byte,SaslClientAuthenticationProvider> providers) {
098    Byte code = provider.getSaslAuthMethod().getCode();
099    SaslClientAuthenticationProvider existingProvider = providers.get(code);
100    if (existingProvider != null) {
101      throw new RuntimeException("Already registered authentication provider with " + code + " "
102          + existingProvider.getClass());
103    }
104    providers.put(code, provider);
105  }
106
107  /**
108   * Instantiates the ProviderSelector implementation from the provided configuration.
109   */
110  static AuthenticationProviderSelector instantiateSelector(Configuration conf,
111      Collection<SaslClientAuthenticationProvider> providers) {
112    Class<? extends AuthenticationProviderSelector> clz = conf.getClass(
113        SELECTOR_KEY, BuiltInProviderSelector.class, AuthenticationProviderSelector.class);
114    try {
115      AuthenticationProviderSelector selector = clz.getConstructor().newInstance();
116      selector.configure(conf, providers);
117      if (LOG.isTraceEnabled()) {
118        LOG.trace("Loaded ProviderSelector {}", selector.getClass());
119      }
120      return selector;
121    } catch (InstantiationException | IllegalAccessException | NoSuchMethodException |
122        InvocationTargetException e) {
123      throw new RuntimeException("Failed to instantiate " + clz +
124          " as the ProviderSelector defined by " + SELECTOR_KEY, e);
125    }
126  }
127
128  /**
129   * Extracts and instantiates authentication providers from the configuration.
130   */
131  static void addExplicitProviders(Configuration conf,
132      HashMap<Byte,SaslClientAuthenticationProvider> providers) {
133    for(String implName : conf.getStringCollection(EXTRA_PROVIDERS_KEY)) {
134      Class<?> clz;
135      // Load the class from the config
136      try {
137        clz = Class.forName(implName);
138      } catch (ClassNotFoundException e) {
139        LOG.warn("Failed to load SaslClientAuthenticationProvider {}", implName, e);
140        continue;
141      }
142
143      // Make sure it's the right type
144      if (!SaslClientAuthenticationProvider.class.isAssignableFrom(clz)) {
145        LOG.warn("Ignoring SaslClientAuthenticationProvider {} because it is not an instance of"
146            + " SaslClientAuthenticationProvider", clz);
147        continue;
148      }
149
150      // Instantiate it
151      SaslClientAuthenticationProvider provider;
152      try {
153        provider = (SaslClientAuthenticationProvider) clz.getConstructor().newInstance();
154      } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
155          | InvocationTargetException e) {
156        LOG.warn("Failed to instantiate SaslClientAuthenticationProvider {}", clz, e);
157        continue;
158      }
159
160      // Add it to our set, only if it doesn't conflict with something else we've
161      // already registered.
162      addProviderIfNotExists(provider, providers);
163    }
164  }
165
166  /**
167   * Instantiates all client authentication providers and returns an instance of
168   * {@link SaslClientAuthenticationProviders}.
169   */
170  static SaslClientAuthenticationProviders instantiate(Configuration conf) {
171    ServiceLoader<SaslClientAuthenticationProvider> loader =
172        ServiceLoader.load(SaslClientAuthenticationProvider.class);
173    HashMap<Byte,SaslClientAuthenticationProvider> providerMap = new HashMap<>();
174    for (SaslClientAuthenticationProvider provider : loader) {
175      addProviderIfNotExists(provider, providerMap);
176    }
177
178    addExplicitProviders(conf, providerMap);
179
180    Collection<SaslClientAuthenticationProvider> providers = Collections.unmodifiableCollection(
181        providerMap.values());
182
183    if (LOG.isTraceEnabled()) {
184      String loadedProviders = providers.stream()
185          .map((provider) -> provider.getClass().getName())
186          .collect(Collectors.joining(", "));
187      LOG.trace("Found SaslClientAuthenticationProviders {}", loadedProviders);
188    }
189
190    AuthenticationProviderSelector selector = instantiateSelector(conf, providers);
191    return new SaslClientAuthenticationProviders(providers, selector);
192  }
193
194  /**
195   * Returns the provider and token pair for SIMPLE authentication.
196   *
197   * This method is a "hack" while SIMPLE authentication for HBase does not flow through
198   * the SASL codepath.
199   */
200  public Pair<SaslClientAuthenticationProvider, Token<? extends TokenIdentifier>>
201      getSimpleProvider() {
202    Optional<SaslClientAuthenticationProvider> optional = providers.stream()
203        .filter((p) -> p instanceof SimpleSaslClientAuthenticationProvider)
204        .findFirst();
205    return new Pair<>(optional.get(), null);
206  }
207
208  /**
209   * Chooses the best authentication provider and corresponding token given the HBase cluster
210   * identifier and the user.
211   */
212  public Pair<SaslClientAuthenticationProvider, Token<? extends TokenIdentifier>> selectProvider(
213      String clusterId, User clientUser) {
214    return selector.selectProvider(clusterId, clientUser);
215  }
216
217  @Override
218  public String toString() {
219    return providers.stream()
220        .map((p) -> p.getClass().getName())
221        .collect(Collectors.joining(", ", "providers=[", "], selector=")) + selector.getClass();
222  }
223}