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;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.HBaseInterfaceAudience;
030import org.apache.hadoop.hbase.security.User;
031import org.apache.hadoop.hbase.util.Pair;
032import org.apache.hadoop.security.token.Token;
033import org.apache.hadoop.security.token.TokenIdentifier;
034import org.apache.yetus.audience.InterfaceAudience;
035import org.apache.yetus.audience.InterfaceStability;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039/**
040 * Accessor for all SaslAuthenticationProvider instances.
041 */
042@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION)
043@InterfaceStability.Evolving
044public final class SaslClientAuthenticationProviders {
045  private static final Logger LOG =
046    LoggerFactory.getLogger(SaslClientAuthenticationProviders.class);
047
048  public static final String SELECTOR_KEY = "hbase.client.sasl.provider.class";
049  public static final String EXTRA_PROVIDERS_KEY = "hbase.client.sasl.provider.extras";
050
051  private static final AtomicReference<SaslClientAuthenticationProviders> providersRef =
052    new AtomicReference<>();
053
054  private final Collection<SaslClientAuthenticationProvider> providers;
055  private final AuthenticationProviderSelector selector;
056
057  private SaslClientAuthenticationProviders(Collection<SaslClientAuthenticationProvider> providers,
058    AuthenticationProviderSelector selector) {
059    this.providers = providers;
060    this.selector = selector;
061  }
062
063  /**
064   * Returns the number of providers that have been registered.
065   */
066  public int getNumRegisteredProviders() {
067    return providers.size();
068  }
069
070  /**
071   * Returns a singleton instance of {@link SaslClientAuthenticationProviders}.
072   */
073  public static synchronized SaslClientAuthenticationProviders getInstance(Configuration conf) {
074    SaslClientAuthenticationProviders providers = providersRef.get();
075    if (providers == null) {
076      providers = instantiate(conf);
077      providersRef.set(providers);
078    }
079
080    return providers;
081  }
082
083  /**
084   * Removes the cached singleton instance of {@link SaslClientAuthenticationProviders}.
085   */
086  public static synchronized void reset() {
087    providersRef.set(null);
088  }
089
090  /**
091   * Adds the given {@code provider} to the set, only if an equivalent provider does not already
092   * exist in the set.
093   */
094  static void addProviderIfNotExists(SaslClientAuthenticationProvider provider,
095    HashMap<Byte, SaslClientAuthenticationProvider> providers) {
096    Byte code = provider.getSaslAuthMethod().getCode();
097    SaslClientAuthenticationProvider existingProvider = providers.get(code);
098    if (existingProvider != null) {
099      throw new RuntimeException("Already registered authentication provider with " + code + " "
100        + existingProvider.getClass());
101    }
102    providers.put(code, provider);
103  }
104
105  /**
106   * Instantiates the ProviderSelector implementation from the provided configuration.
107   */
108  static AuthenticationProviderSelector instantiateSelector(Configuration conf,
109    Collection<SaslClientAuthenticationProvider> providers) {
110    Class<? extends AuthenticationProviderSelector> clz = conf.getClass(SELECTOR_KEY,
111      BuiltInProviderSelector.class, AuthenticationProviderSelector.class);
112    try {
113      AuthenticationProviderSelector selector = clz.getConstructor().newInstance();
114      selector.configure(conf, providers);
115      if (LOG.isTraceEnabled()) {
116        LOG.trace("Loaded ProviderSelector {}", selector.getClass());
117      }
118      return selector;
119    } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
120      | InvocationTargetException e) {
121      throw new RuntimeException(
122        "Failed to instantiate " + clz + " as the ProviderSelector defined by " + SELECTOR_KEY, e);
123    }
124  }
125
126  /**
127   * Extracts and instantiates authentication providers from the configuration.
128   */
129  static void addExplicitProviders(Configuration conf,
130    HashMap<Byte, SaslClientAuthenticationProvider> providers) {
131    for (String implName : conf.getStringCollection(EXTRA_PROVIDERS_KEY)) {
132      Class<?> clz;
133      // Load the class from the config
134      try {
135        clz = Class.forName(implName);
136      } catch (ClassNotFoundException e) {
137        LOG.warn("Failed to load SaslClientAuthenticationProvider {}", implName, e);
138        continue;
139      }
140
141      // Make sure it's the right type
142      if (!SaslClientAuthenticationProvider.class.isAssignableFrom(clz)) {
143        LOG.warn("Ignoring SaslClientAuthenticationProvider {} because it is not an instance of"
144          + " SaslClientAuthenticationProvider", clz);
145        continue;
146      }
147
148      // Instantiate it
149      SaslClientAuthenticationProvider provider;
150      try {
151        provider = (SaslClientAuthenticationProvider) clz.getConstructor().newInstance();
152      } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
153        | InvocationTargetException e) {
154        LOG.warn("Failed to instantiate SaslClientAuthenticationProvider {}", clz, e);
155        continue;
156      }
157
158      // Add it to our set, only if it doesn't conflict with something else we've
159      // already registered.
160      addProviderIfNotExists(provider, providers);
161    }
162  }
163
164  /**
165   * Instantiates all client authentication providers and returns an instance of
166   * {@link SaslClientAuthenticationProviders}.
167   */
168  static SaslClientAuthenticationProviders instantiate(Configuration conf) {
169    ServiceLoader<SaslClientAuthenticationProvider> loader =
170      ServiceLoader.load(SaslClientAuthenticationProvider.class);
171    HashMap<Byte, SaslClientAuthenticationProvider> providerMap = new HashMap<>();
172    for (SaslClientAuthenticationProvider provider : loader) {
173      addProviderIfNotExists(provider, providerMap);
174    }
175
176    addExplicitProviders(conf, providerMap);
177
178    Collection<SaslClientAuthenticationProvider> providers =
179      Collections.unmodifiableCollection(providerMap.values());
180
181    if (LOG.isTraceEnabled()) {
182      String loadedProviders = providers.stream().map((provider) -> provider.getClass().getName())
183        .collect(Collectors.joining(", "));
184      LOG.trace("Found SaslClientAuthenticationProviders {}", loadedProviders);
185    }
186
187    AuthenticationProviderSelector selector = instantiateSelector(conf, providers);
188    return new SaslClientAuthenticationProviders(providers, selector);
189  }
190
191  /**
192   * Returns the provider and token pair for SIMPLE authentication. This method is a "hack" while
193   * SIMPLE authentication for HBase does not flow through the SASL codepath.
194   */
195  public Pair<SaslClientAuthenticationProvider, Token<? extends TokenIdentifier>>
196    getSimpleProvider() {
197    Optional<SaslClientAuthenticationProvider> optional = providers.stream()
198      .filter((p) -> p instanceof SimpleSaslClientAuthenticationProvider).findFirst();
199    return new Pair<>(optional.get(), null);
200  }
201
202  /**
203   * Chooses the best authentication provider and corresponding token given the HBase cluster
204   * identifier and the user.
205   */
206  public Pair<SaslClientAuthenticationProvider, Token<? extends TokenIdentifier>>
207    selectProvider(String clusterId, User clientUser) {
208    return selector.selectProvider(clusterId, clientUser);
209  }
210
211  @Override
212  public String toString() {
213    return providers.stream().map((p) -> p.getClass().getName())
214      .collect(Collectors.joining(", ", "providers=[", "], selector=")) + selector.getClass();
215  }
216}