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}