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.io.IOException;
021import java.lang.reflect.InvocationTargetException;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.Map;
025import java.util.Optional;
026import java.util.ServiceLoader;
027import java.util.concurrent.atomic.AtomicReference;
028import java.util.stream.Collectors;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.yetus.audience.InterfaceAudience;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034@InterfaceAudience.Private
035public final class SaslServerAuthenticationProviders {
036  private static final Logger LOG =
037    LoggerFactory.getLogger(SaslClientAuthenticationProviders.class);
038
039  public static final String EXTRA_PROVIDERS_KEY = "hbase.server.sasl.provider.extras";
040
041  private static final AtomicReference<SaslServerAuthenticationProviders> HOLDER =
042    new AtomicReference<>();
043
044  private final Map<Byte, SaslServerAuthenticationProvider> providers;
045
046  /**
047   * Creates a new instance of SaslServerAuthenticationProviders.
048   * @param conf the configuration to use for loading providers
049   */
050  public SaslServerAuthenticationProviders(Configuration conf) {
051    ServiceLoader<SaslServerAuthenticationProvider> loader =
052      ServiceLoader.load(SaslServerAuthenticationProvider.class);
053    HashMap<Byte, SaslServerAuthenticationProvider> providerMap = new HashMap<>();
054    for (SaslServerAuthenticationProvider provider : loader) {
055      addProviderIfNotExists(provider, providerMap);
056    }
057
058    addExtraProviders(conf, providerMap);
059
060    if (LOG.isTraceEnabled()) {
061      String loadedProviders = providerMap.values().stream()
062        .map((provider) -> provider.getClass().getName()).collect(Collectors.joining(", "));
063      if (loadedProviders.isEmpty()) {
064        loadedProviders = "None!";
065      }
066      LOG.trace("Found SaslServerAuthenticationProviders {}", loadedProviders);
067    }
068
069    // Initialize the providers once, before we get into the RPC path.
070    providerMap.forEach((b, provider) -> {
071      try {
072        // Give them a copy, just to make sure there is no funny-business going on.
073        provider.init(new Configuration(conf));
074      } catch (IOException e) {
075        LOG.error("Failed to initialize {}", provider.getClass(), e);
076        throw new RuntimeException("Failed to initialize " + provider.getClass().getName(), e);
077      }
078    });
079    this.providers = Collections.unmodifiableMap(providerMap);
080  }
081
082  /**
083   * Returns the number of registered providers.
084   */
085  public int getNumRegisteredProviders() {
086    return providers.size();
087  }
088
089  /**
090   * Returns a singleton instance of {@link SaslServerAuthenticationProviders}.
091   * @deprecated Since 2.5.14 and 2.6.4, will be removed in newer minor release lines. This class
092   *             should not be singleton, please do not use it any more. see HBASE-29144 for more
093   *             details.
094   */
095  @Deprecated
096  public static SaslServerAuthenticationProviders getInstance(Configuration conf) {
097    SaslServerAuthenticationProviders providers = HOLDER.get();
098    if (null == providers) {
099      synchronized (HOLDER) {
100        // Someone else beat us here
101        providers = HOLDER.get();
102        if (null != providers) {
103          return providers;
104        }
105
106        providers = new SaslServerAuthenticationProviders(conf);
107        HOLDER.set(providers);
108      }
109    }
110    return providers;
111  }
112
113  /**
114   * Removes the cached singleton instance of {@link SaslServerAuthenticationProviders}.
115   * @deprecated Since 2.5.14 and 2.6.4, will be removed in newer minor release lines. This class
116   *             should not be singleton, please do not use it any more. see HBASE-29144 for more
117   *             details.
118   */
119  @Deprecated
120  public static void reset() {
121    synchronized (HOLDER) {
122      HOLDER.set(null);
123    }
124  }
125
126  /**
127   * Adds the given provider into the map of providers if a mapping for the auth code does not
128   * already exist in the map.
129   */
130  static void addProviderIfNotExists(SaslServerAuthenticationProvider provider,
131    HashMap<Byte, SaslServerAuthenticationProvider> providers) {
132    final byte newProviderAuthCode = provider.getSaslAuthMethod().getCode();
133    final SaslServerAuthenticationProvider alreadyRegisteredProvider =
134      providers.get(newProviderAuthCode);
135    if (alreadyRegisteredProvider != null) {
136      throw new RuntimeException("Trying to load SaslServerAuthenticationProvider "
137        + provider.getClass() + ", but " + alreadyRegisteredProvider.getClass()
138        + " is already registered with the same auth code");
139    }
140    providers.put(newProviderAuthCode, provider);
141  }
142
143  /**
144   * Adds any providers defined in the configuration.
145   */
146  static void addExtraProviders(Configuration conf,
147    HashMap<Byte, SaslServerAuthenticationProvider> providers) {
148    for (String implName : conf.getStringCollection(EXTRA_PROVIDERS_KEY)) {
149      Class<?> clz;
150      try {
151        clz = Class.forName(implName);
152      } catch (ClassNotFoundException e) {
153        LOG.warn("Failed to find SaslServerAuthenticationProvider class {}", implName, e);
154        continue;
155      }
156
157      if (!SaslServerAuthenticationProvider.class.isAssignableFrom(clz)) {
158        LOG.warn("Server authentication class {} is not an instance of "
159          + "SaslServerAuthenticationProvider", clz);
160        continue;
161      }
162
163      try {
164        SaslServerAuthenticationProvider provider =
165          (SaslServerAuthenticationProvider) clz.getConstructor().newInstance();
166        addProviderIfNotExists(provider, providers);
167      } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
168        | InvocationTargetException e) {
169        LOG.warn("Failed to instantiate {}", clz, e);
170      }
171    }
172  }
173
174  /**
175   * Selects the appropriate SaslServerAuthenticationProvider from those available. If there is no
176   * matching provider for the given {@code authByte}, this method will return null.
177   */
178  public SaslServerAuthenticationProvider selectProvider(byte authByte) {
179    return providers.get(Byte.valueOf(authByte));
180  }
181
182  /**
183   * Extracts the SIMPLE authentication provider.
184   */
185  public SaslServerAuthenticationProvider getSimpleProvider() {
186    Optional<SaslServerAuthenticationProvider> opt = providers.values().stream()
187      .filter((p) -> p instanceof SimpleSaslServerAuthenticationProvider).findFirst();
188    if (!opt.isPresent()) {
189      throw new RuntimeException("SIMPLE authentication provider not available when it should be");
190    }
191    return opt.get();
192  }
193}